From 3c8dda0f5c8554ed78da5225810951704353f3f7 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 7 Jun 2026 00:30:59 +0300 Subject: [PATCH] =?UTF-8?q?Movie=20Editor:=20fix=20multi-scene=20generatio?= =?UTF-8?q?n=20=E2=80=94=20always=20emit=20separator=20before=20each=20sce?= =?UTF-8?q?ne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_combined_prompt had a bug: when no anchor text was set, need_sep was False for scene 1 so its text landed in Studio's segments[0] (anchor slot) and never generated. An N-scene project with no anchor only produced N-1 scenes. Fix: always emit a separator before every scene. Studio splits on separators, so segments[0] is always the anchor (empty if no anchor text is set) and segments[1..N] are the generated scenes. Fallback label is now "scene {i+1}" (correctly numbered) instead of the previous "scene {label_n+1}" off-by-one. Also: pass max_scenes = active scene count from the generate route into the builder so the chain sampler always knows the exact number of scenes to produce. Note on per-scene images: only the source image on scene 1 (project anchor image) is wired to studio.source_image. Scene 2+ anchoring works via the carry_i2v_guides flag on the chain sampler, which carries each scene's last frame as the i2v source for the next scene. Per-scene i2v requires carry_i2v_guides to be enabled in the sampler inspector — it is off by default. Co-Authored-By: Claude Sonnet 4.6 --- movie_editor/backend/builder.py | 2 ++ movie_editor/backend/timeline.py | 21 ++++++++++----------- movie_editor/server.py | 2 ++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index bdec49aa..256c498c 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -218,6 +218,8 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No graph["fps"]["inputs"]["value"] = params["frame_rate"] if params.get("seed") is not None: graph["sampler"]["inputs"]["seed"] = params["seed"] + if params.get("max_scenes") is not None: + graph["sampler"]["inputs"]["max_scenes"] = params["max_scenes"] # the result must be written to the output dir so the editor can fetch it back # (the reference graph used preview-only save_output=False). graph["vhs"]["inputs"]["save_output"] = True diff --git a/movie_editor/backend/timeline.py b/movie_editor/backend/timeline.py index 67d49d4f..c71f3e90 100644 --- a/movie_editor/backend/timeline.py +++ b/movie_editor/backend/timeline.py @@ -162,19 +162,18 @@ def build_combined_prompt(project: Project, include_excluded: bool = False) -> s parts.append(anchor) scenes = [s for s in project.scenes if include_excluded or not s.excluded] - label_n = 1 - prev_marker = (project.intro_transition or "").strip() if anchor else "" + # Always emit a separator marker before every scene. Studio's split puts + # segments[0] as the character anchor and segments[1..N] as generated scenes. + # Without a separator before scene 1, scene 1's text would land in segments[0] + # (the anchor slot) and be skipped for generation — only N-1 scenes would be + # produced for an N-scene project. + prev_marker = (project.intro_transition or "").strip() for i, scene in enumerate(scenes): text = (scene.text or "").strip() - # A separator is needed before this scene if there is preceding content - # (the anchor, or an earlier scene) to split away from. - need_sep = bool(anchor) or i > 0 - if need_sep: - marker = prev_marker - if not marker: - marker = f"scene {label_n + 1}" - parts.append(marker) - label_n += 1 + marker = prev_marker + if not marker: + marker = f"scene {i + 1}" + parts.append(marker) if text: parts.append(text) prev_marker = (scene.transition_to_next or "").strip() diff --git a/movie_editor/server.py b/movie_editor/server.py index 49d3a2e9..07ce6ea2 100644 --- a/movie_editor/server.py +++ b/movie_editor/server.py @@ -369,12 +369,14 @@ if web is not None and PromptServer is not None: oi = await bridge.object_info() except Exception as e: # noqa: BLE001 return web.json_response({"detail": f"Node registry unavailable: {e}"}, status=502) + active_scene_count = len([s for s in target.scenes if not s.excluded]) graph, report = builder.build(oi, nodes.load_models(), { "prompt": prompt, "seed": target.seed, "num_frames_per_scene": target.num_frames_per_scene, "frame_rate": target.frame_rate, "width": target.width, "height": target.height, "negative_prompt": target.negative_prompt or None, + "max_scenes": active_scene_count, "studio_inputs": target.studio_inputs if target.conditioning_slot == "funpack" else {}, "sampler_inputs": target.sampler_inputs if target.sampler_slot == "funpack" else {}, }, media=_prepare_media(target))