diff --git a/av/audio/frame.pyi b/av/audio/frame.pyi index 6f7263434..4604f177b 100644 --- a/av/audio/frame.pyi +++ b/av/audio/frame.pyi @@ -1,6 +1,7 @@ -from typing import Any, Self, final +from typing import Self, final import numpy as np +import numpy.typing as npt from av.frame import Frame @@ -10,11 +11,11 @@ from .plane import AudioPlane format_dtypes: dict[str, str] _SupportedNDarray = ( - np.ndarray[Any, np.dtype[np.float64]] # f8 - | np.ndarray[Any, np.dtype[np.float32]] # f4 - | np.ndarray[Any, np.dtype[np.int32]] # i4 - | np.ndarray[Any, np.dtype[np.int16]] # i2 - | np.ndarray[Any, np.dtype[np.uint8]] # u1 + npt.NDArray[np.float64] + | npt.NDArray[np.float32] + | npt.NDArray[np.int32] + | npt.NDArray[np.int16] + | npt.NDArray[np.uint8] ) @final diff --git a/av/container/core.pyi b/av/container/core.pyi index 3d3333bc5..e7694abb8 100644 --- a/av/container/core.pyi +++ b/av/container/core.pyi @@ -3,14 +3,7 @@ from enum import Flag, IntEnum from fractions import Fraction from pathlib import Path from types import TracebackType -from typing import ( - Any, - Literal, - Self, - TypedDict, - overload, - type_check_only, -) +from typing import Any, Literal, Protocol, Self, TypedDict, overload, type_check_only from typing_extensions import disjoint_base @@ -23,6 +16,15 @@ from .output import OutputContainer from .streams import StreamContainer type _Real = int | float | Fraction | AVRational +type _IOOpen = Callable[[str, int, dict[str, str]], object] + +@type_check_only +class _ReadableFile(Protocol): + def read(self, size: int, /) -> bytes: ... + +@type_check_only +class _WritableFile(Protocol): + def write(self, data: bytes, /) -> object: ... class Flags(Flag): _value_: int @@ -91,7 +93,7 @@ class Chapter(TypedDict): class Container: name: str file: Any - io_open: Any + io_open: _IOOpen | None open_files: Any format: ContainerFormat options: dict[str, str] @@ -118,14 +120,14 @@ class Container: @overload def open( - file: Any, + file: str | Path | _ReadableFile, mode: Literal["r"], format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, buffer_size: int = 32768, timeout: _Real | None | tuple[_Real | None, _Real | None] = None, - io_open: Callable[..., Any] | None = None, + io_open: _IOOpen | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer: ... @overload @@ -137,30 +139,30 @@ def open( container_options: dict[str, str] | None = None, buffer_size: int = 32768, timeout: _Real | None | tuple[_Real | None, _Real | None] = None, - io_open: Callable[..., Any] | None = None, + io_open: _IOOpen | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer: ... @overload def open( - file: Any, + file: str | Path | _WritableFile, mode: Literal["w"], format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, buffer_size: int = 32768, timeout: _Real | None | tuple[_Real | None, _Real | None] = None, - io_open: Callable[..., Any] | None = None, + io_open: _IOOpen | None = None, hwaccel: HWAccel | None = None, ) -> OutputContainer: ... @overload def open( - file: Any, + file: str | Path | _ReadableFile | _WritableFile, mode: Literal["r", "w"] | None = None, format: str | None = None, options: dict[str, str] | None = None, container_options: dict[str, str] | None = None, buffer_size: int = 32768, timeout: _Real | None | tuple[_Real | None, _Real | None] = None, - io_open: Callable[..., Any] | None = None, + io_open: _IOOpen | None = None, hwaccel: HWAccel | None = None, ) -> InputContainer | OutputContainer: ... diff --git a/av/container/input.pyi b/av/container/input.pyi index ce6439cb0..e191e03c0 100644 --- a/av/container/input.pyi +++ b/av/container/input.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterator -from typing import Any, final, overload +from typing import final, overload from av.audio.frame import AudioFrame from av.audio.stream import AudioStream @@ -11,6 +11,7 @@ from av.video.frame import VideoFrame from av.video.stream import VideoStream from .core import Container +from .streams import _Indices, _StreamSelector @final class InputContainer(Container): @@ -27,7 +28,7 @@ class InputContainer(Container): self, video_streams: tuple[VideoStream, ...] ) -> Iterator[Packet[VideoStream]]: ... @overload - def demux(self, *, video: Any) -> Iterator[Packet[VideoStream]]: ... + def demux(self, *, video: _Indices) -> Iterator[Packet[VideoStream]]: ... @overload def demux(self, audio_stream: AudioStream) -> Iterator[Packet[AudioStream]]: ... @overload @@ -35,7 +36,7 @@ class InputContainer(Container): self, audio_streams: tuple[AudioStream, ...] ) -> Iterator[Packet[AudioStream]]: ... @overload - def demux(self, *, audio: Any) -> Iterator[Packet[AudioStream]]: ... + def demux(self, *, audio: _Indices) -> Iterator[Packet[AudioStream]]: ... @overload def demux( self, subtitle_stream: SubtitleStream @@ -51,7 +52,7 @@ class InputContainer(Container): self, data_streams: tuple[DataStream, ...] ) -> Iterator[Packet[DataStream]]: ... @overload - def demux(self, *, data: Any) -> Iterator[Packet[DataStream]]: ... + def demux(self, *, data: _Indices) -> Iterator[Packet[DataStream]]: ... @overload def demux( self, attachment_stream: AttachmentStream @@ -61,7 +62,9 @@ class InputContainer(Container): self, attachment_streams: tuple[AttachmentStream, ...] ) -> Iterator[Packet[AttachmentStream]]: ... @overload - def demux(self, *args: Any, **kwargs: Any) -> Iterator[Packet[Stream]]: ... + def demux( + self, *args: _StreamSelector, **kwargs: _Indices + ) -> Iterator[Packet[Stream]]: ... @overload def decode(self, video: int) -> Iterator[VideoFrame]: ... @overload @@ -76,7 +79,7 @@ class InputContainer(Container): def decode(self, *args: SubtitleStream) -> Iterator[SubtitleSet]: ... @overload def decode( - self, *args: Any, **kwargs: Any + self, *args: _StreamSelector, **kwargs: _Indices ) -> Iterator[VideoFrame | AudioFrame | SubtitleSet]: ... def seek( self, diff --git a/av/container/streams.pyi b/av/container/streams.pyi index f401a1cf3..7cf351a26 100644 --- a/av/container/streams.pyi +++ b/av/container/streams.pyi @@ -6,6 +6,11 @@ from av.stream import AttachmentStream, DataStream, Stream from av.subtitles.stream import SubtitleStream from av.video.stream import VideoStream +type _Indices = int | tuple[int, ...] | list[int] +type _StreamSelector = ( + int | Stream | tuple[int | Stream, ...] | list[int | Stream] | dict[str, _Indices] +) + @final class StreamContainer: video: tuple[VideoStream, ...] @@ -21,11 +26,7 @@ class StreamContainer: def __getitem__(self, index: int, /) -> Stream: ... @overload def __getitem__(self, index: slice, /) -> list[Stream]: ... - def get( - self, - *args: int | Stream | dict[str, int | tuple[int, ...]], - **kwargs: int | tuple[int, ...], - ) -> list[Stream]: ... + def get(self, *args: _StreamSelector, **kwargs: _Indices) -> list[Stream]: ... def best( self, type: Literal["video", "audio", "subtitle", "data", "attachment"], diff --git a/av/device.pyi b/av/device.pyi index c336a1f8e..ecce39fc0 100644 --- a/av/device.pyi +++ b/av/device.pyi @@ -5,11 +5,7 @@ class DeviceInfo: media_types: list[str] def __init__( - self, - name: str, - description: str, - is_default: bool, - media_types: list[str], + self, name: str, description: str, is_default: bool, media_types: list[str] ) -> None: ... def __repr__(self) -> str: ... diff --git a/av/dictionary.pyi b/av/dictionary.pyi index 33e7bc851..47e4a4dd8 100644 --- a/av/dictionary.pyi +++ b/av/dictionary.pyi @@ -16,8 +16,5 @@ class Dictionary: def copy(self) -> Dictionary: ... def pop(self, key: str) -> str: ... def update( - self, - other: Mapping[str, str] | Iterable[tuple[str, str]] = (), - /, - **kwds: str, + self, other: Mapping[str, str] | Iterable[tuple[str, str]] = (), /, **kwds: str ) -> None: ... diff --git a/av/filter/graph.pyi b/av/filter/graph.pyi index f4df107d3..70056cb59 100644 --- a/av/filter/graph.pyi +++ b/av/filter/graph.pyi @@ -1,5 +1,5 @@ from fractions import Fraction -from typing import Any, Self, final +from typing import Self, final from av.audio.format import AudioFormat from av.audio.frame import AudioFrame @@ -25,7 +25,7 @@ class Graph: def configure(self, auto_buffer: bool = True, force: bool = False) -> None: ... def link_nodes(self, *nodes: FilterContext) -> Graph: ... def add( - self, filter: str | Filter, args: Any = None, **kwargs: str + self, filter: str | Filter, args: str | None = None, **kwargs: str ) -> FilterContext: ... def add_buffer( self, diff --git a/av/rational.pyi b/av/rational.pyi index a264baee7..ab3764ac7 100644 --- a/av/rational.pyi +++ b/av/rational.pyi @@ -1,6 +1,9 @@ +from decimal import Decimal from fractions import Fraction from numbers import Rational -from typing import Any, final, overload +from typing import final, overload + +_Comparable = AVRational | Rational | Decimal | float @final class AVRational: @@ -14,28 +17,48 @@ class AVRational: def __bool__(self) -> bool: ... def __float__(self) -> float: ... def __hash__(self) -> int: ... - def __eq__(self, other: Any, /) -> bool: ... - def __lt__(self, other: Any, /) -> bool: ... - def __le__(self, other: Any, /) -> bool: ... - def __gt__(self, other: Any, /) -> bool: ... - def __ge__(self, other: Any, /) -> bool: ... + def __eq__(self, other: object, /) -> bool: ... + def __lt__(self, other: _Comparable, /) -> bool: ... + def __le__(self, other: _Comparable, /) -> bool: ... + def __gt__(self, other: _Comparable, /) -> bool: ... + def __ge__(self, other: _Comparable, /) -> bool: ... def __neg__(self) -> AVRational: ... - def __mul__(self, other: Any, /) -> AVRational | Fraction | float: ... + @overload + def __mul__(self, other: AVRational, /) -> AVRational: ... + @overload + def __mul__(self, other: int | Fraction, /) -> Fraction: ... + @overload + def __mul__(self, other: float, /) -> float: ... @overload def __rmul__(self, other: int | Fraction, /) -> Fraction: ... @overload def __rmul__(self, other: float, /) -> float: ... - def __truediv__(self, other: Any, /) -> AVRational | Fraction | float: ... + @overload + def __truediv__(self, other: AVRational, /) -> AVRational: ... + @overload + def __truediv__(self, other: int | Fraction, /) -> Fraction: ... + @overload + def __truediv__(self, other: float, /) -> float: ... @overload def __rtruediv__(self, other: int | Fraction, /) -> Fraction: ... @overload def __rtruediv__(self, other: float, /) -> float: ... - def __add__(self, other: Any, /) -> AVRational | Fraction | float: ... + @overload + def __add__(self, other: AVRational, /) -> AVRational: ... + @overload + def __add__(self, other: int | Fraction, /) -> Fraction: ... + @overload + def __add__(self, other: float, /) -> float: ... @overload def __radd__(self, other: int | Fraction, /) -> Fraction: ... @overload def __radd__(self, other: float, /) -> float: ... - def __sub__(self, other: Any, /) -> AVRational | Fraction | float: ... + @overload + def __sub__(self, other: AVRational, /) -> AVRational: ... + @overload + def __sub__(self, other: int | Fraction, /) -> Fraction: ... + @overload + def __sub__(self, other: float, /) -> float: ... @overload def __rsub__(self, other: int | Fraction, /) -> Fraction: ... @overload diff --git a/av/sidedata/encparams.pyi b/av/sidedata/encparams.pyi index eb9670758..40571e384 100644 --- a/av/sidedata/encparams.pyi +++ b/av/sidedata/encparams.pyi @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import Any, Self, final +from typing import Self, final import numpy as np @@ -20,7 +20,7 @@ class VideoEncParams(SideData): qp: int delta_qp: int def block_params(self, idx: int) -> VideoBlockParams: ... - def qp_map(self) -> np.ndarray[Any, Any]: ... + def qp_map(self) -> np.ndarray[tuple[int, int], np.dtype[np.int32]]: ... @final class VideoBlockParams: diff --git a/av/sidedata/motionvectors.pyi b/av/sidedata/motionvectors.pyi index d4afa758d..584a15082 100644 --- a/av/sidedata/motionvectors.pyi +++ b/av/sidedata/motionvectors.pyi @@ -1,7 +1,8 @@ from collections.abc import Iterator -from typing import Any, Self, final +from typing import Self, final import numpy as np +import numpy.typing as npt from av.frame import Frame @@ -13,7 +14,7 @@ class MotionVectors(SideData): def __getitem__(self, index: int, /) -> MotionVector: ... def __iter__(self) -> Iterator[MotionVector]: ... def __len__(self) -> int: ... - def to_ndarray(self) -> np.ndarray[Any, Any]: ... + def to_ndarray(self) -> npt.NDArray[np.void]: ... @final class MotionVector: diff --git a/av/subtitles/subtitle.pyi b/av/subtitles/subtitle.pyi index a40d0c7fe..103eab94f 100644 --- a/av/subtitles/subtitle.pyi +++ b/av/subtitles/subtitle.pyi @@ -16,11 +16,7 @@ class SubtitleSet: @staticmethod def create( - text: bytes, - start: int, - end: int, - pts: int = 0, - subtitle_format: int = 1, + text: bytes, start: int, end: int, pts: int = 0, subtitle_format: int = 1 ) -> SubtitleSet: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[AssSubtitle | BitmapSubtitle]: ... diff --git a/av/video/frame.pyi b/av/video/frame.pyi index 50e75ef26..033cd5809 100644 --- a/av/video/frame.pyi +++ b/av/video/frame.pyi @@ -1,8 +1,9 @@ from enum import IntEnum from pathlib import Path -from typing import Any, Literal, Self, final, overload +from typing import Literal, Self, TypedDict, Unpack, final, overload import numpy as np +import numpy.typing as npt from PIL.Image import Image from av.frame import Frame @@ -12,14 +13,29 @@ from .plane import VideoPlane from .reformatter import ColorPrimaries, ColorTrc _SupportedNDarray = ( - np.ndarray[Any, np.dtype[np.uint8]] - | np.ndarray[Any, np.dtype[np.uint16]] - | np.ndarray[Any, np.dtype[np.float16]] - | np.ndarray[Any, np.dtype[np.float32]] + npt.NDArray[np.uint8] + | npt.NDArray[np.uint16] + | npt.NDArray[np.float16] + | npt.NDArray[np.float32] ) supported_np_pix_fmts: set[str] +class _ReformatKwargs(TypedDict, total=False): + width: int | None + height: int | None + src_colorspace: str | int | None + dst_colorspace: str | int | None + interpolation: int | str | None + src_color_range: int | str | None + dst_color_range: int | str | None + dst_color_trc: int | ColorTrc | None + dst_color_primaries: int | ColorPrimaries | None + threads: int | None + +class _ToNdarrayKwargs(_ReformatKwargs, total=False): + format: str | None + class PictureType(IntEnum): NONE = 0 I = 1 @@ -88,12 +104,12 @@ class VideoFrame(Frame): dst_color_primaries: int | ColorPrimaries | None = None, threads: int | None = None, ) -> VideoFrame: ... - def to_rgb(self, **kwargs: Any) -> VideoFrame: ... - def save(self, filepath: str | Path, **options: Any) -> None: ... + def to_rgb(self, **kwargs: Unpack[_ReformatKwargs]) -> VideoFrame: ... + def save(self, filepath: str | Path, **options: object) -> None: ... def set_image(self, img: Image) -> None: ... - def to_image(self, **kwargs: Any) -> Image: ... + def to_image(self, **kwargs: Unpack[_ReformatKwargs]) -> Image: ... def to_ndarray( - self, channel_last: bool = False, **kwargs: Any + self, channel_last: bool = False, **kwargs: Unpack[_ToNdarrayKwargs] ) -> _SupportedNDarray: ... @staticmethod def from_image(img: Image) -> VideoFrame: ... @@ -104,9 +120,7 @@ class VideoFrame(Frame): @overload @staticmethod def from_ndarray( - array: tuple[ - np.ndarray[Any, np.dtype[np.uint8]], np.ndarray[Any, np.dtype[np.uint8]] - ], + array: tuple[npt.NDArray[np.uint8], npt.NDArray[np.uint8]], format: Literal["pal8"], channel_last: bool = False, ) -> VideoFrame: ... diff --git a/tests/test_python_io.py b/tests/test_python_io.py index 842b67f13..d1d11f998 100644 --- a/tests/test_python_io.py +++ b/tests/test_python_io.py @@ -114,7 +114,7 @@ def read( ) -> None: wrapped = MethodLogger(fh) - with av.open(wrapped, "r") as container: + with av.open(wrapped, "r") as container: # type: ignore[call-overload] assert container.format.name == "mpegts" assert container.format.long_name == "MPEG-TS (MPEG-2 Transport Stream)" assert len(container.streams) == 1 @@ -130,7 +130,7 @@ def read( def write(fh: io.BufferedWriter | BytesIO) -> None: wrapped = MethodLogger(fh) - with av.open(wrapped, "w", "mp4") as container: + with av.open(wrapped, "w", "mp4") as container: # type: ignore[call-overload] write_rgb_rotate(container) # Check method calls. diff --git a/tests/test_rational.py b/tests/test_rational.py index d374bf476..cf8eb5f4c 100644 --- a/tests/test_rational.py +++ b/tests/test_rational.py @@ -142,7 +142,7 @@ def test_nonfinite_ordering() -> None: def test_nonfinite_ordering_is_consistent() -> None: - values = [ + values: list[AVRational | Fraction | float | Decimal] = [ AVRational(0, 0), AVRational(1, 0), AVRational(-1, 0), @@ -210,9 +210,9 @@ def test_unorderable_types() -> None: for value in ("x", None, 1j, object()): for r in (AVRational(1, 2), AVRational(1, 0), AVRational(0, 0)): with pytest.raises(TypeError): - r < value # noqa: B015 + r < value # type: ignore[operator] # noqa: B015 with pytest.raises(TypeError): - r >= value # noqa: B015 + r >= value # type: ignore[operator] # noqa: B015 def test_fraction_on_the_left_is_asymmetric() -> None: