opencut_ui: split_scenes cuts on expansion-keyed transitions (scene-drop fix)
The canonical generation/preview/key splitter (split_scenes) only treated a shortcut as a scene cut when the shortcut's TRIGGER word was a key in the transition DB. But split markers are commonly keyed by the EXPANSION phrase: a "cut" shortcut expands to "Scene cut.", and the split marker is "scene cut.". So the lookup `custom_map.get(_norm(raw))` missed, the shortcut was treated as plain content, and NO cut was made — split_scenes returned one scene for "A cut B cut C" while the verbatim editor split returned three. That disagreement collapsed the timeline's middle scenes (and would have generated one scene). Fix: when the trigger word isn't in the DB, also match the shortcut's EXPANSION (`custom_map.get(_norm(exp))`). The generic "scene <N>" label is still NOT matched on expansions, so an incidental "scene 2" inside a content shortcut never cuts. Verified against the user's on-disk cut/qcut defs: split_scenes now agrees with the verbatim split (2 scenes for cut x2 / qcut x2 / mixed). Added regression tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7ed0717f73
commit
9d69f4bef8
@@ -905,7 +905,15 @@ def split_scenes(prompt, placement="start"):
|
||||
raw = text[pc.get("orig_start", 0):pc.get("orig_end", 0)]
|
||||
exp = expanded_full[pc.get("exp_start", 0):pc.get("exp_end", 0)]
|
||||
if pc.get("is_shortcut"):
|
||||
# A shortcut is a transition when its TRIGGER word is in the DB (e.g. a literal
|
||||
# "qcut" entry) OR when its EXPANSION resolves to a transition phrase (the common
|
||||
# case: a "cut" shortcut expanding to the "Scene cut." split phrase). Match either,
|
||||
# so an expansion-keyed split marker still cuts here exactly as it does in the
|
||||
# verbatim editor split. Generic "scene <N>" is intentionally NOT matched on
|
||||
# expansions — only user-defined custom triggers — so incidental labels never cut.
|
||||
info = custom_map.get(_norm(raw))
|
||||
if info is None:
|
||||
info = custom_map.get(_norm(exp))
|
||||
if info is not None: # transition-trigger shortcut
|
||||
_transition(raw, exp, info)
|
||||
else: # content shortcut: text + its key
|
||||
|
||||
@@ -83,6 +83,41 @@ def test_verbatim_split_shortcut_driven_split_still_works(monkeypatch):
|
||||
assert "<break>" in v["anchor"]
|
||||
|
||||
|
||||
def test_split_scenes_splits_on_expansion_keyed_transition(monkeypatch):
|
||||
"""Regression: split_scenes (the canonical generation/preview/key splitter) must cut when a
|
||||
shortcut's EXPANSION is a transition phrase (e.g. 'cut'->'Scene cut.'), not only when the
|
||||
shortcut's TRIGGER word is itself in the transition DB. It previously matched only the raw
|
||||
trigger, so an expansion-keyed split marker produced ONE scene here while the verbatim editor
|
||||
split produced TWO — and the disagreement collapsed the timeline's middle scenes."""
|
||||
db = _db({
|
||||
"cut": (["cut"], ["Scene cut."], ""),
|
||||
"qcut": (["qcut"], ["The video rapidly cuts, showing the next view."], ""),
|
||||
})
|
||||
monkeypatch.setattr(templates, "load_shortcut_db", lambda: db)
|
||||
monkeypatch.setattr(templates, "load_custom_transition_triggers", lambda: {
|
||||
"scene cut.": {"placement": "silent", "visual_effect": None},
|
||||
"the video rapidly cuts, showing the next view.": {"placement": "start", "visual_effect": None},
|
||||
})
|
||||
prompt = "a dog runs cut a cat sleeps cut a bird flies"
|
||||
s = conditioning.split_scenes(prompt)
|
||||
assert len(s["scenes"]) == 2 # silent 'cut' x2 -> anchor + 2 scenes
|
||||
assert s["anchor_expanded"] == "a dog runs"
|
||||
# The canonical split must agree with the verbatim editor split (no collapse).
|
||||
v = conditioning.split_timeline_verbatim(prompt)
|
||||
assert len(v["scenes"]) == len(s["scenes"])
|
||||
# Non-silent 'qcut' too.
|
||||
assert len(conditioning.split_scenes("a dog runs qcut a cat sleeps qcut a bird flies")["scenes"]) == 2
|
||||
|
||||
|
||||
def test_split_scenes_no_false_cut_from_generic_label_in_expansion(monkeypatch):
|
||||
"""A content shortcut whose expansion merely CONTAINS 'scene 2' must NOT cut — only a
|
||||
user-defined custom transition phrase does (the generic label is never matched on expansions)."""
|
||||
db = _db({"desc": (["desc"], ["a wide shot like scene 2 of a film"], "")})
|
||||
monkeypatch.setattr(templates, "load_shortcut_db", lambda: db)
|
||||
monkeypatch.setattr(templates, "load_custom_transition_triggers", lambda: {})
|
||||
assert len(conditioning.split_scenes("a dog runs desc here")["scenes"]) == 1
|
||||
|
||||
|
||||
def test_verbatim_split_still_splits_on_literal_cuts(monkeypatch):
|
||||
"""The fix must not suppress real, user-typed scene cuts."""
|
||||
db = _db({"closeup": (["<closeup>"], ["a tight close-up"], "closeupkey")}) # no cut in replacement
|
||||
|
||||
Reference in New Issue
Block a user