Skip to content

Commit 45d3ff0

Browse files
committed
feat: add VideoTrim and VideoCrop nodes with VIDEO_EDIT widget inputs
1 parent b78cec8 commit 45d3ff0

8 files changed

Lines changed: 537 additions & 20 deletions

File tree

comfy_api/latest/_input/video_types.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import Optional, Union, IO
55
import io
66
import av
7-
from .._util import VideoContainer, VideoCodec, VideoComponents
7+
from .._util import VideoContainer, VideoCodec, VideoComponents, normalize_crop_rect
88

99
class VideoInput(ABC):
1010
"""
@@ -31,12 +31,15 @@ def save_to(
3131
bit_depth: int | None = None,
3232
crf: float | None = None,
3333
color_space: str | None = None,
34+
preset: str | None = None,
3435
):
3536
"""
3637
Abstract method to save the video input to a file.
3738
3839
bit_depth selects the encoded bit depth; None keeps the video's native depth.
3940
crf selects the H.264 or AV1 constant rate factor; None uses the encoder default.
41+
preset selects the H.264 encoder speed/compression trade-off (e.g. "ultrafast");
42+
None uses the encoder default. Ignored for other codecs.
4043
color_space="sRGB" selects SDR BT.709/sRGB, "HDR" selects BT.2020/HLG, and "HDR PQ"
4144
selects BT.2020/PQ. Bit depth is selected independently.
4245
Tensor-created videos default to sRGB when color_space is None. Loaded videos keep matching recognized native color
@@ -63,6 +66,45 @@ def as_trimmed(
6366
"""
6467
pass
6568

69+
def as_cropped(
70+
self,
71+
x: int = 0,
72+
y: int = 0,
73+
width: int = 0,
74+
height: int = 0,
75+
) -> VideoInput:
76+
"""
77+
Create a new VideoInput spatially cropped to the given pixel rectangle.
78+
79+
The rectangle is clamped to the frame and even-aligned for encoder
80+
compatibility. An empty or full-frame rectangle returns the input
81+
unchanged.
82+
83+
Default implementation materializes the video via get_components();
84+
subclasses should override with lazier strategies when possible.
85+
"""
86+
components = self.get_components()
87+
rect = normalize_crop_rect(
88+
x, y, width, height, components.images.shape[2], components.images.shape[1]
89+
)
90+
if rect is None:
91+
return self
92+
from .._input_impl.video_types import VideoFromComponents
93+
94+
cx, cy, cw, ch = rect
95+
return VideoFromComponents(
96+
VideoComponents(
97+
images=components.images[:, cy:cy + ch, cx:cx + cw, :].clone(),
98+
audio=components.audio,
99+
frame_rate=components.frame_rate,
100+
metadata=components.metadata,
101+
alpha=components.alpha[:, cy:cy + ch, cx:cx + cw].clone()
102+
if components.alpha is not None
103+
else None,
104+
),
105+
bit_depth=self.get_bit_depth(),
106+
)
107+
66108
def get_stream_source(self) -> Union[str, io.BytesIO]:
67109
"""
68110
Get a streamable source for the video. This allows processing without

0 commit comments

Comments
 (0)