From 8c7f4814b21266332f18177b5afc8a287c56bc04 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Tue, 11 Aug 2026 18:53:07 -0400 Subject: [PATCH] Add InputContainer.start_time_realtime, closes #2365 RTSP derives it from RTCP sender reports, which lets callers align analytics with the sender's wall clock. --- CHANGELOG.rst | 1 + av/container/input.py | 14 ++++++++++++++ av/container/input.pyi | 1 + include/avformat.pxd | 1 + tests/test_file_probing.py | 2 ++ 5 files changed, 19 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2a8d815b7..ac775e946 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -40,6 +40,7 @@ Features: - ``VideoFrame.from_dlpack`` no longer requires restating ``primary_ctx``/``current_ctx`` when passing an explicit ``cuda_context``; the flags are only validated when explicitly given by :gh-user:`WyattBlue`. - Support passing an explicit CUDA stream to FFmpeg CUDA operations, including NVENC input and output, via a ``cuda_stream`` parameter on ``CudaContext``; currently limited to logical CUDA device 0 by :gh-user:`Yozer` (:pr:`2360`). - ``VideoFrame.save`` now forwards keyword arguments to the encoder, letting callers trade file size for speed (e.g. ``pred="none"`` or ``compression_level=1`` for PNG, ``qscale=2`` for JPG) by :gh-user:`WyattBlue`. +- Add ``InputContainer.start_time_realtime``, the stream's start time in microseconds since the Unix epoch, which RTSP derives from RTCP sender reports, by :gh-user:`WyattBlue` (:issue:`2365`). Fixes: diff --git a/av/container/input.py b/av/container/input.py index 18558126c..2e26ef3e5 100644 --- a/av/container/input.py +++ b/av/container/input.py @@ -114,6 +114,20 @@ def start_time(self): if self.ptr.start_time != lib.AV_NOPTS_VALUE: return self.ptr.start_time + @property + def start_time_realtime(self): + """Start time of the stream in real world time, in microseconds since the + Unix epoch, or ``None`` if unknown. + + Only a few demuxers know this; RTSP computes it from RTCP sender reports, + which is what makes it useful for aligning analytics with a camera's clock. + + Wraps :ffmpeg:`AVFormatContext.start_time_realtime`. + """ + self._assert_open() + if self.ptr.start_time_realtime != lib.AV_NOPTS_VALUE: + return self.ptr.start_time_realtime + @property def duration(self): self._assert_open() diff --git a/av/container/input.pyi b/av/container/input.pyi index 88dd07d69..caea60b94 100644 --- a/av/container/input.pyi +++ b/av/container/input.pyi @@ -14,6 +14,7 @@ from .core import Container class InputContainer(Container): start_time: int + start_time_realtime: int | None duration: int | None bit_rate: int size: int diff --git a/include/avformat.pxd b/include/avformat.pxd index 9a18e5a9b..920f18d84 100644 --- a/include/avformat.pxd +++ b/include/avformat.pxd @@ -151,6 +151,7 @@ cdef extern from "libavformat/avformat.h" nogil: AVIOInterruptCB interrupt_callback AVDictionary *metadata int64_t start_time + int64_t start_time_realtime int64_t duration int64_t bit_rate int flags diff --git a/tests/test_file_probing.py b/tests/test_file_probing.py index ce04189f9..8997227ae 100644 --- a/tests/test_file_probing.py +++ b/tests/test_file_probing.py @@ -18,6 +18,8 @@ def test_container_probing(self) -> None: assert self.file.metadata == {} assert self.file.size == 207740 assert self.file.start_time == 1400000 + # Only RTSP-like inputs carry a wall clock; a plain file has none. + assert self.file.start_time_realtime is None assert len(self.file.streams) == 1 def test_stream_probing(self) -> None: