Shot Planner: a plan with no range works on the first 15 s (1.106.2)
A plan that never set its range (fresh node, older workflow, plan made outside the panel) used the whole video, so a long video run through a workflow nobody adjusted generated every shot of it. Unset now means the first 15 s, read the same way by the panel and the server; the whole video is the explicit 'whole video' choice (end frame 0), as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
35ef99090b
commit
d62a72147c
+11
-1
@@ -388,12 +388,22 @@ def detect_cuts(score: list[float], raw: list[float], sensitivity: float, fps: f
|
||||
|
||||
# ---------------------------------------------------------------------------- planning
|
||||
|
||||
# a plan that never set its range works on the first seconds only, never the whole video by surprise: a long video
|
||||
# run by a workflow nobody adjusted would generate every shot of it. The whole video is an explicit end frame 0.
|
||||
DEFAULT_RANGE_S = 15.0
|
||||
|
||||
|
||||
def plan_range(n: int, fps: float, start_s: float = 0.0, end_s: float = 0.0, max_total_s: float = 0.0,
|
||||
start_f: int | None = None, end_f: int | None = None) -> tuple[int, int]:
|
||||
"""The part of the video the plan works on, in timeline frames [lo, hi): start / end frame (end 0 = the end of the
|
||||
video; older plans give seconds), then 'max total seconds' counted from the start."""
|
||||
video; older plans give seconds), then 'max total seconds' counted from the start. Nothing set at all: the first
|
||||
DEFAULT_RANGE_S seconds."""
|
||||
unset = (start_f is None and end_f is None and not float(start_s or 0) and not float(end_s or 0)
|
||||
and not float(max_total_s or 0))
|
||||
a = int(start_f) if start_f is not None else int(round(float(start_s or 0) * fps))
|
||||
b = int(end_f) if end_f is not None else (int(round(float(end_s) * fps)) if end_s and float(end_s) > 0 else 0)
|
||||
if unset:
|
||||
b = a + max(1, int(round(DEFAULT_RANGE_S * fps)))
|
||||
lo = max(0, min(n - 1, a))
|
||||
hi = n if b <= 0 else max(lo + 1, min(n, b))
|
||||
if max_total_s and float(max_total_s) > 0:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "bfsnodes"
|
||||
description = "Custom ComfyUI nodes for reference-guided video: BFS Sampler (guide-driven IC-LoRAs, auto crop, mask inpainting, temporal chunking), LTX Multiple Controls / Identity Transfer / Edit Anything, Head-Swap and Anime2Real conditioning, MiniMax-H3 latent guides, RoPE layouts and loaders, LoRA Surgery, and Shot Planner / Shot Loop (tabbed panel): split long videos into shots at camera cuts, face-tracked cast, SAM 3 mask per shot with full frame, paste, mask, crop and crop + mask generation modes, optional translucent Colored Mark overlay for manually selected regions, optional Qwen3-VL to suggest settings, describe references ({details}) and the person to replace ({target}), continuity between shots, H3 conditioning with an optional duet and prompt writer, join with the original timing, audio and a side-by-side comparison. H3 Duet: pin a source clip beside the video, training-free."
|
||||
version = "1.106.1"
|
||||
version = "1.106.2"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"torch",
|
||||
|
||||
@@ -909,6 +909,10 @@ class RangeTest(unittest.TestCase):
|
||||
self.assertEqual(SL.plan_range(1000, 24.0, 10, 0, 5), (240, 360)) # max total counted from the start
|
||||
self.assertEqual(SL.plan_range(1000, 24.0, 0, 0, 0, start_f=250, end_f=500), (250, 500)) # frames win
|
||||
self.assertEqual(SL.plan_range(1000, 24.0, 0, 0, 0, start_f=250, end_f=0), (250, 1000))
|
||||
# nothing set (a fresh or old plan): the first 15 s, never the whole video by surprise; end 0 = whole, on purpose
|
||||
self.assertEqual(SL.plan_range(1000, 24.0), (0, 360))
|
||||
self.assertEqual(SL.plan_range(200, 24.0), (0, 200))
|
||||
self.assertEqual(SL.plan_range(1000, 24.0, 0, 0, 0, start_f=0, end_f=0), (0, 1000))
|
||||
segs = SL.plan_segments(480, [100, 300], "shots", 107, 10, start=240)
|
||||
self.assertEqual(segs[0]["start"], 240)
|
||||
self.assertEqual(segs[-1]["end"], 480)
|
||||
|
||||
@@ -15,6 +15,7 @@ import { createApp, ref, reactive, computed, watch, nextTick, onMounted, onBefor
|
||||
import { h, rawH, t, uiLanguage, setUiLanguage, DEFAULTS, MASK_DEFAULTS, VLM_DEFAULTS, snapUp, snapDown, segKey, hasMask, pill } from "./shotloop/common.js";
|
||||
import { styles } from "./shotloop/styles.js";
|
||||
// history lengths H3's VAE encodes whole (17k+5); old plans saved 17/34/51, read as the next valid one up
|
||||
const DEFAULT_RANGE_S = 15; // a plan that never set its range works on the first 15 s (bfs_shot_loop.DEFAULT_RANGE_S)
|
||||
const historyLen = n => (n = Number(n) || 22) <= 5 ? 5 : 5 + Math.ceil((n - 5) / 17) * 17;
|
||||
import { timelineCard } from "./shotloop/view_timeline.js";
|
||||
import { videoTab } from "./shotloop/view_video.js";
|
||||
@@ -103,7 +104,11 @@ function Panel(io) {
|
||||
const N = n.value; if (!N) return { lo: 0, hi: 0 };
|
||||
// frames (older plans saved seconds)
|
||||
const a = plan.start_f != null ? +plan.start_f : Math.round((+plan.start_s || 0) * plan.fps);
|
||||
const b = plan.end_f != null ? +plan.end_f : (+plan.end_s > 0 ? Math.round(+plan.end_s * plan.fps) : 0);
|
||||
let b = plan.end_f != null ? +plan.end_f : (+plan.end_s > 0 ? Math.round(+plan.end_s * plan.fps) : 0);
|
||||
// never set: the first 15 s (as the server reads it), so a long video is never run whole by surprise;
|
||||
// "whole video" stores end 0 explicitly
|
||||
if (plan.start_f == null && plan.end_f == null && !+plan.start_s && !+plan.end_s && !+plan.max_total_s)
|
||||
b = a + Math.max(1, Math.round(DEFAULT_RANGE_S * plan.fps));
|
||||
const lo = Math.max(0, Math.min(N - 1, a));
|
||||
let hi = b > 0 ? Math.max(lo + 1, Math.min(N, b)) : N;
|
||||
if (+plan.max_total_s > 0) hi = Math.min(hi, lo + Math.max(1, Math.round(+plan.max_total_s * plan.fps)));
|
||||
@@ -566,7 +571,7 @@ const meta = i => { while (plan.segs.length <= i) plan.segs.push({}); return pla
|
||||
// do not surprise them with a wall of detected shots on first load.
|
||||
plan.mode = "single";
|
||||
plan.start_f = 0;
|
||||
plan.end_f = Math.round(15 * plan.fps);
|
||||
plan.end_f = Math.round(DEFAULT_RANGE_S * plan.fps);
|
||||
plan.start_s = 0; plan.end_s = 0; plan.max_total_s = 0;
|
||||
tab.value = "video";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user