Skip to content

Commit 6232cdd

Browse files
committed
Make av/logging pure
1 parent 714be34 commit 6232cdd

1 file changed

Lines changed: 105 additions & 56 deletions

File tree

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

Lines changed: 105 additions & 56 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,14 +39,15 @@
3839
3940
"""
4041

41-
cimport libav as lib
42-
from libc.stdio cimport fprintf, stderr
43-
from libc.stdlib cimport free, malloc
44-
4542
import logging
4643
import sys
4744
from threading import Lock, get_ident
4845

46+
import cython
47+
import cython.cimports.libav as lib
48+
from cython.cimports.libc.stdio import fprintf, stderr
49+
from cython.cimports.libc.stdlib import free, malloc
50+
4951
# Library levels.
5052
PANIC = lib.AV_LOG_PANIC # 0
5153
FATAL = lib.AV_LOG_FATAL # 8
@@ -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, {})
168172

169-
cdef class Capture:
173+
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 = cython.declare(list, visibility="readonly")
191+
captures = cython.declare(list, visibility="private")
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:
@@ -197,56 +202,73 @@ def __enter__(self):
197202
return self.logs
198203

199204
def __exit__(self, type_, value, traceback):
200-
self.captures.pop(-1)
205+
self.captures.pop()
206+
207+
208+
log_context = cython.struct(
209+
class_=cython.pointer[lib.AVClass],
210+
name=cython.p_char,
211+
)
201212

213+
item_name_func = cython.typedef("const char *(*item_name_func)(void *) noexcept nogil")
202214

203-
cdef struct log_context:
204-
lib.AVClass *class_
205-
const char *name
206215

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

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

214-
cpdef log(int level, str name, str message):
224+
log_class = cython.declare(lib.AVClass)
225+
log_class.item_name = cython.cast(item_name_func, log_context_name)
226+
227+
228+
@cython.ccall
229+
def log(level: cython.int, name: str, message: str):
215230
"""Send a log through the library logging system.
216231
217232
This is mostly for testing.
218-
219233
"""
220-
221-
cdef log_context *obj = <log_context*>malloc(sizeof(log_context))
222-
obj.class_ = &log_class
234+
obj: cython.pointer[log_context] = cython.cast(
235+
cython.pointer[log_context], malloc(cython.sizeof(log_context))
236+
)
237+
obj.class_ = cython.address(log_class)
223238
obj.name = name
224-
cdef bytes message_bytes = message.encode("utf-8")
225-
226-
lib.av_log(<void*>obj, level, "%s", <char*>message_bytes)
239+
message_bytes: bytes = message.encode("utf-8")
240+
241+
lib.av_log(
242+
cython.cast(cython.p_void, obj),
243+
level,
244+
"%s",
245+
cython.cast(cython.p_char, message_bytes),
246+
)
227247
free(obj)
228248

229249

230-
cdef log_callback_gil(int level, const char *c_name, const char *c_message):
250+
@cython.cfunc
251+
def log_callback_gil(
252+
level: cython.int, c_name: cython.p_const_char, c_message: cython.p_char
253+
):
231254
global error_count
232255
global skip_count
233256
global last_log
234257
global last_error
235258

236-
name = <str>c_name if c_name is not NULL else ""
237-
message = (<bytes>c_message).decode("utf8", "backslashreplace")
259+
name = cython.cast(str, c_name) if c_name is not cython.NULL else ""
260+
message = cython.cast(bytes, c_message).decode("utf8", "backslashreplace")
238261
log = (level, name, message)
239262

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

245268
# Skip messages which are identical to the previous.
246269
# TODO: Be smarter about threads.
247-
cdef bint is_repeated = False
248-
249-
cdef object repeat_log = None
270+
is_repeated: cython.bint = False
271+
repeat_log: object = None
250272

251273
with skip_lock:
252274
if is_interesting:
@@ -263,7 +285,7 @@ def __exit__(self, type_, value, traceback):
263285
repeat_log = (
264286
last_log[0],
265287
last_log[1],
266-
"%s (repeated %d more times)" % (last_log[2], skip_count)
288+
"%s (repeated %d more times)" % (last_log[2], skip_count),
267289
)
268290
skip_count = 0
269291

@@ -281,7 +303,8 @@ def __exit__(self, type_, value, traceback):
281303
log_callback_emit(log)
282304

283305

284-
cdef log_callback_emit(log):
306+
@cython.cfunc
307+
def log_callback_emit(log):
285308
lib_level, name, message = log
286309

287310
captures = thread_captures.get(get_ident()) or global_captures
@@ -296,37 +319,63 @@ def __exit__(self, type_, value, traceback):
296319
logger.log(py_level, message.strip())
297320

298321

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()
322+
@cython.cfunc
323+
@cython.nogil
324+
@cython.exceptval(check=False)
325+
def log_callback(
326+
ptr: cython.p_void,
327+
level: cython.int,
328+
format: cython.p_const_char,
329+
args: lib.va_list,
330+
) -> cython.void:
331+
inited: cython.bint = lib.Py_IsInitialized()
301332
if not inited:
302333
return
303334

304-
with gil:
335+
with cython.gil:
305336
if level > level_threshold and level != lib.AV_LOG_ERROR:
306337
return
307338

308339
# Format the message.
309-
cdef char message[1024]
340+
message: cython.char[1024]
310341
lib.vsnprintf(message, 1023, format, args)
311342

312343
# Get the name.
313-
cdef const char *name = NULL
314-
cdef lib.AVClass *cls = (<lib.AVClass**>ptr)[0] if ptr else NULL
344+
name: cython.p_const_char = cython.NULL
345+
cls: cython.pointer[lib.AVClass] = (
346+
cython.cast(cython.pointer[cython.pointer[lib.AVClass]], ptr)[0]
347+
if ptr
348+
else cython.NULL
349+
)
315350
if cls and cls.item_name:
316351
name = cls.item_name(ptr)
317352

318-
with gil:
353+
with cython.gil:
319354
try:
320355
log_callback_gil(level, name, message)
321356
except Exception:
322-
fprintf(stderr, "av.logging: exception while handling %s[%d]: %s\n",
323-
name, level, message)
357+
fprintf(
358+
stderr,
359+
"av.logging: exception while handling %s[%d]: %s\n",
360+
name,
361+
level,
362+
message,
363+
)
324364
# For some reason lib.PyErr_PrintEx(0) won't work.
325365
exc, type_, tb = sys.exc_info()
326366
lib.PyErr_Display(exc, type_, tb)
327367

328368

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

380+
332381
lib.av_log_set_callback(nolog_callback)

0 commit comments

Comments
 (0)