diff --git a/av/sidedata/sidedata.pxd b/av/sidedata/sidedata.pxd index 8a2f6d07c..5e6e5bf4c 100644 --- a/av/sidedata/sidedata.pxd +++ b/av/sidedata/sidedata.pxd @@ -14,6 +14,8 @@ cdef class SideData(Buffer): cdef SideData wrap_side_data(Frame frame, int index) +cdef int get_display_rotation(Frame frame) + cdef class _SideDataContainer: cdef Frame frame diff --git a/av/sidedata/sidedata.pyx b/av/sidedata/sidedata.pyx index 753496fea..24dbae119 100644 --- a/av/sidedata/sidedata.pyx +++ b/av/sidedata/sidedata.pyx @@ -1,3 +1,5 @@ +from libc.stdint cimport int32_t + from collections.abc import Mapping from enum import Enum @@ -45,13 +47,19 @@ class Type(Enum): cdef SideData wrap_side_data(Frame frame, int index): - cdef lib.AVFrameSideDataType type_ = frame.ptr.side_data[index].type - if type_ == lib.AV_FRAME_DATA_MOTION_VECTORS: + if frame.ptr.side_data[index].type == lib.AV_FRAME_DATA_MOTION_VECTORS: return MotionVectors(_cinit_bypass_sentinel, frame, index) else: return SideData(_cinit_bypass_sentinel, frame, index) +cdef int get_display_rotation(Frame frame): + for i in range(frame.ptr.nb_side_data): + if frame.ptr.side_data[i].type == lib.AV_FRAME_DATA_DISPLAYMATRIX: + return int(lib.av_display_rotation_get(frame.ptr.side_data[i].data)) + return 0 + + cdef class SideData(Buffer): def __init__(self, sentinel, Frame frame, int index): if sentinel is not _cinit_bypass_sentinel: diff --git a/av/video/frame.pyi b/av/video/frame.pyi index 0739010c1..a3eea373d 100644 --- a/av/video/frame.pyi +++ b/av/video/frame.pyi @@ -41,6 +41,8 @@ class VideoFrame(Frame): def height(self) -> int: ... @property def interlaced_frame(self) -> bool: ... + @property + def rotation(self) -> int: ... def __init__( self, width: int = 0, height: int = 0, format: str = "yuv420p" ) -> None: ... diff --git a/av/video/frame.pyx b/av/video/frame.pyx index 862db8513..02cde3187 100644 --- a/av/video/frame.pyx +++ b/av/video/frame.pyx @@ -4,6 +4,7 @@ from enum import IntEnum from libc.stdint cimport uint8_t from av.error cimport err_check +from av.sidedata.sidedata cimport get_display_rotation from av.utils cimport check_ndarray from av.video.format cimport get_pix_fmt, get_video_format from av.video.plane cimport VideoPlane @@ -172,6 +173,16 @@ cdef class VideoFrame(Frame): """Height of the image, in pixels.""" return self.ptr.height + @property + def rotation(self): + """The rotation component of the `DISPLAYMATRIX` transformation matrix. + + Returns: + int: The angle (in degrees) by which the transformation rotates the frame + counterclockwise. The angle will be in range [-180, 180]. + """ + return get_display_rotation(self) + @property def interlaced_frame(self): """Is this frame an interlaced or progressive?""" 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..05f636977 100644 --- a/tests/test_decode.py +++ b/tests/test_decode.py @@ -155,3 +155,13 @@ def test_flush_decoded_video_frame_count(self) -> None: output_count += 1 assert output_count == input_count + + def test_no_side_data(self) -> None: + container = av.open(fate_suite("h264/interlaced_crop.mp4")) + frame = next(container.decode(video=0)) + assert frame.rotation == 0 + + def test_side_data(self) -> None: + container = av.open(fate_suite("mov/displaymatrix.mov")) + frame = next(container.decode(video=0)) + assert frame.rotation == -90