diff --git a/av/datasets.py b/av/datasets.py index 237b3bf04..8547c1507 100644 --- a/av/datasets.py +++ b/av/datasets.py @@ -1,6 +1,7 @@ import errno import logging import os +import shutil import sys from collections.abc import Iterator from urllib.request import urlopen @@ -85,12 +86,7 @@ def cached_download(url: str, name: str) -> str: tmp_path = path + ".tmp" with open(tmp_path, "wb") as fh: - while True: - chunk = response.read(8196) - if chunk: - fh.write(chunk) - else: - break + shutil.copyfileobj(response, fh) os.rename(tmp_path, path) diff --git a/av/error.py b/av/error.py index 32fb4d171..941dfcbb8 100644 --- a/av/error.py +++ b/av/error.py @@ -1,3 +1,4 @@ +import enum import errno import os import sys @@ -19,7 +20,6 @@ "HTTPClientError", "UndefinedError", ] -sentinel = cython.declare(object, object()) @cython.ccall @@ -169,86 +169,28 @@ class HTTPClientError(FFmpegError): # fmt: on -class EnumType(type): - def __new__(mcl, name, bases, attrs, *args): - # Just adapting the method signature. - 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): - # We only have one instance per value. - 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) - - -@cython.cclass -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. - """ - - name = cython.declare(str, visibility="readonly") - value = cython.declare(cython.int, visibility="readonly") - - def __cinit__(self, sentinel_, name: str, value: cython.int, 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 +ErrorType = enum.IntEnum( + "ErrorType", + [(name, value) for name, value, *_ in _ffmpeg_specs], + module=__name__, +) +ErrorType.__doc__ = "An enumeration of FFmpeg's error types." - def __int__(self): - return self.value - @property - def tag(self): - return code_to_tag(self.value) +def _error_type_tag(self) -> bytes: + """The FFmpeg byte tag for the error.""" + return code_to_tag(self.value) -ErrorType = EnumType( - "ErrorType", (EnumItem,), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs] -) +def _error_type_strerror(self) -> str: + """The error message that would be returned.""" + if self.value == c_PYAV_STASHED_ERROR: + return PYAV_STASHED_ERROR_message + return lib.av_err2str(-self.value) -for enum in ErrorType: - # Mimick the errno module. - 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) +ErrorType.tag = property(_error_type_tag) +ErrorType.strerror = property(_error_type_strerror) classes: dict = {} diff --git a/av/filter/loudnorm_impl.c b/av/filter/loudnorm_impl.c index 19cb1c554..5d43c28b8 100644 --- a/av/filter/loudnorm_impl.c +++ b/av/filter/loudnorm_impl.c @@ -3,51 +3,23 @@ #include #include #include +#include #include -#ifdef _WIN32 - #include -#else - #include -#endif - -#ifdef _WIN32 - static CRITICAL_SECTION json_mutex; - static CONDITION_VARIABLE json_cond; - static int mutex_initialized = 0; -#else - static pthread_mutex_t json_mutex = PTHREAD_MUTEX_INITIALIZER; - static pthread_cond_t json_cond = PTHREAD_COND_INITIALIZER; -#endif - static char json_buffer[2048] = {0}; static int json_captured = 0; -// Custom logging callback +// Custom logging callback. The loudnorm filter prints its stats as a JSON line. static void logging_callback(void *ptr, int level, const char *fmt, va_list vl) { char line[2048]; vsnprintf(line, sizeof(line), fmt, vl); const char *json_start = strstr(line, "{"); if (json_start) { - #ifdef _WIN32 - EnterCriticalSection(&json_mutex); - #else - pthread_mutex_lock(&json_mutex); - #endif - size_t len = strnlen(json_start, sizeof(json_buffer) - 1); memcpy(json_buffer, json_start, len); json_buffer[len] = '\0'; json_captured = 1; - - #ifdef _WIN32 - WakeConditionVariable(&json_cond); - LeaveCriticalSection(&json_mutex); - #else - pthread_cond_signal(&json_cond); - pthread_mutex_unlock(&json_mutex); - #endif } } @@ -74,15 +46,6 @@ char* loudnorm_get_stats( json_captured = 0; // Reset the captured flag memset(json_buffer, 0, sizeof(json_buffer)); // Clear the buffer - #ifdef _WIN32 - // Initialize synchronization objects if needed - if (!mutex_initialized) { - InitializeCriticalSection(&json_mutex); - InitializeConditionVariable(&json_cond); - mutex_initialized = 1; - } - #endif - av_log_set_callback(logging_callback); AVFilterGraph *filter_graph = NULL; @@ -181,7 +144,8 @@ char* loudnorm_get_stats( end: // Freeing the graph uninits the loudnorm filter, which is what makes it - // emit its JSON stats through our log callback. Safe to call on NULL. + // emit its JSON stats through our log callback. This happens synchronously + // on this thread, so json_buffer is populated by the time the call returns. avfilter_graph_free(&filter_graph); avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); @@ -189,39 +153,12 @@ char* loudnorm_get_stats( av_frame_free(&frame); av_packet_free(&packet); - // If the graph never configured we produced no stats; don't block waiting - // for JSON that will never arrive. Leave result NULL so the caller raises. - if (graph_configured) { - #ifdef _WIN32 - EnterCriticalSection(&json_mutex); - while (!json_captured) { - if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout - fprintf(stderr, "Timeout waiting for JSON data\n"); - break; - } - } - if (json_captured) { - result = _strdup(json_buffer); // Use _strdup on Windows - } - LeaveCriticalSection(&json_mutex); - #else - struct timespec timeout; - clock_gettime(CLOCK_REALTIME, &timeout); - timeout.tv_sec += 5; // 5 second timeout - - pthread_mutex_lock(&json_mutex); - while (json_captured == 0) { - int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout); - if (ret == ETIMEDOUT) { - fprintf(stderr, "Timeout waiting for JSON data\n"); - break; - } - } - if (json_captured) { - result = strdup(json_buffer); - } - pthread_mutex_unlock(&json_mutex); - #endif + if (graph_configured && json_captured) { +#ifdef _WIN32 + result = _strdup(json_buffer); +#else + result = strdup(json_buffer); +#endif } av_log_set_callback(av_log_default_callback);