From f797c535d9a0b30e92c045314dd2ccaa181f84ae Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Wed, 27 May 2026 02:46:32 +0300 Subject: [PATCH] Fix timeline scene count: treat segments[0] as anchor, not a scene Mirrors _v2_transition_scene_texts: the text before the first transition is a character anchor prepended to every scene, not a scene itself. Show it separately above the rail. scenes[] now matches what the Scene Chain sampler actually generates. Co-Authored-By: Claude Sonnet 4.6 --- conditioning.py | 21 ++++++++++++++------- web/funpack_studio.js | 13 ++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/conditioning.py b/conditioning.py index 73a12eb8..85cafdef 100644 --- a/conditioning.py +++ b/conditioning.py @@ -573,20 +573,27 @@ def split_prompt_by_transitions(prompt, placement="start"): def parse_timeline_segments(prompt): - """Return {"scenes": [...], "transitions": [...]} for timeline preview. + """Return {"anchor": str, "scenes": [...], "transitions": [...]} for timeline preview. - Uses split_prompt_by_transitions — the same code path as Studio generation. - scenes: list of {"index": int, "text": str} - transitions: list of {"after_scene": int, "visual_effect": str|None} + Mirrors _v2_transition_scene_texts: segments[0] is the character anchor prepended + to every scene, segments[1:] are the actual generated scenes. + If no transitions found, the single segment is treated as scene 1 with no anchor. """ segments = split_prompt_by_transitions(prompt, placement="silent") - scenes = [{"index": i, "text": text} for i, (text, _) in enumerate(segments)] + if len(segments) == 1: + return { + "anchor": "", + "scenes": [{"index": 0, "text": segments[0][0]}], + "transitions": [], + } + anchor = segments[0][0].strip() + scenes = [{"index": i, "text": text.strip()} for i, (text, _) in enumerate(segments[1:])] transitions = [ {"after_scene": i - 1, "visual_effect": effect} - for i, (_, effect) in enumerate(segments) + for i, (_, effect) in enumerate(segments[1:]) if i > 0 ] - return {"scenes": scenes, "transitions": transitions} + return {"anchor": anchor, "scenes": scenes, "transitions": transitions} class FunPackVideoRefiner: diff --git a/web/funpack_studio.js b/web/funpack_studio.js index 01877240..6fe7ebbd 100644 --- a/web/funpack_studio.js +++ b/web/funpack_studio.js @@ -1283,13 +1283,20 @@ function openPanel(node) { refreshBtn.addEventListener("click", () => { body.replaceChildren(); renderTimeline(); }); body.append(refreshBtn); - const { scenes, transitions } = data; + const { anchor, scenes, transitions } = data; const trByScene = {}; for (const t of transitions) trByScene[t.after_scene] = t; body.append(el("div", "funpack-studio-hint", scenes.length === 1 ? "1 scene — no transitions found." : `${scenes.length} scenes detected.`)); + if (anchor) { + const anchorBox = el("div", "funpack-timeline-anchor"); + anchorBox.append(el("div", "funpack-timeline-badge", "Anchor (all scenes)")); + anchorBox.append(el("div", "funpack-timeline-text", anchor)); + body.append(anchorBox); + } + const rail = el("div", "funpack-timeline-rail"); for (const scene of scenes) { const box = el("div", "funpack-timeline-box"); @@ -1553,6 +1560,10 @@ function injectStyles() { } .funpack-studio-tab-link { color: #58a6d6; text-decoration: underline; cursor: pointer; } .funpack-studio-tab-link:hover { color: #8dcff5; } + .funpack-timeline-anchor { + border: 1px solid rgba(88,166,214,0.35); border-radius: 6px; + background: rgba(22,45,62,0.5); padding: 8px; margin-bottom: 6px; + } .funpack-timeline-rail { display: flex; flex-direction: row; align-items: flex-start; gap: 0; overflow-x: auto; padding-bottom: 6px;