Restore generic 'scene N' split — but literal-only (Movie Editor needs it)

Dropping the generic scene-N pattern broke the Movie Editor: build_combined_prompt
(for_generation) INJECTS 'scene N' markers into the generation prompt to force a
split for any unit whose text lacks a leading transition trigger. Without that
recognition the second unit never split -> 'scene 2 exists only on the timeline,
not generated'.

split_scenes now matches the generic scene-N label again, but ONLY in its
literal-text scanner (never over shortcut expansions). So an editor-injected /
user-typed 'scene 2' cuts, while a 'scene 2' buried inside a shortcut expansion
does not — fixing both the user's phantom split AND the broken montage.

For the reported prompt the piece-walker now returns 2 scenes (was 4), matching
the old generation splitter's 2 -> the guard should pass and keys attribute while
the chain/guides keep working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-15 12:39:52 +03:00
co-authored by Claude Opus 4.8
parent 38509ea0d3
commit 2fae920839
2 changed files with 33 additions and 11 deletions
+14 -11
View File
@@ -886,17 +886,20 @@ def split_scenes(prompt, placement="start"):
_open(effect)
segs[-1]["raw"] += raw_t; segs[-1]["exp"] += exp_t
# Literal-text trigger matcher (custom triggers only; never the generic scene-N pattern).
triggers = [t for t in custom_map.keys() if t]
lit_pat = None
if triggers:
def _tp(t):
end = r"\b" if re.search(r"\w$", t) else r"(?=\s|$)"
return r"\b" + re.escape(t) + end
try:
lit_pat = re.compile("|".join(_tp(t) for t in sorted(triggers, key=len, reverse=True)), re.IGNORECASE)
except re.error:
lit_pat = None
# Literal-text trigger matcher: the user's custom triggers PLUS the generic `scene <N>` label.
# Scoped to LITERAL text only (this matcher never runs over shortcut expansions), so a "scene 2"
# the user types — or, crucially, the one the Movie Editor INJECTS into the generation prompt to
# force a split for a scene with no leading trigger (see build_combined_prompt for_generation) —
# still cuts, while a "scene 2" buried inside a shortcut's expansion does NOT.
def _tp(t):
end = r"\b" if re.search(r"\w$", t) else r"(?=\s|$)"
return r"\b" + re.escape(t) + end
_pats = [_tp(t) for t in sorted((t for t in custom_map.keys() if t), key=len, reverse=True)]
_pats.append(_GENERIC_SCENE_LABEL_PATTERN)
try:
lit_pat = re.compile("|".join(_pats), re.IGNORECASE)
except re.error:
lit_pat = None
for pc in pieces:
raw = text[pc.get("orig_start", 0):pc.get("orig_end", 0)]
+19
View File
@@ -245,6 +245,25 @@ def test_split_scenes_ignores_triggers_inside_expansions(monkeypatch):
assert "Scene cut." not in out["scenes"][1]["expanded"] # silent trigger dropped
def test_split_scenes_literal_scene_label_still_splits(monkeypatch):
"""The generic 'scene N' label MUST still split when it's LITERAL text — the Movie Editor
injects it into the generation prompt to force a split for a unit with no leading trigger
(build_combined_prompt for_generation). But the SAME label inside a shortcut expansion must NOT
split (that was the user's phantom 'scene 2'). One rule: literal cuts, expansion-content doesn't."""
db = _db({"qcut": (["qcut"], ["rapid cut"], ""),
"tth": (["tth"], ["a backflip"], ""),
"cof": (["cof"], ["sitting, scene 9 of many"], "cs")}) # 'scene 9' INSIDE expansion
monkeypatch.setattr(templates, "load_shortcut_db", lambda: db)
monkeypatch.setattr(templates, "load_custom_transition_triggers",
lambda: {"qcut": {"placement": "start", "visual_effect": "none"}})
# explicit qcut leads scene 0; the editor-injected literal 'scene 2' leads scene 1
out = conditioning.split_scenes("CHAR a woman man qcut tth scene 2 cof")
assert len(out["scenes"]) == 2
assert out["scenes"][0]["keys"] == set() # tth has no key
assert out["scenes"][1]["keys"] == {"cs"} # cof, despite 'scene 9' in its expansion
assert out["anchor"] == "CHAR a woman man"
def test_single_scene_with_custom_key_is_detected(monkeypatch):
"""Regression: a custom key fired in a single-logical-scene prompt MUST be detected (was
silently dropped to empty when the old code compared scene counts and fell back)."""