diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2a8d815b7..00b1a7098 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,8 +28,8 @@ We are operating with `semantic versioning `_. - Bug fixes (PATCH) go here. - $CHANGE by :gh-user:`mikeboers` in (:pr:`1`). -v18.1.0 (Unreleased) --------------------- +v18.1.0 +------- Features: @@ -39,11 +39,21 @@ Features: - Support reusing the thread's current CUDA context via a ``current_ctx`` flag on ``CudaContext`` and ``VideoFrame.from_dlpack``, for interop with libraries like PyTorch that initialize CUDA first by :gh-user:`Yozer` (:pr:`2339`). - ``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`). +- Add ``av.AVRational``, an exact rational number stored as two int32s that mirrors FFmpeg's ``AVRational``. ``Codec.frame_rates`` now holds these rather than ``fractions.Fraction``, and every setter that accepts a ``Fraction`` also accepts an ``AVRational``. Unset rational attributes are falsy, so test them with ``if not stream.time_base:`` rather than ``is None`` by :gh-user:`WyattBlue`. +- Add ``Frame.metadata``, the metadata FFmpeg attaches to a frame, by :gh-user:`WyattBlue`. +- Add ``CodecContext.level`` by :gh-user:`WyattBlue`. +- Add ``VideoCodecContext.field_order`` by :gh-user:`WyattBlue`. - ``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: - Fix ``VideoFrame.save`` raising ``AttributeError`` when given a ``Path`` rather than a ``str`` by :gh-user:`WyattBlue`. +- Fix a frame rate assigned to a ``VideoStream`` after ``add_stream`` being dropped from the output; the codec context's frame rate is now copied to the stream before the header is written by :gh-user:`WyattBlue` (:issue:`2301`). + +Misc.: + +- Support building from source against FFmpeg 9.0 by :gh-user:`WyattBlue`. The binary wheels still ship FFmpeg 8.1.2. - Prevent crashes and corrupted output when structural codec properties are changed after an output stream has been opened by :gh-user:`WyattBlue`, reported by :gh-user:`oakaigh` (:issue:`2232`). - Fix a crash when using a stream that has no ``CodecContext`` (a demuxed stream with no available decoder, such as one from a truncated file, or a stream created by ``add_mux_stream``); decoding now raises ``DecoderNotFoundError``, encoding now raises ``EncoderNotFoundError``, and ``BitStreamFilterContext`` accepts such a stream as ``out_stream`` by :gh-user:`WyattBlue`, reported by :gh-user:`justinrmiller` (:issue:`2344`). diff --git a/av/about.py b/av/about.py index c6a8b8ed8..c84c510d7 100644 --- a/av/about.py +++ b/av/about.py @@ -1 +1 @@ -__version__ = "18.0.0" +__version__ = "18.1.0" 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: