Skip to content

Commit e2ec340

Browse files
committed
Index opaque by id and make key_frame writable
1 parent 8e973ab commit e2ec340

File tree

6 files changed

+102
-59
lines changed

6 files changed

+102
-59
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ We are operating with `semantic versioning <https://semver.org>`_.
1616
Note that they these tags will not actually close the issue/PR until they
1717
are merged into the "default" branch.
1818

19+
v15.1.0
20+
-------
21+
22+
Features:
23+
24+
- Make the `Frame.key_frame` flag writable.
25+
1926
v15.0.0
2027
-------
2128

av/frame.pyx renamed to av/frame.py

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
1-
from av.error cimport err_check
2-
from av.opaque cimport opaque_container
3-
from av.utils cimport avrational_to_fraction, to_avrational
1+
import cython
2+
from cython.cimports.av.error import err_check
3+
from cython.cimports.av.opaque import opaque_container
4+
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
45

56
from av.sidedata.sidedata import SideDataContainer
67

78

8-
cdef class Frame:
9+
@cython.cclass
10+
class Frame:
911
"""
1012
Base class for audio and video frames.
1113
1214
See also :class:`~av.audio.frame.AudioFrame` and :class:`~av.video.frame.VideoFrame`.
1315
"""
1416

1517
def __cinit__(self, *args, **kwargs):
16-
with nogil:
18+
with cython.nogil:
1719
self.ptr = lib.av_frame_alloc()
1820

1921
def __dealloc__(self):
20-
with nogil:
21-
# This calls av_frame_unref, and then frees the pointer.
22-
# Thats it.
23-
lib.av_frame_free(&self.ptr)
22+
with cython.nogil:
23+
lib.av_frame_free(cython.address(self.ptr))
2424

2525
def __repr__(self):
26-
return f"av.{self.__class__.__name__} pts={self.pts} at 0x{id(self):x}>"
26+
return f"<av.{self.__class__.__name__} pts={self.pts} at 0x{id(self):x}>"
2727

28-
cdef _copy_internal_attributes(self, Frame source, bint data_layout=True):
29-
"""Mimic another frame."""
28+
@cython.cfunc
29+
def _copy_internal_attributes(self, source: Frame, data_layout: cython.bint = True):
30+
# Mimic another frame
3031
self._time_base = source._time_base
3132
lib.av_frame_copy_props(self.ptr, source.ptr)
3233
if data_layout:
@@ -36,10 +37,12 @@ def __repr__(self):
3637
self.ptr.height = source.ptr.height
3738
self.ptr.ch_layout = source.ptr.ch_layout
3839

39-
cdef _init_user_attributes(self):
40+
@cython.cfunc
41+
def _init_user_attributes(self):
4042
pass # Dummy to match the API of the others.
4143

42-
cdef _rebase_time(self, lib.AVRational dst):
44+
@cython.cfunc
45+
def _rebase_time(self, dst: lib.AVRational):
4346
if not dst.num:
4447
raise ValueError("Cannot rebase to zero time.")
4548

@@ -54,7 +57,9 @@ def __repr__(self):
5457
self.ptr.pts = lib.av_rescale_q(self.ptr.pts, self._time_base, dst)
5558

5659
if self.ptr.duration != 0:
57-
self.ptr.duration = lib.av_rescale_q(self.ptr.duration, self._time_base, dst)
60+
self.ptr.duration = lib.av_rescale_q(
61+
self.ptr.duration, self._time_base, dst
62+
)
5863

5964
self._time_base = dst
6065

