diff --git a/movie_editor/backend/timeline.py b/movie_editor/backend/timeline.py index d82d7b98..a7e2a0aa 100644 --- a/movie_editor/backend/timeline.py +++ b/movie_editor/backend/timeline.py @@ -25,7 +25,7 @@ def _new_id() -> str: class SceneSource: """How a scene's latent is born. V1 ignores this (uniform chain); Phase 2 maps it onto EmptyLTXVLatent (empty/t2v) or LTXV Image to Video (image/i2v).""" - type: str = "empty" # "empty" | "image" | "generated_frame" + type: str = "empty" # "empty" | "image" | "generated_frame" | "carry" media_ref: Optional[str] = None # asset id, for type == "image" frame_ref: Optional[dict[str, Any]] = None # {scene_id, frame_idx}, for "generated_frame" target: Optional[str] = None # wire dest for the image: "port:" | "node::" diff --git a/movie_editor/frontend/inspector.js b/movie_editor/frontend/inspector.js index 7632f6da..394657d7 100644 --- a/movie_editor/frontend/inspector.js +++ b/movie_editor/frontend/inspector.js @@ -5,7 +5,12 @@ const body = document.getElementById("inspector-body"); const title = document.getElementById("inspector-title"); - const SRC = [["empty", "Empty · text-to-video"], ["image", "Image · i2v anchor"], ["generated_frame", "From generated frame"]]; + const SRC = [ + ["empty", "Empty · text-to-video"], + ["image", "Image · i2v anchor"], + ["generated_frame", "From generated frame"], + ["carry", "Carry i2v guide · continue previous"], + ]; function transitionSelect(value, onChange) { const sel = el("select"); @@ -157,6 +162,12 @@ if ((scene.source?.type) === "image") renderImageSource(st, scene); if ((scene.source?.type) === "generated_frame") renderGeneratedFrameSource(st, scene); + if ((scene.source?.type) === "carry") { + const hint = el("div", "insp-block"); + hint.append(el("div", "insp-hint", + "No start frame of its own — this scene continues from the previous scene's i2v guide and overlaps with it (chain-sampler carry behaviour). Use this for a continuous shot; use an Image / generated frame to hard-cut to a new anchor.")); + body.append(hint); + } body.append(field("Transition to next scene", transitionSelect(scene.transition_to_next || "", (v) => S.patchScene(scene.id, { transition_to_next: v })))); diff --git a/movie_editor/frontend/timeline.js b/movie_editor/frontend/timeline.js index 4c0da144..cdc8aff6 100644 --- a/movie_editor/frontend/timeline.js +++ b/movie_editor/frontend/timeline.js @@ -7,7 +7,7 @@ const body = document.getElementById("timeline-body"); const meta = document.getElementById("timeline-meta"); - const SRC_ICON = { empty: "▦", image: "◐", generated_frame: "⛶" }; + const SRC_ICON = { empty: "▦", image: "◐", generated_frame: "⛶", carry: "⇥" }; const ZOOM_KEY = "funpack_tl_zoom"; let pxPerSec = parseFloat(localStorage.getItem(ZOOM_KEY)) || 80; // zoom let scrollLeft = 0;