Optimize long videos ref bookkeeping

This commit is contained in:
2026-08-28 22:09:12 +00:00
parent e29d01ad24
commit 9cbab72e86
+46 -18
View File
@@ -4422,10 +4422,10 @@ def _named_refs_for_text(text, ref_slots, kinds=None):
return matched return matched
def _matched_reference_slots(text, ref_slots): def _matched_reference_slots(text, ref_slots, normalized_refs=None):
matched = [] matched = []
seen = set() seen = set()
normalized_slots = _normalized_ref_slots(ref_slots) normalized_slots = normalized_refs if normalized_refs is not None else _normalized_ref_slots(ref_slots)
for slot_number, ref in ( for slot_number, ref in (
_slot_refs_for_text(text, normalized_slots) _slot_refs_for_text(text, normalized_slots)
+ _named_refs_for_text(text, normalized_slots, kinds=("character", "location")) + _named_refs_for_text(text, normalized_slots, kinds=("character", "location"))
@@ -4437,9 +4437,16 @@ def _matched_reference_slots(text, ref_slots):
return matched return matched
def _reference_context_for_text(text, ref_slots, include_character_wardrobe=True): def _reference_context_for_text(
text,
ref_slots,
include_character_wardrobe=True,
normalized_refs=None,
):
parts = [] parts = []
for slot_number, ref in _matched_reference_slots(text, ref_slots): if normalized_refs is None:
normalized_refs = _normalized_ref_slots(ref_slots)
for slot_number, ref in _matched_reference_slots(text, ref_slots, normalized_refs=normalized_refs):
label = _reference_text(ref.get("name")) or _reference_text(ref.get("id")) or f"reference {slot_number}" label = _reference_text(ref.get("name")) or _reference_text(ref.get("id")) or f"reference {slot_number}"
description = _reference_sentence(ref.get("description")) description = _reference_sentence(ref.get("description"))
wardrobe = (_reference_sentence(ref.get("wardrobe")) wardrobe = (_reference_sentence(ref.get("wardrobe"))
@@ -4480,10 +4487,12 @@ def _inject_reference_context(block, context):
) )
def _reference_character_memory(ref_slots): def _reference_character_memory(ref_slots, normalized_refs=None):
lines = [] lines = []
seen = set() seen = set()
for slot_number, ref in enumerate(_normalized_ref_slots(ref_slots), 1): if normalized_refs is None:
normalized_refs = _normalized_ref_slots(ref_slots)
for slot_number, ref in enumerate(normalized_refs, 1):
if ref is None: if ref is None:
continue continue
if ref.get("kind") != "character": if ref.get("kind") != "character":
@@ -4504,12 +4513,14 @@ def _reference_character_memory(ref_slots):
return "\n".join(lines) return "\n".join(lines)
def _connected_refs(ref_slots): def _connected_refs(ref_slots, normalized_refs=None):
"""Connected refs only, preserving slot order and skipping empty sockets.""" """Connected refs only, preserving slot order and skipping empty sockets."""
return [ref for ref in _normalized_ref_slots(ref_slots) if _reference_image(ref) is not None] if normalized_refs is None:
normalized_refs = _normalized_ref_slots(ref_slots)
return [ref for ref in normalized_refs if _reference_image(ref) is not None]
def resolve_tagged_refs(text, ref_list): def resolve_tagged_refs(text, ref_list, normalized_refs=None):
"""(rewritten text, images, dropped) for the <Picture N> tags in ONE shot. """(rewritten text, images, dropped) for the <Picture N> tags in ONE shot.
The tokenizer numbers references by their position in the list it is handed, so The tokenizer numbers references by their position in the list it is handed, so
@@ -4521,6 +4532,7 @@ def resolve_tagged_refs(text, ref_list):
A tag naming a slot with no image connected refers to nothing at all, so it is A tag naming a slot with no image connected refers to nothing at all, so it is
removed from the text rather than left to confuse the encoder, and reported.""" removed from the text rather than left to confuse the encoder, and reported."""
wanted = picture_tags(text) wanted = picture_tags(text)
if normalized_refs is None:
normalized_refs = _normalized_ref_slots(ref_list) normalized_refs = _normalized_ref_slots(ref_list)
live = [n for n in wanted if 1 <= n <= len(normalized_refs) and _reference_image(normalized_refs[n - 1]) is not None] live = [n for n in wanted if 1 <= n <= len(normalized_refs) and _reference_image(normalized_refs[n - 1]) is not None]
dropped = [n for n in wanted if n not in live] dropped = [n for n in wanted if n not in live]
@@ -4538,7 +4550,7 @@ def resolve_tagged_refs(text, ref_list):
return out.strip(), [normalized_refs[n - 1] for n in live], dropped return out.strip(), [normalized_refs[n - 1] for n in live], dropped
def resolve_prompt_refs(text, ref_list, include_named=True): def resolve_prompt_refs(text, ref_list, include_named=True, normalized_refs=None):
"""(rewritten text, refs, dropped) for the refs a shot actually carries. """(rewritten text, refs, dropped) for the refs a shot actually carries.
Explicit <Picture N> tags still decide which slot numbers the prompt points at, Explicit <Picture N> tags still decide which slot numbers the prompt points at,
@@ -4546,8 +4558,9 @@ def resolve_prompt_refs(text, ref_list, include_named=True):
that split, a shot could inherit the facts/context for "Mara" and "Jon" while that split, a shot could inherit the facts/context for "Mara" and "Jon" while
only carrying a tagged location image, which reads exactly like the names were only carrying a tagged location image, which reads exactly like the names were
understood but the faces were ignored.""" understood but the faces were ignored."""
if normalized_refs is None:
normalized_refs = _normalized_ref_slots(ref_list) normalized_refs = _normalized_ref_slots(ref_list)
rewritten, tagged_refs, dropped = resolve_tagged_refs(text, normalized_refs) rewritten, tagged_refs, dropped = resolve_tagged_refs(text, normalized_refs, normalized_refs=normalized_refs)
refs = list(tagged_refs) refs = list(tagged_refs)
seen = {id(ref) for ref in refs} seen = {id(ref) for ref in refs}
if include_named: if include_named:
@@ -4560,7 +4573,7 @@ def resolve_prompt_refs(text, ref_list, include_named=True):
return rewritten, refs, dropped return rewritten, refs, dropped
def shot_references(ref_list, ref_mode, shot_index, handoff): def shot_references(ref_list, ref_mode, shot_index, handoff, connected_refs=None):
"""Pure: which reference images shot `shot_index` is conditioned on, or [] when """Pure: which reference images shot `shot_index` is conditioned on, or [] when
the shot should use the keyframe handoff instead. the shot should use the keyframe handoff instead.
@@ -4586,7 +4599,7 @@ def shot_references(ref_list, ref_mode, shot_index, handoff):
back as a soft signal (the model is shown where the last back as a soft signal (the model is shown where the last
shot ended rather than told to start exactly there), and it shot ended rather than told to start exactly there), and it
stays a single ref2va task, so nothing conflicts.""" stays a single ref2va task, so nothing conflicts."""
refs = _connected_refs(ref_list) refs = connected_refs if connected_refs is not None else _connected_refs(ref_list)
if not refs: if not refs:
return [] return []
if ref_mode == "auto ref2v": if ref_mode == "auto ref2v":
@@ -6397,7 +6410,9 @@ class H3LongVideos:
), legacy_ref_slots) ), legacy_ref_slots)
) )
ref_slots = direct_ref_slots ref_slots = direct_ref_slots
direct_ref_count = len(_connected_refs(direct_ref_slots)) normalized_ref_slots = _normalized_ref_slots(ref_slots)
connected_refs = [ref for ref in normalized_ref_slots if _reference_image(ref) is not None]
direct_ref_count = len(connected_refs)
explicit_character_memory = (character_memory or "").strip() explicit_character_memory = (character_memory or "").strip()
derived_character_memory = _reference_character_memory(ref_slots) derived_character_memory = _reference_character_memory(ref_slots)
effective_character_memory = explicit_character_memory or derived_character_memory effective_character_memory = explicit_character_memory or derived_character_memory
@@ -6620,6 +6635,7 @@ class H3LongVideos:
block, block,
ref_slots, ref_slots,
include_character_wardrobe=not bool(explicit_character_memory), include_character_wardrobe=not bool(explicit_character_memory),
normalized_refs=normalized_ref_slots,
) )
if context: if context:
block = _inject_reference_context(block, context) block = _inject_reference_context(block, context)
@@ -6696,7 +6712,7 @@ class H3LongVideos:
else "prompt/soundscape silencing only")) else "prompt/soundscape silencing only"))
# Same reference accounting the render reports: which shots lose the # Same reference accounting the render reports: which shots lose the
# handoff is a composition decision, so it belongs in the preview. # handoff is a composition decision, so it belongs in the preview.
n_refs = len(_connected_refs(ref_slots)) n_refs = len(connected_refs)
plan_ref = "" plan_ref = ""
if n_refs: if n_refs:
# Mirror the render's placement exactly: 'where tagged' reads the # Mirror the render's placement exactly: 'where tagged' reads the
@@ -6709,7 +6725,12 @@ class H3LongVideos:
for shot_index, gen in enumerate(gens): for shot_index, gen in enumerate(gens):
shot_mode = beat_ref_mode_directive(beats[shot_index] if shot_index < len(beats) else "") or ref_mode shot_mode = beat_ref_mode_directive(beats[shot_index] if shot_index < len(beats) else "") or ref_mode
if shot_mode in ("where tagged", "auto ref2v") and any_tags_anywhere: if shot_mode in ("where tagged", "auto ref2v") and any_tags_anywhere:
if resolve_prompt_refs(gen, ref_slots, include_named=(shot_mode == "auto ref2v"))[1]: if resolve_prompt_refs(
gen,
ref_slots,
include_named=(shot_mode == "auto ref2v"),
normalized_refs=normalized_ref_slots,
)[1]:
on.append(shot_index + 1) on.append(shot_index + 1)
tagged_used = True tagged_used = True
else: else:
@@ -6768,7 +6789,7 @@ class H3LongVideos:
latent_chunks = [] # per-shot sampled latents, pre-decode latent_chunks = [] # per-shot sampled latents, pre-decode
mouth_settled = [] # shots seeded from a settled (closed) mouth mouth_settled = [] # shots seeded from a settled (closed) mouth
handoff, sr = first_frame, None handoff, sr = first_frame, None
connected_ref_count = len(_connected_refs(ref_slots)) connected_ref_count = len(connected_refs)
ref_shots = [] # which shots ended up ref-conditioned ref_shots = [] # which shots ended up ref-conditioned
ref_missing = [] # <Picture N> tags naming an unconnected slot ref_missing = [] # <Picture N> tags naming an unconnected slot
ref_carried = [] # tagged shots that kept continuity as an extra ref ref_carried = [] # tagged shots that kept continuity as an extra ref
@@ -6807,12 +6828,19 @@ class H3LongVideos:
gen_prompt, gen_prompt,
ref_slots, ref_slots,
include_named=(shot_mode == "auto ref2v"), include_named=(shot_mode == "auto ref2v"),
normalized_refs=normalized_ref_slots,
) )
for n in dropped: for n in dropped:
if n not in ref_missing: if n not in ref_missing:
ref_missing.append(n) ref_missing.append(n)
else: else:
shot_refs = shot_references(ref_slots, shot_mode_eff, i, handoff) shot_refs = shot_references(
ref_slots,
shot_mode_eff,
i,
handoff,
connected_refs=connected_refs,
)
# A shot that follows a strip starts FRESH. Continuing from a frame that # A shot that follows a strip starts FRESH. Continuing from a frame that
# still shows the garment is how it reappears -- the picture outvotes the # still shows the garment is how it reappears -- the picture outvotes the
# text every time. Costs a cut exactly where the state changes, which is # text every time. Costs a cut exactly where the state changes, which is