Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions av/frame.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class SideData(TypedDict, total=False):
class Frame:
dts: int | None
pts: int | None
duration: int | None
time_base: Fraction
side_data: SideData
opaque: object
Expand Down
21 changes: 21 additions & 0 deletions av/frame.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ cdef class Frame:
if self.ptr.pts != lib.AV_NOPTS_VALUE:
self.ptr.pts = lib.av_rescale_q(self.ptr.pts, self._time_base, dst)

if self.ptr.duration != 0:
self.ptr.duration = lib.av_rescale_q(self.ptr.duration, self._time_base, dst)

self._time_base = dst

@property
Expand Down Expand Up @@ -95,6 +98,24 @@ cdef class Frame:
else:
self.ptr.pts = value

@property
def duration(self):
"""
The duration of the frame in :attr:`time_base` units

:type: int
"""
if self.ptr.duration == 0:
return None
return self.ptr.duration

@duration.setter
def duration(self, value):
if value is None:
self.ptr.duration = 0
else:
self.ptr.duration = value

@property
def time(self):
"""
Expand Down
1 change: 1 addition & 0 deletions av/video/frame.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class PictureType(IntEnum):
class VideoFrame(Frame):
format: VideoFormat
pts: int
duration: int
planes: tuple[VideoPlane, ...]
pict_type: int
colorspace: int
Expand Down
2 changes: 2 additions & 0 deletions include/libavcodec/avcodec.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ cdef extern from "libavcodec/avcodec.h" nogil:
AVColorTransferCharacteristic color_trc
AVColorSpace colorspace

int64_t duration

cdef AVFrame* avcodec_alloc_frame()

cdef struct AVPacket:
Expand Down
15 changes: 15 additions & 0 deletions tests/test_videoframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ def test_opaque() -> None:
assert type(frame.opaque) is tuple and len(frame.opaque) == 2


def test_frame_duration_matches_packet() -> None:
with av.open(fate_suite("h264/interlaced_crop.mp4")) as container:
packet_durations = [
(p.pts, p.duration) for p in container.demux() if p.pts is not None
]
packet_durations.sort(key=lambda x: x[0])

with av.open(fate_suite("h264/interlaced_crop.mp4")) as container:
frame_durations = [(f.pts, f.duration) for f in container.decode(video=0)]
frame_durations.sort(key=lambda x: x[0])

assert len(packet_durations) == len(frame_durations)
assert all(pd[1] == fd[1] for pd, fd in zip(packet_durations, frame_durations))


def test_invalid_pixel_format() -> None:
with pytest.raises(ValueError, match="not a pixel format: '__unknown_pix_fmt'"):
VideoFrame(640, 480, "__unknown_pix_fmt")
Expand Down
Loading