forked from PyAV-Org/PyAV
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogging.pyx
More file actions
169 lines (129 loc) · 5.25 KB
/
Copy pathlogging.pyx
File metadata and controls
169 lines (129 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from __future__ import absolute_import
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf, fprintf, stderr
cimport libav as lib
import logging
import sys
# Levels.
QUIET = lib.AV_LOG_QUIET
PANIC = lib.AV_LOG_PANIC
FATAL = lib.AV_LOG_FATAL
ERROR = lib.AV_LOG_ERROR
WARNING = lib.AV_LOG_WARNING
INFO = lib.AV_LOG_INFO
VERBOSE = lib.AV_LOG_VERBOSE
DEBUG = lib.AV_LOG_DEBUG
# Map from AV levels to logging levels.
level_map = {
# lib.AV_LOG_QUIET is not actually a level.
lib.AV_LOG_PANIC: 50, # logging.CRITICAL
lib.AV_LOG_FATAL: 50, # logging.CRITICAL
lib.AV_LOG_ERROR: 40, # logging.ERROR
lib.AV_LOG_WARNING: 30, # logging.WARNING
lib.AV_LOG_INFO: 20, # logging.INFO
lib.AV_LOG_VERBOSE: 10, # logging.DEBUG
lib.AV_LOG_DEBUG: 5, # This is below any logging constant.
}
# While we start with the level quite low, Python defaults to INFO, and so
# they will not show.
cdef int log_level = lib.AV_LOG_VERBOSE
# ... but lets limit ourselves to WARNING immediately.
logging.getLogger('libav').setLevel(logging.WARNING)
def get_level():
"""Return current logging threshold. See :func:`set_level`."""
return log_level
def set_level(int level):
"""set_level(level)
Sets logging threshold when converting from the library's logging system
to Python's. It is recommended to use the constants availible in this
module to set the level: ``QUIET``, ``PANIC``, ``FATAL``, ``ERROR``,
``WARNING``, ``INFO``, ``VERBOSE``, and ``DEBUG``.
While less efficient, it is generally preferable to modify logging
with Python's :mod:`logging`, e.g.::
logging.getLogger('libav').setLevel(logging.ERROR)
PyAV defaults to translating everything except ``AV_LOG_DEBUG``, so this
function is only nessesary to use if you want to see those messages as well.
``AV_LOG_DEBUG`` will be translated to a level 5 message, which is lower
than any builting Python logging level, so you must lower that as well::
logging.getLogger().setLevel(5)
"""
global log_level
log_level = level
cdef bint log_after_shutdown = False
def set_log_after_shutdown(v):
"""Set if logging should continue to ``stderr`` after Python shutdown."""
global log_after_shutdown
log_after_shutdown = v
# Threads sure are a mess!
#
# I simply did not find a way to capture the GIL in a function called from a
# thread that was spawned by Libav. The only solution is the tangle that you
# see before you.
#
# We handle the formatting of the log and extraction of the AVClass' item_name
# immediately, but stuff the resulting message and it's log level into a
# temporary struct. We use the super low-level Py_AddPendingCall to schedule a
# call to run in the main Python thread. That call dumps the message into the
# Python logging system.
cdef struct LogRequest:
int level
const char *item_name
char *message
cdef void log_callback(void *ptr, int level, const char *format, lib.va_list args) nogil:
# We have to filter it ourselves.
# Note that FFmpeg's levels are backwards from Python's.
if level > log_level:
return
cdef LogRequest *req = <LogRequest*>malloc(sizeof(LogRequest))
req.level = level
req.item_name = NULL
# We need to do everything with the `void *ptr` in this function, since
# the object it represents may be freed by the time the async_log_callback
# is run by Python.
cdef lib.AVClass *cls = (<lib.AVClass**>ptr)[0] if ptr else NULL
if cls and cls.item_name:
# I'm not 100% on this, but a `const char*` should be static, and so
# it doesn't matter if the AVClass that returned it vanishes or not.
req.item_name = cls.item_name(ptr)
req.message = <char*>malloc(1024) # This is the default size in FFmpeg.
lib.vsnprintf(req.message, 1023, format, args)
# Schedule this to be called in the main Python thread, but only if
# Python hasn't started finalizing yet.
if lib.Py_IsInitialized():
lib.Py_AddPendingCall(<void*>async_log_callback, <void*>req)
elif log_after_shutdown:
fprintf(stderr, "av.logging: %s[%d]: %s",
req.item_name, req.level, req.message
)
free(req.message)
free(req)
cdef int async_log_callback(void *arg) except -1:
cdef LogRequest *req = <LogRequest*>arg
cdef int level
cdef str logger_name
cdef str item_name
if not lib.Py_IsInitialized():
if log_after_shutdown:
fprintf(stderr, "av.logging: %s[%d]: %s",
req.item_name, req.level, req.message
)
return 0
try:
level = level_map.get(req.level, 20)
item_name = req.item_name if req.item_name else ''
logger_name = 'libav.' + item_name if item_name else 'libav.generic'
logger = logging.getLogger(logger_name)
logger.log(level, req.message.strip())
except Exception as e:
fprintf(stderr, "av.logging: exception while handling %s[%d]: %s",
req.item_name, req.level, req.message
)
# For some reason lib.PyErr_PrintEx(0) won't work.
exc, type_, tb = sys.exc_info()
lib.PyErr_Display(exc, type_, tb)
finally:
free(req.message)
free(req)
return 0
# Start the magic!
lib.av_log_set_callback(log_callback)