Use named character refs for H3 image conditioning

This commit is contained in:
2026-08-28 09:52:02 +00:00
parent af226987d7
commit 9b862928ba
2 changed files with 46 additions and 7 deletions
+27 -7
View File
@@ -4501,11 +4501,31 @@ def resolve_tagged_refs(text, ref_list):
return f"<Picture {renumber[n]}>" if n in renumber else "" return f"<Picture {renumber[n]}>" if n in renumber else ""
out = _PICTURE_TAG.sub(sub, text or "") out = _PICTURE_TAG.sub(sub, text or "")
if dropped: # tidy the gap a removed tag leaves behind if dropped: # tidy the gap a removed tag leaves behind
out = re.sub(r"\s+([,.;:])", r"\1", out) out = re.sub(r"\s+([,.;:])", r"\1", out)
out = re.sub(r"(,\s*){2,}", ", ", out) out = re.sub(r"(,\s*){2,}", ", ", out)
out = re.sub(r"\s{2,}", " ", out) out = re.sub(r"\s{2,}", " ", out)
return out.strip(), [ref_list[n - 1] for n in live], dropped return out.strip(), [ref_list[n - 1] for n in live], dropped
def resolve_prompt_refs(text, ref_list):
"""(rewritten text, refs, dropped) for the refs a shot actually carries.
Explicit <Picture N> tags still decide which slot numbers the prompt points at,
but named character matches must ride into the real ref-image list too. Without
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
understood but the faces were ignored."""
rewritten, tagged_refs, dropped = resolve_tagged_refs(text, ref_list)
refs = list(tagged_refs)
seen = {id(ref) for ref in refs}
for _slot_number, ref in _named_character_refs_for_text(rewritten, ref_list):
marker = id(ref)
if marker in seen:
continue
seen.add(marker)
refs.append(ref)
return rewritten, refs, dropped
def shot_references(ref_list, ref_mode, shot_index, handoff): def shot_references(ref_list, ref_mode, shot_index, handoff):
@@ -6621,7 +6641,7 @@ 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_tagged_refs(gen, ref_slots)[1]: if resolve_prompt_refs(gen, ref_slots)[1]:
on.append(shot_index + 1) on.append(shot_index + 1)
tagged_used = True tagged_used = True
else: else:
@@ -6715,7 +6735,7 @@ class H3LongVideos:
# The prompt itself says where each reference belongs: the shot whose # The prompt itself says where each reference belongs: the shot whose
# text names <Picture N> gets image N, renumbered to match what that # text names <Picture N> gets image N, renumbered to match what that
# shot actually carries. Every untagged shot keeps its handoff. # shot actually carries. Every untagged shot keeps its handoff.
gen_prompt, shot_refs, dropped = resolve_tagged_refs(gen_prompt, ref_slots) gen_prompt, shot_refs, dropped = resolve_prompt_refs(gen_prompt, 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)
+19
View File
@@ -388,6 +388,25 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
) )
self.assertEqual(dropped, [4]) self.assertEqual(dropped, [4])
def test_resolve_prompt_refs_keeps_named_character_images_alongside_tagged_location(self):
refs = [
{"kind": "character", "image": "img1", "name": "Mara"},
{"kind": "character", "image": "img2", "name": "Jon"},
{"kind": "location", "image": "img3", "name": "Hangar"},
]
text, references, dropped = self.module.resolve_prompt_refs(
"Mara and Jon argue inside <Picture 3>.",
refs,
)
self.assertEqual(text, "Mara and Jon argue inside <Picture 1>.")
self.assertEqual(
[self.module._reference_image(ref) for ref in references],
["img3", "img1", "img2"],
)
self.assertEqual(dropped, [])
def test_shot_references_uses_all_connected_sparse_slots(self): def test_shot_references_uses_all_connected_sparse_slots(self):
refs = [ refs = [
None, None,