mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-02 12:27:59 +08:00
[Partner Nodes] fix: respect VideoSlice trim when resizing videos (#14213)
This commit is contained in:
parent
412d9ac33a
commit
0b610bd63a
@ -65,6 +65,12 @@ class VideoInput(ABC):
|
|||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
return buffer
|
return buffer
|
||||||
|
|
||||||
|
def get_active_trim_window(self) -> tuple[float, float]:
|
||||||
|
"""Return the active trim as ``(start_time, duration)`` in seconds (start_time normalized
|
||||||
|
to ``>= 0``; ``duration == 0`` means "until the end"). Default: no trim; trimmable subclasses override.
|
||||||
|
"""
|
||||||
|
return 0.0, 0.0
|
||||||
|
|
||||||
# Provide a default implementation, but subclasses can provide optimized versions
|
# Provide a default implementation, but subclasses can provide optimized versions
|
||||||
# if possible.
|
# if possible.
|
||||||
def get_dimensions(self) -> tuple[int, int]:
|
def get_dimensions(self) -> tuple[int, int]:
|
||||||
|
|||||||
@ -75,6 +75,12 @@ class VideoFromFile(VideoInput):
|
|||||||
self.__file.seek(0)
|
self.__file.seek(0)
|
||||||
return self.__file
|
return self.__file
|
||||||
|
|
||||||
|
def get_active_trim_window(self) -> tuple[float, float]:
|
||||||
|
start_time = self.__start_time
|
||||||
|
if start_time < 0:
|
||||||
|
start_time = max(self._get_raw_duration() + start_time, 0.0)
|
||||||
|
return float(start_time), float(self.__duration)
|
||||||
|
|
||||||
def get_dimensions(self) -> tuple[int, int]:
|
def get_dimensions(self) -> tuple[int, int]:
|
||||||
"""
|
"""
|
||||||
Returns the dimensions of the video input.
|
Returns the dimensions of the video input.
|
||||||
|
|||||||
@ -469,6 +469,11 @@ def _apply_video_scale(video: Input.Video, scale_dims: tuple[int, int]) -> Input
|
|||||||
input_container = None
|
input_container = None
|
||||||
output_container = None
|
output_container = None
|
||||||
|
|
||||||
|
# get_stream_source() is untrimmed, so apply the trim window in this same pass.
|
||||||
|
# start_time is normalized (>= 0); duration == 0 means "until the end".
|
||||||
|
start_time, duration = video.get_active_trim_window()
|
||||||
|
trimming = bool(start_time or duration)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
input_source = video.get_stream_source()
|
input_source = video.get_stream_source()
|
||||||
input_container = av.open(input_source, mode="r")
|
input_container = av.open(input_source, mode="r")
|
||||||
@ -487,16 +492,45 @@ def _apply_video_scale(video: Input.Video, scale_dims: tuple[int, int]) -> Input
|
|||||||
audio_stream.layout = stream.layout
|
audio_stream.layout = stream.layout
|
||||||
break
|
break
|
||||||
|
|
||||||
|
in_video = input_container.streams.video[0]
|
||||||
|
start_pts = int(start_time / in_video.time_base) if trimming else 0
|
||||||
|
end_pts = int((start_time + duration) / in_video.time_base) if duration else None
|
||||||
|
if start_pts:
|
||||||
|
input_container.seek(start_pts, stream=in_video)
|
||||||
|
|
||||||
|
encoded = 0
|
||||||
for frame in input_container.decode(video=0):
|
for frame in input_container.decode(video=0):
|
||||||
|
if trimming:
|
||||||
|
if frame.pts is None or frame.pts < start_pts:
|
||||||
|
continue
|
||||||
|
if end_pts is not None and frame.pts >= end_pts:
|
||||||
|
break
|
||||||
frame = frame.reformat(width=out_w, height=out_h, format="yuv420p")
|
frame = frame.reformat(width=out_w, height=out_h, format="yuv420p")
|
||||||
|
# Re-wrap as a fresh frame: dropping irregular source timestamps (VFR/AVI/GIF/...)
|
||||||
|
# lets the encoder assign clean ones and avoids mp4 muxer errors.
|
||||||
|
frame = av.VideoFrame.from_ndarray(frame.to_ndarray(format="yuv420p"), format="yuv420p")
|
||||||
for packet in video_stream.encode(frame):
|
for packet in video_stream.encode(frame):
|
||||||
output_container.mux(packet)
|
output_container.mux(packet)
|
||||||
|
encoded += 1
|
||||||
for packet in video_stream.encode():
|
for packet in video_stream.encode():
|
||||||
output_container.mux(packet)
|
output_container.mux(packet)
|
||||||
|
|
||||||
|
if encoded == 0:
|
||||||
|
raise ValueError(
|
||||||
|
f"resize produced no frames (start_time={start_time}, duration={duration} "
|
||||||
|
"selected nothing from the source)"
|
||||||
|
)
|
||||||
|
|
||||||
if audio_stream is not None:
|
if audio_stream is not None:
|
||||||
input_container.seek(0)
|
input_container.seek(0)
|
||||||
for audio_frame in input_container.decode(audio=0):
|
for audio_frame in input_container.decode(audio=0):
|
||||||
|
if trimming:
|
||||||
|
if audio_frame.time is None or audio_frame.time < start_time:
|
||||||
|
continue
|
||||||
|
if duration and audio_frame.time > start_time + duration:
|
||||||
|
break
|
||||||
|
# Carry odd audio time bases the mp4 muxer rejects; reset pts, encoder assigns clean ones (MP3-in-AVI)
|
||||||
|
audio_frame.pts = None
|
||||||
for packet in audio_stream.encode(audio_frame):
|
for packet in audio_stream.encode(audio_frame):
|
||||||
output_container.mux(packet)
|
output_container.mux(packet)
|
||||||
for packet in audio_stream.encode():
|
for packet in audio_stream.encode():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user