Skip to content

Commit d3e4d14

Browse files
committed
added packet side-data handler
1 parent 5f33896 commit d3e4d14

File tree

6 files changed

+288
-4
lines changed

6 files changed

+288
-4
lines changed

av/packet.pxd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ from av.buffer cimport Buffer
44
from av.bytesource cimport ByteSource
55
from av.stream cimport Stream
66

7+
from cython.cimports.libc.stdint import uint8_t
8+
9+
cdef class PacketSideData:
10+
cdef uint8_t *data
11+
cdef size_t size
12+
cdef lib.AVPacketSideDataType dtype
713

814
cdef class Packet(Buffer):
915
cdef lib.AVPacket* ptr

av/packet.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,194 @@
44
from cython.cimports.av.error import err_check
55
from cython.cimports.av.opaque import opaque_container
66
from cython.cimports.av.utils import avrational_to_fraction, to_avrational
7+
from cython.cimports.av.error import err_check
8+
from cython.cimports.libc.string import memcpy
9+
10+
from typing import Literal, get_args
11+
12+
# check https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/packet.h#L41 for
13+
# new additions in the future FFmpeg releases
14+
# note: the order must follow that of the AVPacketSideDataType enum def
15+
PacketSideDataTypeLiteral = Literal[
16+
"palette",
17+
"new_extra_data",
18+
"param_change",
19+
"h263_mb_info",
20+
"replay_gain",
21+
"display_matrix",
22+
"stereo_3d",
23+
"audio_service_type",
24+
"quality_stats",
25+
"fallback_track",
26+
"cpb_properties",
27+
"skip_samples",
28+
"jp_dual_mono",
29+
"strings_metadata",
30+
"subtitle_position",
31+
"matroska_block_additional",
32+
"webvtt_identifier",
33+
"webvtt_settings",
34+
"metadata_update",
35+
"mpegts_stream_id",
36+
"mastering_display_metadata",
37+
"spherical",
38+
"content_light_level",
39+
"a53_cc",
40+
"encryption_init_info",
41+
"encryption_info",
42+
"afd",
43+
"prft",
44+
"icc_profile",
45+
"dovi_conf",
46+
"s12m_timecode",
47+
"dynamic_hdr10_plus",
48+
"iamf_mix_gain_param",
49+
"iamf_info_param",
50+
"iamf_recon_gain_info_param",
51+
"ambient_viewing_environment",
52+
"frame_cropping",
53+
"lcevc",
54+
"3d_reference_displays",
55+
"rtcp_sr",
56+
]
57+
58+
59+
def _packet_side_data_type_to_literal(
60+
data_type: lib.AVPacketSideDataType,
61+
) -> PacketSideDataTypeLiteral:
62+
63+
return get_args(PacketSideDataTypeLiteral)[cython.cast(int, data_type)]
64+
65+
66+
def _packet_side_data_type_from_literal(
67+
data_type_name: PacketSideDataTypeLiteral,
68+
) -> lib.AVPacketSideDataType:
69+
70+
i = cython.declare(
71+
cython.int, get_args(PacketSideDataTypeLiteral).index(data_type_name)
72+
)
73+
74+
return cython.cast(lib.AVPacketSideDataType, i)
75+
76+
77+
def packet_side_data_type_desc(data_type_name: PacketSideDataTypeLiteral) -> str | None:
78+
"""FFmpeg description of packet side data type"""
79+
dtype = _packet_side_data_type_from_literal(data_type_name)
80+
res = lib.av_packet_side_data_name(dtype)
81+
return res if res != cython.NULL else None
82+
83+
84+
@cython.cclass
85+
class PacketSideData:
86+
87+
@staticmethod
88+
def from_packet(
89+
packet: "Packet", data_type: PacketSideDataTypeLiteral
90+
) -> "PacketSideData":
91+
"""create new PacketSideData by copying an existing packet's side data
92+
93+
:param packet: Source packet
94+
:type packet: :class:`~av.packet.Packet`
95+
:param data_type: side data type
96+
:return: newly created copy of the side data if the side data of the
97+
requested type is found in the packet, else an empty object
98+
:rtype: :class:`~av.packet.PacketSideData`
99+
"""
100+
101+
dtype = _packet_side_data_type_from_literal(data_type)
102+
c_ptr = lib.av_packet_side_data_get(
103+
packet.ptr.side_data, packet.ptr.side_data_elems, dtype
104+
)
105+
106+
found = cython.declare(cython.bint, c_ptr != cython.NULL)
107+
108+
sdata = PacketSideData(dtype, c_ptr.size if found else 0)
109+
110+
with cython.nogil:
111+
if found:
112+
memcpy(sdata.data, c_ptr.data, c_ptr.size)
113+
114+
return sdata
115+
116+
def __cinit__(self, dtype: lib.AVPacketSideDataType, size: cython.size_t):
117+
self.dtype = dtype
118+
with cython.nogil:
119+
if size:
120+
self.data = cython.cast(cython.p_uchar, lib.av_malloc(size))
121+
if self.data == cython.NULL:
122+
raise MemoryError("Failed to allocate memory")
123+
else:
124+
self.data = cython.NULL
125+
self.size = size
126+
127+
def __dealloc__(self):
128+
with cython.nogil:
129+
lib.av_freep(cython.address(self.data))
130+
131+
def to_packet(self, packet: "Packet", move: cython.bint = False):
132+
"""copy or move side data to the specified packet
133+
134+
:param packet: Target packet
135+
:type packet: :class:`~av.packet.Packet`
136+
:param move: True to move the data from this object to the packet,
137+
defaults to False.
138+
:type move: bool
139+
"""
140+
if self.size == 0:
141+
# nothing to add, should clear existing side_data in packet?
142+
return
143+
144+
data = self.data
145+
146+
with cython.nogil:
147+
if not move:
148+
data = cython.cast(cython.p_uchar, lib.av_malloc(self.size))
149+
if data == cython.NULL:
150+
raise MemoryError("Failed to allocate memory")
151+
memcpy(data, self.data, self.size)
152+
153+
res = lib.av_packet_add_side_data(packet.ptr, self.dtype, data, self.size)
154+
err_check(res)
155+
156+
if move:
157+
self.data = cython.NULL
158+
self.size = 0
159+
160+
@property
161+
def data_type(self) -> str:
162+
"""
163+
The type of this packet side data.
164+
165+
:type: str
166+
"""
167+
return _packet_side_data_type_to_literal(self.dtype)
168+
169+
@property
170+
def data_desc(self) -> str:
171+
"""
172+
The description of this packet side data type.
173+
174+
:type: str
175+
"""
176+
177+
return lib.av_packet_side_data_name(self.dtype)
178+
179+
@property
180+
def data_size(self) -> int:
181+
"""
182+
The size in bytes of this packet side data.
183+
184+
:type: int
185+
"""
186+
return self.size
187+
188+
def __bool__(self) -> bool:
189+
"""
190+
True if this object holds side data.
191+
192+
:type: bool
193+
"""
194+
return self.data != cython.NULL
7195

