diff --git a/av/video/__init__.py b/av/video/__init__.py index 4a25d8837..55fe73f35 100644 --- a/av/video/__init__.py +++ b/av/video/__init__.py @@ -1,2 +1,3 @@ from .frame import VideoFrame from .stream import VideoStream +from . import display diff --git a/av/video/display.pyi b/av/video/display.pyi new file mode 100644 index 000000000..e1988c2e0 --- /dev/null +++ b/av/video/display.pyi @@ -0,0 +1,16 @@ +from av.sidedata.sidedata import SideData + +def get_display_rotation(matrix: SideData) -> float: + """Extract the rotation component of the `DISPLAYMATRIX` transformation matrix. + + Args: + matrix (SideData): The transformation matrix. + + Returns: + float: The angle (in degrees) by which the transformation rotates the frame + counterclockwise. The angle will be in range [-180.0, 180.0]. + + Note: + Floating point numbers are inherently inexact, so callers are + recommended to round the return value to the nearest integer before use. + """ diff --git a/av/video/display.pyx b/av/video/display.pyx new file mode 100644 index 000000000..627d8f8d1 --- /dev/null +++ b/av/video/display.pyx @@ -0,0 +1,13 @@ +from libc.stdint cimport int32_t +cimport libav as lib +import numpy as np +from av.sidedata.sidedata import SideData, Type as SideDataType + +def get_display_rotation(matrix): + if not isinstance(matrix, SideData) or matrix.type != SideDataType.DISPLAYMATRIX: + raise ValueError("Matrix must be `SideData` of type `DISPLAYMATRIX`") + cdef const int32_t[:] view = np.frombuffer(matrix, dtype=np.int32) + if view.shape[0] != 9: + raise ValueError("Matrix must be 3x3 represented as a 9-element array") + return lib.av_display_rotation_get(&view[0]) + diff --git a/include/libavutil/avutil.pxd b/include/libavutil/avutil.pxd index ed281aeaf..58dd43922 100644 --- a/include/libavutil/avutil.pxd +++ b/include/libavutil/avutil.pxd @@ -4,6 +4,9 @@ from libc.stdint cimport int64_t, uint8_t, uint64_t, int32_t cdef extern from "libavutil/mathematics.h" nogil: pass +cdef extern from "libavutil/display.h" nogil: + cdef double av_display_rotation_get(const int32_t matrix[9]) + cdef extern from "libavutil/rational.h" nogil: cdef int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max) diff --git a/tests/test_decode.py b/tests/test_decode.py index 20abdf840..452fd237f 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -155,3 +155,16 @@ def test_flush_decoded_video_frame_count(self) -> None: output_count += 1 assert output_count == input_count + + def test_no_side_data(self): + container = av.open(fate_suite("h264/interlaced_crop.mp4")) + frame = next(container.decode(video=0)) + matrix = frame.side_data.get(av.sidedata.sidedata.Type.DISPLAYMATRIX) + assert matrix is None + + def test_side_data(self): + container = av.open(fate_suite("mov/displaymatrix.mov")) + frame = next(container.decode(video=0)) + matrix = frame.side_data.get(av.sidedata.sidedata.Type.DISPLAYMATRIX) + rotation = av.video.display.get_display_rotation(matrix) + self.assertEqual(rotation, -90.0)