Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ v18.1.0 (Unreleased)

Features:

- Infer the specific stream and codec context types for all FFmpeg encoder names in type-checked code by :gh-user:`WyattBlue`.
- Add ``CodecContext.supported_options`` to discover generic and codec-specific options by :gh-user:`WyattBlue` (:issue:`2032`).
- Add ``Packet.rescale_ts()`` to rescale packet PTS, DTS, and duration to a new ``AVRational`` time base by :gh-user:`WyattBlue`.
- 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`.

Fixes:

- 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`).

v18.0.0
Expand Down
106 changes: 106 additions & 0 deletions av/audio/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,120 @@ from typing import Literal
from .frame import AudioFrame
from .stream import AudioStream

# FFmpeg 8.1 encoders and the codec descriptor aliases that resolve to them.
_AudioCodecName = Literal[
"aac",
"aac_at",
"aac_mf",
"ac3",
"ac3_fixed",
"ac3_mf",
"adpcm_adx",
"adpcm_argo",
"adpcm_g722",
"adpcm_g726",
"adpcm_g726le",
"adpcm_ima_alp",
"adpcm_ima_amv",
"adpcm_ima_apm",
"adpcm_ima_qt",
"adpcm_ima_ssi",
"adpcm_ima_wav",
"adpcm_ima_ws",
"adpcm_ms",
"adpcm_swf",
"adpcm_yamaha",
"alac",
"alac_at",
"amr_nb",
"amr_wb",
"anull",
"aptx",
"aptx_hd",
"codec2",
"comfortnoise",
"dca",
"dfpwm",
"dts",
"eac3",
"flac",
"g722",
"g723_1",
"g726",
"g726le",
"gsm",
"gsm_ms",
"ilbc",
"ilbc_at",
"lc3",
"libcodec2",
"libfdk_aac",
"libgsm",
"libgsm_ms",
"libilbc",
"liblc3",
"libmp3lame",
"libopencore_amrnb",
"libopus",
"libshine",
"libspeex",
"libtwolame",
"libvo_amrwbenc",
"libvorbis",
"mlp",
"mp2",
"mp2fixed",
"mp3",
"mp3_mf",
"nellymoser",
"opus",
"pcm_alaw",
"pcm_alaw_at",
"pcm_bluray",
"pcm_dvd",
"pcm_f32be",
"pcm_f32le",
"pcm_f64be",
"pcm_f64le",
"pcm_mulaw",
"pcm_mulaw_at",
"pcm_s16be",
"pcm_s16be_planar",
"pcm_s16le",
"pcm_s16le_planar",
"pcm_s24be",
"pcm_s24daud",
"pcm_s24le",
"pcm_s24le_planar",
"pcm_s32be",
"pcm_s32le",
"pcm_s32le_planar",
"pcm_s64be",
"pcm_s64le",
"pcm_s8",
"pcm_s8_planar",
"pcm_u16be",
"pcm_u16le",
"pcm_u24be",
"pcm_u24le",
"pcm_u32be",
"pcm_u32le",
"pcm_u8",
"pcm_vidc",
"ra_144",
"real_144",
"roq_dpcm",
"s302m",
"sbc",
"sonic",
"sonicls",
"speex",
"truehd",
"tta",
"vorbis",
"wavpack",
"wmav1",
"wmav2",
]

__all__ = ("AudioFrame", "AudioStream")
3 changes: 3 additions & 0 deletions av/audio/codeccontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def sample_rate(self):

@sample_rate.setter
def sample_rate(self, value: cython.int):
self._assert_not_open("sample_rate")
self.ptr.sample_rate = value

@property
Expand All @@ -88,6 +89,7 @@ def layout(self):

@layout.setter
def layout(self, value):
self._assert_not_open("layout")
layout: AudioLayout = AudioLayout(value)
self.ptr.ch_layout = layout.layout

Expand All @@ -102,5 +104,6 @@ def format(self):

