Beta/venv/lib/python3.12/site-packages/av/video/reformatter.py

323 lines
12 KiB
Python
Raw Normal View History

2026-06-16 17:09:34 +00:00
from enum import IntEnum
import cython
import cython.cimports.libav as lib
from cython.cimports.av.error import err_check
from cython.cimports.av.video.format import VideoFormat, get_pix_fmt
from cython.cimports.av.video.frame import alloc_video_frame
class Interpolation(IntEnum):
FAST_BILINEAR: "Fast bilinear" = SWS_FAST_BILINEAR
BILINEAR: "Bilinear" = SWS_BILINEAR
BICUBIC: "2-tap cubic B-spline" = SWS_BICUBIC
X: "Experimental" = SWS_X
POINT: "Nearest neighbor / point" = SWS_POINT
AREA: "Area averaging" = SWS_AREA
BICUBLIN: "Bicubic luma / Bilinear chroma" = SWS_BICUBLIN
GAUSS: "Gaussian approximation" = SWS_GAUSS
SINC: "Unwindowed Sinc" = SWS_SINC
LANCZOS: "3-tap sinc/sinc" = SWS_LANCZOS
SPLINE: "Unwindowed natural cubic spline" = SWS_SPLINE
class Colorspace(IntEnum):
ITU709 = SWS_CS_ITU709
FCC = SWS_CS_FCC
ITU601 = SWS_CS_ITU601
ITU624 = SWS_CS_ITU624
SMPTE170M = SWS_CS_SMPTE170M
SMPTE240M = SWS_CS_SMPTE240M
DEFAULT = SWS_CS_DEFAULT
# Lowercase for b/c.
itu709 = SWS_CS_ITU709
fcc = SWS_CS_FCC
itu601 = SWS_CS_ITU601
itu624 = SWS_CS_ITU624
smpte170m = SWS_CS_SMPTE170M
smpte240m = SWS_CS_SMPTE240M
default = SWS_CS_DEFAULT
class ColorRange(IntEnum):
UNSPECIFIED: "Unspecified" = lib.AVCOL_RANGE_UNSPECIFIED
MPEG: "MPEG (limited) YUV range, 219*2^(n-8)" = lib.AVCOL_RANGE_MPEG
JPEG: "JPEG (full) YUV range, 2^n-1" = lib.AVCOL_RANGE_JPEG
NB: "Not part of ABI" = lib.AVCOL_RANGE_NB
class ColorTrc(IntEnum):
"""Transfer characteristic (gamma curve) of a video frame.
Maps to FFmpeg's ``AVColorTransferCharacteristic``.
"""
BT709: "BT.709" = lib.AVCOL_TRC_BT709
UNSPECIFIED: "Unspecified" = lib.AVCOL_TRC_UNSPECIFIED
GAMMA22: "Gamma 2.2 (BT.470M)" = lib.AVCOL_TRC_GAMMA22
GAMMA28: "Gamma 2.8 (BT.470BG)" = lib.AVCOL_TRC_GAMMA28
SMPTE170M: "SMPTE 170M" = lib.AVCOL_TRC_SMPTE170M
SMPTE240M: "SMPTE 240M" = lib.AVCOL_TRC_SMPTE240M
LINEAR: "Linear" = lib.AVCOL_TRC_LINEAR
LOG: "Logarithmic (100:1 range)" = lib.AVCOL_TRC_LOG
LOG_SQRT: "Logarithmic (100*sqrt(10):1 range)" = lib.AVCOL_TRC_LOG_SQRT
IEC61966_2_4: "IEC 61966-2-4 (sRGB)" = lib.AVCOL_TRC_IEC61966_2_4
BT1361_ECG: "BT.1361 extended colour gamut" = lib.AVCOL_TRC_BT1361_ECG
IEC61966_2_1: "IEC 61966-2-1 (sYCC)" = lib.AVCOL_TRC_IEC61966_2_1
BT2020_10: "BT.2020 10-bit" = lib.AVCOL_TRC_BT2020_10
BT2020_12: "BT.2020 12-bit" = lib.AVCOL_TRC_BT2020_12
SMPTE2084: "SMPTE 2084 (PQ, HDR10)" = lib.AVCOL_TRC_SMPTE2084
SMPTE428: "SMPTE 428-1" = lib.AVCOL_TRC_SMPTE428
ARIB_STD_B67: "ARIB STD-B67 (HLG)" = lib.AVCOL_TRC_ARIB_STD_B67
class ColorPrimaries(IntEnum):
"""Color primaries of a video frame.
Maps to FFmpeg's ``AVColorPrimaries``.
"""
BT709: "BT.709 / sRGB / sYCC" = lib.AVCOL_PRI_BT709
UNSPECIFIED: "Unspecified" = lib.AVCOL_PRI_UNSPECIFIED
BT470M: "BT.470M" = lib.AVCOL_PRI_BT470M
BT470BG: "BT.470BG / BT.601-6 625" = lib.AVCOL_PRI_BT470BG
SMPTE170M: "SMPTE 170M / BT.601-6 525" = lib.AVCOL_PRI_SMPTE170M
SMPTE240M: "SMPTE 240M" = lib.AVCOL_PRI_SMPTE240M
FILM: "Generic film (Illuminant C)" = lib.AVCOL_PRI_FILM
BT2020: "BT.2020 / BT.2100" = lib.AVCOL_PRI_BT2020
SMPTE428: "SMPTE 428-1 / XYZ" = lib.AVCOL_PRI_SMPTE428
SMPTE431: "SMPTE 431-2 (DCI-P3)" = lib.AVCOL_PRI_SMPTE431
SMPTE432: "SMPTE 432-1 (Display P3)" = lib.AVCOL_PRI_SMPTE432
EBU3213: "EBU 3213-E / JEDEC P22" = lib.AVCOL_PRI_EBU3213
@cython.cfunc
@cython.inline
def _resolve_enum_value(
value: object, enum_class: object, default: cython.int
) -> cython.int:
# Helper function to resolve enum values from different input types.
if value is None:
return default
if isinstance(value, enum_class):
return value.value
if isinstance(value, int):
return value
if isinstance(value, str):
return enum_class[value].value
raise ValueError(f"Cannot convert {value} to {enum_class.__name__}")
@cython.cfunc
@cython.inline
def _resolve_format(format: object, default: lib.AVPixelFormat) -> lib.AVPixelFormat:
if format is None:
return default
if isinstance(format, VideoFormat):
return cython.cast(VideoFormat, format).pix_fmt
return get_pix_fmt(format)
@cython.cfunc
def _set_frame_colorspace(
frame: cython.pointer(lib.AVFrame),
colorspace: cython.int,
color_range: cython.int,
):
"""Set AVFrame colorspace/range from SWS_CS_* and AVColorRange values."""
if color_range != lib.AVCOL_RANGE_UNSPECIFIED:
frame.color_range = cython.cast(lib.AVColorRange, color_range)
# Mapping from SWS_CS_* (swscale colorspace) to AVColorSpace (frame metadata).
# Note: SWS_CS_ITU601, SWS_CS_ITU624, SWS_CS_SMPTE170M, and SWS_CS_DEFAULT all have
# the same value (5), so we map 5 -> AVCOL_SPC_SMPTE170M as the most common case.
# SWS_CS_DEFAULT is handled specially by not setting frame metadata.
if colorspace == SWS_CS_ITU709:
frame.colorspace = lib.AVCOL_SPC_BT709
elif colorspace == SWS_CS_FCC:
frame.colorspace = lib.AVCOL_SPC_FCC
elif colorspace == SWS_CS_ITU601:
frame.colorspace = lib.AVCOL_SPC_SMPTE170M
elif colorspace == SWS_CS_SMPTE240M:
frame.colorspace = lib.AVCOL_SPC_SMPTE240M
@cython.final
@cython.cclass
class VideoReformatter:
"""An object for reformatting size and pixel format of :class:`.VideoFrame`.
It is most efficient to have a reformatter object for each set of parameters
you will use as calling :meth:`reformat` will reconfigure the internal object.
"""
def __dealloc__(self):
with cython.nogil:
sws_free_context(cython.address(self.ptr))
def reformat(
self,
frame: VideoFrame,
width=None,
height=None,
format=None,
src_colorspace=None,
dst_colorspace=None,
interpolation=None,
src_color_range=None,
dst_color_range=None,
dst_color_trc=None,
dst_color_primaries=None,
threads=None,
):
"""Create a new :class:`VideoFrame` with the given width/height/format/colorspace.
Returns the same frame untouched if nothing needs to be done to it.
:param int width: New width, or ``None`` for the same width.
:param int height: New height, or ``None`` for the same height.
:param format: New format, or ``None`` for the same format.
:type format: :class:`.VideoFormat` or ``str``
:param src_colorspace: Current colorspace, or ``None`` for the frame colorspace.
:type src_colorspace: :class:`Colorspace` or ``str``
:param dst_colorspace: Desired colorspace, or ``None`` for the frame colorspace.
:type dst_colorspace: :class:`Colorspace` or ``str``
:param interpolation: The interpolation method to use, or ``None`` for ``BILINEAR``.
:type interpolation: :class:`Interpolation` or ``str``
:param src_color_range: Current color range, or ``None`` for the ``UNSPECIFIED``.
:type src_color_range: :class:`ColorRange` or ``str``
:param dst_color_range: Desired color range, or ``None`` for the ``UNSPECIFIED``.
:type dst_color_range: :class:`ColorRange` or ``str``
:param dst_color_trc: Desired transfer characteristic to tag on the output frame,
or ``None`` to preserve the source frame's value. This sets frame metadata only;
it does not perform a pixel-level transfer function conversion.
:type dst_color_trc: :class:`ColorTrc` or ``int``
:param dst_color_primaries: Desired color primaries to tag on the output frame,
or ``None`` to preserve the source frame's value.
:type dst_color_primaries: :class:`ColorPrimaries` or ``int``
:param int threads: How many threads to use for scaling, or ``0`` for automatic
selection based on the number of available CPUs. Defaults to ``0`` (auto).
"""
c_dst_format = _resolve_format(format, frame.format.pix_fmt)
c_src_colorspace = _resolve_enum_value(
src_colorspace, Colorspace, frame.ptr.colorspace
)
c_dst_colorspace = _resolve_enum_value(
dst_colorspace, Colorspace, frame.ptr.colorspace
)
c_interpolation = _resolve_enum_value(
interpolation, Interpolation, SWS_BILINEAR
)
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_primaries = _resolve_enum_value(
dst_color_primaries, ColorPrimaries, 0
)
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,
c_height,
c_dst_format,
c_src_colorspace,
c_dst_colorspace,
c_interpolation,
c_src_color_range,
c_dst_color_range,
c_dst_color_trc,
c_dst_color_primaries,
set_dst_color_trc,
set_dst_color_primaries,
c_threads,
)
@cython.cfunc
def _reformat(
self,
frame: VideoFrame,
width: cython.int,
height: cython.int,
dst_format: lib.AVPixelFormat,
src_colorspace: cython.int,
dst_colorspace: cython.int,
interpolation: cython.int,
src_color_range: cython.int,
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.pts = frame.pts
frame_sw._init_user_attributes()
frame = frame_sw
src_format = cython.cast(lib.AVPixelFormat, frame.ptr.format)
if self.ptr == cython.NULL:
self.ptr = sws_alloc_context()
if self.ptr == cython.NULL:
raise MemoryError("Could not allocate SwsContext")
self.ptr.threads = threads
self.ptr.flags = cython.cast(cython.uint, interpolation)
new_frame: VideoFrame = alloc_video_frame()
new_frame._copy_internal_attributes(frame)
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)
# Restore source frame colorspace/range to avoid side effects
frame.ptr.colorspace = frame_src_colorspace
frame.ptr.color_range = frame_src_color_range
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