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 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-27 02:46:32 +03:00
co-authored by Claude Sonnet 4.6
parent 6f540791fe
commit f797c535d9
2 changed files with 26 additions and 8 deletions
+14 -7
View File
@@ -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:
+12 -1
View File
@@ -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;