From a182819fb516677415092e87ec75d778f08ef2dc Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Thu, 4 Jun 2026 03:42:34 -0400 Subject: [PATCH] Assign inferred timing to packets, closes #1919 Assign parser-inferred timing to packets in `CodecContext.parse` --- av/codec/context.py | 14 ++++++++++++++ include/avcodec.pxd | 8 +++++++- tests/test_codec_context.py | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/av/codec/context.py b/av/codec/context.py index 43d39c0ed..2c82f2de8 100644 --- a/av/codec/context.py +++ b/av/codec/context.py @@ -272,6 +272,10 @@ def parse(self, raw_input=None): It will return all packets that are fully contained within the given input, and will buffer partial packets until they are complete. + Any timing information the parser is able to infer (``pts``, ``dts``, + ``duration``, ``pos`` and the keyframe flag) is assigned onto the + returned packets. Fields the parser cannot determine are left unset. + :param ByteSource raw_input: A chunk of a byte-stream to process. Anything that can be turned into a :class:`.ByteSource` is fine. ``None`` or empty inputs will flush the parser's buffers. @@ -327,6 +331,16 @@ def parse(self, raw_input=None): packet = Packet(out_size) memcpy(packet.ptr.data, out_data, out_size) + # Propagate the timing information the parser inferred for + # this frame onto the packet (mirrors FFmpeg's parse_packet). + packet.ptr.pts = self.parser.pts + packet.ptr.dts = self.parser.dts + packet.ptr.pos = self.parser.pos + if self.parser.duration: + packet.ptr.duration = self.parser.duration + if self.parser.key_frame == 1: + packet.ptr.flags |= lib.AV_PKT_FLAG_KEY + packets.append(packet) if not in_size: diff --git a/include/avcodec.pxd b/include/avcodec.pxd index c273443f0..21b5e8324 100644 --- a/include/avcodec.pxd +++ b/include/avcodec.pxd @@ -448,7 +448,13 @@ cdef extern from "libavcodec/avcodec.h" nogil: int codec_ids[5] cdef struct AVCodecParserContext: - pass + int64_t pts + int64_t dts + int64_t pos + int64_t last_pos + int64_t offset + int duration + int key_frame cdef AVCodecParserContext *av_parser_init(int codec_id) cdef int av_parser_parse2( diff --git a/tests/test_codec_context.py b/tests/test_codec_context.py index d0629480e..1f9dc2ee0 100644 --- a/tests/test_codec_context.py +++ b/tests/test_codec_context.py @@ -211,6 +211,22 @@ def _assert_parse(self, codec_name: str, path: str) -> None: assert len(parsed_source) == len(full_source) assert full_source == parsed_source + def test_parse_assigns_packet_timing(self) -> None: + # Regression test for #1919: the parser-inferred timing information + # should be propagated onto the returned packets. + path = fate_suite("mpeg2/mpeg2_field_encoding.ts") + full_source = b"".join(bytes(p) for p in av.open(path).demux(video=0)) + + ctx = Codec("mpeg2video").create() + packets = [] + for i in range(0, len(full_source), 4096): + packets.extend(ctx.parse(full_source[i : i + 4096])) + packets.extend(ctx.parse()) + + # The parser is able to determine the byte position for this stream, + # so at least some packets should carry it through. + assert any(p.pos is not None for p in packets) + class TestEncoding(TestCase): def test_encoding_png(self) -> None: