From 542b209a089a04e2bdab26de4835ac101f91276c Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 23 Jun 2026 15:39:10 -0700 Subject: [PATCH] Leverage sws_is_noop for video reformat shortcuts --- av/video/reformatter.pxd | 2 +- av/video/reformatter.py | 93 ++++++++++++++-------------------------- 2 files changed, 34 insertions(+), 61 deletions(-) diff --git a/av/video/reformatter.pxd b/av/video/reformatter.pxd index fe4b68db5..eb148b4d4 100644 --- a/av/video/reformatter.pxd +++ b/av/video/reformatter.pxd @@ -36,6 +36,7 @@ cdef extern from "libswscale/swscale.h" nogil: cdef SwsContext *sws_alloc_context() cdef void sws_free_context(SwsContext **ctx) + cdef int sws_is_noop(const lib.AVFrame *dst, const lib.AVFrame *src) cdef int sws_scale_frame(SwsContext *c, lib.AVFrame *dst, const lib.AVFrame *src) cdef class VideoReformatter: @@ -45,5 +46,4 @@ cdef class VideoReformatter: int dst_colorspace, int interpolation, int src_color_range, int dst_color_range, int dst_color_trc, int dst_color_primaries, - bint set_dst_color_trc, bint set_dst_color_primaries, int threads) diff --git a/av/video/reformatter.py b/av/video/reformatter.py index e95c871a3..e30b29f80 100644 --- a/av/video/reformatter.py +++ b/av/video/reformatter.py @@ -220,18 +220,16 @@ def reformat( ) c_src_color_range = _resolve_enum_value(src_color_range, ColorRange, 0) c_dst_color_range = _resolve_enum_value(dst_color_range, ColorRange, 0) - c_dst_color_trc = _resolve_enum_value(dst_color_trc, ColorTrc, 0) + c_dst_color_trc = _resolve_enum_value( + dst_color_trc, ColorTrc, frame.ptr.color_trc + ) c_dst_color_primaries = _resolve_enum_value( - dst_color_primaries, ColorPrimaries, 0 + dst_color_primaries, ColorPrimaries, frame.ptr.color_primaries ) c_threads: cython.int = threads if threads is not None else 0 c_width: cython.int = width if width is not None else frame.ptr.width c_height: cython.int = height if height is not None else frame.ptr.height - # Track whether user explicitly specified destination metadata - set_dst_color_trc: cython.bint = dst_color_trc is not None - set_dst_color_primaries: cython.bint = dst_color_primaries is not None - return self._reformat( frame, c_width, @@ -244,8 +242,6 @@ def reformat( c_dst_color_range, c_dst_color_trc, c_dst_color_primaries, - set_dst_color_trc, - set_dst_color_primaries, c_threads, ) @@ -263,46 +259,41 @@ def _reformat( dst_color_range: cython.int, dst_color_trc: cython.int, dst_color_primaries: cython.int, - set_dst_color_trc: cython.bint, - set_dst_color_primaries: cython.bint, threads: cython.int, ): - if frame.ptr.format < 0: - raise ValueError("Frame does not have format set.") - - src_format = cython.cast(lib.AVPixelFormat, frame.ptr.format) - - # Shortcut! - if ( - dst_format == src_format - and width == frame.ptr.width - and height == frame.ptr.height - and dst_colorspace == src_colorspace - and src_color_range == dst_color_range - and not set_dst_color_trc - and not set_dst_color_primaries - ): - return frame - if frame.ptr.hw_frames_ctx: frame_sw = alloc_video_frame() err_check(lib.av_hwframe_transfer_data(frame_sw.ptr, frame.ptr, 0)) frame_sw._copy_internal_attributes(frame, data_layout=False) + frame_sw._init_user_attributes() frame = frame_sw - src_format = cython.cast(lib.AVPixelFormat, frame.ptr.format) - - # Check for shortcut again, in case dst_format matches the downloaded frame's sw_format - if ( - dst_format == src_format - and width == frame.ptr.width - and height == frame.ptr.height - and dst_colorspace == src_colorspace - and src_color_range == dst_color_range - and not set_dst_color_trc - and not set_dst_color_primaries - ): - frame._init_user_attributes() - return frame + + new_frame: VideoFrame = alloc_video_frame() + new_frame._copy_internal_attributes(frame, data_layout=False) + new_frame.ptr.format = dst_format + new_frame.ptr.width = width + new_frame.ptr.height = height + new_frame.ptr.color_trc = cython.cast( + lib.AVColorTransferCharacteristic, dst_color_trc + ) + new_frame.ptr.color_primaries = cython.cast( + lib.AVColorPrimaries, dst_color_primaries + ) + + # Translate source and destination colorspace/range from SWS_CS_* to AVCOL_* + # so sws_is_noop and sws_scale_frame understand them + frame_src_colorspace: lib.AVColorSpace = frame.ptr.colorspace + frame_src_color_range: lib.AVColorRange = frame.ptr.color_range + _set_frame_colorspace(frame.ptr, src_colorspace, src_color_range) + _set_frame_colorspace(new_frame.ptr, dst_colorspace, dst_color_range) + + # Shortcut if sws_scale_frame would be a no-op + is_noop: cython.bint = sws_is_noop(new_frame.ptr, frame.ptr) != 0 + if is_noop: + # Restore source frame colorspace/range to avoid side effects + frame.ptr.colorspace = frame_src_colorspace + frame.ptr.color_range = frame_src_color_range + return frame if self.ptr == cython.NULL: self.ptr = sws_alloc_context() @@ -311,16 +302,8 @@ def _reformat( self.ptr.threads = threads self.ptr.flags = cython.cast(cython.uint, interpolation) - new_frame: VideoFrame = alloc_video_frame() - new_frame._copy_internal_attributes(frame) + # Allocate frame buffers and perform the conversion new_frame._init(dst_format, width, height) - - # Set source frame colorspace/range so sws_scale_frame can read it - frame_src_colorspace: lib.AVColorSpace = frame.ptr.colorspace - frame_src_color_range: lib.AVColorRange = frame.ptr.color_range - _set_frame_colorspace(frame.ptr, src_colorspace, src_color_range) - _set_frame_colorspace(new_frame.ptr, dst_colorspace, dst_color_range) - with cython.nogil: ret = sws_scale_frame(self.ptr, new_frame.ptr, frame.ptr) @@ -330,14 +313,4 @@ def _reformat( err_check(ret) - # Set metadata-only properties on the output frame if explicitly specified - if set_dst_color_trc: - new_frame.ptr.color_trc = cython.cast( - lib.AVColorTransferCharacteristic, dst_color_trc - ) - if set_dst_color_primaries: - new_frame.ptr.color_primaries = cython.cast( - lib.AVColorPrimaries, dst_color_primaries - ) - return new_frame