Skip to content

Commit 6e19add

Browse files
committed
Make av/logging pure
1 parent 714be34 commit 6e19add

1 file changed

Lines changed: 99 additions & 53 deletions

File tree

av/logging.pyx renamed to av/logging.py

Lines changed: 99 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# type: ignore
12
"""
23
FFmpeg has a logging system that it uses extensively. It's very noisy, so PyAV turns it
34
off by default. This unfortunately has the effect of making raised errors have less
@@ -38,9 +39,10 @@
3839
3940
"""
4041

41-
cimport libav as lib
42-
from libc.stdio cimport fprintf, stderr
43-
from libc.stdlib cimport free, malloc
42+
import cython
43+
import cython.cimports.libav as lib
44+
from cython.cimports.libc.stdio import fprintf, stderr
45+
from cython.cimports.libc.stdlib import free, malloc
4446

4547
import logging
4648
import sys
@@ -60,9 +62,9 @@
6062
CRITICAL = FATAL
6163

6264

63-
cpdef adapt_level(int level):
65+
@cython.ccall
66+
def adapt_level(level: cython.int):
6467
"""Convert a library log level to a Python log level."""
65-
6668
if level <= lib.AV_LOG_FATAL: # Includes PANIC
6769
return 50 # logging.CRITICAL
6870
elif level <= lib.AV_LOG_ERROR:
@@ -79,7 +81,7 @@
7981
return 1
8082

8183

82-
cdef object level_threshold = None
84+
level_threshold = cython.declare(object, None)
8385

8486
# ... but lets limit ourselves to WARNING (assuming nobody already did this).
8587
if "libav" not in logging.Logger.manager.loggerDict:
@@ -133,10 +135,10 @@ def restore_default_callback():
133135
lib.av_log_set_callback(lib.av_log_default_callback)
134136

135137

136-
cdef bint skip_repeated = True
137-
cdef skip_lock = Lock()
138-
cdef object last_log = None
139-
cdef int skip_count = 0
138+
skip_repeated = cython.declare(cython.bint, True)
139+
skip_lock = cython.declare(object, Lock())
140+
last_log = cython.declare(object, None)
141+
skip_count = cython.declare(cython.int, 0)
140142

141143

142144
def get_skip_repeated():
@@ -151,10 +153,12 @@ def set_skip_repeated(v):
151153

152154

153155
# For error reporting.
154-
cdef object last_error = None
155-
cdef int error_count = 0
156+
last_error = cython.declare(object, None)
157+
error_count = cython.declare(cython.int, 0)
158+
156159

157-
cpdef get_last_error():
160+
@cython.ccall
161+
def get_last_error():
158162
"""Get the last log that was at least ``ERROR``."""
159163
if error_count:
160164
with skip_lock:
@@ -163,10 +167,12 @@ def set_skip_repeated(v):
163167
return 0, None
164168

165169

166-
cdef global_captures = []
167-
cdef thread_captures = {}
170+
global_captures = cython.declare(list, [])
171+
thread_captures = cython.declare(dict, {})
172+
168173

169-
cdef class Capture:
174+
@cython.cclass
175+
class Capture:
170176
"""A context manager for capturing logs.
171177
172178
:param bool local: Should logs from all threads be captured, or just one
@@ -181,12 +187,11 @@ def set_skip_repeated(v):
181187
182188
"""
183189

184-
cdef readonly list logs
185-
cdef list captures
190+
logs: list
191+
captures: list
186192

187-
def __init__(self, bint local=True):
193+
def __init__(self, local: cython.bint = True):
188194
self.logs = []
189-
190195
if local:
191196
self.captures = thread_captures.setdefault(get_ident(), [])
192197
else:
@@ -200,53 +205,67 @@ def __exit__(self, type_, value, traceback):
200205
self.captures.pop(-1)
201206

202207

203-
cdef struct log_context:
204-
lib.AVClass *class_
205-
const char *name
208+
log_context = cython.struct(
209+
class_=cython.pointer[lib.AVClass],
210+
name=cython.p_char,
211+
)
212+
213+
item_name_func = cython.typedef(
214+
"const char *(*item_name_func)(void *) noexcept nogil"
215+
)
216+
206217

207-
cdef const char *log_context_name(void *ptr) noexcept nogil:
208-
cdef log_context *obj = <log_context*>ptr
218+
@cython.cfunc
219+
@cython.nogil
220+
@cython.exceptval(check=False)
221+
def log_context_name(ptr: cython.p_void) -> cython.p_char:
222+
obj: cython.pointer[log_context] = cython.cast(cython.pointer[log_context], ptr)
209223
return obj.name
210224

211-
cdef lib.AVClass log_class
212-
log_class.item_name = log_context_name
213225

214-
cpdef log(int level, str name, str message):
226+
log_class = cython.declare(lib.AVClass)
227+
log_class.item_name = cython.cast(item_name_func, log_context_name)
228+
229+
230+
@cython.ccall
231+
def log(level: cython.int, name: str, message: str):
215232
"""Send a log through the library logging system.
216233
217234
This is mostly for testing.
218-
219235
"""
220-
221-
cdef log_context *obj = <log_context*>malloc(sizeof(log_context))
222-
obj.class_ = &log_class
236+
obj: cython.pointer[log_context] = cython.cast(
237+
cython.pointer[log_context], malloc(cython.sizeof(log_context))
238+
)
239+
obj.class_ = cython.address(log_class)
223240
obj.name = name
224-
cdef bytes message_bytes = message.encode("utf-8")
241+
message_bytes: bytes = message.encode("utf-8")
225242

