B/A switch
This commit is contained in:
@@ -50,11 +50,12 @@ class PowerCompareVideo:
|
||||
previous run's input (e.g. upstream was fully cached), the
|
||||
saved frames are left untouched - no promotion, no rewrite -
|
||||
so B keeps pointing at the last genuinely DIFFERENT video.
|
||||
output_pick - "A" (default), "B", or "A/B": which video the images
|
||||
output returns. With images_b connected, B is returned
|
||||
losslessly; without it, B is decoded back from the cached
|
||||
previous-run frames. "A/B" returns BOTH videos stitched
|
||||
into a single video according to ab_stitch.
|
||||
output_pick - "A" (default), "B", "A/B", or "B/A": which video the
|
||||
images output returns. With images_b connected, B is
|
||||
returned losslessly; without it, B is decoded back from
|
||||
the cached previous-run frames. "A/B" / "B/A" return BOTH
|
||||
videos stitched into a single video according to
|
||||
ab_stitch ("A/B": A first - top/left; "B/A": B first).
|
||||
ab_stitch - Stitch orientation for output_pick = "A/B":
|
||||
"vertical" (default, A on top / B below) or
|
||||
"horizontal" (A left / B right). The frontend keeps this
|
||||
@@ -82,7 +83,7 @@ class PowerCompareVideo:
|
||||
"optional": {
|
||||
"fps": ("FLOAT", {"default": 24, "min": 1, "max": 120, "step": 1}),
|
||||
"images_b": ("IMAGE",),
|
||||
"output_pick": (["A", "B", "A/B"], {"default": "A"}),
|
||||
"output_pick": (["A", "B", "A/B", "B/A"], {"default": "A"}),
|
||||
"ab_stitch": (["vertical", "horizontal"], {"default": "vertical"}),
|
||||
"start_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}),
|
||||
"end_frame": ("INT", {"default": 0, "min": 0, "max": 10000000, "step": 1}),
|
||||
@@ -249,10 +250,12 @@ class PowerCompareVideo:
|
||||
# is rescaled to match A along the stitch axis, aspect preserved;
|
||||
# if frame counts differ the shorter video repeats its last frame
|
||||
# so both play to the end (same rule as the preview).
|
||||
# - pick B/A: same stitched output with the videos swapped
|
||||
# (B first: top in vertical, left in horizontal).
|
||||
pick = "A"
|
||||
if isinstance(output_pick, str):
|
||||
pick = output_pick.strip().upper()
|
||||
if pick not in ("A", "B", "A/B"):
|
||||
if pick not in ("A", "B", "A/B", "B/A"):
|
||||
pick = "A"
|
||||
horizontal = isinstance(ab_stitch, str) and ab_stitch.strip().lower() == "horizontal"
|
||||
|
||||
@@ -314,11 +317,11 @@ class PowerCompareVideo:
|
||||
b_tensor = _crop_tensor(_decode_b_tensor())
|
||||
if b_tensor is not None:
|
||||
out_tensor = b_tensor
|
||||
elif pick == "A/B":
|
||||
elif pick in ("A/B", "B/A"):
|
||||
a_tensor = _crop_tensor(images)
|
||||
b_tensor = _crop_tensor(_decode_b_tensor())
|
||||
if b_tensor is not None:
|
||||
out_tensor = _stitch(a_tensor, b_tensor)
|
||||
out_tensor = _stitch(a_tensor, b_tensor) if pick == "A/B" else _stitch(b_tensor, a_tensor)
|
||||
|
||||
# NOTE: every ui value must be a list - the server iterates over each
|
||||
# value when merging ui outputs (scalars crash with 'float' not iterable)
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
* Compare Row Widget for Power Compare Video
|
||||
*
|
||||
* Row above the playback area: three mode buttons on the left
|
||||
* (slide-compare / B-on-right / B-on-bottom, canvas-drawn icons), then three
|
||||
* pick buttons (A / B / A&B - which video the images output returns; "A/B"
|
||||
* returns both videos stitched into one), and an fps stepper on the right
|
||||
* (same look as PowerLoadVideo's top row).
|
||||
* (slide-compare / B-on-right / B-on-bottom, canvas-drawn icons), then four
|
||||
* pick buttons (A / B / A/B / B/A - which video the images output returns;
|
||||
* "A/B"/"B/A" return both videos stitched into one, with the videos swapped
|
||||
* for "B/A"), and an fps stepper on the right (same look as PowerLoadVideo's
|
||||
* top row).
|
||||
* The active compare mode lives on the node (node.compareMode) and is
|
||||
* persisted via node.properties.compare_mode; the output pick lives on
|
||||
* node.outputPick and is persisted via node.properties.output_pick plus the
|
||||
@@ -32,6 +33,7 @@ export class PowerCompareRowWidget extends RgthreeBaseWidget {
|
||||
pickA: { bounds: [0, 0, 0, 0] },
|
||||
pickB: { bounds: [0, 0, 0, 0] },
|
||||
pickAB: { bounds: [0, 0, 0, 0] },
|
||||
pickBA: { bounds: [0, 0, 0, 0] },
|
||||
fpsDec: { bounds: [0, 0, 0, 0] },
|
||||
fpsVal: { bounds: [0, 0, 0, 0] },
|
||||
fpsInc: { bounds: [0, 0, 0, 0] },
|
||||
@@ -68,12 +70,13 @@ export class PowerCompareRowWidget extends RgthreeBaseWidget {
|
||||
}
|
||||
});
|
||||
|
||||
// === Pick A / B / A&B buttons (after a small gap) ===
|
||||
// === Pick A / B / A&B / B&A buttons (after a small gap) ===
|
||||
const groupGap = 14;
|
||||
const picks = [
|
||||
["pickA", "A", btnW],
|
||||
["pickB", "B", btnW],
|
||||
["pickAB", "A/B", btnW + 10],
|
||||
["pickBA", "B/A", btnW + 10],
|
||||
];
|
||||
let pickX = margin + modes.length * (btnW + spacing) + groupGap - spacing;
|
||||
picks.forEach(([key, pick, w]) => {
|
||||
@@ -218,8 +221,8 @@ export class PowerCompareRowWidget extends RgthreeBaseWidget {
|
||||
// Keep the hidden backend widget in sync (serialization + next run)
|
||||
const w = node.widgets?.find((w) => w.name === "output_pick");
|
||||
if (w) w.value = pick;
|
||||
// A/B stitching follows the current compare mode
|
||||
if (pick === "A/B") {
|
||||
// A/B + B/A stitching follows the current compare mode
|
||||
if (pick === "A/B" || pick === "B/A") {
|
||||
this.applyAbStitchFromMode(node);
|
||||
}
|
||||
node.setDirtyCanvas(true, true);
|
||||
|
||||
@@ -97,17 +97,18 @@ app.registerExtension({
|
||||
this.rowWidget.fpsValue = Math.max(1, Math.round(fpsWidget.value));
|
||||
}
|
||||
|
||||
// Output pick (A/B): restored from properties/widget, edited via
|
||||
// the row's pick buttons - hide the default combo widget
|
||||
// Output pick (A/B/B/A): restored from properties/widget, edited
|
||||
// via the row's pick buttons - hide the default combo widget
|
||||
const validPicks = ['A', 'B', 'A/B', 'B/A'];
|
||||
this.outputPick = this.properties?.output_pick || 'A';
|
||||
if (this.outputPick !== 'A' && this.outputPick !== 'B' && this.outputPick !== 'A/B') {
|
||||
if (!validPicks.includes(this.outputPick)) {
|
||||
this.outputPick = 'A';
|
||||
}
|
||||
const pickWidget = this.widgets.find(w => w.name === 'output_pick');
|
||||
if (pickWidget) {
|
||||
pickWidget.computeSize = () => [0, 0];
|
||||
pickWidget.hidden = true;
|
||||
if (pickWidget.value !== 'A' && pickWidget.value !== 'B' && pickWidget.value !== 'A/B') {
|
||||
if (!validPicks.includes(pickWidget.value)) {
|
||||
pickWidget.value = 'A';
|
||||
}
|
||||
this.outputPick = pickWidget.value;
|
||||
@@ -504,16 +505,17 @@ app.registerExtension({
|
||||
}
|
||||
// output_pick widget values are applied just before this hook,
|
||||
// so the widget wins; fall back to saved properties
|
||||
const validPicks = ['A', 'B', 'A/B', 'B/A'];
|
||||
const pickWidget = this.widgets?.find(w => w.name === 'output_pick');
|
||||
if (pickWidget) {
|
||||
if (pickWidget.value !== 'A' && pickWidget.value !== 'B' && pickWidget.value !== 'A/B') {
|
||||
if (!validPicks.includes(pickWidget.value)) {
|
||||
pickWidget.value = this.properties?.output_pick || 'A';
|
||||
}
|
||||
this.outputPick = pickWidget.value;
|
||||
} else {
|
||||
this.outputPick = this.properties?.output_pick || 'A';
|
||||
}
|
||||
if (this.outputPick !== 'A' && this.outputPick !== 'B' && this.outputPick !== 'A/B') {
|
||||
if (!validPicks.includes(this.outputPick)) {
|
||||
this.outputPick = 'A';
|
||||
}
|
||||
const stitchWidget = this.widgets?.find(w => w.name === 'ab_stitch');
|
||||
|
||||
Reference in New Issue
Block a user