| | cimport libav as lib |
| | from libc.stdio cimport fprintf, stderr |
| | from libc.stdlib cimport free, malloc |
| |
|
| | from av.logging cimport get_last_error |
| |
|
| | import errno |
| | import os |
| | import sys |
| | import traceback |
| | from threading import local |
| |
|
| | |
| | __all__ = [ |
| | "ErrorType", "FFmpegError", "LookupError", "HTTPError", "HTTPClientError", |
| | "UndefinedError", |
| | ] |
| |
|
| |
|
| | cpdef code_to_tag(int code): |
| | """Convert an integer error code into 4-byte tag. |
| | |
| | >>> code_to_tag(1953719668) |
| | b'test' |
| | |
| | """ |
| | return bytes(( |
| | code & 0xff, |
| | (code >> 8) & 0xff, |
| | (code >> 16) & 0xff, |
| | (code >> 24) & 0xff, |
| | )) |
| |
|
| | cpdef tag_to_code(bytes tag): |
| | """Convert a 4-byte error tag into an integer code. |
| | |
| | >>> tag_to_code(b'test') |
| | 1953719668 |
| | |
| | """ |
| | if len(tag) != 4: |
| | raise ValueError("Error tags are 4 bytes.") |
| | return ( |
| | (tag[0]) + |
| | (tag[1] << 8) + |
| | (tag[2] << 16) + |
| | (tag[3] << 24) |
| | ) |
| |
|
| |
|
| | class FFmpegError(Exception): |
| | """Exception class for errors from within FFmpeg. |
| | |
| | .. attribute:: errno |
| | |
| | FFmpeg's integer error code. |
| | |
| | .. attribute:: strerror |
| | |
| | FFmpeg's error message. |
| | |
| | .. attribute:: filename |
| | |
| | The filename that was being operated on (if available). |
| | |
| | .. attribute:: log |
| | |
| | The tuple from :func:`av.logging.get_last_log`, or ``None``. |
| | |
| | """ |
| |
|
| | def __init__(self, code, message, filename=None, log=None): |
| | self.errno = code |
| | self.strerror = message |
| |
|
| | args = [code, message] |
| | if filename or log: |
| | args.append(filename) |
| | if log: |
| | args.append(log) |
| | super(FFmpegError, self).__init__(*args) |
| | self.args = tuple(args) |
| |
|
| | @property |
| | def filename(self): |
| | try: |
| | return self.args[2] |
| | except IndexError: |
| | pass |
| |
|
| | @property |
| | def log(self): |
| | try: |
| | return self.args[3] |
| | except IndexError: |
| | pass |
| |
|
| | def __str__(self): |
| | msg = "" |
| | if self.errno is not None: |
| | msg = f"{msg}[Errno {self.errno}] " |
| | if self.strerror is not None: |
| | msg = f"{msg}{self.strerror}" |
| | if self.filename: |
| | msg = f"{msg}: {self.filename!r}" |
| | if self.log: |
| | msg = f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}" |
| |
|
| | return msg |
| |
|
| |
|
| | |
| | cdef int c_PYAV_STASHED_ERROR = tag_to_code(b"PyAV") |
| | cdef str PYAV_STASHED_ERROR_message = "Error in PyAV callback" |
| |
|
| |
|
| | |
| | class LookupError(FFmpegError, LookupError): |
| | pass |
| |
|
| |
|
| | class HTTPError(FFmpegError): |
| | pass |
| |
|
| |
|
| | class HTTPClientError(FFmpegError): |
| | pass |
| |
|
| |
|
| | |
| | _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), |
| | ) |
| |
|
| | cdef sentinel = object() |
| |
|
| |
|
| | class EnumType(type): |
| | def __new__(mcl, name, bases, attrs, *args): |
| | |
| | return super().__new__(mcl, name, bases, attrs) |
| |
|
| | def __init__(self, name, bases, attrs, items): |
| | self._by_name = {} |
| | self._by_value = {} |
| | self._all = [] |
| |
|
| | for spec in items: |
| | self._create(*spec) |
| |
|
| | def _create(self, name, value, doc=None, by_value_only=False): |
| | |
| | try: |
| | item = self._by_value[value] |
| | except KeyError: |
| | item = self(sentinel, name, value, doc) |
| | self._by_value[value] = item |
| |
|
| | return item |
| |
|
| | def __len__(self): |
| | return len(self._all) |
| |
|
| | def __iter__(self): |
| | return iter(self._all) |
| |
|
| | def __getitem__(self, key): |
| | if isinstance(key, str): |
| | return self._by_name[key] |
| |
|
| | if isinstance(key, int): |
| | try: |
| | return self._by_value[key] |
| | except KeyError: |
| | pass |
| |
|
| | raise KeyError(key) |
| |
|
| | if isinstance(key, self): |
| | return key |
| |
|
| | raise TypeError(f"{self.__name__} indices must be str, int, or itself") |
| |
|
| | def _get(self, long value, bint create=False): |
| | try: |
| | return self._by_value[value] |
| | except KeyError: |
| | pass |
| |
|
| | if not create: |
| | return |
| |
|
| | return self._create(f"{self.__name__.upper()}_{value}", value, by_value_only=True) |
| |
|
| | def get(self, key, default=None, create=False): |
| | try: |
| | return self[key] |
| | except KeyError: |
| | if create: |
| | return self._get(key, create=True) |
| | return default |
| |
|
| |
|
| | cdef class EnumItem: |
| | """An enumeration of FFmpeg's error types. |
| | |
| | .. attribute:: tag |
| | |
| | The FFmpeg byte tag for the error. |
| | |
| | .. attribute:: strerror |
| | |
| | The error message that would be returned. |
| | |
| | """ |
| | cdef readonly str name |
| | cdef readonly int value |
| |
|
| | def __cinit__(self, sentinel_, str name, int value, doc=None): |
| | if sentinel_ is not sentinel: |
| | raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.") |
| |
|
| | self.name = name |
| | self.value = value |
| | self.__doc__ = doc |
| |
|
| | def __repr__(self): |
| | return f"<{self.__class__.__module__}.{self.__class__.__name__}:{self.name}(0x{self.value:x})>" |
| |
|
| | def __str__(self): |
| | return self.name |
| |
|
| | def __int__(self): |
| | return self.value |
| |
|
| | @property |
| | def tag(self): |
| | return code_to_tag(self.value) |
| |
|
| |
|
| | ErrorType = EnumType("ErrorType", (EnumItem, ), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs]) |
| |
|
| |
|
| | for enum in ErrorType: |
| | |
| | globals()[enum.name] = enum |
| | if enum.value == c_PYAV_STASHED_ERROR: |
| | enum.strerror = PYAV_STASHED_ERROR_message |
| | else: |
| | enum.strerror = lib.av_err2str(-enum.value) |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | classes = {} |
| |
|
| |
|
| | def _extend_builtin(name, codes): |
| | base = getattr(__builtins__, name, OSError) |
| | cls = type(name, (FFmpegError, base), dict(__module__=__name__)) |
| |
|
| | |
| | for code in codes: |
| | classes[code] = cls |
| |
|
| | |
| | globals()[name] = cls |
| | __all__.append(name) |
| |
|
| | return cls |
| |
|
| |
|
| | |
| | _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("ValueError", (errno.EINVAL, )) |
| | _extend_builtin("MemoryError", (errno.ENOMEM, )) |
| | _extend_builtin("NotImplementedError", (errno.ENOSYS, )) |
| | _extend_builtin("OverflowError", (errno.ERANGE, )) |
| |
|
| | |
| | _extend_builtin("OSError", [code for code in errno.errorcode if code not in classes]) |
| |
|
| | |
| | for enum_name, code, name, base in _ffmpeg_specs: |
| | name = name or enum_name.title().replace("_", "") + "Error" |
| |
|
| | if base is None: |
| | bases = (FFmpegError,) |
| | elif issubclass(base, FFmpegError): |
| | bases = (base,) |
| | else: |
| | bases = (FFmpegError, base) |
| |
|
| | cls = type(name, bases, {"__module__": __name__}) |
| |
|
| | |
| | classes[code] = cls |
| |
|
| | |
| | globals()[name] = cls |
| | __all__.append(name) |
| |
|
| | del _ffmpeg_specs |
| |
|
| |
|
| | |
| | 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) |
| | if existing is not None: |
| | fprintf(stderr, "PyAV library exception being dropped:\n") |
| | traceback.print_exception(*existing) |
| | _err_count -= 1 |
| |
|
| | exc_info = exc_info or sys.exc_info() |
| | _local.exc_info = exc_info |
| | if exc_info: |
| | _err_count += 1 |
| |
|
| | return -c_PYAV_STASHED_ERROR |
| |
|
| |
|
| | cdef int _last_log_count = 0 |
| |
|
| | cpdef int err_check(int res, filename=None) except -1: |
| | """Raise appropriate exceptions from library return code.""" |
| |
|
| | global _err_count |
| | global _last_log_count |
| |
|
| | |
| | if _err_count: |
| | exc_info = getattr(_local, "exc_info", None) |
| | if exc_info is not None: |
| | _err_count -= 1 |
| | _local.exc_info = None |
| | raise exc_info[0], exc_info[1], exc_info[2] |
| |
|
| | if res >= 0: |
| | return res |
| |
|
| | |
| | log_count, last_log = get_last_error() |
| | if log_count > _last_log_count: |
| | _last_log_count = log_count |
| | log = last_log |
| | else: |
| | log = None |
| |
|
| | cdef int code = -res |
| | cdef char* error_buffer = <char*>malloc(lib.AV_ERROR_MAX_STRING_SIZE * sizeof(char)) |
| | if error_buffer == NULL: |
| | raise MemoryError() |
| |
|
| | try: |
| | if code == c_PYAV_STASHED_ERROR: |
| | message = PYAV_STASHED_ERROR_message |
| | else: |
| | lib.av_strerror(res, error_buffer, lib.AV_ERROR_MAX_STRING_SIZE) |
| | |
| | message = error_buffer or os.strerror(code) |
| |
|
| | cls = classes.get(code, UndefinedError) |
| | raise cls(code, message, filename, log) |
| | finally: |
| | free(error_buffer) |
| |
|
| |
|
| | class UndefinedError(FFmpegError): |
| | """Fallback exception type in case FFmpeg returns an error we don't know about.""" |
| | pass |
| |
|