From c9a04c4ca4cbbb529987476c114d1417b6101920 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 4 Nov 2026 23:03:42 -0500 Subject: [PATCH 1/5] Type SideDataContainer.get() defaults per key Simplify the default parameter to T and add default-taking overloads for the motion vectors and video encoding params keys. --- av/sidedata/sidedata.pyi | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/av/sidedata/sidedata.pyi b/av/sidedata/sidedata.pyi index 99beaa035..89809079e 100644 --- a/av/sidedata/sidedata.pyi +++ b/av/sidedata/sidedata.pyi @@ -67,6 +67,8 @@ class SideDataContainer(Mapping[Type, SideData]): @overload def get(self, key: str | int | Type, /) -> SideData | None: ... @overload - def get[T]( - self, key: str | int | Type, default: SideData | T, / - ) -> SideData | T: ... + def get[T](self, key: _MotionVectorsKey, default: T, /) -> MotionVectors | T: ... + @overload + def get[T](self, key: _VideoEncParamsKey, default: T, /) -> VideoEncParams | T: ... + @overload + def get[T](self, key: str | int | Type, default: T, /) -> SideData | T: ... From d71b18367d188952abcaf016ee254249f14c2475 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 4 Nov 2026 23:20:04 -0500 Subject: [PATCH 2/5] Fix incorrect types in stubs - VideoEncParams.delta_qp is a nested list - to_ndarray(format="pal8") returns an (image, palette) tuple - Remove nonexistent Flags.shortest - Only re-export names av.container and av.filter actually provide - Widen params that accept more than the stubs allowed - Reject None when setting VideoCodecContext.format and pix_fmt --- av/audio/resampler.pyi | 4 ++-- av/bitstream.pyi | 2 +- av/buffer.pyi | 2 +- av/codec/context.pyi | 2 +- av/container/__init__.pyi | 9 ++++++--- av/container/core.pyi | 16 ++++++++++++---- av/filter/__init__.pyi | 8 ++++---- av/sidedata/encparams.pyi | 2 +- av/video/codeccontext.pyi | 10 ++++++++-- av/video/format.pyi | 4 +++- av/video/frame.pyi | 13 +++++++++++-- av/video/reformatter.pyi | 7 ++++--- tests/test_filters.py | 2 +- 13 files changed, 55 insertions(+), 26 deletions(-) diff --git a/av/audio/resampler.pyi b/av/audio/resampler.pyi index 55b41b705..b1066ab42 100644 --- a/av/audio/resampler.pyi +++ b/av/audio/resampler.pyi @@ -16,8 +16,8 @@ class AudioResampler: def __new__( cls, - format: str | int | AudioFormat | None = None, - layout: str | int | AudioLayout | None = None, + format: str | AudioFormat | None = None, + layout: str | AudioLayout | None = None, rate: int | None = None, frame_size: int | None = None, options: dict[str, str] | None = None, diff --git a/av/bitstream.pyi b/av/bitstream.pyi index 03b7f4151..302628884 100644 --- a/av/bitstream.pyi +++ b/av/bitstream.pyi @@ -12,7 +12,7 @@ class BitStreamFilterContext: in_stream: Stream | Codec | str | None = None, out_stream: Stream | None = None, ) -> Self: ... - def filter(self, packet: Packet[Any] | None) -> list[Packet[Stream]]: ... + def filter(self, packet: Packet[Any] | None = None) -> list[Packet[Stream]]: ... def flush(self) -> None: ... bitstream_filters_available: set[str] diff --git a/av/buffer.pyi b/av/buffer.pyi index a77156409..82e34fc44 100644 --- a/av/buffer.pyi +++ b/av/buffer.pyi @@ -10,5 +10,5 @@ class ByteSource: ... class Buffer(_Buffer): buffer_size: int buffer_ptr: int - def update(self, input: bytes) -> None: ... + def update(self, input: _Buffer) -> None: ... def __buffer__(self, flags: int, /) -> memoryview: ... diff --git a/av/codec/context.pyi b/av/codec/context.pyi index e5b81d351..405595a4a 100644 --- a/av/codec/context.pyi +++ b/av/codec/context.pyi @@ -124,7 +124,7 @@ class CodecOptionSet: @disjoint_base class CodecContext: name: str - type: Literal["video", "audio", "data", "subtitle", "attachment"] + type: Literal["video", "audio", "data", "subtitle", "attachment", "unknown"] options: dict[str, str] @property def supported_options(self) -> CodecOptionSet: ... diff --git a/av/container/__init__.pyi b/av/container/__init__.pyi index 7160777cc..c45d20653 100644 --- a/av/container/__init__.pyi +++ b/av/container/__init__.pyi @@ -1,3 +1,6 @@ -from .core import * -from .input import * -from .output import * +from .core import Chapter as Chapter +from .core import Container as Container +from .core import Flags as Flags +from .core import open as open +from .input import InputContainer as InputContainer +from .output import OutputContainer as OutputContainer diff --git a/av/container/core.pyi b/av/container/core.pyi index e7694abb8..8ef88806f 100644 --- a/av/container/core.pyi +++ b/av/container/core.pyi @@ -3,7 +3,16 @@ from enum import Flag, IntEnum from fractions import Fraction from pathlib import Path from types import TracebackType -from typing import Any, Literal, Protocol, Self, TypedDict, overload, type_check_only +from typing import ( + Any, + Literal, + NotRequired, + Protocol, + Self, + TypedDict, + overload, + type_check_only, +) from typing_extensions import disjoint_base @@ -41,7 +50,6 @@ class Flags(Flag): bitexact = ... sort_dts = ... fast_seek = ... - shortest = ... auto_bsf = ... class AudioCodec(IntEnum): @@ -86,8 +94,8 @@ class Chapter(TypedDict): id: int start: int end: int - time_base: AVRational - metadata: dict[str, str] + time_base: AVRational | Fraction + metadata: NotRequired[dict[str, str]] @disjoint_base class Container: diff --git a/av/filter/__init__.pyi b/av/filter/__init__.pyi index 5be1326c9..a8d863759 100644 --- a/av/filter/__init__.pyi +++ b/av/filter/__init__.pyi @@ -1,4 +1,4 @@ -from .context import * -from .filter import * -from .graph import * -from .loudnorm import * +from .filter import Filter as Filter +from .filter import filters_available as filters_available +from .graph import Graph as Graph +from .loudnorm import stats as stats diff --git a/av/sidedata/encparams.pyi b/av/sidedata/encparams.pyi index 40571e384..44e6f4aed 100644 --- a/av/sidedata/encparams.pyi +++ b/av/sidedata/encparams.pyi @@ -18,7 +18,7 @@ class VideoEncParams(SideData): block_size: int codec_type: VideoEncParamsType qp: int - delta_qp: int + delta_qp: list[list[int]] def block_params(self, idx: int) -> VideoBlockParams: ... def qp_map(self) -> np.ndarray[tuple[int, int], np.dtype[np.int32]]: ... diff --git a/av/video/codeccontext.pyi b/av/video/codeccontext.pyi index a846bb59d..7c5db293d 100644 --- a/av/video/codeccontext.pyi +++ b/av/video/codeccontext.pyi @@ -13,11 +13,17 @@ from .reformatter import VideoReformatter @final class VideoCodecContext(CodecContext): - format: VideoFormat | None + @property + def format(self) -> VideoFormat | None: ... + @format.setter + def format(self, value: VideoFormat) -> None: ... width: int height: int bits_per_coded_sample: int - pix_fmt: str | None + @property + def pix_fmt(self) -> str | None: ... + @pix_fmt.setter + def pix_fmt(self, value: str) -> None: ... reformatter: VideoReformatter | None @property def sw_format(self) -> VideoFormat | None: ... diff --git a/av/video/format.pyi b/av/video/format.pyi index 9f418c185..136bc2de0 100644 --- a/av/video/format.pyi +++ b/av/video/format.pyi @@ -17,7 +17,9 @@ class VideoFormat: def is_rgb(self) -> bool: ... @property def is_bayer(self) -> bool: ... - def __new__(cls, name: str, width: int = 0, height: int = 0) -> Self: ... + def __new__( + cls, name: str | VideoFormat, width: int = 0, height: int = 0 + ) -> Self: ... def chroma_width(self, luma_width: int = 0) -> int: ... def chroma_height(self, luma_height: int = 0) -> int: ... def __int__(self) -> int: ... diff --git a/av/video/frame.pyi b/av/video/frame.pyi index a2a5f1f78..a2febe2e5 100644 --- a/av/video/frame.pyi +++ b/av/video/frame.pyi @@ -34,7 +34,7 @@ class _ReformatKwargs(TypedDict, total=False): threads: int | None class _ToNdarrayKwargs(_ReformatKwargs, total=False): - format: str | None + format: str | VideoFormat | None class PictureType(IntEnum): NONE = 0 @@ -98,7 +98,7 @@ class VideoFrame(Frame): self, width: int | None = None, height: int | None = None, - format: str | None = None, + format: str | VideoFormat | None = None, src_colorspace: str | int | None = None, dst_colorspace: str | int | None = None, interpolation: int | str | None = None, @@ -112,6 +112,15 @@ class VideoFrame(Frame): def save(self, filepath: str | Path, **options: object) -> None: ... def set_image(self, img: Image) -> None: ... def to_image(self, **kwargs: Unpack[_ReformatKwargs]) -> Image: ... + @overload + def to_ndarray( # type: ignore[overload-overlap] + self, + channel_last: bool = False, + *, + format: Literal["pal8"], + **kwargs: Unpack[_ReformatKwargs], + ) -> tuple[npt.NDArray[np.uint8], npt.NDArray[np.uint8]]: ... + @overload def to_ndarray( self, channel_last: bool = False, **kwargs: Unpack[_ToNdarrayKwargs] ) -> _SupportedNDarray: ... diff --git a/av/video/reformatter.pyi b/av/video/reformatter.pyi index b53bff923..79ac52e07 100644 --- a/av/video/reformatter.pyi +++ b/av/video/reformatter.pyi @@ -1,6 +1,7 @@ from enum import IntEnum, IntFlag from typing import final +from .format import VideoFormat from .frame import VideoFrame class Interpolation(IntFlag): @@ -100,9 +101,9 @@ class VideoReformatter: frame: VideoFrame, width: int | None = None, height: int | None = None, - format: str | None = None, - src_colorspace: int | None = None, - dst_colorspace: int | None = None, + format: str | VideoFormat | None = None, + src_colorspace: str | int | None = None, + dst_colorspace: str | int | None = None, interpolation: Interpolation | int | str | None = None, src_color_range: int | str | None = None, dst_color_range: int | str | None = None, diff --git a/tests/test_filters.py b/tests/test_filters.py index a8822077a..4ffd10dca 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -31,7 +31,7 @@ def generate_audio_frame( data = np.zeros(frame_size, dtype=format_dtypes[input_format]) for j in range(frame_size): data[j] = np.sin(2 * np.pi * (frame_num + j) * (i + 1) / float(frame_size)) - frame.planes[i].update(data) # type: ignore + frame.planes[i].update(data) return frame From fee0d91b6ef71bcb05668bec1324bef7f6f9cb01 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 4 Nov 2026 23:23:06 -0500 Subject: [PATCH 3/5] Mark read-only attributes as properties in stubs --- av/audio/codeccontext.pyi | 6 ++-- av/audio/format.pyi | 25 +++++++++------ av/audio/frame.pyi | 6 ++-- av/audio/layout.pyi | 9 ++++-- av/audio/resampler.pyi | 22 ++++++++----- av/audio/stream.pyi | 21 ++++++++----- av/buffer.pyi | 6 ++-- av/codec/codec.pyi | 16 ++++++---- av/codec/context.pyi | 8 +++-- av/codec/hwaccel.pyi | 11 ++++--- av/container/core.pyi | 33 +++++++++++++------- av/container/input.pyi | 24 +++++++++----- av/container/streams.pyi | 16 ++++++---- av/filter/context.pyi | 7 +++-- av/filter/filter.pyi | 10 +++--- av/filter/graph.pyi | 3 +- av/filter/link.pyi | 15 ++++++--- av/index.pyi | 21 ++++++++----- av/logging.pyi | 4 +-- av/packet.pyi | 19 ++++++----- av/rational.pyi | 6 ++-- av/sidedata/encparams.pyi | 33 +++++++++++++------- av/sidedata/motionvectors.pyi | 30 ++++++++++++------ av/sidedata/sidedata.pyi | 3 +- av/stream.pyi | 59 ++++++++++++++++++++++++----------- av/subtitles/codeccontext.pyi | 3 +- av/subtitles/stream.pyi | 12 ++++--- av/subtitles/subtitle.pyi | 40 +++++++++++++++--------- av/video/codeccontext.pyi | 19 ++++++----- av/video/format.pyi | 52 +++++++++++++++++++----------- av/video/frame.pyi | 6 ++-- av/video/plane.pyi | 13 +++++--- av/video/stream.pyi | 44 ++++++++++++++++---------- 33 files changed, 392 insertions(+), 210 deletions(-) diff --git a/av/audio/codeccontext.pyi b/av/audio/codeccontext.pyi index 8af27b26c..03e237c4e 100644 --- a/av/audio/codeccontext.pyi +++ b/av/audio/codeccontext.pyi @@ -11,10 +11,12 @@ from .layout import AudioLayout @final class AudioCodecContext(CodecContext): - frame_size: int + @property + def frame_size(self) -> int: ... sample_rate: int rate: int - type: Literal["audio"] + @property + def type(self) -> Literal["audio"]: ... @property def format(self) -> AudioFormat | None: ... @format.setter diff --git a/av/audio/format.pyi b/av/audio/format.pyi index f3ef36583..5fd6e962d 100644 --- a/av/audio/format.pyi +++ b/av/audio/format.pyi @@ -2,13 +2,20 @@ from typing import Self, final @final class AudioFormat: - name: str - bytes: int - bits: int - is_planar: bool - is_packed: bool - planar: AudioFormat - packed: AudioFormat - container_name: str - + @property + def name(self) -> str: ... + @property + def bytes(self) -> int: ... + @property + def bits(self) -> int: ... + @property + def is_planar(self) -> bool: ... + @property + def is_packed(self) -> bool: ... + @property + def planar(self) -> AudioFormat: ... + @property + def packed(self) -> AudioFormat: ... + @property + def container_name(self) -> str: ... def __new__(cls, name: str | AudioFormat) -> Self: ... diff --git a/av/audio/frame.pyi b/av/audio/frame.pyi index dd5ac4082..772374e88 100644 --- a/av/audio/frame.pyi +++ b/av/audio/frame.pyi @@ -20,8 +20,10 @@ type _SupportedNDarray = ( @final class AudioFrame(Frame): - planes: tuple[AudioPlane, ...] - samples: int + @property + def planes(self) -> tuple[AudioPlane, ...]: ... + @property + def samples(self) -> int: ... sample_rate: int rate: int @property diff --git a/av/audio/layout.pyi b/av/audio/layout.pyi index 9438292f4..2b02ec927 100644 --- a/av/audio/layout.pyi +++ b/av/audio/layout.pyi @@ -3,9 +3,12 @@ from typing import Self, final @final class AudioLayout: - name: str - nb_channels: int - channels: tuple[AudioChannel, ...] + @property + def name(self) -> str: ... + @property + def nb_channels(self) -> int: ... + @property + def channels(self) -> tuple[AudioChannel, ...]: ... def __new__(cls, layout: str | AudioLayout) -> Self: ... @dataclass diff --git a/av/audio/resampler.pyi b/av/audio/resampler.pyi index b1066ab42..4b5ac6699 100644 --- a/av/audio/resampler.pyi +++ b/av/audio/resampler.pyi @@ -6,14 +6,20 @@ from .layout import AudioLayout @final class AudioResampler: - rate: int - frame_size: int - threads: int - format: AudioFormat - layout: AudioLayout - options: dict[str, str] - is_passthrough: bool - + @property + def rate(self) -> int: ... + @property + def frame_size(self) -> int: ... + @property + def threads(self) -> int: ... + @property + def format(self) -> AudioFormat: ... + @property + def layout(self) -> AudioLayout: ... + @property + def options(self) -> dict[str, str]: ... + @property + def is_passthrough(self) -> bool: ... def __new__( cls, format: str | AudioFormat | None = None, diff --git a/av/audio/stream.pyi b/av/audio/stream.pyi index b2ae7cfa3..9f133b399 100644 --- a/av/audio/stream.pyi +++ b/av/audio/stream.pyi @@ -14,8 +14,10 @@ from .layout import AudioLayout @final class AudioStream(Stream): - cctx: AudioCodecContext - codec_context: AudioCodecContext + @property + def cctx(self) -> AudioCodecContext: ... + @property + def codec_context(self) -> AudioCodecContext: ... def encode(self, frame: AudioFrame | None = None) -> list[Packet[AudioStream]]: ... def encode_lazy( self, frame: AudioFrame | None = None @@ -23,17 +25,22 @@ class AudioStream(Stream): def decode(self, packet: Packet[Any] | None = None) -> list[AudioFrame]: ... # From codec context - name: str - codec: Codec + @property + def name(self) -> str: ... + @property + def codec(self) -> Codec: ... codec_tag: str options: dict[str, str] - frame_size: int + @property + def frame_size(self) -> int: ... sample_rate: int bit_rate: int | None max_bit_rate: int | None rate: int - channels: int - type: Literal["audio"] + @property + def channels(self) -> int: ... + @property + def type(self) -> Literal["audio"]: ... @property @deprecated("Use AudioStream.cctx.format instead") def format(self) -> AudioFormat | None: ... diff --git a/av/buffer.pyi b/av/buffer.pyi index 82e34fc44..ad7a62f06 100644 --- a/av/buffer.pyi +++ b/av/buffer.pyi @@ -8,7 +8,9 @@ class ByteSource: ... @disjoint_base class Buffer(_Buffer): - buffer_size: int - buffer_ptr: int + @property + def buffer_size(self) -> int: ... + @property + def buffer_ptr(self) -> int: ... def update(self, input: _Buffer) -> None: ... def __buffer__(self, flags: int, /) -> memoryview: ... diff --git a/av/codec/codec.pyi b/av/codec/codec.pyi index 3af942bb6..6c1c72829 100644 --- a/av/codec/codec.pyi +++ b/av/codec/codec.pyi @@ -81,12 +81,16 @@ class Codec: ) -> Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]: ... @property def id(self) -> int: ... - frame_rates: list[AVRational] | None - audio_rates: list[int] | None - video_formats: list[VideoFormat] | None - audio_formats: list[AudioFormat] | None - hardware_configs: tuple[HWConfig, ...] - + @property + def frame_rates(self) -> list[AVRational] | None: ... + @property + def audio_rates(self) -> list[int] | None: ... + @property + def video_formats(self) -> list[VideoFormat] | None: ... + @property + def audio_formats(self) -> list[AudioFormat] | None: ... + @property + def hardware_configs(self) -> tuple[HWConfig, ...]: ... @property def properties(self) -> int: ... @property diff --git a/av/codec/context.pyi b/av/codec/context.pyi index 405595a4a..b10ae088d 100644 --- a/av/codec/context.pyi +++ b/av/codec/context.pyi @@ -123,8 +123,12 @@ class CodecOptionSet: @disjoint_base class CodecContext: - name: str - type: Literal["video", "audio", "data", "subtitle", "attachment", "unknown"] + @property + def name(self) -> str: ... + @property + def type( + self, + ) -> Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]: ... options: dict[str, str] @property def supported_options(self) -> CodecOptionSet: ... diff --git a/av/codec/hwaccel.pyi b/av/codec/hwaccel.pyi index 4f87f50ce..97158909a 100644 --- a/av/codec/hwaccel.pyi +++ b/av/codec/hwaccel.pyi @@ -41,9 +41,10 @@ class HWConfig: @final class HWDevice: - options: dict[str, object] - flags: int - + @property + def options(self) -> dict[str, object]: ... + @property + def flags(self) -> int: ... def __init__( self, device_type: str | int | HWDeviceType, @@ -59,8 +60,8 @@ class HWAccel: options: dict[str, object] flags: int allow_software_fallback: bool - config: HWConfig | None - + @property + def config(self) -> HWConfig | None: ... @property def is_hw_owned(self) -> bool: ... @property diff --git a/av/container/core.pyi b/av/container/core.pyi index 8ef88806f..0e1f29faf 100644 --- a/av/container/core.pyi +++ b/av/container/core.pyi @@ -99,17 +99,28 @@ class Chapter(TypedDict): @disjoint_base class Container: - name: str - file: Any - io_open: _IOOpen | None - open_files: Any - format: ContainerFormat - options: dict[str, str] - container_options: dict[str, str] - streams: StreamContainer - metadata: dict[str, str] - open_timeout: _Real | None - read_timeout: _Real | None + @property + def name(self) -> str: ... + @property + def file(self) -> Any: ... + @property + def io_open(self) -> _IOOpen | None: ... + @property + def open_files(self) -> Any: ... + @property + def format(self) -> ContainerFormat: ... + @property + def options(self) -> dict[str, str]: ... + @property + def container_options(self) -> dict[str, str]: ... + @property + def streams(self) -> StreamContainer: ... + @property + def metadata(self) -> dict[str, str]: ... + @property + def open_timeout(self) -> _Real | None: ... + @property + def read_timeout(self) -> _Real | None: ... flags: int video_codec_id: int def dumps_format(self) -> str: ... diff --git a/av/container/input.pyi b/av/container/input.pyi index b309e6a0f..0ae9cc677 100644 --- a/av/container/input.pyi +++ b/av/container/input.pyi @@ -36,16 +36,24 @@ class Program: @final class InputContainer(Container): - start_time: int | None - start_time_realtime: int | None - duration: int | None - bit_rate: int - size: int | None - probe_score: int + @property + def start_time(self) -> int | None: ... + @property + def start_time_realtime(self) -> int | None: ... + @property + def duration(self) -> int | None: ... + @property + def bit_rate(self) -> int: ... + @property + def size(self) -> int | None: ... + @property + def probe_score(self) -> int: ... @property def programs(self) -> tuple[Program, ...]: ... - duration_estimation_method: Literal["pts", "stream", "bitrate"] | None - + @property + def duration_estimation_method( + self, + ) -> Literal["pts", "stream", "bitrate"] | None: ... @overload def demux(self, video_stream: VideoStream) -> Iterator[Packet[VideoStream]]: ... @overload diff --git a/av/container/streams.pyi b/av/container/streams.pyi index 7cf351a26..5ae8cf0ec 100644 --- a/av/container/streams.pyi +++ b/av/container/streams.pyi @@ -13,12 +13,16 @@ type _StreamSelector = ( @final class StreamContainer: - video: tuple[VideoStream, ...] - audio: tuple[AudioStream, ...] - subtitles: tuple[SubtitleStream, ...] - attachments: tuple[AttachmentStream, ...] - data: tuple[DataStream, ...] - + @property + def video(self) -> tuple[VideoStream, ...]: ... + @property + def audio(self) -> tuple[AudioStream, ...]: ... + @property + def subtitles(self) -> tuple[SubtitleStream, ...]: ... + @property + def attachments(self) -> tuple[AttachmentStream, ...]: ... + @property + def data(self) -> tuple[DataStream, ...]: ... def __init__(self) -> None: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[Stream]: ... diff --git a/av/filter/context.pyi b/av/filter/context.pyi index 4753118f2..0f0712e85 100644 --- a/av/filter/context.pyi +++ b/av/filter/context.pyi @@ -7,9 +7,10 @@ from av.frame import Frame @final class FilterContext: - name: str | None - filter: Filter - + @property + def name(self) -> str | None: ... + @property + def filter(self) -> Filter: ... @property def inputs(self) -> tuple[FilterContextPad, ...]: ... @property diff --git a/av/filter/filter.pyi b/av/filter/filter.pyi index 1a7ed9380..92eab11a9 100644 --- a/av/filter/filter.pyi +++ b/av/filter/filter.pyi @@ -4,10 +4,12 @@ from .link import FilterPad @final class Filter: - name: str - description: str - flags: int - + @property + def name(self) -> str: ... + @property + def description(self) -> str: ... + @property + def flags(self) -> int: ... @property def inputs(self) -> tuple[FilterPad, ...]: ... @property diff --git a/av/filter/graph.pyi b/av/filter/graph.pyi index 70056cb59..592125f02 100644 --- a/av/filter/graph.pyi +++ b/av/filter/graph.pyi @@ -16,7 +16,8 @@ from .filter import Filter @final class Graph: - configured: bool + @property + def configured(self) -> bool: ... threads: int def __new__(cls, hw_device: HWDevice | None = None) -> Self: ... diff --git a/av/filter/link.pyi b/av/filter/link.pyi index 86eb1860c..5dd8bda23 100644 --- a/av/filter/link.pyi +++ b/av/filter/link.pyi @@ -8,10 +8,14 @@ from av.filter.graph import Graph @disjoint_base class FilterPad: - filter: Filter - context: FilterContext | None - is_input: bool - index: int + @property + def filter(self) -> Filter: ... + @property + def context(self) -> FilterContext | None: ... + @property + def is_input(self) -> bool: ... + @property + def index(self) -> int: ... def __eq__(self, other: object, /) -> bool: ... def __hash__(self) -> int: ... @property @@ -23,7 +27,8 @@ class FilterPad: @final class FilterContextPad(FilterPad): - context: FilterContext + @property + def context(self) -> FilterContext: ... @property def link(self) -> FilterLink | None: ... @property diff --git a/av/index.pyi b/av/index.pyi index 3d86df8f0..c5f5d8e6d 100644 --- a/av/index.pyi +++ b/av/index.pyi @@ -3,13 +3,20 @@ from typing import final, overload @final class IndexEntry: - pos: int - timestamp: int - flags: int - is_keyframe: bool - is_discard: bool - size: int - min_distance: int + @property + def pos(self) -> int: ... + @property + def timestamp(self) -> int: ... + @property + def flags(self) -> int: ... + @property + def is_keyframe(self) -> bool: ... + @property + def is_discard(self) -> bool: ... + @property + def size(self) -> int: ... + @property + def min_distance(self) -> int: ... @final class IndexEntries: diff --git a/av/logging.pyi b/av/logging.pyi index e489700f1..5f93152bc 100644 --- a/av/logging.pyi +++ b/av/logging.pyi @@ -23,8 +23,8 @@ def log(level: int, name: str, message: str) -> None: ... @final class Capture: - logs: list[tuple[int, str, str]] - + @property + def logs(self) -> list[tuple[int, str, str]]: ... def __init__(self, local: bool = True) -> None: ... def __enter__(self) -> list[tuple[int, str, str]]: ... def __exit__( diff --git a/av/packet.pyi b/av/packet.pyi index 11b9168f3..44611b196 100644 --- a/av/packet.pyi +++ b/av/packet.pyi @@ -86,23 +86,28 @@ class Packet[StreamT: Stream](Buffer): def stream(self) -> StreamT | None: ... @stream.setter def stream(self, value: StreamT) -> None: ... - stream_index: int + @property + def stream_index(self) -> int: ... @property def time_base(self) -> AVRational: ... @time_base.setter def time_base(self, value: AVRational | Fraction | int) -> None: ... pts: int | None dts: int | None - pos: int | None - size: int + @property + def pos(self) -> int | None: ... + @property + def size(self) -> int: ... duration: int | None opaque: object is_keyframe: bool is_corrupt: bool - is_discard: bool - is_trusted: bool - is_disposable: bool - + @property + def is_discard(self) -> bool: ... + @property + def is_trusted(self) -> bool: ... + @property + def is_disposable(self) -> bool: ... def __init__( self: Packet[Stream], input: int | str | _BufferLike | None = None ) -> None: ... diff --git a/av/rational.pyi b/av/rational.pyi index 64f905787..58d97d228 100644 --- a/av/rational.pyi +++ b/av/rational.pyi @@ -7,8 +7,10 @@ type _Comparable = AVRational | Rational | Decimal | float @final class AVRational: - num: int - den: int + @property + def num(self) -> int: ... + @property + def den(self) -> int: ... def __init__(self, num: int | Rational = 0, den: int = 1) -> None: ... @property def numerator(self) -> int: ... diff --git a/av/sidedata/encparams.pyi b/av/sidedata/encparams.pyi index 44e6f4aed..d4cf31131 100644 --- a/av/sidedata/encparams.pyi +++ b/av/sidedata/encparams.pyi @@ -13,20 +13,31 @@ class VideoEncParamsType(IntEnum): @final class VideoEncParams(SideData): - nb_blocks: int - blocks_offset: int - block_size: int - codec_type: VideoEncParamsType - qp: int - delta_qp: list[list[int]] + @property + def nb_blocks(self) -> int: ... + @property + def blocks_offset(self) -> int: ... + @property + def block_size(self) -> int: ... + @property + def codec_type(self) -> VideoEncParamsType: ... + @property + def qp(self) -> int: ... + @property + def delta_qp(self) -> list[list[int]]: ... def block_params(self, idx: int) -> VideoBlockParams: ... def qp_map(self) -> np.ndarray[tuple[int, int], np.dtype[np.int32]]: ... @final class VideoBlockParams: - src_x: int - src_y: int - w: int - h: int - delta_qp: int + @property + def src_x(self) -> int: ... + @property + def src_y(self) -> int: ... + @property + def w(self) -> int: ... + @property + def h(self) -> int: ... + @property + def delta_qp(self) -> int: ... def __new__(cls, video_enc_params: VideoEncParams, idx: int, /) -> Self: ... diff --git a/av/sidedata/motionvectors.pyi b/av/sidedata/motionvectors.pyi index 584a15082..8c3c8300c 100644 --- a/av/sidedata/motionvectors.pyi +++ b/av/sidedata/motionvectors.pyi @@ -18,16 +18,26 @@ class MotionVectors(SideData): @final class MotionVector: - source: int - w: int - h: int - src_x: int - src_y: int - dst_x: int - dst_y: int - motion_x: int - motion_y: int - motion_scale: int + @property + def source(self) -> int: ... + @property + def w(self) -> int: ... + @property + def h(self) -> int: ... + @property + def src_x(self) -> int: ... + @property + def src_y(self) -> int: ... + @property + def dst_x(self) -> int: ... + @property + def dst_y(self) -> int: ... + @property + def motion_x(self) -> int: ... + @property + def motion_y(self) -> int: ... + @property + def motion_scale(self) -> int: ... def __new__( cls, sentinel: object, parent: MotionVectors, index: int, / ) -> Self: ... diff --git a/av/sidedata/sidedata.pyi b/av/sidedata/sidedata.pyi index 89809079e..f35f6444e 100644 --- a/av/sidedata/sidedata.pyi +++ b/av/sidedata/sidedata.pyi @@ -50,7 +50,8 @@ type _VideoEncParamsKey = Literal["VIDEO_ENC_PARAMS", Type.VIDEO_ENC_PARAMS] @disjoint_base class SideData(Buffer): - type: Type + @property + def type(self) -> Type: ... def __new__(cls, sentinel: object, frame: Frame, index: int, /) -> Self: ... class SideDataContainer(Mapping[Type, SideData]): diff --git a/av/stream.pyi b/av/stream.pyi index 87b6632ec..46eea0f7c 100644 --- a/av/stream.pyi +++ b/av/stream.pyi @@ -42,41 +42,62 @@ class Discard(IntEnum): @disjoint_base class Stream: - name: str | None - container: Container - cctx: CodecContext | None - codec_context: CodecContext | None - metadata: dict[str, str] + @property + def name(self) -> str | None: ... + @property + def container(self) -> Container: ... + @property + def cctx(self) -> CodecContext | None: ... + @property + def codec_context(self) -> CodecContext | None: ... + @property + def metadata(self) -> dict[str, str]: ... @property def index_entries(self) -> IndexEntries: ... id: int - profiles: list[str] + @property + def profiles(self) -> list[str]: ... profile: str | None - index: int + @property + def index(self) -> int: ... @property def time_base(self) -> AVRational: ... @time_base.setter def time_base(self, value: AVRational | Fraction | int) -> None: ... - start_time: int | None - duration: int | None + @property + def start_time(self) -> int | None: ... + @property + def duration(self) -> int | None: ... disposition: Disposition discard: Discard - frames: int - language: str | None - type: Literal["video", "audio", "data", "subtitle", "attachment", "unknown"] + @property + def frames(self) -> int: ... + @property + def language(self) -> str | None: ... + @property + def type( + self, + ) -> Literal["video", "audio", "data", "subtitle", "attachment", "unknown"]: ... @final class DataStream(Stream): - cctx: None - codec_context: None - type: Literal["data"] - name: str | None + @property + def cctx(self) -> None: ... + @property + def codec_context(self) -> None: ... + @property + def type(self) -> Literal["data"]: ... + @property + def name(self) -> str | None: ... @final class AttachmentStream(Stream): - cctx: None - codec_context: None - type: Literal["attachment"] + @property + def cctx(self) -> None: ... + @property + def codec_context(self) -> None: ... + @property + def type(self) -> Literal["attachment"]: ... @property def mimetype(self) -> str | None: ... @property diff --git a/av/subtitles/codeccontext.pyi b/av/subtitles/codeccontext.pyi index cc18057b6..6a3b8dc6d 100644 --- a/av/subtitles/codeccontext.pyi +++ b/av/subtitles/codeccontext.pyi @@ -7,7 +7,8 @@ from av.subtitles.subtitle import AssSubtitle, BitmapSubtitle, SubtitleSet @final class SubtitleCodecContext(CodecContext): - type: Literal["subtitle"] + @property + def type(self) -> Literal["subtitle"]: ... subtitle_header: bytes | None @override def decode( diff --git a/av/subtitles/stream.pyi b/av/subtitles/stream.pyi index b97d5dcca..d2b40d62b 100644 --- a/av/subtitles/stream.pyi +++ b/av/subtitles/stream.pyi @@ -9,12 +9,16 @@ from .codeccontext import SubtitleCodecContext @final class SubtitleStream(Stream): - cctx: SubtitleCodecContext - codec_context: SubtitleCodecContext + @property + def cctx(self) -> SubtitleCodecContext: ... + @property + def codec_context(self) -> SubtitleCodecContext: ... # From codec context - name: str - codec: Codec + @property + def name(self) -> str: ... + @property + def codec(self) -> Codec: ... codec_tag: str options: dict[str, str] def decode( diff --git a/av/subtitles/subtitle.pyi b/av/subtitles/subtitle.pyi index 103eab94f..f757eb984 100644 --- a/av/subtitles/subtitle.pyi +++ b/av/subtitles/subtitle.pyi @@ -12,8 +12,8 @@ class SubtitleSet: start_display_time: int end_display_time: int pts: int - rects: tuple[AssSubtitle | BitmapSubtitle, ...] - + @property + def rects(self) -> tuple[AssSubtitle | BitmapSubtitle, ...]: ... @staticmethod def create( text: bytes, start: int, end: int, pts: int = 0, subtitle_format: int = 1 @@ -24,31 +24,43 @@ class SubtitleSet: @disjoint_base class Subtitle: - type: bytes + @property + def type(self) -> bytes: ... @final class BitmapSubtitle(Subtitle): - type: Literal[b"bitmap"] - x: int - y: int - width: int - height: int - nb_colors: int - planes: tuple[BitmapSubtitlePlane, ...] + @property + def type(self) -> Literal[b"bitmap"]: ... + @property + def x(self) -> int: ... + @property + def y(self) -> int: ... + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... + @property + def nb_colors(self) -> int: ... + @property + def planes(self) -> tuple[BitmapSubtitlePlane, ...]: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[BitmapSubtitlePlane]: ... def __getitem__(self, i: int, /) -> BitmapSubtitlePlane: ... @final class BitmapSubtitlePlane: - subtitle: BitmapSubtitle - index: int - buffer_size: int + @property + def subtitle(self) -> BitmapSubtitle: ... + @property + def index(self) -> int: ... + @property + def buffer_size(self) -> int: ... def __buffer__(self, flags: int, /) -> memoryview: ... @final class AssSubtitle(Subtitle): - type: Literal[b"ass", b"text"] + @property + def type(self) -> Literal[b"ass", b"text"]: ... @property def ass(self) -> bytes: ... @property diff --git a/av/video/codeccontext.pyi b/av/video/codeccontext.pyi index 7c5db293d..d4f711bff 100644 --- a/av/video/codeccontext.pyi +++ b/av/video/codeccontext.pyi @@ -24,7 +24,8 @@ class VideoCodecContext(CodecContext): def pix_fmt(self) -> str | None: ... @pix_fmt.setter def pix_fmt(self, value: str) -> None: ... - reformatter: VideoReformatter | None + @property + def reformatter(self) -> VideoReformatter | None: ... @property def sw_format(self) -> VideoFormat | None: ... @sw_format.setter @@ -42,12 +43,16 @@ class VideoCodecContext(CodecContext): def sample_aspect_ratio(self) -> AVRational: ... @sample_aspect_ratio.setter def sample_aspect_ratio(self, value: AVRational | Fraction | int) -> None: ... - display_aspect_ratio: AVRational - has_b_frames: bool + @property + def display_aspect_ratio(self) -> AVRational: ... + @property + def has_b_frames(self) -> bool: ... reorder_depth: int max_b_frames: int - coded_width: int - coded_height: int + @property + def coded_width(self) -> int: ... + @property + def coded_height(self) -> int: ... color_range: int color_primaries: int color_trc: int @@ -58,8 +63,8 @@ class VideoCodecContext(CodecContext): mb_decision: int qmin: int qmax: int - type: Literal["video"] - + @property + def type(self) -> Literal["video"]: ... @override def encode(self, frame: VideoFrame | None = None) -> list[Packet[Stream]]: ... # type: ignore[override] @override diff --git a/av/video/format.pyi b/av/video/format.pyi index 136bc2de0..5da2bedd1 100644 --- a/av/video/format.pyi +++ b/av/video/format.pyi @@ -2,15 +2,24 @@ from typing import Self, final @final class VideoFormat: - name: str - bits_per_pixel: int - padded_bits_per_pixel: int - is_big_endian: bool - has_palette: bool - is_bit_stream: bool - is_planar: bool - width: int - height: int + @property + def name(self) -> str: ... + @property + def bits_per_pixel(self) -> int: ... + @property + def padded_bits_per_pixel(self) -> int: ... + @property + def is_big_endian(self) -> bool: ... + @property + def has_palette(self) -> bool: ... + @property + def is_bit_stream(self) -> bool: ... + @property + def is_planar(self) -> bool: ... + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... @property def components(self) -> tuple[VideoFormatComponent, ...]: ... @property @@ -26,15 +35,22 @@ class VideoFormat: @final class VideoFormatComponent: - index: int - plane: int - bits: int - is_alpha: bool - is_luma: bool - is_chroma: bool - width: int - height: int - + @property + def index(self) -> int: ... + @property + def plane(self) -> int: ... + @property + def bits(self) -> int: ... + @property + def is_alpha(self) -> bool: ... + @property + def is_luma(self) -> bool: ... + @property + def is_chroma(self) -> bool: ... + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... def __new__(cls, format: VideoFormat, index: int) -> Self: ... names: set[str] diff --git a/av/video/frame.pyi b/av/video/frame.pyi index a2febe2e5..b48009980 100644 --- a/av/video/frame.pyi +++ b/av/video/frame.pyi @@ -66,8 +66,10 @@ class CudaContext: @final class VideoFrame(Frame): - format: VideoFormat - planes: tuple[VideoPlane, ...] + @property + def format(self) -> VideoFormat: ... + @property + def planes(self) -> tuple[VideoPlane, ...]: ... pict_type: int repeat_pict: int colorspace: int diff --git a/av/video/plane.pyi b/av/video/plane.pyi index 3346a6435..abe0099b4 100644 --- a/av/video/plane.pyi +++ b/av/video/plane.pyi @@ -7,11 +7,14 @@ from .frame import VideoFrame @final class VideoPlane(Plane): - line_size: int - width: int - height: int - buffer_size: int - + @property + def line_size(self) -> int: ... + @property + def width(self) -> int: ... + @property + def height(self) -> int: ... + @property + def buffer_size(self) -> int: ... def __new__(cls, frame: VideoFrame, index: int) -> Self: ... def __dlpack_device__(self) -> tuple[int, int]: ... def __dlpack__(self, *, stream: int | None = None) -> CapsuleType: ... diff --git a/av/video/stream.pyi b/av/video/stream.pyi index b026ffe8c..05376a27d 100644 --- a/av/video/stream.pyi +++ b/av/video/stream.pyi @@ -19,10 +19,12 @@ class VideoStream(Stream): max_bit_rate: int | None bit_rate_tolerance: int sample_aspect_ratio: AVRational - display_aspect_ratio: AVRational - cctx: VideoCodecContext - codec_context: VideoCodecContext - + @property + def display_aspect_ratio(self) -> AVRational: ... + @property + def cctx(self) -> VideoCodecContext: ... + @property + def codec_context(self) -> VideoCodecContext: ... def encode(self, frame: VideoFrame | None = None) -> list[Packet[VideoStream]]: ... def encode_lazy( self, frame: VideoFrame | None = None @@ -32,16 +34,22 @@ class VideoStream(Stream): def set_display_rotation( self, degrees: float, hflip: bool = ..., vflip: bool = ... ) -> None: ... - - average_rate: AVRational - base_rate: AVRational - guessed_rate: AVRational - codec_rate: AVRational - video_delay: int + @property + def average_rate(self) -> AVRational: ... + @property + def base_rate(self) -> AVRational: ... + @property + def guessed_rate(self) -> AVRational: ... + @property + def codec_rate(self) -> AVRational: ... + @property + def video_delay(self) -> int: ... # from codec context - name: str - codec: Codec + @property + def name(self) -> str: ... + @property + def codec(self) -> Codec: ... codec_tag: str options: dict[str, str] @property @@ -67,13 +75,17 @@ class VideoStream(Stream): bits_per_coded_sample: int pix_fmt: str | None gop_size: int - has_b_frames: bool + @property + def has_b_frames(self) -> bool: ... max_b_frames: int - coded_width: int - coded_height: int + @property + def coded_width(self) -> int: ... + @property + def coded_height(self) -> int: ... color_range: int color_primaries: int color_trc: int colorspace: int chroma_sample_location: int - type: Literal["video"] + @property + def type(self) -> Literal["video"]: ... From 4b0deb3450765259d56bbda2a6cf21624b440c68 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 4 Nov 2026 23:27:08 -0500 Subject: [PATCH 4/5] Add Stream.name to the base class Return the codec context's name, falling back to the codec descriptor's name when the stream has no codec context. --- av/stream.py | 31 +++++++++++++++++++------------ av/stream.pyi | 2 -- tests/test_streams.py | 6 ++++++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/av/stream.py b/av/stream.py index cd6e297ba..1aa6314b1 100644 --- a/av/stream.py +++ b/av/stream.py @@ -143,10 +143,9 @@ def __repr__(self): f"" ) - name = getattr(self, "name", None) return ( f"'}/" - f"{name or ''} at 0x{id(self):x}>" + f"{self.name or ''} at 0x{id(self):x}>" ) def __setattr__(self, name, value): @@ -327,6 +326,24 @@ def discard(self): self._assert_open() return Discard(self.ptr.discard) + @property + def name(self): + """ + The name of the stream's codec context, or the codec descriptor's name + when the stream has no codec context. + + :type: str | None + """ + if self.cctx is not None: + return self.cctx.name + self._assert_open() + desc: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( + lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) + ) + if desc == cython.NULL: + return None + return desc.name + @property def type(self): """ @@ -353,16 +370,6 @@ def __repr__(self): f"{self.name or ''} at 0x{id(self):x}>" ) - @property - def name(self): - self._assert_open() - desc: cython.pointer[cython.const[lib.AVCodecDescriptor]] = ( - lib.avcodec_descriptor_get(self.ptr.codecpar.codec_id) - ) - if desc == cython.NULL: - return None - return desc.name - @cython.final @cython.cclass diff --git a/av/stream.pyi b/av/stream.pyi index 46eea0f7c..033eb36c7 100644 --- a/av/stream.pyi +++ b/av/stream.pyi @@ -87,8 +87,6 @@ class DataStream(Stream): def codec_context(self) -> None: ... @property def type(self) -> Literal["data"]: ... - @property - def name(self) -> str | None: ... @final class AttachmentStream(Stream): diff --git a/tests/test_streams.py b/tests/test_streams.py index f400e9320..40c033d35 100644 --- a/tests/test_streams.py +++ b/tests/test_streams.py @@ -227,6 +227,12 @@ def test_printing_video_stream2(self) -> None: container.close() input_.close() + def test_name_uses_codec_context(self) -> None: + # The encoder name differs from the codec descriptor name ("ac3"). + with av.open(io.BytesIO(), "w", format="ac3") as container: + stream = container.add_stream("ac3_fixed") + assert stream.name == "ac3_fixed" + def test_data_stream(self) -> None: # First test writing and reading a simple data stream container1 = av.open("data.ts", "w") From 19475ecd33e12b71a20b87d252a9dc5a34a6e1ee Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Wed, 4 Nov 2026 23:33:45 -0500 Subject: [PATCH 5/5] Expose Frame.key_frame setter in stub --- av/frame.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/av/frame.pyi b/av/frame.pyi index d5ad001de..47641cc2c 100644 --- a/av/frame.pyi +++ b/av/frame.pyi @@ -36,4 +36,6 @@ class Frame: def decode_error_flags(self) -> DecodeError: ... @property def key_frame(self) -> bool: ... + @key_frame.setter + def key_frame(self, value: bool) -> None: ... def make_writable(self) -> None: ...