From 2cbb4773b0bb2adeecbf02fc002ebc0333e9759c Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Thu, 23 Nov 2023 13:10:26 -0500 Subject: [PATCH] Move `open` to container --- av/__init__.pyi | 66 ++----------------- av/container/__init__.pyi | 62 ++++++++++++++++++ av/error.pyi | 15 +++++ av/error.pyx | 130 ++++++++++++++++++-------------------- tests/test_errors.py | 18 ++---- 5 files changed, 149 insertions(+), 142 deletions(-) create mode 100644 av/container/__init__.pyi create mode 100644 av/error.pyi diff --git a/av/__init__.pyi b/av/__init__.pyi index d6141d65f..85a4b13f1 100644 --- a/av/__init__.pyi +++ b/av/__init__.pyi @@ -3,10 +3,7 @@ from numbers import Real from pathlib import Path from typing import Any, Iterator, Literal, overload -from av import logging - -class FFmpegError(Exception): - def __init__(self, code, message, filename=None, log=None): ... +from av import error, logging class Codec: name: str @@ -23,6 +20,7 @@ class CodecContext: pix_fmt: str | None sample_aspect_ratio: Fraction | None sample_rate: int | None + channels: int extradata_size: int is_open: Literal[0, 1] is_encoder: Literal[0, 1] @@ -58,6 +56,7 @@ class Stream: pix_fmt: str | None sample_aspect_ratio: Fraction | None sample_rate: int | None + channels: int extradata_size: int is_open: Literal[0, 1] is_encoder: Literal[0, 1] @@ -83,6 +82,7 @@ class StreamContainer: def __getitem__(self, index: int) -> Stream: ... @overload def __getitem__(self, index: slice) -> list[Stream]: ... + @overload def __getitem__(self, index: int | slice) -> Stream | list[Stream]: ... def get( self, @@ -110,6 +110,8 @@ class Container: open_timeout: Real | None read_timeout: Real | None + def __enter__(self) -> Container: ... + def __exit__(self, exc_type, exc_val, exc_tb): ... def err_check(self, value: int) -> int: ... def set_timeout(self, timeout: Real | None) -> None: ... def start_timeout(self) -> None: ... @@ -123,59 +125,3 @@ class InputContainer(Container): class OutputContainer(Container): def start_encoding(self) -> None: ... - -@overload -def open( - file: Any, - mode: Literal["r"] = None, - format: str | None = None, - options: dict[str, str] | None = None, - container_options: dict[str, str] | None = None, - stream_options: list[str] | None = None, - metadata_encoding: str = "utf-8", - metadata_errors: str = "strict", - buffer_size: int = 32768, - timeout=Real | None | tuple[Real | None, Real | None], - io_open=None, -) -> InputContainer: ... -@overload -def open( - file: str | Path, - mode: Literal["r"] | None = None, - format: str | None = None, - options: dict[str, str] | None = None, - container_options: dict[str, str] | None = None, - stream_options: list[str] | None = None, - metadata_encoding: str = "utf-8", - metadata_errors: str = "strict", - buffer_size: int = 32768, - timeout=Real | None | tuple[Real | None, Real | None], - io_open=None, -) -> InputContainer: ... -@overload -def open( - file: Any, - mode: Literal["w"], - format: str | None = None, - options: dict[str, str] | None = None, - container_options: dict[str, str] | None = None, - stream_options: list[str] | None = None, - metadata_encoding: str = "utf-8", - metadata_errors: str = "strict", - buffer_size: int = 32768, - timeout=Real | None | tuple[Real | None, Real | None], - io_open=None, -) -> OutputContainer: ... -def open( - file: Any, - mode: Literal["r", "w"] | None = None, - format: str | None = None, - options: dict[str, str] | None = None, - container_options: dict[str, str] | None = None, - stream_options: list[str] | None = None, - metadata_encoding: str = "utf-8", - metadata_errors: str = "strict", - buffer_size: int = 32768, - timeout=Real | None | tuple[Real | None, Real | None], - io_open=None, -) -> InputContainer | OutputContainer: ... diff --git a/av/container/__init__.pyi b/av/container/__init__.pyi new file mode 100644 index 000000000..fcae47156 --- /dev/null +++ b/av/container/__init__.pyi @@ -0,0 +1,62 @@ +from numbers import Real +from pathlib import Path +from typing import Any, Literal, overload + +from av import InputContainer, OutputContainer + +@overload +def open( + file: Any, + mode: Literal["r"], + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout=Real | None | tuple[Real | None, Real | None], + io_open=None, +) -> InputContainer: ... +@overload +def open( + file: str | Path, + mode: Literal["r"] | None = None, + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout=Real | None | tuple[Real | None, Real | None], + io_open=None, +) -> InputContainer: ... +@overload +def open( + file: Any, + mode: Literal["w"], + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout=Real | None | tuple[Real | None, Real | None], + io_open=None, +) -> OutputContainer: ... +@overload +def open( + file: Any, + mode: Literal["r", "w"] | None = None, + format: str | None = None, + options: dict[str, str] | None = None, + container_options: dict[str, str] | None = None, + stream_options: list[str] | None = None, + metadata_encoding: str = "utf-8", + metadata_errors: str = "strict", + buffer_size: int = 32768, + timeout=Real | None | tuple[Real | None, Real | None], + io_open=None, +) -> InputContainer | OutputContainer: ... diff --git a/av/error.pyi b/av/error.pyi new file mode 100644 index 000000000..739e52ec1 --- /dev/null +++ b/av/error.pyi @@ -0,0 +1,15 @@ +from av.logging import get_last_error + +classes: dict[int, Exception] + +def code_to_tag(code: int) -> bytes: ... +def tag_to_code(tag: bytes) -> int: ... +def make_error(res: int, filename=None, log=None): ... + +class FFmpegError(Exception): + def __init__(self, code: int, message, filename=None, log=None): ... + +class LookupError(FFmpegError): ... +class HTTPError(FFmpegError): ... +class HTTPClientError(FFmpegError): ... +class UndefinedError(FFmpegError): ... diff --git a/av/error.pyx b/av/error.pyx index 4e14d5a7d..d40de505d 100644 --- a/av/error.pyx +++ b/av/error.pyx @@ -12,8 +12,8 @@ from av.enum import define_enum # Will get extended with all of the exceptions. __all__ = [ - 'ErrorType', 'FFmpegError', 'LookupError', 'HTTPError', 'HTTPClientError', - 'UndefinedError', + "ErrorType", "FFmpegError", "LookupError", "HTTPError", "HTTPClientError", + "UndefinedError", ] @@ -40,12 +40,7 @@ cpdef tag_to_code(bytes tag): """ if len(tag) != 4: raise ValueError("Error tags are 4 bytes.") - return ( - (tag[0]) + - (tag[1] << 8) + - (tag[2] << 16) + - (tag[3] << 24) - ) + return tag[0] + (tag[1] << 8) + (tag[2] << 16) + (tag[3] << 24) class FFmpegError(Exception): @@ -82,20 +77,20 @@ class FFmpegError(Exception): pass def __str__(self): - msg = f'[Errno {self.errno}] {self.strerror}' + msg = f"[Errno {self.errno}] {self.strerror}" if self.filename: - msg = f'{msg}: {self.filename!r}' + msg = f"{msg}: {self.filename!r}" if self.log: - msg = f'{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}' + msg = f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}" return msg # Our custom error, used in callbacks. -cdef int c_PYAV_STASHED_ERROR = tag_to_code(b'PyAV') -cdef str PYAV_STASHED_ERROR_message = 'Error in PyAV callback' +cdef int c_PYAV_STASHED_ERROR = tag_to_code(b"PyAV") +cdef str PYAV_STASHED_ERROR_message = "Error in PyAV callback" # Bases for the FFmpeg-based exceptions. @@ -113,32 +108,32 @@ class HTTPClientError(FFmpegError): # Tuples of (enum_name, enum_value, exc_name, exc_base). _ffmpeg_specs = ( - ('BSF_NOT_FOUND', -lib.AVERROR_BSF_NOT_FOUND, 'BSFNotFoundError', LookupError), - ('BUG', -lib.AVERROR_BUG, None, RuntimeError), - ('BUFFER_TOO_SMALL', -lib.AVERROR_BUFFER_TOO_SMALL, None, ValueError), - ('DECODER_NOT_FOUND', -lib.AVERROR_DECODER_NOT_FOUND, None, LookupError), - ('DEMUXER_NOT_FOUND', -lib.AVERROR_DEMUXER_NOT_FOUND, None, LookupError), - ('ENCODER_NOT_FOUND', -lib.AVERROR_ENCODER_NOT_FOUND, None, LookupError), - ('EOF', -lib.AVERROR_EOF, 'EOFError', EOFError), - ('EXIT', -lib.AVERROR_EXIT, None, None), - ('EXTERNAL', -lib.AVERROR_EXTERNAL, None, None), - ('FILTER_NOT_FOUND', -lib.AVERROR_FILTER_NOT_FOUND, None, LookupError), - ('INVALIDDATA', -lib.AVERROR_INVALIDDATA, 'InvalidDataError', ValueError), - ('MUXER_NOT_FOUND', -lib.AVERROR_MUXER_NOT_FOUND, None, LookupError), - ('OPTION_NOT_FOUND', -lib.AVERROR_OPTION_NOT_FOUND, None, LookupError), - ('PATCHWELCOME', -lib.AVERROR_PATCHWELCOME, 'PatchWelcomeError', None), - ('PROTOCOL_NOT_FOUND', -lib.AVERROR_PROTOCOL_NOT_FOUND, None, LookupError), - ('UNKNOWN', -lib.AVERROR_UNKNOWN, None, None), - ('EXPERIMENTAL', -lib.AVERROR_EXPERIMENTAL, None, None), - ('INPUT_CHANGED', -lib.AVERROR_INPUT_CHANGED, None, None), - ('OUTPUT_CHANGED', -lib.AVERROR_OUTPUT_CHANGED, None, None), - ('HTTP_BAD_REQUEST', -lib.AVERROR_HTTP_BAD_REQUEST, 'HTTPBadRequestError', HTTPClientError), - ('HTTP_UNAUTHORIZED', -lib.AVERROR_HTTP_UNAUTHORIZED, 'HTTPUnauthorizedError', HTTPClientError), - ('HTTP_FORBIDDEN', -lib.AVERROR_HTTP_FORBIDDEN, 'HTTPForbiddenError', HTTPClientError), - ('HTTP_NOT_FOUND', -lib.AVERROR_HTTP_NOT_FOUND, 'HTTPNotFoundError', HTTPClientError), - ('HTTP_OTHER_4XX', -lib.AVERROR_HTTP_OTHER_4XX, 'HTTPOtherClientError', HTTPClientError), - ('HTTP_SERVER_ERROR', -lib.AVERROR_HTTP_SERVER_ERROR, 'HTTPServerError', HTTPError), - ('PYAV_CALLBACK', c_PYAV_STASHED_ERROR, 'PyAVCallbackError', RuntimeError), + ("BSF_NOT_FOUND", -lib.AVERROR_BSF_NOT_FOUND, "BSFNotFoundError", LookupError), + ("BUG", -lib.AVERROR_BUG, None, RuntimeError), + ("BUFFER_TOO_SMALL", -lib.AVERROR_BUFFER_TOO_SMALL, None, ValueError), + ("DECODER_NOT_FOUND", -lib.AVERROR_DECODER_NOT_FOUND, None, LookupError), + ("DEMUXER_NOT_FOUND", -lib.AVERROR_DEMUXER_NOT_FOUND, None, LookupError), + ("ENCODER_NOT_FOUND", -lib.AVERROR_ENCODER_NOT_FOUND, None, LookupError), + ("EOF", -lib.AVERROR_EOF, "EOFError", EOFError), + ("EXIT", -lib.AVERROR_EXIT, None, None), + ("EXTERNAL", -lib.AVERROR_EXTERNAL, None, None), + ("FILTER_NOT_FOUND", -lib.AVERROR_FILTER_NOT_FOUND, None, LookupError), + ("INVALIDDATA", -lib.AVERROR_INVALIDDATA, "InvalidDataError", ValueError), + ("MUXER_NOT_FOUND", -lib.AVERROR_MUXER_NOT_FOUND, None, LookupError), + ("OPTION_NOT_FOUND", -lib.AVERROR_OPTION_NOT_FOUND, None, LookupError), + ("PATCHWELCOME", -lib.AVERROR_PATCHWELCOME, "PatchWelcomeError", None), + ("PROTOCOL_NOT_FOUND", -lib.AVERROR_PROTOCOL_NOT_FOUND, None, LookupError), + ("UNKNOWN", -lib.AVERROR_UNKNOWN, None, None), + ("EXPERIMENTAL", -lib.AVERROR_EXPERIMENTAL, None, None), + ("INPUT_CHANGED", -lib.AVERROR_INPUT_CHANGED, None, None), + ("OUTPUT_CHANGED", -lib.AVERROR_OUTPUT_CHANGED, None, None), + ("HTTP_BAD_REQUEST", -lib.AVERROR_HTTP_BAD_REQUEST, "HTTPBadRequestError", HTTPClientError), + ("HTTP_UNAUTHORIZED", -lib.AVERROR_HTTP_UNAUTHORIZED, "HTTPUnauthorizedError", HTTPClientError), + ("HTTP_FORBIDDEN", -lib.AVERROR_HTTP_FORBIDDEN, "HTTPForbiddenError", HTTPClientError), + ("HTTP_NOT_FOUND", -lib.AVERROR_HTTP_NOT_FOUND, "HTTPNotFoundError", HTTPClientError), + ("HTTP_OTHER_4XX", -lib.AVERROR_HTTP_OTHER_4XX, "HTTPOtherClientError", HTTPClientError), + ("HTTP_SERVER_ERROR", -lib.AVERROR_HTTP_SERVER_ERROR, "HTTPServerError", HTTPError), + ("PYAV_CALLBACK", c_PYAV_STASHED_ERROR, "PyAVCallbackError", RuntimeError), ) @@ -161,7 +156,6 @@ ErrorType.tag = property(lambda self: code_to_tag(self.value)) for enum in ErrorType: - # Mimick the errno module. globals()[enum.name] = enum if enum.value == c_PYAV_STASHED_ERROR: @@ -193,7 +187,7 @@ classes = {} def _extend_builtin(name, codes): base = getattr(__builtins__, name, OSError) - cls = type(name, (FFmpegError, base), dict(__module__=__name__)) + cls = type(name, (FFmpegError, base), {__module__: __name__}) # Register in builder. for code in codes: @@ -207,35 +201,35 @@ def _extend_builtin(name, codes): # PEP 3151 builtins. -_extend_builtin('PermissionError', (errno.EACCES, errno.EPERM)) -_extend_builtin('BlockingIOError', (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK)) -_extend_builtin('ChildProcessError', (errno.ECHILD, )) -_extend_builtin('ConnectionAbortedError', (errno.ECONNABORTED, )) -_extend_builtin('ConnectionRefusedError', (errno.ECONNREFUSED, )) -_extend_builtin('ConnectionResetError', (errno.ECONNRESET, )) -_extend_builtin('FileExistsError', (errno.EEXIST, )) -_extend_builtin('InterruptedError', (errno.EINTR, )) -_extend_builtin('IsADirectoryError', (errno.EISDIR, )) -_extend_builtin('FileNotFoundError', (errno.ENOENT, )) -_extend_builtin('NotADirectoryError', (errno.ENOTDIR, )) -_extend_builtin('BrokenPipeError', (errno.EPIPE, errno.ESHUTDOWN)) -_extend_builtin('ProcessLookupError', (errno.ESRCH, )) -_extend_builtin('TimeoutError', (errno.ETIMEDOUT, )) +_extend_builtin("PermissionError", (errno.EACCES, errno.EPERM)) +_extend_builtin("BlockingIOError", (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK)) +_extend_builtin("ChildProcessError", (errno.ECHILD, )) +_extend_builtin("ConnectionAbortedError", (errno.ECONNABORTED, )) +_extend_builtin("ConnectionRefusedError", (errno.ECONNREFUSED, )) +_extend_builtin("ConnectionResetError", (errno.ECONNRESET, )) +_extend_builtin("FileExistsError", (errno.EEXIST, )) +_extend_builtin("InterruptedError", (errno.EINTR, )) +_extend_builtin("IsADirectoryError", (errno.EISDIR, )) +_extend_builtin("FileNotFoundError", (errno.ENOENT, )) +_extend_builtin("NotADirectoryError", (errno.ENOTDIR, )) +_extend_builtin("BrokenPipeError", (errno.EPIPE, errno.ESHUTDOWN)) +_extend_builtin("ProcessLookupError", (errno.ESRCH, )) +_extend_builtin("TimeoutError", (errno.ETIMEDOUT, )) # Other obvious ones. -_extend_builtin('ValueError', (errno.EINVAL, )) -_extend_builtin('MemoryError', (errno.ENOMEM, )) -_extend_builtin('NotImplementedError', (errno.ENOSYS, )) -_extend_builtin('OverflowError', (errno.ERANGE, )) +_extend_builtin("ValueError", (errno.EINVAL, )) +_extend_builtin("MemoryError", (errno.ENOMEM, )) +_extend_builtin("NotImplementedError", (errno.ENOSYS, )) +_extend_builtin("OverflowError", (errno.ERANGE, )) if IOError is not OSError: - _extend_builtin('IOError', (errno.EIO, )) + _extend_builtin("IOError", (errno.EIO, )) # The rest of them (for now) -_extend_builtin('OSError', [code for code in errno.errorcode if code not in classes]) +_extend_builtin("OSError", [code for code in errno.errorcode if code not in classes]) # Classes for the FFmpeg errors. for enum_name, code, name, base in _ffmpeg_specs: - name = name or enum_name.title().replace('_', '') + 'Error' + name = name or enum_name.title().replace("_", "") + "Error" if base is None: bases = (FFmpegError, ) @@ -259,12 +253,11 @@ cdef object _local = local() cdef int _err_count = 0 cdef int stash_exception(exc_info=None): - global _err_count - existing = getattr(_local, 'exc_info', None) + existing = getattr(_local, "exc_info", None) if existing is not None: - print >> sys.stderr, 'PyAV library exception being dropped:' + print >> sys.stderr, "PyAV library exception being dropped:" traceback.print_exception(*existing) _err_count -= 1 # Balance out the +=1 that is coming. @@ -286,7 +279,7 @@ cpdef int err_check(int res, filename=None) except -1: # Check for stashed exceptions. if _err_count: - exc_info = getattr(_local, 'exc_info', None) + exc_info = getattr(_local, "exc_info", None) if exc_info is not None: _err_count -= 1 _local.exc_info = None @@ -312,22 +305,19 @@ class UndefinedError(FFmpegError): cpdef make_error(int res, filename=None, log=None): - cdef int code = -res cdef bytes py_buffer cdef char *c_buffer if code == c_PYAV_STASHED_ERROR: message = PYAV_STASHED_ERROR_message - else: - # Jump through some hoops due to Python 2 in same codebase. py_buffer = b"\0" * lib.AV_ERROR_MAX_STRING_SIZE c_buffer = py_buffer lib.av_strerror(res, c_buffer, lib.AV_ERROR_MAX_STRING_SIZE) py_buffer = c_buffer - message = py_buffer.decode('latin1') + message = py_buffer.decode("latin1") # Default to the OS if we have no message; this should not get called. message = message or os.strerror(code) diff --git a/tests/test_errors.py b/tests/test_errors.py index 088838625..718003114 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -10,26 +10,20 @@ class TestErrorBasics(TestCase): def test_stringify(self): for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo") - self.assertEqual(str(e), "[Errno 1] foo") - self.assertEqual(repr(e), f"{cls.__name__}(1, 'foo')") + self.assertEqual(f"{e}", "[Errno 1] foo") + self.assertEqual(f"{e!r}", f"{cls.__name__}(1, 'foo')") self.assertEqual( traceback.format_exception_only(cls, e)[-1], - "{}{}: [Errno 1] foo\n".format( - "av.error.", - cls.__name__, - ), + f"av.error.{cls.__name__}: [Errno 1] foo\n", ) for cls in (av.ValueError, av.FileNotFoundError, av.DecoderNotFoundError): e = cls(1, "foo", "bar.txt") - self.assertEqual(str(e), "[Errno 1] foo: 'bar.txt'") - self.assertEqual(repr(e), f"{cls.__name__}(1, 'foo', 'bar.txt')") + self.assertEqual(f"{e}", "[Errno 1] foo: 'bar.txt'") + self.assertEqual(f"{e!r}", f"{cls.__name__}(1, 'foo', 'bar.txt')") self.assertEqual( traceback.format_exception_only(cls, e)[-1], - "{}{}: [Errno 1] foo: 'bar.txt'\n".format( - "av.error.", - cls.__name__, - ), + f"av.error.{cls.__name__}: [Errno 1] foo: 'bar.txt'\n", ) def test_bases(self):