fix: the rewriter's own cut openers and views never become camera targets

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-09-30 18:17:04 +03:00
co-authored by Claude Sonnet 5.5
parent efc5757950
commit 2abd7fc30f
2 changed files with 46 additions and 2 deletions
+21 -2
View File
@@ -232,7 +232,7 @@ def add_camera_moves(text, seed=None, chance=1.0):
bodies = [text[m.end():(marks[i + 1].start() if i + 1 < len(marks) else len(text))]
for i, m in enumerate(marks)]
parts = [_split_sound(b) for b in bodies]
cands = [candidates(p[0]) for p in parts]
cands = [candidates(own_words_removed(p[0])) for p in parts]
header_lemmas = {c[0] for c in candidates(header)}
# In (nearly) every shot = about the whole prompt, not this shot. 70%: with three shots a
# noun shared by two of them is still a topic of those two, only all three is a constant.
@@ -326,7 +326,7 @@ def add_shot_cuts(text, seconds, seed=0, chance=0.5, pieces=()):
return text, info
header = text[:marks[0].start()]
parts = [_split_sound(b) for b in bodies]
cands = [candidates(p[0]) for p in parts]
cands = [candidates(own_words_removed(p[0])) for p in parts]
header_lemmas = {c[0] for c in candidates(header)}
need = max(2, math.ceil(0.7 * len(bodies))) if len(bodies) > 1 else 10 ** 9
seen = {}
@@ -413,3 +413,22 @@ def add_shot_views(text, seed=0, chance=0.4):
last = view
out.append(m.group(0) + body)
return "".join(out), added
_VIEW_SENTENCE = re.compile(r"^\s*(?:" + "|".join(re.escape(v) for v in VIEWS) + r")\.\s*", re.I)
def own_words_removed(picture):
"""`picture` without what this module wrote into it (its own cut openers and views) and
without the bare cut words of a user-written opener ("At 00:03.500, the camera cuts to"),
whose content after them is kept: those words are not the shot's content and must never
become a target or a topic."""
picture = _OUR_OPENER.sub("", picture, count=1)
picture = _CUT_WORDS.sub("", picture, count=1)
return _VIEW_SENTENCE.sub("", picture, count=1)
_OUR_OPENER = re.compile(r"^\s*At \d\d:\d\d\.\d{3},\s*(?:" + "|".join(re.escape(o) for o in CUT_OPENERS)
+ r")\.\s*", re.I)
_CUT_WORDS = re.compile(r"^\s*(?:At \d\d:\d\d\.\d{3},\s*)?(?:the\s+)?(?:camera|shot)\s+"
r"(?:cuts|transitions|changes|switches)\s+to\b\s*", re.I)
+25
View File
@@ -370,3 +370,28 @@ def test_the_phrases_we_write_never_say_the_camera_pulls_out():
"""Only what the rewriter writes; a shortcut may say it as an action."""
for group in (sc.MOVES_DETAIL, sc.MOVES_OTHER, sc.MOVES_TRAVEL, sc.MOVES_THEN, sc.MOVES_FINISH):
assert not any("pulls out" in m for m in group), group
def test_words_the_rewriter_wrote_never_become_targets():
"""'The camera pans from the shot transitions to the tip': the cut opener was read as
the shot's own content."""
pytest.importorskip("spacy")
P = (HEAD + "[Shot 1] <Subject 1> waves. [Shot 2] At 00:04.000, the shot transitions to the next "
"moment. Side view. <Subject 2> touches <Subject 2>'s chin, then reaches for the lamp.")
_out, rep = sc.add_camera_moves(P, seed=1, chance=1.0)
m = rep[1]["move"] or ""
assert "transition" not in m and "moment" not in m and "view" not in m.lower(), m
assert "chin" in m or "lamp" in m
def test_the_full_pipeline_output_has_no_own_words_in_a_move():
pytest.importorskip("spacy")
P = (HEAD + f"[Shot 1] {PA} [Shot 2] {PB} {PC} " + MUSIC)
for seed in range(20):
a, _ = sc.add_shot_cuts(P, 12, seed=seed, chance=1.0, pieces=PIECES)
b, _ = sc.add_shot_views(a, seed=seed, chance=1.0)
_out, rep = sc.add_camera_moves(b, seed=seed, chance=1.0)
for r in rep:
t = (r["target"] or "").lower()
assert not any(w in t for w in ("transition", "moment", "beat", "new angle", "new view",
"switch", "changes")), t