1- cimport libav as lib
2- from libc.stdio cimport fprintf, stderr
3- from libc.stdlib cimport free, malloc
4-
5- from av.logging cimport get_last_error
6-
71import errno
82import os
93import sys
104import traceback
115from threading import local
126
7+ import cython
8+ from cython .cimports import libav as lib
9+ from cython .cimports .av .logging import get_last_error
10+ from cython .cimports .libc .stdio import fprintf , stderr
11+ from cython .cimports .libc .stdlib import free , malloc
12+
1313# Will get extended with all of the exceptions.
1414__all__ = [
15- " ErrorType" , " FFmpegError" , " LookupError" , " HTTPError" , " HTTPClientError" ,
15+ "ErrorType" ,
16+ "FFmpegError" ,
17+ "LookupError" ,
18+ "HTTPError" ,
19+ "HTTPClientError" ,
1620 "UndefinedError" ,
1721]
22+ sentinel = cython .declare (object , object ())
1823
1924
20- cpdef code_to_tag(int code):
25+ @cython .ccall
26+ def code_to_tag (code : cython .int ) -> bytes :
2127 """Convert an integer error code into 4-byte tag.
2228
2329 >>> code_to_tag(1953719668)
2430 b'test'
2531
2632 """
27- return bytes((
28- code & 0xff ,
29- (code >> 8 ) & 0xff ,
30- (code >> 16 ) & 0xff ,
31- (code >> 24 ) & 0xff ,
32- ))
33-
34- cpdef tag_to_code(bytes tag):
33+ return bytes (
34+ (
35+ code & 0xFF ,
36+ (code >> 8 ) & 0xFF ,
37+ (code >> 16 ) & 0xFF ,
38+ (code >> 24 ) & 0xFF ,
39+ )
40+ )
41+
42+
43+ @cython .ccall
44+ def tag_to_code (tag : bytes ) -> cython .int :
3545 """Convert a 4-byte error tag into an integer code.
3646
3747 >>> tag_to_code(b'test')
4050 """
4151 if len (tag ) != 4 :
4252 raise ValueError ("Error tags are 4 bytes." )
43- return (
44- (tag[0 ]) +
45- (tag[1 ] << 8 ) +
46- (tag[2 ] << 16 ) +
47- (tag[3 ] << 24 )
48- )
53+ return (tag [0 ]) + (tag [1 ] << 8 ) + (tag [2 ] << 16 ) + (tag [3 ] << 24 )
4954
5055
5156class FFmpegError (Exception ):
@@ -78,8 +83,8 @@ def __init__(self, code, message, filename=None, log=None):
7883 args .append (filename )
7984 if log :
8085 args .append (log )
81- super (FFmpegError, self ).__init__(* args)
82- self .args = tuple (args) # FileNotFoundError/etc. only pulls 2 args.
86+ super ().__init__ (* args )
87+ self .args = tuple (args )
8388
8489 @property
8590 def filename (self ):
@@ -104,14 +109,16 @@ def __str__(self):
104109 if self .filename :
105110 msg = f"{ msg } : { self .filename !r} "
106111 if self .log :
107- msg = f" {msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}"
112+ msg = (
113+ f"{ msg } ; last error log: [{ self .log [1 ].strip ()} ] { self .log [2 ].strip ()} "
114+ )
108115
109116 return msg
110117
111118
112119# Our custom error, used in callbacks.
113- cdef int c_PYAV_STASHED_ERROR = tag_to_code(b" PyAV" )
114- cdef str PYAV_STASHED_ERROR_message = " Error in PyAV callback"
120+ c_PYAV_STASHED_ERROR : cython . int = tag_to_code (b"PyAV" )
121+ PYAV_STASHED_ERROR_message : str = "Error in PyAV callback"
115122
116123
117124# Bases for the FFmpeg-based exceptions.
@@ -128,6 +135,8 @@ class HTTPClientError(FFmpegError):
128135
129136
130137# Tuples of (enum_name, enum_value, exc_name, exc_base).
138+ # tuple[str, int, str | None, Exception | none]
139+ # fmt: off
131140_ffmpeg_specs = (
132141 ("BSF_NOT_FOUND" , - lib .AVERROR_BSF_NOT_FOUND , "BSFNotFoundError" , LookupError ),
133142 ("BUG" , - lib .AVERROR_BUG , None , RuntimeError ),
@@ -156,8 +165,7 @@ class HTTPClientError(FFmpegError):
156165 ("HTTP_SERVER_ERROR" , - lib .AVERROR_HTTP_SERVER_ERROR , "HTTPServerError" , HTTPError ),
157166 ("PYAV_CALLBACK" , c_PYAV_STASHED_ERROR , "PyAVCallbackError" , RuntimeError ),
158167)
159-
160- cdef sentinel = object ()
168+ # fmt: on
161169
162170
163171class EnumType (type ):
@@ -189,59 +197,24 @@ def __len__(self):
189197 def __iter__ (self ):
190198 return iter (self ._all )
191199
192- def __getitem__ (self , key ):
193- if isinstance (key, str ):
194- return self ._by_name[key]
195200
196- if isinstance (key, int ):
197- try :
198- return self ._by_value[key]
199- except KeyError :
200- pass
201-
202- raise KeyError (key)
203-
204- if isinstance (key, self ):
205- return key
206-
207- raise TypeError (f" {self.__name__} indices must be str, int, or itself" )
208-
209- def _get (self , long value , bint create = False ):
210- try :
211- return self ._by_value[value]
212- except KeyError :
213- pass
214-
215- if not create:
216- return
217-
218- return self ._create(f" {self.__name__.upper()}_{value}" , value, by_value_only = True )
219-
220- def get (self , key , default = None , create = False ):
221- try :
222- return self [key]
223- except KeyError :
224- if create:
225- return self ._get(key, create = True )
226- return default
227-
228-
229- cdef class EnumItem:
201+ @cython .cclass
202+ class EnumItem :
230203 """An enumeration of FFmpeg's error types.
231204
232- .. attribute:: tag
205+ .. attribute:: tag
233206
234- The FFmpeg byte tag for the error.
207+ The FFmpeg byte tag for the error.
235208
236- .. attribute:: strerror
209+ .. attribute:: strerror
237210
238- The error message that would be returned.
211+ The error message that would be returned.
212+ """
239213
240- """
241- cdef readonly str name
242- cdef readonly int value
214+ name = cython .declare (str , visibility = "readonly" )
215+ value = cython .declare (cython .int , visibility = "readonly" )
243216
244- def __cinit__ (self , sentinel_ , str name , int value , doc = None ):
217+ def __cinit__ (self , sentinel_ , name : str , value : cython . int , doc = None ):
245218 if sentinel_ is not sentinel :
246219 raise RuntimeError (f"Cannot instantiate { self .__class__ .__name__ } ." )
247220
@@ -263,42 +236,25 @@ def tag(self):
263236 return code_to_tag (self .value )
264237
265238
266- ErrorType = EnumType(" ErrorType" , (EnumItem, ), {" __module__" : __name__ }, [x[:2 ] for x in _ffmpeg_specs])
239+ ErrorType = EnumType (
240+ "ErrorType" , (EnumItem ,), {"__module__" : __name__ }, [x [:2 ] for x in _ffmpeg_specs ]
241+ )
267242
268243
269244for enum in ErrorType :
270- # Mimic the errno module.
245+ # Mimick the errno module.
271246 globals ()[enum .name ] = enum
272247 if enum .value == c_PYAV_STASHED_ERROR :
273248 enum .strerror = PYAV_STASHED_ERROR_message
274249 else :
275250 enum .strerror = lib .av_err2str (- enum .value )
276251
277-
278- # Mimic the builtin exception types.
279- # See https://www.python.org/dev/peps/pep-3151/#new-exception-classes
280- # Use the named ones we have, otherwise default to OSError for anything in errno.
281-
282- # See this command for the count of POSIX codes used:
283- #
284- # egrep -IR 'AVERROR\(E[A-Z]+\)' vendor/ffmpeg-4.2 |\
285- # sed -E 's/.*AVERROR\((E[A-Z]+)\).*/\1/' | \
286- # sort | uniq -c
287- #
288- # The biggest ones that don't map to PEP 3151 builtins:
289- #
290- # 2106 EINVAL -> ValueError
291- # 649 EIO -> IOError (if it is distinct from OSError)
292- # 4080 ENOMEM -> MemoryError
293- # 340 ENOSYS -> NotImplementedError
294- # 35 ERANGE -> OverflowError
295-
296- classes = {}
252+ classes : dict = {}
297253
298254
299255def _extend_builtin (name , codes ):
300256 base = getattr (__builtins__ , name , OSError )
301- cls = type (name, (FFmpegError, base), dict ( __module__ = __name__ ) )
257+ cls = type (name , (FFmpegError , base ), { " __module__" : __name__ } )
302258
303259 # Register in builder.
304260 for code in codes :
@@ -311,30 +267,56 @@ def _extend_builtin(name, codes):
311267 return cls
312268
313269
314- # PEP 3151 builtins.
315270_extend_builtin ("PermissionError" , (errno .EACCES , errno .EPERM ))
316- _extend_builtin(" BlockingIOError" , (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK))
317- _extend_builtin(" ChildProcessError" , (errno.ECHILD, ))
318- _extend_builtin(" ConnectionAbortedError" , (errno.ECONNABORTED, ))
319- _extend_builtin(" ConnectionRefusedError" , (errno.ECONNREFUSED, ))
320- _extend_builtin(" ConnectionResetError" , (errno.ECONNRESET, ))
321- _extend_builtin(" FileExistsError" , (errno.EEXIST, ))
322- _extend_builtin(" InterruptedError" , (errno.EINTR, ))
323- _extend_builtin(" IsADirectoryError" , (errno.EISDIR, ))
324- _extend_builtin(" FileNotFoundError" , (errno.ENOENT, ))
325- _extend_builtin(" NotADirectoryError" , (errno.ENOTDIR, ))
271+ _extend_builtin (
272+ "BlockingIOError" ,
273+ (errno .EAGAIN , errno .EALREADY , errno .EINPROGRESS , errno .EWOULDBLOCK ),
274+ )
275+ _extend_builtin ("ChildProcessError" , (errno .ECHILD ,))
276+ _extend_builtin ("ConnectionAbortedError" , (errno .ECONNABORTED ,))
277+ _extend_builtin ("ConnectionRefusedError" , (errno .ECONNREFUSED ,))
278+ _extend_builtin ("ConnectionResetError" , (errno .ECONNRESET ,))
279+ _extend_builtin ("FileExistsError" , (errno .EEXIST ,))
280+ _extend_builtin ("InterruptedError" , (errno .EINTR ,))
281+ _extend_builtin ("IsADirectoryError" , (errno .EISDIR ,))
282+ _extend_builtin ("FileNotFoundError" , (errno .ENOENT ,))
283+ _extend_builtin ("NotADirectoryError" , (errno .ENOTDIR ,))
326284_extend_builtin ("BrokenPipeError" , (errno .EPIPE , errno .ESHUTDOWN ))
327- _extend_builtin(" ProcessLookupError" , (errno.ESRCH, ))
328- _extend_builtin(" TimeoutError" , (errno.ETIMEDOUT, ))
285+ _extend_builtin ("ProcessLookupError" , (errno .ESRCH ,))
286+ _extend_builtin ("TimeoutError" , (errno .ETIMEDOUT ,))
287+ _extend_builtin ("MemoryError" , (errno .ENOMEM ,))
288+ _extend_builtin ("NotImplementedError" , (errno .ENOSYS ,))
289+ _extend_builtin ("OverflowError" , (errno .ERANGE ,))
290+ _extend_builtin ("OSError" , [code for code in errno .errorcode if code not in classes ])
329291
330- # Other obvious ones.
331- _extend_builtin(" ValueError" , (errno.EINVAL, ))
332- _extend_builtin(" MemoryError" , (errno.ENOMEM, ))
333- _extend_builtin(" NotImplementedError" , (errno.ENOSYS, ))
334- _extend_builtin(" OverflowError" , (errno.ERANGE, ))
335292
336- # The rest of them (for now)
337- _extend_builtin(" OSError" , [code for code in errno.errorcode if code not in classes])
293+ class ArgumentError (FFmpegError ):
294+ def __str__ (self ):
295+ msg = ""
296+ if self .strerror is not None :
297+ msg = f"{ msg } { self .strerror } "
298+ if self .filename :
299+ msg = f"{ msg } : { self .filename !r} "
300+ if self .errno is not None :
301+ msg = f"{ msg } returned { self .errno } "
302+ if self .log :
303+ msg = (
304+ f"{ msg } ; last error log: [{ self .log [1 ].strip ()} ] { self .log [2 ].strip ()} "
305+ )
306+
307+ return msg
308+
309+
310+ class UndefinedError (FFmpegError ):
311+ """Fallback exception type in case FFmpeg returns an error we don't know about."""
312+
313+ pass
314+
315+
316+ classes [errno .EINVAL ] = ArgumentError
317+ globals ()["ArgumentError" ] = ArgumentError
318+ __all__ .append ("ArgumentError" )
319+
338320
339321# Classes for the FFmpeg errors.
340322for enum_name , code , name , base in _ffmpeg_specs :
@@ -360,10 +342,12 @@ def _extend_builtin(name, codes):
360342
361343
362344# Storage for stashing.
363- cdef object _local = local()
364- cdef int _err_count = 0
345+ _local : object = local ()
346+ _err_count : cython .int = 0
347+
365348
366- cdef int stash_exception(exc_info = None ):
349+ @cython .cfunc
350+ def stash_exception (exc_info = None ) -> cython .int :
367351 global _err_count
368352
369353 existing = getattr (_local , "exc_info" , None )
@@ -380,9 +364,12 @@ def _extend_builtin(name, codes):
380364 return - c_PYAV_STASHED_ERROR
381365
382366
383- cdef int _last_log_count = 0
367+ _last_log_count : cython . int = 0
384368
385- cpdef int err_check(int res, filename = None ) except - 1 :
369+
370+ @cython .ccall
371+ @cython .exceptval (- 1 , check = False )
372+ def err_check (res : cython .int , filename = None ) -> cython .int :
386373 """Raise appropriate exceptions from library return code."""
387374
388375 global _err_count
@@ -394,7 +381,7 @@ def _extend_builtin(name, codes):
394381 if exc_info is not None :
395382 _err_count -= 1
396383 _local .exc_info = None
397- raise exc_info[0 ], exc_info[ 1 ], exc_info[2 ]
384+ raise exc_info [1 ]. with_traceback ( exc_info [2 ])
398385
399386 if res >= 0 :
400387 return res
@@ -407,9 +394,11 @@ def _extend_builtin(name, codes):
407394 else :
408395 log = None
409396
410- cdef int code = - res
411- cdef char * error_buffer = < char * > malloc(lib.AV_ERROR_MAX_STRING_SIZE * sizeof(char ))
412- if error_buffer == NULL :
397+ code : cython .int = - res
398+ error_buffer : cython .p_char = cython .cast (
399+ cython .p_char , malloc (lib .AV_ERROR_MAX_STRING_SIZE * cython .sizeof (char ))
400+ )
401+ if error_buffer == cython .NULL :
413402 raise MemoryError ()
414403
415404 try :
@@ -424,8 +413,3 @@ def _extend_builtin(name, codes):
424413 raise cls (code , message , filename , log )
425414 finally :
426415 free (error_buffer )
427-
428-
429- class UndefinedError (FFmpegError ):
430- """ Fallback exception type in case FFmpeg returns an error we don't know about."""
431- pass
0 commit comments