@format.setter
def format(self, value):
self._assert_not_open("format")
format: AudioFormat = AudioFormat(value)
self.ptr.sample_fmt = format.sample_fmt
1 change: 1 addition & 0 deletions av/codec/context.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cdef class CodecContext:

cdef lib.AVCodecParserContext *parser
cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec, HWAccel hwaccel)
cdef _assert_not_open(self, name)

# Public API.
cdef readonly bint is_open
Expand Down
5 changes: 5 additions & 0 deletions av/codec/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@ def _init(
self.ptr.thread_count = 0 # use as many threads as there are CPUs.
self.ptr.thread_type = 0x02 # thread within a frame. Does not change the API.

@cython.cfunc
def _assert_not_open(self, name):
if self.is_open:
raise RuntimeError(f"Cannot change {name} after codec is open.")

@property
def flags(self):
"""
Expand Down
9 changes: 9 additions & 0 deletions av/codec/context.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ from typing import ClassVar, Literal, cast, overload
from av.audio import _AudioCodecName
from av.audio.codeccontext import AudioCodecContext
from av.packet import Packet
from av.subtitles import _SubtitleCodecName
from av.subtitles.codeccontext import SubtitleCodecContext
from av.video import _VideoCodecName
from av.video.codeccontext import VideoCodecContext

Expand Down Expand Up @@ -169,6 +171,13 @@ class CodecContext:
) -> VideoCodecContext: ...
@overload
@staticmethod
def create(
codec: _SubtitleCodecName,
mode: Literal["r", "w"] | None = None,
hwaccel: HWAccel | None = None,
) -> SubtitleCodecContext: ...
@overload
@staticmethod
def create(
codec: str | Codec,
mode: Literal["r", "w"] | None = None,
Expand Down
6 changes: 6 additions & 0 deletions av/container/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ def add_stream(
:param \\**kwargs: Set attributes for the stream.
:rtype: The new :class:`~av.stream.Stream`.

.. warning::

Configure every output stream before muxing the first packet. Writing
the file header opens all stream codec contexts, after which structural
properties such as format, dimensions, layout, and rate cannot change.

"""

codec_obj: Codec = Codec(codec_name, "w")
Expand Down
9 changes: 9 additions & 0 deletions av/container/output.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ from av.audio.stream import AudioStream
from av.codec.hwaccel import HWAccel
from av.packet import Packet
from av.stream import AttachmentStream, DataStream, Stream
from av.subtitles import _SubtitleCodecName
from av.subtitles.stream import SubtitleStream
from av.video import _VideoCodecName
from av.video.stream import VideoStream
Expand Down Expand Up @@ -34,6 +35,14 @@ class OutputContainer(Container):
**kwargs,
) -> VideoStream: ...
@overload
def add_stream(
self,
codec_name: _SubtitleCodecName,
rate: Fraction | int | None = None,
options: dict[str, str] | None = None,
**kwargs,
) -> SubtitleStream: ...
@overload
def add_stream(
self,
codec_name: str,
Expand Down
18 changes: 18 additions & 0 deletions av/subtitles/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Literal

# FFmpeg 8.1 encoders and the codec descriptor aliases that resolve to them.
_SubtitleCodecName = Literal[
"ass",
"dvb_subtitle",
"dvbsub",
"dvd_subtitle",
"dvdsub",
"mov_text",
"srt",
"ssa",
"subrip",
"text",
"ttml",
"webvtt",
"xsub",
]
3 changes: 3 additions & 0 deletions av/subtitles/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ from av.packet import Packet
from av.stream import Stream
from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle, SubtitleSet

from .codeccontext import SubtitleCodecContext

class SubtitleStream(Stream):
codec_context: SubtitleCodecContext
def decode(
self, packet: Packet | None = None
) -> list[AssSubtitle] | list[BitmapSubtitle]: ...
Expand Down
Loading
Loading