Skip to content

Commit 41530f4

Browse files
committed
Fix side data type tables lagging FFmpeg
CodecContext.coded_side_data and decoded_side_data raise if a type has no name in PyAV, and both tables had drifted: sidedata.Type was missing five members and PktSideDataT three, including the AV_PKT_DATA_HEVC_CONF that mov and matroska attach to HEVC streams, and the AV_PKT_DATA_EXIF. Complete both tables, skip names a newer FFmpeg may add rather than losing the whole dict to one of them.
1 parent e21a345 commit 41530f4

8 files changed

Lines changed: 64 additions & 20 deletions

File tree

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ Features:
4747
- ``ContainerFormat.fixed_framesize`` reports whether a format wants fixed size audio frames.
4848
- :class:`.CodecContext` exposes more of ``AVCodecContext``: ``pkt_timebase``, ``frame_num``, ``active_thread_type``, ``bits_per_raw_sample``, ``compression_level``, ``rc_buffer_size``, ``min_bit_rate``, a setter for ``max_bit_rate``, the audio ``initial_padding``, ``trailing_padding``, and ``seek_preroll``, and ``stats_in``/``stats_out`` for two-pass encoding. ``VideoCodecContext`` gains ``chroma_sample_location``, ``refs``, and ``mb_decision``; ``AudioCodecContext`` gains ``block_align``.
4949
- ``CodecContext.coded_side_data`` and ``CodecContext.decoded_side_data`` expose the context's global side data as dicts of ``bytes``, keyed by packet side data name and :class:`~av.sidedata.sidedata.Type` respectively. Stream wide HDR metadata, such as mastering display and content light level, arrives in ``decoded_side_data`` once a frame has been decoded.
50-
- Enums gained the members FFmpeg has since added: ``Properties.FIELDS``, ``Properties.ENHANCEMENT``, ``PixFmtLoss.EXCESS_RESOLUTION``, ``PixFmtLoss.EXCESS_DEPTH``, ``Flags2.icc_profiles``, ``format.Flags.experimental``, ``Interpolation.STRICT``, ``Interpolation.UNSTABLE``, ``ColorTrc.V_LOG``, ``ColorPrimaries.V_GAMUT``, and the ``LCEVC``, ``VIEW_ID``, ``THREE_D_REFERENCE_DISPLAYS``, and ``EXIF`` members of ``sidedata.Type``.
50+
- Enums gained the members FFmpeg has since added: ``Properties.FIELDS``, ``Properties.ENHANCEMENT``, ``PixFmtLoss.EXCESS_RESOLUTION``, ``PixFmtLoss.EXCESS_DEPTH``, ``Flags2.icc_profiles``, ``format.Flags.experimental``, ``Interpolation.STRICT``, ``Interpolation.UNSTABLE``, ``ColorTrc.V_LOG``, ``ColorPrimaries.V_GAMUT``, the ``LCEVC``, ``VIEW_ID``, ``THREE_D_REFERENCE_DISPLAYS``, ``EXIF``, ``DYNAMIC_HDR_SMPTE_2094_APP5``, ``IAMF_MIX_GAIN_PARAM``, ``IAMF_DEMIXING_INFO_PARAM``, ``IAMF_RECON_GAIN_INFO_PARAM``, and ``RAW_COLOR_PARAMS`` members of ``sidedata.Type``, and the ``exif``, ``dynamic_hdr_smpte_2094_app5``, and ``hevc_conf`` packet side data names.
5151

5252
Fixes:
5353

54+
- ``Frame.side_data`` and ``PacketSideData.data_type`` no longer raise on side data types FFmpeg has added since PyAV last listed them. ``sidedata.Type`` was missing five members and the packet side data names three, so reading, say, the ``AV_PKT_DATA_HEVC_CONF`` an HEVC stream in MP4 or Matroska carries raised ``IndexError``.
5455
- ``CodecContext.bit_rate_tolerance`` returns its value instead of always ``None``; the getter was missing its ``return``.
5556
- A rejected ``add_stream()`` or ``add_mux_stream()`` no longer breaks the container.
5657
- ``InputContainer.size`` returns ``None`` when the size cannot be determined rather than the negative ``AVERROR`` it was passing through, which read as a plausible byte count. A non-seekable input, such as a pipe, reported ``-78``.

av/codec/context.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,8 +1083,7 @@ def delay(self):
10831083

10841084
@property
10851085
def pkt_timebase(self):
1086-
"""Timebase of the packets fed to this context, as a
1087-
:class:`~fractions.Fraction`.
1086+
"""Timebase of the packets fed to this context.
10881087
10891088
Decoders use it to set :attr:`.Frame.time_base`. Containers set it for
10901089
you; set it yourself when driving a bare CodecContext.
@@ -1123,16 +1122,24 @@ def bits_per_raw_sample(self, value: cython.int):
11231122

11241123
@property
11251124
def initial_padding(self):
1126-
"""Audio only. Samples the decoder should skip at the start of the
1127-
stream, i.e. the encoder delay. Needed for gapless playback.
1125+
"""Audio only. Priming samples the encoder inserted at the start of the
1126+
stream, which must be discarded to recover the original audio. Needed
1127+
for gapless playback.
1128+
1129+
Set by libavcodec when encoding, and taken from the stream parameters
1130+
when decoding.
11281131
11291132
Wraps :ffmpeg:`AVCodecContext.initial_padding`.
11301133
"""
11311134
return self.ptr.initial_padding
11321135

11331136
@property
11341137
def trailing_padding(self):
1135-
"""Audio only. Samples to discard at the end of the stream.
1138+
"""Audio only. Padding samples appended by the encoder, which must be
1139+
discarded from the end of the stream to recover the original audio.
1140+
1141+
libavcodec neither sets nor acts on this; it only travels between the
1142+
context and the container's stream parameters.
11361143
11371144
Wraps :ffmpeg:`AVCodecContext.trailing_padding`.
11381145
"""
@@ -1144,8 +1151,8 @@ def trailing_padding(self, value: cython.int):
11441151

