Let the clip's IN handle drag when the Length covers the whole clip
A Length as long as the clip or longer locked IN at frame 0, and the only way to trim the start was to turn Length off. A workflow that sets a Length as an upper limit (240 frames, say) hit this with every shorter clip. Now such a Length only caps the clip: the clip's own end is OUT and IN moves over the whole clip. A Length shorter than the clip still stops IN where OUT meets the end, as before. The row reads "= the whole clip" and the tooltips say what dragging does. Every IN this allows was already reachable with Length off; the backend runs each one (checked on a 116-frame clip with Length 240: IN at 0, 1.83 s, 4.5 s and the last frame all run).
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to ComfyUI-AusBoss are documented here.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- **The video clip's IN handle always drags.** On Video Crop + Rotate + Pad
|
||||
(Clip), a Length as long as the clip or longer used to lock IN, and the
|
||||
only way to trim the start was to turn Length off. Now IN drags anyway:
|
||||
the Length then only caps the clip, and the clip's own end is OUT. A
|
||||
Length shorter than the clip works as before.
|
||||
|
||||
## 2.25.1 - 2026-11-13
|
||||
|
||||
- **Edit Lock Restore 🆎 takes out the pink tint.** MiniMax H3 Image gives
|
||||
|
||||
@@ -60,8 +60,9 @@ model that paints black regions.
|
||||
sits that many frames after IN and follows it when you drag IN, and dragging OUT or
|
||||
typing the number changes the Length. To move the whole clip, drag IN or the small
|
||||
grip in the middle of the kept part (the grip also slides a trimmed window with
|
||||
Length off). When the Length is the whole clip there is nowhere to move it, and the
|
||||
row says so: "= the whole clip. Shorten it to move IN." The Length stays when you
|
||||
Length off). When the Length is as long as the clip or longer, the row says
|
||||
"= the whole clip": the clip's own end is OUT, and dragging IN starts the clip
|
||||
later without changing the Length. The Length stays when you
|
||||
swap the video, so a workflow built for 97 frames keeps taking 97. Turning it on or
|
||||
off never moves anything: the count becomes the OUT, or the OUT the count. If the
|
||||
source ends first, the footer says how many frames are left. **To start** moves IN
|
||||
|
||||
@@ -217,6 +217,16 @@ export function latestFirstFor(info, frames, everyNth = 1) {
|
||||
return Math.max(0, info.count - 1 - (lastFrameFor(0, frames, everyNth)));
|
||||
}
|
||||
|
||||
// How far a drag can move IN while a Length is on. A Length that fits stops
|
||||
// IN where OUT meets the source's end, so the count stays whole. A Length
|
||||
// as long as the clip or longer cannot keep its count anywhere: it only caps
|
||||
// the clip, the source's end is OUT, and IN moves over the whole clip.
|
||||
export function latestInFor(info, frames, everyNth = 1) {
|
||||
if (!info?.count) return 0;
|
||||
const latest = latestFirstFor(info, frames, everyNth);
|
||||
return latest > 0 ? latest : info.count - 1;
|
||||
}
|
||||
|
||||
// Everything the rail draws for the clip node, from the stored values:
|
||||
// mode which way sets the length (lengthMode)
|
||||
// first IN, the first source frame the run keeps
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
framesThrough,
|
||||
keyboardStep,
|
||||
lastFrameFor,
|
||||
latestFirstFor,
|
||||
latestInFor,
|
||||
railZone,
|
||||
setTrimFrame,
|
||||
slideRange,
|
||||
@@ -235,8 +235,9 @@ export function mountTransformTrim({ get, set, has = () => true, driven = () =>
|
||||
let parked;
|
||||
if (edge === "start") {
|
||||
if (current.mode === "length") {
|
||||
// A Length drags the whole window, stopping where OUT meets the end.
|
||||
parked = Math.max(0, Math.min(clampFrame(frame, clip), latestFirstFor(clip, current.requested ?? get("max_frames", 1), nth)));
|
||||
// A Length drags the whole window, stopping where OUT meets the end;
|
||||
// a Length past the clip's end only caps it, so IN moves freely.
|
||||
parked = Math.max(0, Math.min(clampFrame(frame, clip), latestInFor(clip, current.requested ?? get("max_frames", 1), nth)));
|
||||
set("end_seconds", 0);
|
||||
} else if (current.mode === "wired" && current.latestStart != null && !countAtRunTime()) {
|
||||
parked = Math.min(clampFrame(frame, clip), current.latestStart);
|
||||
@@ -359,7 +360,7 @@ export function mountTransformTrim({ get, set, has = () => true, driven = () =>
|
||||
handle.title = isLocked ? lockTip(edge)
|
||||
: edge === "start"
|
||||
? current.mode === "free" ? "Drag IN to trim; Shift + arrow moves one frame."
|
||||
: fills ? "The Length is the whole clip, so IN has nowhere to go. Shorten the Length to move IN."
|
||||
: fills ? "Drag IN to start later. The Length is as long as the clip, so the clip's end is OUT."
|
||||
: "Drag IN to move the clip; OUT follows at the Length. Shift + arrow moves one frame."
|
||||
: current.mode === "free" ? "Drag OUT to trim; it lands on the last frame the run keeps." : "Drag OUT to change the Length; IN stays.";
|
||||
handle.setAttribute("aria-valuemin", "0");
|
||||
@@ -606,9 +607,9 @@ export function mountTransformTrim({ get, set, has = () => true, driven = () =>
|
||||
count.set(blocked ? (current.requested ?? current.frames ?? 0) : (current.requested ?? get("max_frames", 1)));
|
||||
disableControl(count, blocked, lockTip("end"));
|
||||
const fills = lengthFillsClip(info(), current, everyNth());
|
||||
hint.textContent = blocked ? `from ${wired}` : current.mode === "fixed" ? "exact (older setting)" : fills ? "= the whole clip. Shorten it to move IN" : active ? "" : "OUT ends the clip";
|
||||
hint.textContent = blocked ? `from ${wired}` : current.mode === "fixed" ? "exact (older setting)" : fills ? "= the whole clip" : active ? "" : "OUT ends the clip";
|
||||
hint.classList.toggle("note", fills && !blocked);
|
||||
hint.title = fills && !blocked ? `The Length (${current.requested ?? current.frames} frames) covers the whole clip, so IN and the clip cannot move. The bar still scrubs.` : "";
|
||||
hint.title = fills && !blocked ? `The Length (${current.requested ?? current.frames} frames) is as long as the clip, so the clip's end is OUT. Drag IN to start later; drag OUT or type a smaller Length to end sooner.` : "";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ test("fixed frame duration follows output fps while position follows source fps"
|
||||
});
|
||||
|
||||
// --- One way to set the length -------------------------------------------------
|
||||
import { clipLengthPlan, framesThrough, lastFrameFor, latestFirstFor, lengthMode, snapStep, snapToValid } from "../js/shared/timeline_math.mjs";
|
||||
import { clipLengthPlan, framesThrough, lastFrameFor, latestFirstFor, latestInFor, lengthMode, snapStep, snapToValid } from "../js/shared/timeline_math.mjs";
|
||||
|
||||
test("one mode sets the length: a wired input, an old fixed count, a Length, or OUT", () => {
|
||||
assert.equal(lengthMode({}), "free");
|
||||
@@ -214,6 +214,16 @@ test("OUT lands on a frame the run keeps", () => {
|
||||
assert.equal(latestFirstFor(info, 20), 77);
|
||||
assert.equal(latestFirstFor(info, 97), 0);
|
||||
assert.equal(latestFirstFor(info, 200), 0);
|
||||
// A Length that fits stops IN where OUT meets the end; one as long as the
|
||||
// clip or longer only caps it, so IN reaches every frame (a 240-frame
|
||||
// Length on a 116-frame clip used to pin IN at 0).
|
||||
assert.equal(latestInFor(info, 20), 77);
|
||||
assert.equal(latestInFor(info, 97), 96);
|
||||
assert.equal(latestInFor(info, 200), 96);
|
||||
const short = clipInfo({ fps: 24, frame_count: 116, duration: 116 / 24 });
|
||||
assert.equal(latestInFor(short, 240), 115);
|
||||
const late = clipLengthPlan(short, { start: windowSeconds(short, 40, 40).start_seconds, end: 0, maxFrames: 240 });
|
||||
assert.deepEqual([late.first, late.last, late.frames, late.truncated], [40, 115, 76, true]);
|
||||
});
|
||||
|
||||
test("free trim: the handles are IN and the last frame the run keeps", () => {
|
||||
|
||||
Reference in New Issue
Block a user