Add the megapixel resize to Video Crop + Rotate + Pad → Frame
The image and clip transform nodes could resize their output to a pixel budget; the frame picker, whose single frame often feeds an image or video model, could not. It takes the same appended resize inputs, off by default, and its face and editor show them like the other two.
This commit is contained in:
@@ -109,7 +109,7 @@ Returns frames, lazy audio, frame count, fps, width, height, duration, and a cor
|
||||
|
||||
### Video Crop + Rotate + Pad → Frame 🆎
|
||||
|
||||
Find **one frame** in an uploaded or local video, then apply the image transform controls. A playhead rail on the node scrubs to the frame; the editor adds exact frame stepping and playback. Returns one transformed `image`, its generated-area `mask`, a `stitcher`, the selected `original` frame, and the output `width` and `height`. Use the Clip variant for an entire sequence.
|
||||
Find **one frame** in an uploaded or local video, then apply the image transform controls and an optional megapixel resize. A playhead rail on the node scrubs to the frame; the editor adds exact frame stepping and playback. Returns one transformed `image`, its generated-area `mask`, a `stitcher`, the selected `original` frame, and the output `width` and `height`. Use the Clip variant for an entire sequence.
|
||||
|
||||
### Video Crop + Rotate + Pad → Clip 🆎
|
||||
|
||||
|
||||
@@ -166,9 +166,8 @@ def transform_inputs(*, feather: int = 24, fill_color: str = "#808080") -> dict[
|
||||
}
|
||||
|
||||
|
||||
# The image and clip nodes only (the frame node keeps its source geometry):
|
||||
# resize the transformed output to a pixel budget, core
|
||||
# ImageScaleToTotalPixels-style.
|
||||
# Every transform node, appended after its other widgets: resize the
|
||||
# transformed output to a pixel budget, core ImageScaleToTotalPixels-style.
|
||||
RESIZE_METHODS = ["lanczos", "area", "bicubic", "bilinear", "nearest-exact"]
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,13 @@ from ._media_helpers import (
|
||||
resolve_video_path,
|
||||
)
|
||||
from ._inpaint_crop_helpers import build_transform_stitcher
|
||||
from ._transform_engine import original_image_batch, stable_file_fingerprint, transform_pil_batch
|
||||
from ._transform_inputs import spec_from_values, transform_inputs
|
||||
from ._transform_engine import (
|
||||
original_image_batch,
|
||||
resize_batch_to_megapixels,
|
||||
stable_file_fingerprint,
|
||||
transform_pil_batch,
|
||||
)
|
||||
from ._transform_inputs import resize_inputs, spec_from_values, transform_inputs
|
||||
|
||||
|
||||
class AusBossVideoCropRotatePad:
|
||||
@@ -71,6 +76,9 @@ class AusBossVideoCropRotatePad:
|
||||
),
|
||||
}
|
||||
required.update(transform_inputs())
|
||||
# Appended AFTER the stable widgets, so saved workflows' positional
|
||||
# widgets_values keep loading; missing values fall back to defaults.
|
||||
required.update(resize_inputs())
|
||||
return {"required": required}
|
||||
|
||||
# Appended outputs only: saved links ride slot indices.
|
||||
@@ -80,9 +88,9 @@ class AusBossVideoCropRotatePad:
|
||||
"The selected and transformed frame as a one-image BHWC batch.",
|
||||
"BHW generated-area mask: rotation corners and padding.",
|
||||
"Full-canvas stitcher: restores kept source pixels over an outpaint result; wire to Stitch Inpaint.",
|
||||
"The picked source frame before rotation, crop, or padding, as a BHWC RGB batch.",
|
||||
"Output width after the transform.",
|
||||
"Output height after the transform.",
|
||||
"The picked source frame before rotation, crop, padding, or resize, as a BHWC RGB batch.",
|
||||
"Output width after the transform and any resize.",
|
||||
"Output height after the transform and any resize.",
|
||||
)
|
||||
FUNCTION = "load_transform"
|
||||
|
||||
@@ -94,11 +102,19 @@ class AusBossVideoCropRotatePad:
|
||||
seek_mode: str,
|
||||
frame_index: int,
|
||||
frame_time: float,
|
||||
resize_to_megapixels=False,
|
||||
megapixels=1.0,
|
||||
resize_method="lanczos",
|
||||
resolution_steps=1,
|
||||
**values,
|
||||
):
|
||||
path = resolve_video_path(source_mode, video, local_path)
|
||||
frame, _, _ = decode_video_frame(path, seek_mode, frame_index, frame_time)
|
||||
output, mask, geometry = transform_pil_batch([frame], spec_from_values(**values))
|
||||
if resize_to_megapixels:
|
||||
output, mask = resize_batch_to_megapixels(
|
||||
output, mask, float(megapixels), str(resize_method), int(resolution_steps)
|
||||
)
|
||||
stitcher = build_transform_stitcher(
|
||||
output, mask, geometry, 32, source="Video Crop + Rotate + Pad -> Frame"
|
||||
)
|
||||
@@ -131,6 +147,7 @@ class AusBossVideoCropRotatePad:
|
||||
except Exception:
|
||||
path = local_path if source_mode == "local path" else video
|
||||
spec = spec_from_values(**values)
|
||||
resize = {name: values.get(name) for name in resize_inputs()}
|
||||
return stable_file_fingerprint(
|
||||
path,
|
||||
{
|
||||
@@ -140,6 +157,7 @@ class AusBossVideoCropRotatePad:
|
||||
"frame_index": int(frame_index),
|
||||
"frame_time": round(float(frame_time), 6),
|
||||
**spec.__dict__,
|
||||
**resize,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -55,6 +55,22 @@ class TransformOutputTests(unittest.TestCase):
|
||||
self.assertEqual(tuple(original.shape), (1, 96, 128, 3))
|
||||
self.assertEqual(tuple(apply_stitch(stitcher, image).shape), tuple(image.shape))
|
||||
|
||||
@unittest.skipUnless(COMFY_ROOT, "Set AUSBOSS_COMFY_ROOT for core resize integration")
|
||||
def test_video_frame_resizes_to_a_budget_like_the_image_node(self):
|
||||
with patch('nodes.node_video_crop_rotate_pad.resolve_video_path', return_value=Path('source.mp4')), patch('nodes.node_video_crop_rotate_pad.decode_video_frame', return_value=(self.source, 0, 0)):
|
||||
image, mask, stitcher, original, width, height = AusBossVideoCropRotatePad().load_transform(
|
||||
'source.mp4', 'input folder', '', 'frame index', 0, 0,
|
||||
resize_to_megapixels=True, megapixels=.05, resolution_steps=16, pad_left=32, feather=0,
|
||||
)
|
||||
self.assertEqual((width, height), (int(image.shape[2]), int(image.shape[1])))
|
||||
# 0.05 MP x 1024 x 1024 is larger than the 160 x 96 canvas: the
|
||||
# budget scales the frame to it either way, aspect kept, to steps of 16.
|
||||
budget = 0.05 * 1024 * 1024
|
||||
self.assertLess(abs(width * height - budget) / budget, 0.1)
|
||||
self.assertEqual((width % 16, height % 16), (0, 0))
|
||||
self.assertEqual(tuple(stitcher['canvas'].shape), tuple(image.shape))
|
||||
self.assertEqual(tuple(original.shape), (1, 96, 128, 3))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user