11451152
@property
11461153
def seek_preroll(self):
1147-
"""Number of samples to decode before the target seek point for the
1148-
output to be correct, in ``1 / AV_TIME_BASE`` units.
1154+
"""Audio only. Number of samples to skip after a discontinuity, such as
1155+
a seek, before the decoded output is correct.
11491156
11501157
Wraps :ffmpeg:`AVCodecContext.seek_preroll`.
11511158
"""
@@ -1184,8 +1191,13 @@ def stats_in(self, value):
11841191
self.ptr.stats_in = cython.NULL
11851192
return
11861193

1194+
if type(value) is str:
1195+
value = value.encode("utf-8")
1196+
elif not isinstance(value, (bytes, bytearray)):
1197+
raise TypeError("stats_in must be str, bytes, or None")
1198+
11871199
# libavcodec never frees stats_in, so we keep the bytes alive ourselves.
1188-
self._stats_in = value.encode("utf-8") if type(value) is str else bytes(value)
1200+
self._stats_in = bytes(value)
11891201
self.ptr.stats_in = self._stats_in
11901202

11911203
@property
@@ -1196,14 +1208,16 @@ def coded_side_data(self):
11961208
Wraps :ffmpeg:`AVCodecContext.coded_side_data`.
11971209
"""
11981210
i: cython.int
1199-
return {
1200-
packet_sidedata_type_to_literal(
1201-
self.ptr.coded_side_data[i].type
1202-
): _to_bytes(
1211+
out = {}
1212+
for i in range(self.ptr.nb_coded_side_data):
1213+
try:
1214+
key = packet_sidedata_type_to_literal(self.ptr.coded_side_data[i].type)
1215+
except IndexError:
1216+
continue
1217+
out[key] = _to_bytes(
12031218
self.ptr.coded_side_data[i].data, self.ptr.coded_side_data[i].size
12041219
)
1205-
for i in range(self.ptr.nb_coded_side_data)
1206-
}
1220+
return out
12071221

12081222
@property
12091223
def decoded_side_data(self):
@@ -1218,9 +1232,13 @@ def decoded_side_data(self):
12181232
from av.sidedata.sidedata import Type
12191233

12201234
i: cython.int
1221-
return {
1222-
Type(self.ptr.decoded_side_data[i].type): _to_bytes(
1235+
out = {}
1236+
for i in range(self.ptr.nb_decoded_side_data):
1237+
try:
1238+
key = Type(self.ptr.decoded_side_data[i].type)
1239+
except ValueError:
1240+
continue
1241+
out[key] = _to_bytes(
12231242
self.ptr.decoded_side_data[i].data, self.ptr.decoded_side_data[i].size
12241243
)
1225-
for i in range(self.ptr.nb_decoded_side_data)
1226-
}
1244+
return out

av/packet.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
"lcevc",
5757
"3d_reference_displays",
5858
"rtcp_sr",
59+
"exif",
60+
"dynamic_hdr_smpte_2094_app5",
61+
"hevc_conf",
5962
]
6063

6164

av/packet.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ PktSideDataT = Literal[
5656
"lcevc",
5757
"3d_reference_displays",
5858
"rtcp_sr",
59+
"exif",
60+
"dynamic_hdr_smpte_2094_app5",
61+
"hevc_conf",
5962
]
6063

6164
class PacketSideData(Buffer):

av/sidedata/sidedata.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class Type(Enum):
5050
VIEW_ID = lib.AV_FRAME_DATA_VIEW_ID
5151
THREE_D_REFERENCE_DISPLAYS = lib.AV_FRAME_DATA_3D_REFERENCE_DISPLAYS
5252
EXIF = lib.AV_FRAME_DATA_EXIF
53+
DYNAMIC_HDR_SMPTE_2094_APP5 = lib.AV_FRAME_DATA_DYNAMIC_HDR_SMPTE_2094_APP5
54+
IAMF_MIX_GAIN_PARAM = lib.AV_FRAME_DATA_IAMF_MIX_GAIN_PARAM
55+
IAMF_DEMIXING_INFO_PARAM = lib.AV_FRAME_DATA_IAMF_DEMIXING_INFO_PARAM
56+
IAMF_RECON_GAIN_INFO_PARAM = lib.AV_FRAME_DATA_IAMF_RECON_GAIN_INFO_PARAM
57+
RAW_COLOR_PARAMS = lib.AV_FRAME_DATA_RAW_COLOR_PARAMS
5358

5459

5560
@cython.cfunc

av/sidedata/sidedata.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ class Type(Enum):
3838
VIEW_ID = cast(ClassVar[Type], ...)
3939
THREE_D_REFERENCE_DISPLAYS = cast(ClassVar[Type], ...)
4040
EXIF = cast(ClassVar[Type], ...)
41+
DYNAMIC_HDR_SMPTE_2094_APP5 = cast(ClassVar[Type], ...)
42+
IAMF_MIX_GAIN_PARAM = cast(ClassVar[Type], ...)
43+
IAMF_DEMIXING_INFO_PARAM = cast(ClassVar[Type], ...)
44+
IAMF_RECON_GAIN_INFO_PARAM = cast(ClassVar[Type], ...)
45+
RAW_COLOR_PARAMS = cast(ClassVar[Type], ...)
4146

4247
class SideData(Buffer):
4348
type: Type

include/avcodec.pxd

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,11 @@ cdef extern from "libavcodec/avcodec.h" nogil:
352352
AV_FRAME_DATA_VIEW_ID
353353
AV_FRAME_DATA_3D_REFERENCE_DISPLAYS
354354
AV_FRAME_DATA_EXIF
355+
AV_FRAME_DATA_DYNAMIC_HDR_SMPTE_2094_APP5
356+
AV_FRAME_DATA_IAMF_MIX_GAIN_PARAM
357+
AV_FRAME_DATA_IAMF_DEMIXING_INFO_PARAM
358+
AV_FRAME_DATA_IAMF_RECON_GAIN_INFO_PARAM
359+
AV_FRAME_DATA_RAW_COLOR_PARAMS
355360

356361
cdef struct AVFrameSideData:
357362
AVFrameSideDataType type
@@ -540,4 +545,3 @@ cdef extern from "libavcodec/packet.h" nogil:
540545
AVPacket *pkt, AVPacketSideDataType type, uint8_t *data, size_t size
541546
)
542547
const char *av_packet_side_data_name(AVPacketSideDataType type)
543-
const char *av_frame_side_data_name(AVFrameSideDataType type)

tests/test_codec_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,11 @@ def test_encoder_scalars_roundtrip(self) -> None:
649649
assert ctx.pkt_timebase == Fraction(1, 1000)
650650
assert ctx.frame_num == 0
651651

652+
def test_stats_in_rejects_junk(self) -> None:
653+
ctx = av.CodecContext.create("libx264", "w")
654+
with pytest.raises(TypeError):
655+
ctx.stats_in = 5 # type: ignore[assignment]
656+
652657
def test_stats_in_out(self) -> None:
653658
ctx = av.CodecContext.create("libx264", "w")
654659
assert ctx.stats_in is None

0 commit comments

Comments
 (0)