feat: camera-move wording follows H3's own prompt guide; a shot opening with a cut still gets a move
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
0fc080b057
commit
4b182524e6
+17
-13
@@ -20,9 +20,13 @@ NAMES = ("Mariel", "Tobias", "Odette", "Rurik", "Sabine", "Teodor", "Ilsa", "Cor
|
||||
STAND_IN = re.compile(r"\b(" + "|".join(NAMES) + r")\b")
|
||||
AUDIO = re.compile(r"\b(music|soundtrack|score|melody|instrumental|beat|bassline|synth|"
|
||||
r"song|tempo|audio|ambient sound|sound of)\b", re.I)
|
||||
# H3's own vocabulary (VIDEO_PROMPT_WRITING_GUIDE_base_en.md): a shot opening "the camera cuts
|
||||
# to" is a cut, not a camera move, and must not make the shot look like it already has one.
|
||||
CUT = re.compile(r"\b(?:the )?(?:camera|shot) (?:cuts|transitions|changes|switches) to\b", re.I)
|
||||
CAMERA = re.compile(r"\b(camera|zoom(?:s|ing)?|close-?up|pan(?:s|ning)?|dolly|tracking|"
|
||||
r"rack focus|focus(?:es|ing)? (?:on|at)|tilt(?:s|ing)?|rotate|orbit|"
|
||||
r"push[- ]in|pull[- ]back|wide shot|handheld|crane)\b", re.I)
|
||||
r"rack(?:s|ing)? focus|focus(?:es|ing)? (?:on|at)|tilt(?:s|ing)?|rotate|orbit|"
|
||||
r"push(?:es)?[- ]in|pull(?:s)?[- ](?:out|back)|arc(?:s)? |truck(?:s|ing)?|"
|
||||
r"pedestal|roll(?:s)? (?:clockwise|counter)|pov|wide shot|handheld|crane)\b", re.I)
|
||||
GENERIC = {"camera", "video", "scene", "shot", "frame", "background", "foreground", "view",
|
||||
"image", "screen", "moment", "time", "way", "side", "front", "middle", "one",
|
||||
"other", "something", "everything", "thing", "music", "sound", "style", "lighting",
|
||||
@@ -32,17 +36,17 @@ PARTS = {"lip", "mouth", "tongue", "tooth", "teeth", "eye", "eyes", "face", "che
|
||||
"skin", "expression", "smile", "gaze", "back", "chin", "brow"}
|
||||
PLACES = {"room", "street", "kitchen", "bedroom", "bed", "table", "floor", "stage", "hall",
|
||||
"garden", "beach", "forest", "city", "car", "office", "bathroom", "field"}
|
||||
MOVES_DETAIL = ("Camera moves into a close-up on {x}.", "Camera focuses at {x}.",
|
||||
"Camera zooms in at {x}.")
|
||||
MOVES_OTHER = ("Camera zooms in at {x}.", "Camera moves around {x}.",
|
||||
"Camera rotates to {x}.")
|
||||
MOVES_TRAVEL = ("Camera moves from {x} to {y}.", "Camera pans from {x} to {y}.",
|
||||
"Camera shifts focus from {x} to {y}.")
|
||||
MOVES_DETAIL = ("The camera pushes in toward {x}.", "The camera racks focus to {x}.",
|
||||
"The camera zooms in on {x}.")
|
||||
MOVES_OTHER = ("The camera zooms in on {x}.", "The camera arcs around {x}.",
|
||||
"The camera pushes in toward {x}.")
|
||||
MOVES_TRAVEL = ("The camera pans from {x} to {y}.", "The camera racks focus from {x} to {y}.",
|
||||
"The camera moves from {x} to {y}.")
|
||||
# Chained moves (varied mode): a follow-up to a first move, and a closing pull-back.
|
||||
MOVES_THEN = ("Then the camera moves to {y}.", "Then the camera pans to {y}.",
|
||||
"Then the camera focuses at {y}.")
|
||||
MOVES_FINISH = ("Then the camera zooms out.", "Then the camera pulls back.",
|
||||
"Then the camera pulls back to a wider view.")
|
||||
MOVES_THEN = ("Then the camera pans to {y}.", "Then the camera racks focus to {y}.",
|
||||
"Then the camera pushes in toward {y}.")
|
||||
MOVES_FINISH = ("Then the camera pulls out.", "Then the camera zooms out.",
|
||||
"Then the camera pulls out to a wider view.")
|
||||
COUNT_WEIGHTS = (6, 3, 1) # one move / two / three, in a shot that gets any
|
||||
TRAVEL_SHARE = 0.5 # of one-move shots with two topics, how many travel X -> Y
|
||||
DETERMINERS = {"the", "a", "an", "this", "that", "these", "those"}
|
||||
@@ -229,7 +233,7 @@ def add_camera_moves(text, seed=None, chance=1.0):
|
||||
entry = {"shot": int(m.group(1)), "move": None, "target": None, "why": ""}
|
||||
rng = random.Random(f"{seed}:{i}") if seed is not None else None
|
||||
pool = [c for c in cands[i] if c[0] not in constant]
|
||||
if CAMERA.search(picture):
|
||||
if CAMERA.search(CUT.sub("", picture)):
|
||||
entry["why"] = "already has a camera move"
|
||||
elif not pool:
|
||||
entry["why"] = "nothing specific to aim at"
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_move_aims_at_a_part_of_the_subject_never_the_subject():
|
||||
assert rep[1]["target"] == "<Subject 1>'s lower lip"
|
||||
for r in rep:
|
||||
assert r["move"] and "<Subject 1>." not in r["move"].replace("'s", "")
|
||||
assert "Camera moves into a close-up on <Subject 1>'s hand." in out
|
||||
assert rep[0]["move"] in out and "<Subject 1>'s hand" in rep[0]["move"]
|
||||
|
||||
|
||||
def test_moves_sit_inside_their_shot_and_before_the_music():
|
||||
@@ -90,7 +90,7 @@ def test_refiner_wrapper_returns_the_prompt_and_says_what_it_did(capsys):
|
||||
pytest.importorskip("spacy")
|
||||
import conditioning
|
||||
out = conditioning.FunPackVideoRefinerV2._v2_camera_moves(PROMPT, "prompt")
|
||||
assert "Camera moves into a close-up on <Subject 1>'s hand." in out
|
||||
assert "The camera" in out and "<Subject 1>'s hand" in out
|
||||
assert "camera moves: Active" in capsys.readouterr().out
|
||||
assert conditioning.FunPackVideoRefinerV2._v2_camera_moves("no shots here", "prompt") == "no shots here"
|
||||
|
||||
@@ -151,4 +151,15 @@ def test_a_single_topic_shot_can_still_chain_with_a_pull_back():
|
||||
pytest.importorskip("spacy")
|
||||
moves = {sc.add_camera_moves(ONE, seed=s)[1][0]["move"] for s in range(60)}
|
||||
assert any("Then the camera" in m for m in moves) and any("Then" not in m for m in moves)
|
||||
assert all(m.startswith("Camera") for m in moves)
|
||||
assert all(m.startswith("The camera") for m in moves)
|
||||
|
||||
|
||||
def test_a_shot_that_opens_with_a_cut_is_not_taken_for_having_a_camera_move():
|
||||
"""H3 writes cuts at a shot's opening: "At 00:03.500, the camera cuts to ..."."""
|
||||
pytest.importorskip("spacy")
|
||||
p = (HEAD + "[Shot 1] <Subject 1> raises <Subject 1>'s hand. "
|
||||
"[Shot 2] At 00:03.500, the camera cuts to <Subject 2> who lifts <Subject 2>'s chin.")
|
||||
_out, rep = sc.add_camera_moves(p)
|
||||
assert rep[1]["move"] and "already" not in rep[1]["why"]
|
||||
p2 = HEAD + "[Shot 1] <Subject 1> raises <Subject 1>'s hand as the camera pushes in slowly."
|
||||
assert "already" in sc.add_camera_moves(p2)[1][0]["why"]
|
||||
|
||||
Reference in New Issue
Block a user