8196

9197
@cython.cclass
@@ -235,3 +423,40 @@ def opaque(self, v):
235423
if v is None:
236424
return
237425
self.ptr.opaque_ref = opaque_container.add(v)
426+
427+
def has_side_data(self, data_type: str) -> bool:
428+
"""True if this packet has the specified side data
429+
430+
:param data_type: side data type
431+
:type data_type: str
432+
"""
433+
434+
dtype = _packet_side_data_type_from_literal(data_type)
435+
return (
436+
lib.av_packet_side_data_get(
437+
self.ptr.side_data, self.ptr.side_data_elems, dtype
438+
)
439+
!= cython.NULL
440+
)
441+
442+
def get_side_data(self, data_type: str) -> PacketSideData:
443+
"""get a copy of the side data
444+
445+
:param data_type: side data type (:method:`~av.packet.PacketSideData.side_data_types` for the full list of options)
446+
:type data_type: str
447+
:return: newly created copy of the side data if the side data of the
448+
requested type is found in the packet, else an empty object
449+
:rtype: :class:`~av.packet.PacketSideData`
450+
"""
451+
return PacketSideData.from_packet(self, data_type)
452+
453+
def set_side_data(self, side_data: PacketSideData, move: bool = False):
454+
"""copy or move side data to this packet
455+
456+
:param side_data: Source packet side data
457+
:type side_data: :class:`~av.packet.PacketSideData`
458+
:param move: True to move the data from `side_data` object,
459+
defaults to False.
460+
:type move: bool
461+
"""
462+
side_data.to_packet(self, move)

