Simplify H3 beat timing and cleanup
This commit is contained in:
+14
-58
@@ -10,7 +10,7 @@ The node does two jobs at once:
|
|||||||
Think of it like this:
|
Think of it like this:
|
||||||
|
|
||||||
- `prompt` decides what happens.
|
- `prompt` decides what happens.
|
||||||
- `shot_seconds`, `per_beat_length`, and beat-level `seconds:` decide how long each beat is allowed to be.
|
- `shot_seconds` and beat-level `seconds:` decide how long each beat is allowed to be.
|
||||||
- `first_frame` and the handoff system decide continuity.
|
- `first_frame` and the handoff system decide continuity.
|
||||||
- `ref_1` to `ref_9` decide identity and location reference conditioning.
|
- `ref_1` to `ref_9` decide identity and location reference conditioning.
|
||||||
|
|
||||||
@@ -375,60 +375,38 @@ This is the most important group to understand.
|
|||||||
These settings work together:
|
These settings work together:
|
||||||
|
|
||||||
- `shot_seconds`
|
- `shot_seconds`
|
||||||
- `per_beat_length`
|
|
||||||
- beat-level `seconds:` or `duration:`
|
- beat-level `seconds:` or `duration:`
|
||||||
- `allow_oversize_shots`
|
|
||||||
- `megapixels`
|
- `megapixels`
|
||||||
- `vram_headroom_gb`
|
- `vram_headroom_gb`
|
||||||
- `allow_res_backoff`
|
- `allow_res_backoff`
|
||||||
|
|
||||||
### The Short Version
|
### The Short Version
|
||||||
|
|
||||||
`shot_seconds` is a ceiling, not a promise.
|
`shot_seconds` is the socket-defined maximum.
|
||||||
|
|
||||||
A beat-level `seconds:` line is a request, not absolute law.
|
A beat-level `seconds:` line wins for that beat.
|
||||||
|
|
||||||
The final beat length is limited by:
|
If a requested length is too much for the hardware, the node keeps it and lets the render fail instead of quietly shrinking it.
|
||||||
|
|
||||||
- H3's hard single-shot max
|
|
||||||
- the VRAM budget
|
|
||||||
- the current resolution and pixel budget
|
|
||||||
|
|
||||||
### Exact Decision Order
|
### Exact Decision Order
|
||||||
|
|
||||||
For each beat, the node decides length like this:
|
For each beat, the node decides length like this:
|
||||||
|
|
||||||
1. If the beat has `seconds:` or `duration:`, use that as the requested length.
|
1. If the beat has `seconds:` or `duration:`, use that as the requested length.
|
||||||
2. Otherwise, if `per_beat_length` is on, estimate the beat length from its content.
|
2. Otherwise, estimate the beat length from its content.
|
||||||
3. Otherwise, use the global shot ceiling.
|
3. Snap the result to H3's frame grid.
|
||||||
4. Clamp that result to the global `shot_seconds` ceiling if one is set.
|
|
||||||
5. Clamp again if H3 or VRAM will not allow that size.
|
|
||||||
6. Snap the result to H3's frame grid.
|
|
||||||
|
|
||||||
### `shot_seconds`
|
### `shot_seconds`
|
||||||
|
|
||||||
Global per-shot ceiling.
|
Global per-shot maximum.
|
||||||
|
|
||||||
Important:
|
Important:
|
||||||
|
|
||||||
- This does not mean every beat will be exactly this long.
|
- This does not mean every beat will be exactly this long.
|
||||||
- It means no beat is allowed to be longer than this, unless other limits clamp it first.
|
- It means beats are sized against this socket value when the node needs a maximum.
|
||||||
|
|
||||||
If left unconnected or set to `0`, the node uses the largest safe shot length it can budget automatically.
|
If left unconnected or set to `0`, the node uses the largest safe shot length it can budget automatically.
|
||||||
|
|
||||||
### `per_beat_length`
|
|
||||||
|
|
||||||
When on, the node estimates beat length from the beat content if there is no explicit beat-level `seconds:` line.
|
|
||||||
|
|
||||||
That means:
|
|
||||||
|
|
||||||
- short simple beats become shorter shots
|
|
||||||
- more complex beats become longer shots
|
|
||||||
|
|
||||||
When off:
|
|
||||||
|
|
||||||
- every beat uses the full global ceiling unless that beat has its own explicit `seconds:` line
|
|
||||||
|
|
||||||
### Beat-Level `seconds:` Or `duration:`
|
### Beat-Level `seconds:` Or `duration:`
|
||||||
|
|
||||||
Inside a beat, you can write:
|
Inside a beat, you can write:
|
||||||
@@ -445,29 +423,7 @@ duration: 8
|
|||||||
|
|
||||||
This has the highest priority for that beat.
|
This has the highest priority for that beat.
|
||||||
|
|
||||||
But it can still be clamped down if:
|
It is not silently shortened by the node. If you ask for more than your hardware can handle, the render can fail.
|
||||||
|
|
||||||
- it exceeds the global `shot_seconds` ceiling
|
|
||||||
- it exceeds H3's hard max shot length
|
|
||||||
- it exceeds the VRAM budget and `allow_oversize_shots` is off
|
|
||||||
|
|
||||||
### `allow_oversize_shots`
|
|
||||||
|
|
||||||
This only affects forced global `shot_seconds`.
|
|
||||||
|
|
||||||
When off:
|
|
||||||
|
|
||||||
- too-long requests are clamped down to what fits
|
|
||||||
|
|
||||||
When on:
|
|
||||||
|
|
||||||
- the node will try to honor the requested global ceiling even if it exceeds the safe VRAM budget
|
|
||||||
|
|
||||||
Trade-off:
|
|
||||||
|
|
||||||
- slower renders
|
|
||||||
- possible spill to system RAM
|
|
||||||
- possible OOM
|
|
||||||
|
|
||||||
This does not bypass H3's hard max shot size.
|
This does not bypass H3's hard max shot size.
|
||||||
|
|
||||||
@@ -1287,9 +1243,9 @@ Useful on tighter cards at larger resolutions.
|
|||||||
|
|
||||||
### `cleanup_between_shots`
|
### `cleanup_between_shots`
|
||||||
|
|
||||||
Moves completed shot data off the GPU and clears memory between beats.
|
The node always moves completed shot data off the GPU and clears memory between beats.
|
||||||
|
|
||||||
Good default for long chains.
|
This is hard-coded for long-chain stability.
|
||||||
|
|
||||||
### `upscale`
|
### `upscale`
|
||||||
|
|
||||||
@@ -1313,7 +1269,7 @@ Use them in this order:
|
|||||||
|
|
||||||
1. Get the render stable.
|
1. Get the render stable.
|
||||||
2. If decode fails, use decode tiling.
|
2. If decode fails, use decode tiling.
|
||||||
3. If shot length is too short, reduce pixel cost before forcing oversize.
|
3. If shot length is too short, reduce pixel cost before forcing a longer shot.
|
||||||
4. Only upscale after the base render is working.
|
4. Only upscale after the base render is working.
|
||||||
|
|
||||||
## Group 10: Overlay Controls
|
## Group 10: Overlay Controls
|
||||||
@@ -1408,7 +1364,7 @@ Recommended pattern:
|
|||||||
- one character ref in `ref_1`
|
- one character ref in `ref_1`
|
||||||
- `ref_mode = auto ref2v`
|
- `ref_mode = auto ref2v`
|
||||||
- no explicit `<Picture 1>` tags needed
|
- no explicit `<Picture 1>` tags needed
|
||||||
- `per_beat_length = on`
|
- put `seconds:` on a beat when you want a specific length
|
||||||
|
|
||||||
### Two Characters Plus One Location
|
### Two Characters Plus One Location
|
||||||
|
|
||||||
@@ -1422,7 +1378,7 @@ Recommended pattern:
|
|||||||
- use `shot_seconds` as a safe ceiling
|
- use `shot_seconds` as a safe ceiling
|
||||||
- put `seconds:` inside beats that need exact control
|
- put `seconds:` inside beats that need exact control
|
||||||
- check `plan_only` first
|
- check `plan_only` first
|
||||||
- expect clamping if you ask for more than H3 or VRAM allows
|
- expect an OOM if you ask for more than H3 or your hardware allows
|
||||||
|
|
||||||
## Best Way To Check What Will Really Happen
|
## Best Way To Check What Will Really Happen
|
||||||
|
|
||||||
|
|||||||
+33
-64
@@ -271,7 +271,7 @@ def split_paragraphs(text, delimiter):
|
|||||||
# APPEND widget names to this tuple; never insert into the middle. Pure sockets
|
# APPEND widget names to this tuple; never insert into the middle. Pure sockets
|
||||||
# that carry no widget value can stay grouped in INPUT_TYPES without being listed.
|
# that carry no widget value can stay grouped in INPUT_TYPES without being listed.
|
||||||
ADDED_WIDGETS = (
|
ADDED_WIDGETS = (
|
||||||
"beat_split", "per_beat_length",
|
"beat_split",
|
||||||
"watermark_text", "watermark_position", "watermark_size", "watermark_opacity",
|
"watermark_text", "watermark_position", "watermark_size", "watermark_opacity",
|
||||||
"watermark_margin", "intro_text", "intro_position", "intro_seconds",
|
"watermark_margin", "intro_text", "intro_position", "intro_seconds",
|
||||||
"intro_fade", "intro_size", "overlay_font", "overlay_stroke",
|
"intro_fade", "intro_size", "overlay_font", "overlay_stroke",
|
||||||
@@ -3093,19 +3093,18 @@ def beat_seconds_directive(beat):
|
|||||||
beat_seconds_directive = lru_cache(maxsize=2048)(beat_seconds_directive)
|
beat_seconds_directive = lru_cache(maxsize=2048)(beat_seconds_directive)
|
||||||
|
|
||||||
|
|
||||||
def plan_beat_frames(beats, fps, budget, per_beat=True):
|
def plan_beat_frames(beats, fps, budget):
|
||||||
"""Per-beat shot lengths in frames. Returns (lengths, notes).
|
"""Per-beat shot lengths in frames. Returns (lengths, notes).
|
||||||
|
|
||||||
`budget` is the CEILING -- the VRAM budget, or a forced shot_seconds already
|
`budget` is the socket-defined ceiling used for the beat-shot maximum.
|
||||||
clamped to it. Per-beat sizing can only ever make a shot shorter than that
|
Priority per beat:
|
||||||
ceiling, never longer. Priority per beat:
|
|
||||||
|
|
||||||
1. an explicit 'seconds: N' line in the beat -- always honored, down to
|
1. an explicit 'seconds: N' line in the beat -- always honored, down to
|
||||||
H3's real 5-frame minimum, because you stated a duration outright;
|
H3's real 5-frame minimum, because you stated a duration outright;
|
||||||
2. its own content -- action clauses and quoted dialogue (see
|
2. its own content -- action clauses and quoted dialogue (see
|
||||||
estimate_beat_seconds), floored at MIN_CONTENT_FRAMES so a shot always
|
estimate_beat_seconds), floored at MIN_CONTENT_FRAMES so a shot always
|
||||||
has room for one action;
|
has room for one action;
|
||||||
3. with per_beat off, the ceiling, exactly as before.
|
3. otherwise its own content estimate.
|
||||||
|
|
||||||
Why estimate at all, when action prose has no *reliable* duration? Because the
|
Why estimate at all, when action prose has no *reliable* duration? Because the
|
||||||
alternative is not "no guess" -- it is "guess the maximum", which is what giving
|
alternative is not "no guess" -- it is "guess the maximum", which is what giving
|
||||||
@@ -3115,11 +3114,8 @@ def plan_beat_frames(beats, fps, budget, per_beat=True):
|
|||||||
shot continues from the handoff frame; leaning long costs a jacket that takes
|
shot continues from the handoff frame; leaning long costs a jacket that takes
|
||||||
itself off and puts itself back on."""
|
itself off and puts itself back on."""
|
||||||
beats = beats if beats else [""]
|
beats = beats if beats else [""]
|
||||||
# MIN_SHOT_FRAMES is the floor of the *VRAM budget* -- the shortest shot the node
|
# MIN_SHOT_FRAMES is the floor when the node has to guess with no information
|
||||||
# falls back to when it has to guess with no information at all. It must not raise
|
# at all. A stated or estimated length is not silently rewritten upward.
|
||||||
# a length that came from you or from the beat's own content: `max(floor, ...)`
|
|
||||||
# silently turned every request below ~5.2s into 124f, so 1s/2s/3s/4s all rendered
|
|
||||||
# identically and both the widget and the `seconds:` directive looked broken.
|
|
||||||
cap = max(5, int(budget))
|
cap = max(5, int(budget))
|
||||||
content_floor = align_frame_count(MIN_CONTENT_FRAMES)
|
content_floor = align_frame_count(MIN_CONTENT_FRAMES)
|
||||||
out, notes = [], []
|
out, notes = [], []
|
||||||
@@ -3128,12 +3124,12 @@ def plan_beat_frames(beats, fps, budget, per_beat=True):
|
|||||||
want, src, floor = beat_seconds_directive(b), "seconds:", 5
|
want, src, floor = beat_seconds_directive(b), "seconds:", 5
|
||||||
snap = align_frame_count # a stated length is never rounded DOWN
|
snap = align_frame_count # a stated length is never rounded DOWN
|
||||||
if want is None:
|
if want is None:
|
||||||
want = estimate_beat_seconds(b) if per_beat else 0.0
|
want = estimate_beat_seconds(b)
|
||||||
src, floor, snap = "content", content_floor, align_frame_count_nearest
|
src, floor, snap = "content", content_floor, align_frame_count_nearest
|
||||||
if want <= 0: # no signal -> the ceiling
|
if want <= 0: # no signal -> the ceiling
|
||||||
out.append(cap)
|
out.append(cap)
|
||||||
continue
|
continue
|
||||||
n = min(cap, max(floor, snap(int(round(want * fps)))))
|
n = max(floor, snap(int(round(want * fps))))
|
||||||
out.append(n)
|
out.append(n)
|
||||||
if n != cap:
|
if n != cap:
|
||||||
notes.append(f"shot {i}: {n}f (~{n / fps:.1f}s, from {src})")
|
notes.append(f"shot {i}: {n}f (~{n / fps:.1f}s, from {src})")
|
||||||
@@ -3879,23 +3875,23 @@ def estimate_shot_frames(total_gb, resident_gb, headroom_gb, pixels=None, free_g
|
|||||||
|
|
||||||
|
|
||||||
def resolve_shot_frames(shot_seconds, fps, total_gb, resident_gb, headroom_gb,
|
def resolve_shot_frames(shot_seconds, fps, total_gb, resident_gb, headroom_gb,
|
||||||
allow_oversize=False, pixels=None, free_gb=None):
|
pixels=None, free_gb=None):
|
||||||
"""Returns (frames, note).
|
"""Returns (frames, note).
|
||||||
|
|
||||||
Auto mode (shot_seconds <= 0): frames = the VRAM budget estimate (resolution-
|
Auto mode (shot_seconds <= 0): frames = the VRAM budget estimate (resolution-
|
||||||
scaled). Forced mode: the requested length is clamped DOWN to the budget
|
scaled). Forced mode: the requested length is honored as requested. When VRAM
|
||||||
unless allow_oversize is set. When VRAM is unknown the request is honored."""
|
is unknown the request is also honored."""
|
||||||
budget = estimate_shot_frames(total_gb, resident_gb, headroom_gb, pixels, free_gb)
|
budget = estimate_shot_frames(total_gb, resident_gb, headroom_gb, pixels, free_gb)
|
||||||
if not (shot_seconds and float(shot_seconds) > 0):
|
if not (shot_seconds and float(shot_seconds) > 0):
|
||||||
return budget, ""
|
return budget, ""
|
||||||
requested = align_frame_count(min(H3_MAX_FRAMES, max(5, round(float(shot_seconds) * fps))))
|
requested = align_frame_count(min(H3_MAX_FRAMES, max(5, round(float(shot_seconds) * fps))))
|
||||||
if total_gb <= 0 or requested <= budget:
|
if total_gb <= 0:
|
||||||
return requested, ""
|
return requested, ""
|
||||||
if allow_oversize:
|
if requested > budget:
|
||||||
return requested, (f"OVERSIZE: {requested}f requested vs {budget}f budget -- honoring it; "
|
return requested, (f"requested {requested}f (~{requested/max(1,fps):.1f}s) exceeds the "
|
||||||
f"may spill to system RAM (slow) or OOM")
|
f"~{budget}f VRAM budget -- honoring it; may spill to system RAM "
|
||||||
return budget, (f"requested {requested}f (~{requested/max(1,fps):.1f}s) exceeds the ~{budget}f VRAM "
|
f"(slow) or OOM")
|
||||||
f"budget -- clamped to {budget}f (~{budget/max(1,fps):.1f}s). Set allow_oversize_shots to override")
|
return requested, ""
|
||||||
|
|
||||||
|
|
||||||
def _is_oom(e):
|
def _is_oom(e):
|
||||||
@@ -6047,19 +6043,14 @@ class H3LongVideos:
|
|||||||
"mid-word open-mouth pose. Trims the matching audio tail too. 0 = last frame."}),
|
"mid-word open-mouth pose. Trims the matching audio tail too. 0 = last frame."}),
|
||||||
"shot_seconds": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 15.1, "step": 0.5,
|
"shot_seconds": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 15.1, "step": 0.5,
|
||||||
"forceInput": True,
|
"forceInput": True,
|
||||||
"tooltip": "GLOBAL per-shot ceiling in seconds, not 'force every beat to exactly "
|
"tooltip": "GLOBAL per-shot maximum in seconds. H3 Shot Length is the intended "
|
||||||
"this length'. H3 Shot Length is the intended source because it snaps to "
|
"source because it snaps to H3's frame grid and reports the real duration.\n\n"
|
||||||
"H3's frame grid and reports the real duration.\n\n"
|
|
||||||
"Leave UNCONNECTED for auto: the node picks the largest shot that fits "
|
"Leave UNCONNECTED for auto: the node picks the largest shot that fits "
|
||||||
"the current size/VRAM budget. Connect a value to cap every beat at that "
|
"the current size/VRAM budget. Connect a value to cap every beat at that "
|
||||||
"length. A beat's own `seconds:` directive can still ask for less, and any "
|
"length. A beat's own `seconds:` directive can still ask for less. If the "
|
||||||
"request above H3's hard ~15.1s single-shot limit is clamped. Total video "
|
"requested duration exceeds your hardware, the node will keep it and let the "
|
||||||
"length is the sum of the beat shots, not simply beat-count x this value."}),
|
"render fail instead of shrinking it. Total video length is the sum of the "
|
||||||
"allow_oversize_shots": ("BOOLEAN", {"default": False,
|
"beat shots, not simply beat-count x this value."}),
|
||||||
"tooltip": "OFF (default): a forced shot_seconds that won't fit VRAM is clamped DOWN to "
|
|
||||||
"what fits, and the clamp is reported in info. ON: honor the requested length "
|
|
||||||
"even if it exceeds the budget -- the render may spill into system RAM (slow) "
|
|
||||||
"or OOM. Only affects forced shot_seconds, not auto."}),
|
|
||||||
"vram_headroom_gb": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 32.0, "step": 0.5,
|
"vram_headroom_gb": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 32.0, "step": 0.5,
|
||||||
"tooltip": "Safety margin RESERVED from free VRAM before the node budgets shot length. "
|
"tooltip": "Safety margin RESERVED from free VRAM before the node budgets shot length. "
|
||||||
"Higher = shorter safer shots. Lower = longer shots but more risk of spill or "
|
"Higher = shorter safer shots. Lower = longer shots but more risk of spill or "
|
||||||
@@ -6093,11 +6084,6 @@ class H3LongVideos:
|
|||||||
"decode_tile_size": ("INT", {"default": 0, "min": 0, "max": 1024, "step": 32,
|
"decode_tile_size": ("INT", {"default": 0, "min": 0, "max": 1024, "step": 32,
|
||||||
"tooltip": "Spatial tile size for the VAE decode (tile_x/tile_y). 0 = ComfyUI default. "
|
"tooltip": "Spatial tile size for the VAE decode (tile_x/tile_y). 0 = ComfyUI default. "
|
||||||
"Try 256 on a tight card at 1344x768."}),
|
"Try 256 on a tight card at 1344x768."}),
|
||||||
"cleanup_between_shots": ("BOOLEAN", {"default": True,
|
|
||||||
"tooltip": "Between beats, move each shot's decoded video+audio to system RAM and run "
|
|
||||||
"a full VRAM+RAM purge (GC + CUDA cache), so a long chain doesn't accumulate "
|
|
||||||
"on the GPU and OOM. Recommended on 16GB. Turn off only on a big card where "
|
|
||||||
"you want to skip the per-shot cleanup cost."}),
|
|
||||||
"upscale": (["off", "rtx", "model", "lanczos"], {"default": "off",
|
"upscale": (["off", "rtx", "model", "lanczos"], {"default": "off",
|
||||||
"tooltip": "Optional post-pass on the finished frames. 'rtx' = NVIDIA RTX Video Super "
|
"tooltip": "Optional post-pass on the finished frames. 'rtx' = NVIDIA RTX Video Super "
|
||||||
"Resolution (Tensor Cores -- fastest and best for video; needs the "
|
"Resolution (Tensor Cores -- fastest and best for video; needs the "
|
||||||
@@ -6195,20 +6181,6 @@ class H3LongVideos:
|
|||||||
"When this is filled in, EVERY paragraph of the prompt box is a beat/shot -- "
|
"When this is filled in, EVERY paragraph of the prompt box is a beat/shot -- "
|
||||||
"nothing is consumed as the identity anchor. Put the permanent identity here "
|
"nothing is consumed as the identity anchor. Put the permanent identity here "
|
||||||
"(hair, face, build, age) and the clothing in character_memory."}),
|
"(hair, face, build, age) and the clothing in character_memory."}),
|
||||||
"per_beat_length": ("BOOLEAN", {"default": True,
|
|
||||||
"tooltip": "PACING. Size each shot from what its beat actually stages, instead of giving "
|
|
||||||
"every shot the same length. ON (default): a beat's time is ~2s of setup plus "
|
|
||||||
"~2.5s per action clause, or its spoken line, whichever is longer -- so 'she "
|
|
||||||
"takes off her jacket and drops it on the bench' gets ~7s and a three-part "
|
|
||||||
"beat gets more. OFF: every shot gets the full ceiling. WHY IT MATTERS: a 3s "
|
|
||||||
"action in a 12s shot leaves 9 seconds the model was told nothing about, and "
|
|
||||||
"it fills them by repeating or REVERSING the action -- which is why clothing "
|
|
||||||
"comes off and goes back on. The estimate leans SHORT on purpose: an "
|
|
||||||
"unfinished action is continued by the next shot from the handoff frame, "
|
|
||||||
"while an overlong one is unrecoverable. Never exceeds the ceiling "
|
|
||||||
"(shot_seconds or the VRAM budget) and always lands on the 17n+5 grid. "
|
|
||||||
"Override any single beat with 'seconds: 8' on its own line inside that "
|
|
||||||
"paragraph -- that wins over everything, including this toggle."}),
|
|
||||||
"auto_soundscape": (["off", "fill if blank", "always"], {"default": "fill if blank",
|
"auto_soundscape": (["off", "fill if blank", "always"], {"default": "fill if blank",
|
||||||
"tooltip": "Build the ambient bed from the scene instead of typing one. Reads the "
|
"tooltip": "Build the ambient bed from the scene instead of typing one. Reads the "
|
||||||
"ANCHOR (the soundscape is global, so it must describe the PLACE, not "
|
"ANCHOR (the soundscape is global, so it must describe the PLACE, not "
|
||||||
@@ -6509,9 +6481,8 @@ class H3LongVideos:
|
|||||||
shift_video=12.0, shift_audio=3.0, trim_seam=True, vary_seed_per_shot=False,
|
shift_video=12.0, shift_audio=3.0, trim_seam=True, vary_seed_per_shot=False,
|
||||||
handoff_offset=0, vram_headroom_gb=1.5, allow_res_backoff=True,
|
handoff_offset=0, vram_headroom_gb=1.5, allow_res_backoff=True,
|
||||||
decode_tile_frames=0, decode_tile_size=0,
|
decode_tile_frames=0, decode_tile_size=0,
|
||||||
cleanup_between_shots=True,
|
anchor_override="", shot_seconds=0.0,
|
||||||
anchor_override="", shot_seconds=0.0, allow_oversize_shots=False,
|
beat_split="auto",
|
||||||
per_beat_length=True, beat_split="auto",
|
|
||||||
character_memory="", auto_wardrobe=True, auto_props=True, prevent_nudity=True,
|
character_memory="", auto_wardrobe=True, auto_props=True, prevent_nudity=True,
|
||||||
exposed_terms="", anatomy_guard="auto", lock_restraints=True,
|
exposed_terms="", anatomy_guard="auto", lock_restraints=True,
|
||||||
solidity_guard="auto", motion_guard="auto", contact_guard="auto",
|
solidity_guard="auto", motion_guard="auto", contact_guard="auto",
|
||||||
@@ -6661,7 +6632,7 @@ class H3LongVideos:
|
|||||||
lora_gb = lora_overhead_gb(model)
|
lora_gb = lora_overhead_gb(model)
|
||||||
eff_headroom = vram_headroom_gb + lora_gb
|
eff_headroom = vram_headroom_gb + lora_gb
|
||||||
ln, ln_note = resolve_shot_frames(shot_seconds, fps, total_gb, resident_gb,
|
ln, ln_note = resolve_shot_frames(shot_seconds, fps, total_gb, resident_gb,
|
||||||
eff_headroom, allow_oversize_shots, w * h, free_gb)
|
eff_headroom, w * h, free_gb)
|
||||||
if lora_gb:
|
if lora_gb:
|
||||||
ln_note = ((ln_note + " ") if ln_note else "") + (
|
ln_note = ((ln_note + " ") if ln_note else "") + (
|
||||||
f"reserved ~{lora_gb:.1f}GB for bypass-LoRA adapters (they stay resident in bf16 "
|
f"reserved ~{lora_gb:.1f}GB for bypass-LoRA adapters (they stay resident in bf16 "
|
||||||
@@ -6733,14 +6704,13 @@ class H3LongVideos:
|
|||||||
# this". Forcing a length used to DISABLE per-beat sizing entirely, which is
|
# this". Forcing a length used to DISABLE per-beat sizing entirely, which is
|
||||||
# why a plan made with a forced length disagreed with the auto render: two
|
# why a plan made with a forced length disagreed with the auto render: two
|
||||||
# different code paths for the same question.
|
# different code paths for the same question.
|
||||||
lens, len_notes = plan_beat_frames(beats, fps, ln, per_beat=bool(per_beat_length))
|
lens, len_notes = plan_beat_frames(beats, fps, ln)
|
||||||
secs = [n / fps for n in lens]
|
secs = [n / fps for n in lens]
|
||||||
if len_notes:
|
if len_notes:
|
||||||
n_short = sum(1 for n in lens if n < ln)
|
n_short = sum(1 for n in lens if n < ln)
|
||||||
ln_note = ((ln_note + " ") if ln_note else "") + (
|
ln_note = ((ln_note + " ") if ln_note else "") + (
|
||||||
f"per-beat pacing sized {n_short} of {len(lens)} shot(s) under the {ln}f "
|
f"content pacing sized {n_short} of {len(lens)} shot(s) under the {ln}f "
|
||||||
f"(~{ln / fps:.1f}s) ceiling from their own content: " + "; ".join(len_notes)
|
f"(~{ln / fps:.1f}s) ceiling from their own content: " + "; ".join(len_notes))
|
||||||
+ ". Turn per_beat_length OFF to give every shot the full ceiling")
|
|
||||||
# With pacing OFF, every beat gets the ceiling whether it has anything to fill
|
# With pacing OFF, every beat gets the ceiling whether it has anything to fill
|
||||||
# it with or not -- so say which beats are too thin for the length they got.
|
# it with or not -- so say which beats are too thin for the length they got.
|
||||||
# This is the failure that reads as an action repeating or playing backwards.
|
# This is the failure that reads as an action repeating or playing backwards.
|
||||||
@@ -6749,16 +6719,14 @@ class H3LongVideos:
|
|||||||
ln_note = ((ln_note + " ") if ln_note else "") + (
|
ln_note = ((ln_note + " ") if ln_note else "") + (
|
||||||
"THIN BEATS -- the model must invent the remaining time, which it fills by "
|
"THIN BEATS -- the model must invent the remaining time, which it fills by "
|
||||||
"repeating or REVERSING the action: " + "; ".join(pace_warnings)
|
"repeating or REVERSING the action: " + "; ".join(pace_warnings)
|
||||||
+ ". Add a second clause to the beat, set 'seconds:' on it, or turn "
|
+ ". Add a second clause to the beat, or set 'seconds:' on it")
|
||||||
"per_beat_length ON to size shots from their content")
|
|
||||||
fit_warnings = dialogue_fit_warnings(beats, secs)
|
fit_warnings = dialogue_fit_warnings(beats, secs)
|
||||||
# The opposite error, and the one that babbles: far more shot than line.
|
# The opposite error, and the one that babbles: far more shot than line.
|
||||||
filler_warnings = dialogue_filler_warnings(beats, secs)
|
filler_warnings = dialogue_filler_warnings(beats, secs)
|
||||||
if filler_warnings:
|
if filler_warnings:
|
||||||
ln_note = ((ln_note + " ") if ln_note else "") + (
|
ln_note = ((ln_note + " ") if ln_note else "") + (
|
||||||
"BABBLE RISK -- " + "; ".join(filler_warnings)
|
"BABBLE RISK -- " + "; ".join(filler_warnings)
|
||||||
+ ". Turn per_beat_length ON to size these shots from their line, or set "
|
+ ". Set 'seconds:' on the beat if you want a fixed longer take")
|
||||||
"'seconds:' on the beat")
|
|
||||||
wardrobe_notes = []
|
wardrobe_notes = []
|
||||||
strip_shots = [] # shots that newly bared a zone -> the NEXT shot starts fresh
|
strip_shots = [] # shots that newly bared a zone -> the NEXT shot starts fresh
|
||||||
raw_gens = distribute_generations(anchor, beats, global_soundscape.strip(),
|
raw_gens = distribute_generations(anchor, beats, global_soundscape.strip(),
|
||||||
@@ -6947,6 +6915,7 @@ class H3LongVideos:
|
|||||||
continuity_used = []
|
continuity_used = []
|
||||||
ref_aug_used = []
|
ref_aug_used = []
|
||||||
shot_timings = []
|
shot_timings = []
|
||||||
|
cleanup_between_shots = True
|
||||||
if cleanup_between_shots:
|
if cleanup_between_shots:
|
||||||
_deep_cleanup() # start the first (heaviest) shot with max free VRAM
|
_deep_cleanup() # start the first (heaviest) shot with max free VRAM
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const GROUPS = [
|
|||||||
id: "prompt",
|
id: "prompt",
|
||||||
label: "Prompt",
|
label: "Prompt",
|
||||||
defaultCollapsed: false,
|
defaultCollapsed: false,
|
||||||
widgets: ["prompt", "resolution", "megapixels", "beat_split", "anchor_override", "per_beat_length", "shot_seconds", "plan_only", "fps"],
|
widgets: ["prompt", "resolution", "megapixels", "beat_split", "anchor_override", "shot_seconds", "plan_only", "fps"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "refs",
|
id: "refs",
|
||||||
@@ -27,8 +27,8 @@ const GROUPS = [
|
|||||||
widgets: [
|
widgets: [
|
||||||
"steps", "cfg", "sampler_name", "scheduler", "seed",
|
"steps", "cfg", "sampler_name", "scheduler", "seed",
|
||||||
"apply_model_sampling", "shift_video", "shift_audio",
|
"apply_model_sampling", "shift_video", "shift_audio",
|
||||||
"allow_oversize_shots", "vram_headroom_gb", "allow_res_backoff",
|
"vram_headroom_gb", "allow_res_backoff",
|
||||||
"decode_tile_frames", "decode_tile_size", "cleanup_between_shots",
|
"decode_tile_frames", "decode_tile_size",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -734,14 +734,27 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
|||||||
|
|
||||||
for index in range(1, 10):
|
for index in range(1, 10):
|
||||||
self.assertNotIn(f"ref_image_{index}", optional)
|
self.assertNotIn(f"ref_image_{index}", optional)
|
||||||
|
self.assertNotIn("per_beat_length", optional)
|
||||||
|
self.assertNotIn("cleanup_between_shots", optional)
|
||||||
|
|
||||||
def test_shot_seconds_tooltip_describes_ceiling_behavior(self):
|
def test_shot_seconds_tooltip_describes_ceiling_behavior(self):
|
||||||
optional = self.module.H3LongVideos.INPUT_TYPES()["optional"]
|
optional = self.module.H3LongVideos.INPUT_TYPES()["optional"]
|
||||||
tooltip = optional["shot_seconds"][1]["tooltip"]
|
tooltip = optional["shot_seconds"][1]["tooltip"]
|
||||||
|
|
||||||
self.assertIn("GLOBAL per-shot ceiling", tooltip)
|
self.assertIn("GLOBAL per-shot maximum", tooltip)
|
||||||
self.assertIn("not 'force every beat to exactly this length'", tooltip)
|
self.assertIn("A beat's own `seconds:` directive can still ask for less", tooltip)
|
||||||
self.assertIn("hard ~15.1s single-shot limit", tooltip)
|
self.assertIn("honoring it; may spill to system RAM (slow) or OOM", tooltip)
|
||||||
|
|
||||||
|
def test_resolve_shot_frames_honors_forced_request_over_budget(self):
|
||||||
|
original_estimate_shot_frames = self.module.estimate_shot_frames
|
||||||
|
try:
|
||||||
|
self.module.estimate_shot_frames = lambda *_args, **_kwargs: 73
|
||||||
|
frames, note = self.module.resolve_shot_frames(10.0, 24, 16.0, 8.0, 1.5)
|
||||||
|
|
||||||
|
self.assertEqual(frames, 243)
|
||||||
|
self.assertIn("honoring it", note)
|
||||||
|
finally:
|
||||||
|
self.module.estimate_shot_frames = original_estimate_shot_frames
|
||||||
|
|
||||||
def test_run_defaults_match_declared_ref_widget_defaults(self):
|
def test_run_defaults_match_declared_ref_widget_defaults(self):
|
||||||
node = self.module.H3LongVideos()
|
node = self.module.H3LongVideos()
|
||||||
@@ -1152,7 +1165,7 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
|||||||
module.quant_accel_note = lambda *_args, **_kwargs: ""
|
module.quant_accel_note = lambda *_args, **_kwargs: ""
|
||||||
module.lora_active = lambda _model: False
|
module.lora_active = lambda _model: False
|
||||||
module.resolve_shot_frames = lambda *_args, **_kwargs: (73, "")
|
module.resolve_shot_frames = lambda *_args, **_kwargs: (73, "")
|
||||||
module.plan_beat_frames = lambda beats, fps, budget, per_beat=True: ([73] * len(beats), [])
|
module.plan_beat_frames = lambda beats, fps, budget: ([73] * len(beats), [])
|
||||||
module.dialogue_fit_warnings = lambda *_args, **_kwargs: []
|
module.dialogue_fit_warnings = lambda *_args, **_kwargs: []
|
||||||
module.dialogue_filler_warnings = lambda *_args, **_kwargs: []
|
module.dialogue_filler_warnings = lambda *_args, **_kwargs: []
|
||||||
module.distribute_generations = lambda anchor, beats, *_args, **_kwargs: [
|
module.distribute_generations = lambda anchor, beats, *_args, **_kwargs: [
|
||||||
|
|||||||
Reference in New Issue
Block a user