226-
lib.av_log(<void*>obj, level, "%s", <char*>message_bytes)
243+
lib.av_log(cython.cast(cython.p_void, obj), level, "%s", cython.cast(cython.p_char, message_bytes))
227244
free(obj)
228245

229246

230-
cdef log_callback_gil(int level, const char *c_name, const char *c_message):
247+
@cython.cfunc
248+
def log_callback_gil(
249+
level: cython.int, c_name: cython.p_const_char, c_message: cython.p_char
250+
):
231251
global error_count
232252
global skip_count
233253
global last_log
234254
global last_error
235255

236-
name = <str>c_name if c_name is not NULL else ""
237-
message = (<bytes>c_message).decode("utf8", "backslashreplace")
256+
name = cython.cast(str, c_name) if c_name is not cython.NULL else ""
257+
message = cython.cast(bytes, c_message).decode("utf8", "backslashreplace")
238258
log = (level, name, message)
239259

240260
# We have to filter it ourselves, but we will still process it in general so
241261
# it is available to our error handling.
242262
# Note that FFmpeg's levels are backwards from Python's.
243-
cdef bint is_interesting = level <= level_threshold
263+
is_interesting: cython.bint = level <= level_threshold
244264

245265
# Skip messages which are identical to the previous.
246266
# TODO: Be smarter about threads.
247-
cdef bint is_repeated = False
248-
249-
cdef object repeat_log = None
267+
is_repeated: cython.bint = False
268+
repeat_log: object = None
250269

251270
with skip_lock:
252271
if is_interesting:
@@ -263,7 +282,7 @@ def __exit__(self, type_, value, traceback):
263282
repeat_log = (
264283
last_log[0],
265284
last_log[1],
266-
"%s (repeated %d more times)" % (last_log[2], skip_count)
285+
"%s (repeated %d more times)" % (last_log[2], skip_count),
267286
)
268287
skip_count = 0
269288

@@ -281,7 +300,8 @@ def __exit__(self, type_, value, traceback):
281300
log_callback_emit(log)
282301

283302

284-
cdef log_callback_emit(log):
303+
@cython.cfunc
304+
def log_callback_emit(log):
285305
lib_level, name, message = log
286306

287307
captures = thread_captures.get(get_ident()) or global_captures
@@ -296,37 +316,63 @@ def __exit__(self, type_, value, traceback):
296316
logger.log(py_level, message.strip())
297317

298318

299-
cdef void log_callback(void *ptr, int level, const char *format, lib.va_list args) noexcept nogil:
300-
cdef bint inited = lib.Py_IsInitialized()
319+
@cython.cfunc
320+
@cython.nogil
321+
@cython.exceptval(check=False)
322+
def log_callback(
323+
ptr: cython.p_void,
324+
level: cython.int,
325+
format: cython.p_const_char,
326+
args: lib.va_list,
327+
) -> cython.void:
328+
inited: cython.bint = lib.Py_IsInitialized()
301329
if not inited:
302330
return
303331

304-
with gil:
332+
with cython.gil:
305333
if level > level_threshold and level != lib.AV_LOG_ERROR:
306334
return
307335

308336
# Format the message.
309-
cdef char message[1024]
337+
message: cython.char[1024]
310338
lib.vsnprintf(message, 1023, format, args)
311339

312340
# Get the name.
313-
cdef const char *name = NULL
314-
cdef lib.AVClass *cls = (<lib.AVClass**>ptr)[0] if ptr else NULL
341+
name: cython.p_const_char = cython.NULL
342+
cls: cython.pointer[lib.AVClass] = (
343+
cython.cast(cython.pointer[cython.pointer[lib.AVClass]], ptr)[0]
344+
if ptr
345+
else cython.NULL
346+
)
315347
if cls and cls.item_name:
316348
name = cls.item_name(ptr)
317349

318-
with gil:
350+
with cython.gil:
319351
try:
320352
log_callback_gil(level, name, message)
321353
except Exception:
322-
fprintf(stderr, "av.logging: exception while handling %s[%d]: %s\n",
323-
name, level, message)
354+
fprintf(
355+
stderr,
356+
"av.logging: exception while handling %s[%d]: %s\n",
357+
name,
358+
level,
359+
message,
360+
)
324361
# For some reason lib.PyErr_PrintEx(0) won't work.
325362
exc, type_, tb = sys.exc_info()
326363
lib.PyErr_Display(exc, type_, tb)
327364

328365

329-
cdef void nolog_callback(void *ptr, int level, const char *format, lib.va_list args) noexcept nogil:
366+
@cython.cfunc
367+
@cython.nogil
368+
@cython.exceptval(check=False)
369+
def nolog_callback(
370+
ptr: cython.p_void,
371+
level: cython.int,
372+
format: cython.p_const_char,
373+
args: lib.va_list,
374+
) -> cython.void:
330375
pass
331376

377+
332378
lib.av_log_set_callback(nolog_callback)

0 commit comments

Comments
 (0)