include/libavcodec/avcodec.pxd

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t
1+
from libc.stdint cimport int8_t, int64_t, uint16_t, uint32_t, uint8_t
22

33
cdef extern from "libavcodec/codec.h":
44
struct AVCodecTag:
@@ -17,6 +17,17 @@ cdef extern from "libavcodec/packet.h" nogil:
1717
int free_opaque
1818
)
1919

20+
const AVPacketSideData *av_packet_side_data_get(const AVPacketSideData *sd,
21+
int nb_sd,
22+
AVPacketSideDataType type)
23+
24+
uint8_t* av_packet_get_side_data(const AVPacket *pkt, AVPacketSideDataType type,
25+
size_t *size)
26+
27+
int av_packet_add_side_data(AVPacket *pkt, AVPacketSideDataType type,
28+
uint8_t *data, size_t size)
29+
30+
const char *av_packet_side_data_name(AVPacketSideDataType type)
2031

2132
cdef extern from "libavutil/channel_layout.h":
2233
ctypedef enum AVChannelOrder:
@@ -469,6 +480,8 @@ cdef extern from "libavcodec/avcodec.h" nogil:
469480
int size
470481
int stream_index
471482
int flags
483+
AVPacketSideData *side_data
484+
int side_data_elems
472485
int duration
473486
int64_t pos
474487
void *opaque

test1/rawvideo_pal8.avi

1.29 MB
Binary file not shown.

tests/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
import numpy as np
1313

14-
from av.datasets import fate as fate_suite
14+
from av.datasets import fate as fate_suite, curated as pyav_curated
1515

1616
try:
1717
import PIL # noqa
@@ -28,7 +28,7 @@
2828
T = TypeVar("T")
2929

3030

31-
__all__ = ("fate_suite",)
31+
__all__ = ("fate_suite", "pyav_curated")
3232

3333

3434
is_windows = os.name == "nt"

tests/test_packet.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import av
22

3-
from .common import fate_suite
3+
from .common import fate_suite, pyav_curated
4+
5+
from typing import get_args
46

57

68
class TestProperties:
@@ -48,3 +50,41 @@ def test_set_duration(self) -> None:
4850
packet.duration += 10
4951

5052
assert packet.duration == old_duration + 10
53+
54+
55+
class TestPacketSideData:
56+
57+
def test_data_types(self):
58+
dtypes = get_args(av.packet.PacketSideDataTypeLiteral)
59+
for dtype in dtypes:
60+
av_enum = av.packet._packet_side_data_type_from_literal(dtype)
61+
assert dtype == av.packet._packet_side_data_type_to_literal(av_enum)
62+
assert av.packet.packet_side_data_type_desc(dtype) != None
63+
64+
def test_palette(self):
65+
66+
with av.open(pyav_curated("ffmpeg/rawvideo_pal8.avi")) as container:
67+
# its first packet has palette side data
68+
69+
iterpackets = container.demux()
70+
pkt = next(iterpackets) # has palette
71+
72+
assert pkt.has_side_data("palette")
73+
74+
sdata = pkt.get_side_data("palette")
75+
assert sdata.data_type == "palette"
76+
assert bool(sdata)
77+
assert sdata.data_size == 1024
78+
assert sdata.data_desc == 'Palette'
79+
80+
nxt = next(iterpackets) # has no palette
81+
82+
assert not nxt.has_side_data("palette")
83+
84+
sdata1 = nxt.get_side_data("palette")
85+
assert sdata1.data_type == "palette"
86+
assert not bool(sdata1)
87+
assert sdata1.data_size == 0
88+
89+
nxt.set_side_data(sdata, move=True)
90+
assert not bool(sdata)

0 commit comments

Comments
 (0)