@@ -65,7 +70,7 @@ def dts(self):
6570
6671
(if frame threading isn't used) This is also the Presentation time of this frame calculated from only :attr:`.Packet.dts` values without pts values.
6772
68-
:type: int
73+
:type: int | None
6974
"""
7075
if self.ptr.pkt_dts == lib.AV_NOPTS_VALUE:
7176
return None
@@ -85,7 +90,7 @@ def pts(self):
8590
8691
This is the time at which the frame should be shown to the user.
8792
88-
:type: int
93+
:type: int | None
8994
"""
9095
if self.ptr.pts == lib.AV_NOPTS_VALUE:
9196
return None
@@ -105,16 +110,11 @@ def duration(self):
105110
106111
:type: int
107112
"""
108-
if self.ptr.duration == 0:
109-
return None
110113
return self.ptr.duration
111114

112115
@duration.setter
113116
def duration(self, value):
114-
if value is None:
115-
self.ptr.duration = 0
116-
else:
117-
self.ptr.duration = value
117+
self.ptr.duration = value
118118

119119
@property
120120
def time(self):
@@ -123,26 +123,25 @@ def time(self):
123123
124124
This is the time at which the frame should be shown to the user.
125125
126-
:type: float
126+
:type: float | None
127127
"""
128128
if self.ptr.pts == lib.AV_NOPTS_VALUE:
129129
return None
130-
else:
131-
return float(self.ptr.pts) * self._time_base.num / self._time_base.den
130+
return float(self.ptr.pts) * self._time_base.num / self._time_base.den
132131

133132
@property
134133
def time_base(self):
135134
"""
136135
The unit of time (in fractional seconds) in which timestamps are expressed.
137136
138-
:type: fractions.Fraction
137+
:type: fractions.Fraction | None
139138
"""
140139
if self._time_base.num:
141-
return avrational_to_fraction(&self._time_base)
140+
return avrational_to_fraction(cython.address(self._time_base))
142141

143142
@time_base.setter
144143
def time_base(self, value):
145-
to_avrational(value, &self._time_base)
144+
to_avrational(value, cython.address(self._time_base))
146145

147146
@property
148147
def is_corrupt(self):
@@ -151,7 +150,9 @@ def is_corrupt(self):
151150
152151
:type: bool
153152
"""
154-
return self.ptr.decode_error_flags != 0 or bool(self.ptr.flags & lib.AV_FRAME_FLAG_CORRUPT)
153+
return self.ptr.decode_error_flags != 0 or bool(
154+
self.ptr.flags & lib.AV_FRAME_FLAG_CORRUPT
155+
)
155156

156157
@property
157158
def key_frame(self):
@@ -162,6 +163,13 @@ def key_frame(self):
162163
"""
163164
return bool(self.ptr.flags & lib.AV_FRAME_FLAG_KEY)
164165

166+
@key_frame.setter
167+
def key_frame(self, v):
168+
# PyAV makes no guarantees this does anything.
169+
if v:
170+
self.ptr.flags |= lib.AV_FRAME_FLAG_KEY
171+
else:
172+
self.ptr.flags &= ~lib.AV_FRAME_FLAG_KEY
165173

166174
@property
167175
def side_data(self):
@@ -174,20 +182,19 @@ def make_writable(self):
174182
Ensures that the frame data is writable. Copy the data to new buffer if it is not.
175183
This is a wrapper around :ffmpeg:`av_frame_make_writable`.
176184
"""
177-
cdef int ret
178-
179-
ret = lib.av_frame_make_writable(self.ptr)
185+
ret: cython.int = lib.av_frame_make_writable(self.ptr)
180186
err_check(ret)
181187

182188
@property
183189
def opaque(self):
184-
if self.ptr.opaque_ref is not NULL:
185-
return opaque_container.get(<char *> self.ptr.opaque_ref.data)
190+
if self.ptr.opaque_ref is not cython.NULL:
191+
return opaque_container.get(
192+
cython.cast(cython.p_char, self.ptr.opaque_ref.data)
193+
)
186194

187195
@opaque.setter
188196
def opaque(self, v):
189-
lib.av_buffer_unref(&self.ptr.opaque_ref)
197+
lib.av_buffer_unref(cython.address(self.ptr.opaque_ref))
190198

191-
if v is None:
192-
return
193-
self.ptr.opaque_ref = opaque_container.add(v)
199+
if v is not None:
200+
self.ptr.opaque_ref = opaque_container.add(v)

av/frame.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class SideData(TypedDict, total=False):
99
class Frame:
1010
dts: int | None
1111
pts: int | None
12-
duration: int | None
13-
time_base: Fraction
12+
duration: int
13+
time_base: Fraction | None
1414
side_data: SideData
1515
opaque: object
1616
@property

av/opaque.pxd

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ cimport libav as lib
22

33

44
cdef class OpaqueContainer:
5-
cdef dict _by_name
5+
cdef dict _objects
66

77
cdef lib.AVBufferRef *add(self, object v)
8-
cdef object get(self, bytes name)
9-
cdef object pop(self, bytes name)
8+
cdef object get(self, char *name)
9+
cdef object pop(self, char *name)
1010

1111

1212
cdef OpaqueContainer opaque_container

av/opaque.pyx

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
cimport libav as lib
22
from libc.stdint cimport uint8_t
3-
4-
from uuid import uuid4
3+
from libc.string cimport memcpy
54

65

76
cdef void key_free(void *opaque, uint8_t *data) noexcept nogil:
@@ -11,22 +10,37 @@ cdef void key_free(void *opaque, uint8_t *data) noexcept nogil:
1110

1211

1312
cdef class OpaqueContainer:
14-
"""A container that holds references to Python objects, indexed by uuid"""
15-
1613
def __cinit__(self):
17-
self._by_name = {}
14+
self._objects = {}
15+
16+
cdef lib.AVBufferRef *add(self, object v):
17+
# Use object's memory address as key
18+
cdef size_t key = id(v)
19+
self._objects[key] = v
20+
21+
cdef uint8_t *data = <uint8_t *>lib.av_malloc(sizeof(size_t))
22+
if data == NULL:
23+
raise MemoryError("Failed to allocate memory for key")
24+
25+
memcpy(data, &key, sizeof(size_t))
26+
27+
# Create the buffer with our free callback
28+
cdef lib.AVBufferRef *buffer_ref = lib.av_buffer_create(
29+
data, sizeof(size_t), key_free, NULL, 0
30+
)
31+
32+
if buffer_ref == NULL:
33+
raise MemoryError("Failed to create AVBufferRef")
1834

19-
cdef lib.AVBufferRef *add(self, v):
20-
cdef bytes uuid = str(uuid4()).encode("utf-8")
21-
cdef lib.AVBufferRef *ref = lib.av_buffer_create(uuid, len(uuid), &key_free, NULL, 0)
22-
self._by_name[uuid] = v
23-
return ref
35+
return buffer_ref
2436

25-
cdef object get(self, bytes name):
26-
return self._by_name.get(name)
37+
cdef object get(self, char *name):
38+
cdef size_t key = (<size_t *>name)[0]
39+
return self._objects.get(key)
2740

28-
cdef object pop(self, bytes name):
29-
return self._by_name.pop(name)
41+
cdef object pop(self, char *name):
42+
cdef size_t key = (<size_t *>name)[0]
43+
return self._objects.pop(key, None)
3044

3145

32-
cdef opaque_container = OpaqueContainer()
46+
cdef OpaqueContainer opaque_container = OpaqueContainer()

tests/test_videoframe.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ def test_memoryview_read() -> None:
122122
assert mem[:7] == b"0.234xx"
123123

124124

125+
def test_opaque() -> None:
126+
frame = VideoFrame(640, 480, "rgb24")
127+
frame.opaque = 3
128+
assert frame.opaque == 3
129+
frame.opaque = "a"
130+
assert frame.opaque == "a"
131+
132+
greeting = "Hello World!"
133+
frame.opaque = greeting
134+
assert frame.opaque is greeting
135+
136+
frame.opaque = None
137+
assert frame.opaque is None
138+
139+
125140
def test_interpolation() -> None:
126141
container = av.open(fate_png())
127142
for _ in container.decode(video=0):

0 commit comments

Comments
 (0)