From ea75014f0856bab5d7c7d4bed4275eaf63b6f95b Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Mon, 14 Sep 2026 22:46:20 -0500 Subject: [PATCH 1/6] Replace ZeroMQ logging with independent file and console logging Add severity levels and environment configuration, preserve source aliases and raw crash-log output, and remove ZeroMQ build dependencies. Document the API and cover filtering, environment precedence, concurrent writes, and Python bindings. --- .github/workflows/ci.yml | 3 +- INSTALL.md | 6 - README.md | 2 + bindings/java/openshot.i | 4 +- bindings/python/CMakeLists.txt | 10 ++ bindings/python/openshot.i | 9 +- bindings/python/test_logger.py | 108 ++++++++++++++ bindings/ruby/openshot.i | 4 +- cmake/Modules/FindZeroMQ.cmake | 45 ------ doc/INSTALL-LINUX.md | 6 - doc/INSTALL-MAC.md | 5 - doc/INSTALL-WINDOWS.md | 17 +-- doc/logging.rst | 80 ++++++++++ examples/qt-demo/main.cpp | 6 +- src/CMakeLists.txt | 22 +-- src/CVObjectDetection.cpp | 10 +- src/CVObjectMask.cpp | 12 +- src/Clip.cpp | 14 +- src/CrashHandler.cpp | 8 +- src/CrashHandler.h | 4 +- src/EffectBase.cpp | 4 +- src/FFmpegReader.cpp | 138 ++++++++--------- src/FFmpegWriter.cpp | 86 +++++------ src/FrameMapper.cpp | 26 ++-- src/ImageWriter.cpp | 8 +- src/Logger.cpp | 204 +++++++++++++++++++++++++ src/Logger.h | 53 +++++++ src/OpenShot.h | 2 + src/Qt/AudioPlaybackThread.cpp | 18 +-- src/Qt/VideoPlaybackThread.cpp | 6 +- src/Settings.h | 2 +- src/Timeline.cpp | 46 +++--- src/WaylandScreenCaptureReader.cpp | 10 +- src/ZmqLogger.cpp | 230 ----------------------------- src/ZmqLogger.h | 105 +------------ src/effects/Displace.cpp | 4 +- tests/CMakeLists.txt | 1 + tests/Logger.cpp | 84 +++++++++++ 38 files changed, 765 insertions(+), 637 deletions(-) create mode 100644 bindings/python/test_logger.py delete mode 100644 cmake/Modules/FindZeroMQ.cmake create mode 100644 doc/logging.rst create mode 100644 src/Logger.cpp create mode 100644 src/Logger.h delete mode 100644 src/ZmqLogger.cpp create mode 100644 tests/Logger.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eec88f957..8b7d60ef6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,14 +75,13 @@ jobs: if: ${{ runner.os == 'linux' }} run: | sudo apt update - sudo apt remove libzmq5 # See actions/virtual-environments#3317 sudo apt install \ cmake swig doxygen graphviz curl lcov \ libasound2-dev \ qtbase5-dev qtbase5-dev-tools libqt5svg5-dev \ libfdk-aac-dev libavcodec-dev libavdevice-dev libavformat-dev \ libavutil-dev libswscale-dev libswresample-dev \ - libzmq3-dev libbabl-dev \ + libbabl-dev \ libopencv-dev libprotobuf-dev protobuf-compiler \ cargo libomp5 libomp-dev diff --git a/INSTALL.md b/INSTALL.md index d99f59c09..80a563be5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -54,12 +54,6 @@ Libraries and executables have been labeled in the list below to help distinguis apply image effects, and many other utility functions, such as file system manipulation, high resolution timers, etc. -#### ZeroMQ (libzmq) -* **(Library)** - -* This library is used to communicate between libopenshot and other applications (publisher / subscriber). - Primarily used to send debug data from libopenshot. - #### OpenMP (`-fopenmp`) * **(Compiler Flag)** diff --git a/README.md b/README.md index 8fd820999..eabc32fe1 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ Please see [`doc/HW-ACCEL.md`](doc/HW-ACCEL.md) for more information. ## Documentation +See [Logging](doc/logging.rst) for file output, verbosity, and environment settings. + Beautiful HTML documentation can be generated using Doxygen. ``` make doc diff --git a/bindings/java/openshot.i b/bindings/java/openshot.i index 9f8d1794f..cc13f189c 100644 --- a/bindings/java/openshot.i +++ b/bindings/java/openshot.i @@ -130,7 +130,7 @@ typedef struct OpenShotByteBuffer { #include "TimelineBase.h" #include "Timeline.h" #include "Qt/VideoCacheThread.h" -#include "ZmqLogger.h" +#include "Logger.h" %} // Prevent SWIG from ever generating a wrapper for juce::Thread’s constructor (or run()) @@ -235,7 +235,7 @@ typedef struct OpenShotByteBuffer { %include "TimelineBase.h" %include "Qt/VideoCacheThread.h" %include "Timeline.h" -%include "ZmqLogger.h" +%include "Logger.h" #ifdef USE_IMAGEMAGICK %include "ImageReader.h" diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index d748f252d..a2bbdc06d 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -138,3 +138,13 @@ install(TARGETS ${_pyopenshot_target} DESTINATION ${PYTHON_MODULE_PATH} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/openshot.py DESTINATION ${PYTHON_MODULE_PATH} ) + +if(BUILD_TESTING) + add_test(NAME Logger:PythonEnvironment + COMMAND ${CMAKE_COMMAND} -E env + "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}" + "OPENSHOT_TEST_MODULE_DIR=$" + "OPENSHOT_TEST_DLL_DIR=$" + ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/test_logger.py") + set_tests_properties(Logger:PythonEnvironment PROPERTIES LABELS Logger) +endif() diff --git a/bindings/python/openshot.i b/bindings/python/openshot.i index 73d3da46e..da0fde570 100644 --- a/bindings/python/openshot.i +++ b/bindings/python/openshot.i @@ -114,7 +114,7 @@ class QWidget; #include "TimelineBase.h" #include "Timeline.h" #include "Qt/VideoCacheThread.h" -#include "ZmqLogger.h" +#include "Logger.h" #include static void *openshot_swig_pylong_as_ptr(PyObject *obj) { @@ -523,7 +523,7 @@ static int openshot_swig_is_qwidget(PyObject *obj) { %include "TimelineBase.h" %include "Qt/VideoCacheThread.h" %include "Timeline.h" -%include "ZmqLogger.h" +%include "Logger.h" #ifdef USE_OPENCV %include "ClipProcessingJobs.h" @@ -566,3 +566,8 @@ static int openshot_swig_is_qwidget(PyObject *obj) { %include "effects/ObjectDetection.h" %include "effects/Outline.h" #endif + +%pythoncode %{ +# Deprecated source compatibility alias (no networking). +ZmqLogger = Logger +%} diff --git a/bindings/python/test_logger.py b/bindings/python/test_logger.py new file mode 100644 index 000000000..423fd5d26 --- /dev/null +++ b/bindings/python/test_logger.py @@ -0,0 +1,108 @@ +"""Exercise native logger configuration in fresh processes through SWIG.""" +import os +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +class NativeLoggerTests(unittest.TestCase): + def run_logger(self, variables, body=''): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / 'native-é.log' + env = {key: value for key, value in os.environ.items() + if not key.startswith(('OPENSHOT_LOG_', 'LIBOPENSHOT_LOG_')) + and key != 'LIBOPENSHOT_DEBUG'} + env.update(variables, LIBOPENSHOT_LOG_FILE=str(path)) + module_dir = env.get('OPENSHOT_TEST_MODULE_DIR') + if module_dir: + env['PYTHONPATH'] = module_dir + os.pathsep + env.get('PYTHONPATH', '') + result = subprocess.run([sys.executable, '-c', ''' +import os +_dll_handles = [] +if os.name == 'nt' and os.environ.get('OPENSHOT_TEST_DLL_DIR'): + _dll_handles.append(os.add_dll_directory(os.environ['OPENSHOT_TEST_DLL_DIR'])) +import openshot +logger = openshot.Logger.Instance() +assert openshot.ZmqLogger is openshot.Logger +''' + body + ''' +logger.Log("native-debug-record", openshot.Logger.LevelDebug) +logger.Log("native-info-record", openshot.Logger.LevelInfo) +logger.Log("native-warning-record", openshot.Logger.LevelWarning) +logger.Log("native-error-record", openshot.Logger.LevelError) +logger.Log("native-critical-record", openshot.Logger.LevelCritical) +logger.Close() +'''], env=env, text=True, capture_output=True, timeout=20) + self.assertEqual(result.returncode, 0, result.stderr) + return path.read_text(), result.stderr + + def test_default_and_legacy_console(self): + for variables, debug_console in (({}, False), ({'LIBOPENSHOT_DEBUG': '0'}, True)): + with self.subTest(variables=variables): + file, console = self.run_logger(variables) + self.assertNotIn('native-debug-record', file) + self.assertEqual('native-debug-record' in console, debug_console) + self.assertIn('native-info-record', file) + self.assertIn('native-info-record', console) + + def test_component_and_destination_precedence(self): + file, console = self.run_logger({ + 'OPENSHOT_LOG_FILE_LEVEL': 'off', + 'LIBOPENSHOT_LOG_LEVEL': 'debug', + 'LIBOPENSHOT_LOG_CONSOLE_LEVEL': 'error'}) + self.assertIn('native-debug-record', file) + self.assertNotIn('native-info-record', console) + self.assertIn('native-error-record', console) + + def test_invalid_level_fallback(self): + file, console = self.run_logger({ + 'OPENSHOT_LOG_LEVEL': 'error', + 'LIBOPENSHOT_LOG_FILE_LEVEL': 'invalid', + 'LIBOPENSHOT_DEBUG': '1'}) + self.assertNotIn('native-info-record', file) + self.assertNotIn('native-debug-record', console) + self.assertIn('native-error-record', file) + self.assertEqual(console.count('ignoring invalid'), 1) + + def test_every_environment_variable_filters_the_correct_output(self): + for prefix in ('OPENSHOT', 'LIBOPENSHOT'): + for suffix, debug_file, debug_console in ( + ('LEVEL', True, True), ('FILE_LEVEL', True, False), + ('CONSOLE_LEVEL', False, True)): + variable = prefix + '_LOG_' + suffix + with self.subTest(variable=variable): + file, console = self.run_logger({variable: 'debug'}) + self.assertEqual('native-debug-record' in file, debug_file) + self.assertEqual('native-debug-record' in console, debug_console) + + def test_all_level_thresholds(self): + levels = ('debug', 'info', 'warning', 'error', 'critical', 'off') + for threshold, level in enumerate(levels): + with self.subTest(level=level): + file, console = self.run_logger({'LIBOPENSHOT_LOG_LEVEL': level}) + for index, message_level in enumerate(levels[:-1]): + message = 'native-' + message_level + '-record' + self.assertEqual(message in file, index >= threshold) + self.assertEqual(message in console, index >= threshold) + + def test_api_overrides_environment_and_preserves_crash_output(self): + file, console = self.run_logger({'LIBOPENSHOT_LOG_LEVEL': 'off'}, ''' +logger.SetFileLevel("debug") +logger.SetConsoleLevel("error") +logger.LogToFile("---- Unhandled Exception: Stack Trace ----\\ncrash-evidence\\n---- End of Stack Trace ----\\n") +try: + logger.SetFileLevel("invalid") +except RuntimeError: + pass +else: + raise AssertionError("invalid level accepted") +''') + self.assertIn('native-debug-record', file) + self.assertIn('crash-evidence', file) + self.assertIn('native-error-record', console) + self.assertNotIn('native-debug-record', console) + + +if __name__ == '__main__': + unittest.main() diff --git a/bindings/ruby/openshot.i b/bindings/ruby/openshot.i index 566a84b13..d039b66a0 100644 --- a/bindings/ruby/openshot.i +++ b/bindings/ruby/openshot.i @@ -136,7 +136,7 @@ typedef struct OpenShotByteBuffer { #include "TimelineBase.h" #include "Timeline.h" #include "Qt/VideoCacheThread.h" -#include "ZmqLogger.h" +#include "Logger.h" /* Move FFmpeg's RSHIFT to FF_RSHIFT, if present */ #ifdef RSHIFT @@ -272,7 +272,7 @@ typedef struct OpenShotByteBuffer { %include "TimelineBase.h" %include "Qt/VideoCacheThread.h" %include "Timeline.h" -%include "ZmqLogger.h" +%include "Logger.h" #ifdef USE_IMAGEMAGICK %include "ImageReader.h" diff --git a/cmake/Modules/FindZeroMQ.cmake b/cmake/Modules/FindZeroMQ.cmake deleted file mode 100644 index cec8113e4..000000000 --- a/cmake/Modules/FindZeroMQ.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# © OpenShot Studios, LLC -# -# SPDX-License-Identifier: LGPL-3.0-or-later - -set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) -find_package(PkgConfig) -pkg_check_modules(PC_LIBZMQ QUIET libzmq) - -set(ZeroMQ_VERSION ${PC_LIBZMQ_VERSION}) - -find_path(ZeroMQ_INCLUDE_DIR zmq.h - PATHS - ${ZeroMQ_DIR}/include - ${PC_LIBZMQ_INCLUDE_DIRS}) - -find_library(ZeroMQ_LIBRARY - NAMES zmq - PATHS - ${ZeroMQ_DIR}/lib - ${PC_LIBZMQ_LIBDIR} - ${PC_LIBZMQ_LIBRARY_DIRS}) - -if(ZeroMQ_LIBRARY) - set(ZeroMQ_FOUND ON) -endif() - -set ( ZeroMQ_LIBRARIES ${ZeroMQ_LIBRARY} ) -set ( ZeroMQ_INCLUDE_DIRS ${ZeroMQ_INCLUDE_DIR} ) - -if(NOT TARGET libzmq) - add_library(libzmq UNKNOWN IMPORTED) - set_target_properties(libzmq PROPERTIES - IMPORTED_LOCATION ${ZeroMQ_LIBRARIES} - INTERFACE_INCLUDE_DIRECTORIES ${ZeroMQ_INCLUDE_DIRS}) -endif() - -include ( FindPackageHandleStandardArgs ) -# handle the QUIETLY and REQUIRED arguments and set ZMQ_FOUND to TRUE -# if all listed variables are TRUE -find_package_handle_standard_args(ZeroMQ - REQUIRED_VARS - ZeroMQ_LIBRARIES - ZeroMQ_INCLUDE_DIRS - VERSION_VAR - ZeroMQ_VERSION) diff --git a/doc/INSTALL-LINUX.md b/doc/INSTALL-LINUX.md index 7633da879..669f0c52c 100644 --- a/doc/INSTALL-LINUX.md +++ b/doc/INSTALL-LINUX.md @@ -72,10 +72,6 @@ list below to help distinguish between them. * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -### ZeroMQ (libzmq) - * http://zeromq.org/ `(Library)` - * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. - ### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -166,7 +162,6 @@ software packages available to download and install. libxcursor-dev \ libxinerama-dev \ libxrandr-dev \ - libzmq3-dev \ pkg-config \ python3-dev \ qtbase5-dev \ @@ -174,7 +169,6 @@ software packages available to download and install. swig \ xdg-desktop-portal \ xdg-desktop-portal-gtk \ - python3-zmq \ python3-pyqt5.qtwebengine ``` diff --git a/doc/INSTALL-MAC.md b/doc/INSTALL-MAC.md index 183603207..8851c3958 100644 --- a/doc/INSTALL-MAC.md +++ b/doc/INSTALL-MAC.md @@ -67,10 +67,6 @@ list below to help distinguish between them. * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -### ZeroMQ (libzmq) - * http://zeromq.org/ `(Library)` - * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. - ### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -151,7 +147,6 @@ brew install doxygen brew install unittest-cpp --cc=gcc-4.8. You must specify the c++ compiler with the --cc flag to be 4.7 or 4.8. brew install qt5 brew install cmake -brew install zeromq brew install babl ``` diff --git a/doc/INSTALL-WINDOWS.md b/doc/INSTALL-WINDOWS.md index 2fe1e464a..7dcd3e2f8 100644 --- a/doc/INSTALL-WINDOWS.md +++ b/doc/INSTALL-WINDOWS.md @@ -69,10 +69,6 @@ have been labeled in the list below to help distinguish between them. * https://github.com/unittest-cpp/ `(Library)` * This library is used to execute unit tests for libopenshot. It contains many macros used to keep our unit testing code very clean and simple. -### ZeroMQ (libzmq) - * http://zeromq.org/ `(Library)` - * This library is used to communicate between libopenshot and other applications (publisher / subscriber). Primarily used to send debug data from libopenshot. - ### OpenMP (-fopenmp) * http://openmp.org/wp/ `(Compiler Flag)` * If your compiler supports this flag (GCC, Clang, and most other compilers), it provides libopenshot with easy methods of using parallel programming techniques to improve performance and take advantage of multi-core processors. @@ -116,7 +112,6 @@ check each folder path for accuracy, as your paths will likely be different than * QTDIR (`C:\qt5`) * SNDFILE_DIR (`C:\Program Files\libsndfile`) * UNITTEST_DIR (`C:\UnitTest++`) -* ZMQDIR (`C:\msys2\usr\local\`) * PATH (`The following paths are an example`) * `C:\Qt5\bin; C:\Qt5\MinGW\bin\; C:\msys\1.0\local\lib; C:\Program Files\CMake 2.8\bin; C:\UnitTest++\build; C:\libopenshot\build\src; C:\Program Files\doxygen\bin; C:\ffmpeg-git-95f163b-win32-dev\lib; C:\swigwin-2.0.4; C:\Python33; C:\Program Files\Project\lib; C:\msys2\usr\local\` @@ -196,7 +191,7 @@ pacman -S --needed --noconfirm \ mingw-w64-x86_64-rust base-devel mingw-w64-x86_64-toolchain \ mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-qt5 mingw-w64-x86_64-python3-pyqt5 \ mingw-w64-x86_64-swig mingw-w64-x86_64-cmake mingw-w64-x86_64-doxygen \ -mingw-w64-x86_64-python3-pip mingw-w64-i686-zeromq mingw-w64-x86_64-python3-pyzmq \ +mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python3-cx_Freeze mingw-w64-x86_64-ninja mingw-w64-x86_64-catch \ mingw-w64-x86_64-python3-PyOpenGL mingw-w64-clang-x86_64-python-pyopengl-accelerate \ mingw-w64-x86_64-python-pyopengl-accelerate mingw-w64-x86_64-python-pywin32 git @@ -212,7 +207,7 @@ pacman -S --needed --noconfirm \ mingw-w64-i686-rust mingw-w64-i686-toolchain mingw-w64-i686-ffmpeg \ mingw-w64-i686-qt5 mingw-w64-i686-python3-pyqt5 mingw-w64-i686-swig \ mingw-w64-i686-cmake mingw-w64-i686-doxygen mingw-w64-i686-python3-pip \ -mingw-w64-i686-zeromq mingw-w64-i686-python3-pyzmq mingw-w64-i686-python3-cx_Freeze \ +mingw-w64-i686-python3-cx_Freeze \ mingw-w64-i686-ninja mingw-w64-i686-catch mingw-w64-i686-python-pyopengl \ mingw-w64-i686-python-pyopengl-accelerate mingw-w64-i686-python-pywin32 @@ -288,8 +283,6 @@ mkdir -p /usr/include/resvg/ cp crates/c-api/*.h /usr/include/resvg/ ``` -11) ZMQ++ Header (This might not be needed anymore) - NOTE: Download and copy zmq.hpp into the /c/msys64/mingw64/include/ folder ## Manual Dependencies @@ -310,12 +303,6 @@ Create an environment variable called DXSDK_DIR and set the value to `C:\Program * Download and Install the Win32 Setup program. * Create an environment variable called SNDFILE_DIR and set the value to `C:\Program Files\libsndfile`. This environment variable will be used by CMake to find the binary and header files. -### libzmq - * http://zeromq.org/intro:get-the-software - * Download source code (zip) - * Follow their instructions, and build with mingw - * Create an environment variable called ZMQDIR and set the value to `C:\libzmq\build\` (the location of the compiled version). This environment variable will be used by CMake to find the binary and header files. - ## Windows Build Instructions (libopenshot-audio) In order to compile libopenshot-audio, launch a command prompt and enter the following commands. This does not require the MSYS2 prompt, but it should work in both the Windows command prompt and the MSYS2 prompt. diff --git a/doc/logging.rst b/doc/logging.rst new file mode 100644 index 000000000..b1901e7f6 --- /dev/null +++ b/doc/logging.rst @@ -0,0 +1,80 @@ +Logging +========= + +``openshot::Logger`` provides synchronous, thread-safe ordinary logging to an +append-only file and stderr, with independent severity thresholds. It uses +standard C++ and requires no ZeroMQ dependency or forwarding thread. + +Configuration +------------- + +Both thresholds default to INFO. Supported levels are DEBUG, INFO, WARNING, +ERROR, CRITICAL, and OFF (case-insensitive). Existing method traces use DEBUG. +No file is opened until a path is supplied through the API or environment. + +.. code-block:: cpp + + #include "Logger.h" + + auto* logger = openshot::Logger::Instance(); + logger->Path("render.log"); // UTF-8; relative to the working directory + logger->SetFileLevel("DEBUG"); + logger->SetConsoleLevel("WARNING"); + logger->Log("Starting export", openshot::Logger::LevelInfo); + +The same API is exposed by the Python binding. For a standalone script: + +.. code-block:: bash + + LIBOPENSHOT_LOG_FILE=render.log LIBOPENSHOT_LOG_LEVEL=debug python3 render.py + +.. list-table:: Environment variables + :header-rows: 1 + :widths: 50 50 + + * - Variable + - Scope + * - ``LIBOPENSHOT_LOG_FILE`` + - Output file path; no standalone default + * - ``LIBOPENSHOT_LOG_LEVEL`` + - File and stderr thresholds + * - ``LIBOPENSHOT_LOG_FILE_LEVEL`` + - File threshold + * - ``LIBOPENSHOT_LOG_CONSOLE_LEVEL`` + - Stderr threshold + * - ``OPENSHOT_LOG_LEVEL`` + - Shared fallback for both thresholds + * - ``OPENSHOT_LOG_FILE_LEVEL`` / ``OPENSHOT_LOG_CONSOLE_LEVEL`` + - Shared fallback for the respective threshold + * - ``LIBOPENSHOT_DEBUG`` + - Legacy stderr DEBUG fallback; any present value, including ``0`` + +Environment variables are read at singleton initialization. Precedence is +explicit API configuration > ``LIBOPENSHOT_`` variables > ``OPENSHOT_`` variables +> legacy debug switch > INFO. Within each variable group, destination-specific +levels override the general level. Invalid environment levels emit a diagnostic +and fall back; invalid API levels throw ``std::invalid_argument``. + +In openshot-qt, the host resolves CLI > environment > saved preferences > defaults +and configures this API. ``--debug-engine`` enables engine DEBUG in its file and +stderr; **Video & Audio Engine Debug Logging** affects only ``libopenshot.log``. +``--debug-ui`` (aliases ``--debug``, ``-d``) and **User Interface Debug Logging** +control the separate Python logger and ``openshot-qt.log``. The GUI selects its +own file path under ``~/.openshot_qt/``. + +Behavior and compatibility +-------------------------- + +* Records include local time, severity, and thread ID. ``ShouldLog(level)`` can + guard expensive message construction. File writes flush per record; there is + no rotation or asynchronous queue. DEBUG can generate substantial I/O; avoid + logging from real-time audio callbacks. +* ``Close()`` closes the file and disables ordinary output. Reopening requires + setting the path and desired levels again. +* ``ZmqLogger`` is a deprecated C++/Python source alias; rebuild consumers and + bindings. ``Connection()`` is a no-op. ``Enable(true/false)`` selects file + DEBUG/OFF. ``Settings::DEBUG_TO_STDERR`` remains a legacy console switch, + overridden by explicit modern console configuration. +* ``LogToFile()`` preserves the existing unfiltered raw crash-write path and + does not acquire the ordinary logger mutex. Crash markers and error handling + are unchanged. Stream flushing does not guarantee durability after power loss. diff --git a/examples/qt-demo/main.cpp b/examples/qt-demo/main.cpp index 2ef142248..9dfaa050e 100644 --- a/examples/qt-demo/main.cpp +++ b/examples/qt-demo/main.cpp @@ -11,15 +11,15 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "Qt/PlayerDemo.h" -#include "ZmqLogger.h" +#include "Logger.h" #include int main(int argc, char *argv[]) { // Enable logging for openshot-player since this is primarily used for // profiling and debugging video playback issues. - openshot::ZmqLogger::Instance()->Enable(true); - openshot::ZmqLogger::Instance()->Path("./player.log"); + openshot::Logger::Instance()->Enable(true); + openshot::Logger::Instance()->Path("./player.log"); QApplication app(argc, argv); PlayerDemo demo; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a27f2f7ac..7452807ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,7 +92,7 @@ set(OPENSHOT_SOURCES TimelineBase.cpp Timeline.cpp TrackedObjectBase.cpp - ZmqLogger.cpp + Logger.cpp ) set(OPENSHOT_WAYLAND_CAPTURE FALSE) @@ -514,26 +514,6 @@ endif() target_link_libraries(openshot PUBLIC OpenMP::OpenMP_CXX) -### -### ZeroMQ -### - -# Find ZeroMQ library (used for socket communication & logging) -find_package(ZeroMQ REQUIRED) # Creates libzmq target - -# Some platforms package the header-only cppzmq C++ bindings separately, -# others (Ubuntu) bundle them in with libzmq itself -find_package(cppzmq QUIET) # Creates cppzmq target - -# Link ZeroMQ library -if (TARGET libzmq) - target_link_libraries(openshot PUBLIC libzmq) -endif() -# Include cppzmq headers, if not bundled into libzmq -if (TARGET cppzmq) - target_link_libraries(openshot PUBLIC cppzmq) -endif() - ### ### Babl ### diff --git a/src/CVObjectDetection.cpp b/src/CVObjectDetection.cpp index 7fe03387b..7f3c0c1aa 100644 --- a/src/CVObjectDetection.cpp +++ b/src/CVObjectDetection.cpp @@ -18,7 +18,7 @@ #include "CVObjectDetection.h" #include "Exceptions.h" -#include "ZmqLogger.h" +#include "Logger.h" #define int64 int64_t #define uint64 uint64_t @@ -266,7 +266,7 @@ void CVObjectDetection::setProcessingDevice(){ if (processingDevice == "CPU") { net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); - ZmqLogger::Instance()->Log("Object Detection DNN device: requested CPU, selected CPU"); + Logger::Instance()->Log("Object Detection DNN device: requested CPU, selected CPU"); return; } @@ -276,7 +276,7 @@ void CVObjectDetection::setProcessingDevice(){ if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_CUDA) != targets.end()) { net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); - ZmqLogger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected CUDA"); + Logger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected CUDA"); return; } } catch (const cv::Exception&) { @@ -290,7 +290,7 @@ void CVObjectDetection::setProcessingDevice(){ cv::ocl::setUseOpenCL(true); net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); net.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL); - ZmqLogger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected OpenCL"); + Logger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected OpenCL"); return; } } catch (const cv::Exception&) { @@ -300,7 +300,7 @@ void CVObjectDetection::setProcessingDevice(){ processingDevice = "CPU"; net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); - ZmqLogger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected CPU"); + Logger::Instance()->Log("Object Detection DNN device: requested " + requestedDevice + ", selected CPU"); } void CVObjectDetection::detectObjectsClip(openshot::Clip &video, size_t _start, size_t _end, bool process_interval) diff --git a/src/CVObjectMask.cpp b/src/CVObjectMask.cpp index d977ec4fd..e1ad218f1 100644 --- a/src/CVObjectMask.cpp +++ b/src/CVObjectMask.cpp @@ -13,7 +13,7 @@ #include "CVObjectMask.h" #include "Exceptions.h" -#include "ZmqLogger.h" +#include "Logger.h" #include "objdetectdata.pb.h" #define int64 int64_t @@ -861,7 +861,7 @@ void CVObjectMask::SetProcessingDevice() if (processingDevice == "CPU") { efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); - ZmqLogger::Instance()->Log("Object Mask EfficientSAM DNN device: requested CPU, selected CPU"); + Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested CPU, selected CPU"); return; } @@ -871,7 +871,7 @@ void CVObjectMask::SetProcessingDevice() if (std::find(targets.begin(), targets.end(), cv::dnn::DNN_TARGET_CUDA) != targets.end()) { efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); - ZmqLogger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CUDA"); + Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CUDA"); return; } } catch (const cv::Exception&) { @@ -885,7 +885,7 @@ void CVObjectMask::SetProcessingDevice() cv::ocl::setUseOpenCL(true); efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL); - ZmqLogger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected OpenCL"); + Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected OpenCL"); return; } } catch (const cv::Exception&) { @@ -895,7 +895,7 @@ void CVObjectMask::SetProcessingDevice() processingDevice = "CPU"; efficientSam.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); efficientSam.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); - ZmqLogger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CPU"); + Logger::Instance()->Log("Object Mask EfficientSAM DNN device: requested " + requestedDevice + ", selected CPU"); } void CVObjectMask::maskClip(openshot::Clip& video, size_t _start, size_t _end, bool process_interval) @@ -948,7 +948,7 @@ void CVObjectMask::maskClip(openshot::Clip& video, size_t _start, size_t _end, b try { cutie.Load(cutieEncodeKeyModelPath, cutieEncodeValueModelPath, cutieMemoryReadoutModelPath, cutieDecodeModelPath); const std::string cutieDevice = cutie.SetDevice(processingDevice); - ZmqLogger::Instance()->Log("Object Mask Cutie DNN device: requested " + processingDevice + ", selected " + cutieDevice); + Logger::Instance()->Log("Object Mask Cutie DNN device: requested " + processingDevice + ", selected " + cutieDevice); } catch (const cv::Exception& e) { processingController->SetError(true, std::string("Failed to load Cutie ONNX models: ") + e.what()); error = true; diff --git a/src/Clip.cpp b/src/Clip.cpp index 585990a49..a03df145e 100644 --- a/src/Clip.cpp +++ b/src/Clip.cpp @@ -20,7 +20,7 @@ #include "ChunkReader.h" #include "DummyReader.h" #include "Timeline.h" -#include "ZmqLogger.h" +#include "Logger.h" #include "effects/AudioVisualization.h" #include @@ -411,7 +411,7 @@ void Clip::Open() void Clip::Close() { if (is_open && reader) { - ZmqLogger::Instance()->AppendDebugMethod("Clip::Close"); + Logger::Instance()->AppendDebugMethod("Clip::Close"); // Close the reader reader->Close(); @@ -765,7 +765,7 @@ std::shared_ptr Clip::GetOrCreateFrame(int64_t number, bool enable_time) } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Clip::GetOrCreateFrame (from reader)", "number", number, "clip_frame_number", clip_frame_number); @@ -802,7 +802,7 @@ std::shared_ptr Clip::GetOrCreateFrame(int64_t number, bool enable_time) int estimated_samples_in_frame = Frame::GetSamplesPerFrame(number, reader->info.fps, reader->info.sample_rate, reader->info.channels); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Clip::GetOrCreateFrame (create blank)", "number", number, "estimated_samples_in_frame", estimated_samples_in_frame); @@ -1477,7 +1477,7 @@ void Clip::apply_waveform(std::shared_ptr frame, QSize timeline_size) { } // Debug output - ZmqLogger::Instance()->AppendDebugMethod("Clip::apply_waveform (Generate Waveform Image)", + Logger::Instance()->AppendDebugMethod("Clip::apply_waveform (Generate Waveform Image)", "frame->number", frame->number, "Waveform()", Waveform(), "width", timeline_size.width(), @@ -1673,7 +1673,7 @@ QTransform Clip::get_transform(std::shared_ptr frame, int width, int heig } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Clip::get_transform (Gravity)", "frame->number", frame->number, "source_clip->gravity", gravity, @@ -1708,7 +1708,7 @@ QTransform Clip::get_transform(std::shared_ptr frame, int width, int heig float origin_y_value = origin_y.GetValue(frame->number); // Transform source image (if needed) - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Clip::get_transform (Build QTransform - if needed)", "frame->number", frame->number, "x", x, "y", y, diff --git a/src/CrashHandler.cpp b/src/CrashHandler.cpp index 26018cc56..c47b98c80 100644 --- a/src/CrashHandler.cpp +++ b/src/CrashHandler.cpp @@ -112,7 +112,7 @@ void CrashHandler::abortHandler( int signum, siginfo_t* si, void* unused ) void CrashHandler::printStackTrace(FILE *out, unsigned int max_frames) { fprintf(out, "---- Unhandled Exception: Stack Trace ----\n"); - ZmqLogger::Instance()->LogToFile("---- Unhandled Exception: Stack Trace ----\n"); + Logger::Instance()->LogToFile("---- Unhandled Exception: Stack Trace ----\n"); stringstream stack_output; #ifdef __MINGW32__ @@ -197,7 +197,7 @@ void CrashHandler::printStackTrace(FILE *out, unsigned int max_frames) if ( addrlen == 0 ) { fprintf(out, " No stack trace found (addrlen == 0)\n"); - ZmqLogger::Instance()->LogToFile(" No stack trace found (addrlen == 0)\n"); + Logger::Instance()->LogToFile(" No stack trace found (addrlen == 0)\n"); return; } @@ -302,8 +302,8 @@ void CrashHandler::printStackTrace(FILE *out, unsigned int max_frames) #endif // Write stacktrace to file (if log path set) - ZmqLogger::Instance()->LogToFile(stack_output.str()); + Logger::Instance()->LogToFile(stack_output.str()); fprintf(out, "---- End of Stack Trace ----\n"); - ZmqLogger::Instance()->LogToFile("---- End of Stack Trace ----\n"); + Logger::Instance()->LogToFile("---- End of Stack Trace ----\n"); } diff --git a/src/CrashHandler.h b/src/CrashHandler.h index 1fe5d8526..ca13029e5 100644 --- a/src/CrashHandler.h +++ b/src/CrashHandler.h @@ -25,7 +25,7 @@ #endif #include #include -#include "ZmqLogger.h" +#include "Logger.h" namespace openshot { @@ -33,7 +33,7 @@ namespace openshot { * @brief This class is designed to catch exceptions thrown by libc (SIGABRT, SIGSEGV, SIGILL, SIGFPE) * * This class is a singleton which only needs to be instantiated 1 time, and it will register as a signal - * handler with libc, and log errors using the ZmqLogger class. + * handler with libc, and log errors using the Logger class. */ class CrashHandler { private: diff --git a/src/EffectBase.cpp b/src/EffectBase.cpp index fc8cc0087..d3fd1c80d 100644 --- a/src/EffectBase.cpp +++ b/src/EffectBase.cpp @@ -24,7 +24,7 @@ #include "ChunkReader.h" #include "FFmpegReader.h" #include "QtImageReader.h" -#include "ZmqLogger.h" +#include "Logger.h" #include #include #include @@ -526,7 +526,7 @@ std::shared_ptr EffectBase::GetMaskImage(std::shared_ptr target_ source_mask = std::make_shared(*source_frame->GetImage()); } } catch (const std::exception& e) { - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( std::string("EffectBase::GetMaskImage unable to read mask frame: ") + e.what()); source_mask.reset(); } diff --git a/src/FFmpegReader.cpp b/src/FFmpegReader.cpp index ad901b1f0..a7fa7c123 100644 --- a/src/FFmpegReader.cpp +++ b/src/FFmpegReader.cpp @@ -29,7 +29,7 @@ #include "Exceptions.h" #include "MemoryTrim.h" #include "Timeline.h" -#include "ZmqLogger.h" +#include "Logger.h" #define ENABLE_VAAPI 0 @@ -236,7 +236,7 @@ static enum AVPixelFormat get_hw_dec_format(AVCodecContext *ctx, const enum AVPi break; } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format (Unable to decode this file using hardware decode)"); + Logger::Instance()->AppendDebugMethod("FFmpegReader::get_hw_dec_format (Unable to decode this file using hardware decode)"); return AV_PIX_FMT_NONE; } @@ -273,7 +273,7 @@ void FFmpegReader::Open() { hw_decode_failed = false; hw_decode_error_count = 0; hw_decode_succeeded = false; - ZmqLogger::Instance()->AppendDebugMethod("Decode hardware acceleration settings", "hw_de_on", hw_de_on, "HARDWARE_DECODER", openshot::Settings::Instance()->HARDWARE_DECODER); + Logger::Instance()->AppendDebugMethod("Decode hardware acceleration settings", "hw_de_on", hw_de_on, "HARDWARE_DECODER", openshot::Settings::Instance()->HARDWARE_DECODER); } // Open video file @@ -353,7 +353,7 @@ void FFmpegReader::Open() { char *adapter_ptr = NULL; int adapter_num; adapter_num = openshot::Settings::Instance()->HW_DE_DEVICE_SET; - ZmqLogger::Instance()->AppendDebugMethod("Hardware decoding device number", "adapter_num", adapter_num); + Logger::Instance()->AppendDebugMethod("Hardware decoding device number", "adapter_num", adapter_num); // Set hardware pix format (callback) pCodecCtx->get_format = get_hw_dec_format; @@ -429,11 +429,11 @@ void FFmpegReader::Open() { #elif defined(__APPLE__) if( adapter_ptr != NULL ) { #endif - ZmqLogger::Instance()->AppendDebugMethod("Decode Device present using device"); + Logger::Instance()->AppendDebugMethod("Decode Device present using device"); } else { adapter_ptr = NULL; // use default - ZmqLogger::Instance()->AppendDebugMethod("Decode Device not present using default"); + Logger::Instance()->AppendDebugMethod("Decode Device not present using default"); } hw_device_ctx = NULL; @@ -442,7 +442,7 @@ void FFmpegReader::Open() { const char* hw_name = av_hwdevice_get_type_name(hw_de_av_device_type); std::string hw_msg = "HW decode active: "; hw_msg += (hw_name ? hw_name : "unknown"); - ZmqLogger::Instance()->Log(hw_msg); + Logger::Instance()->Log(hw_msg); if (!(pCodecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx))) { throw InvalidCodec("Hardware device reference create failed.", path); } @@ -475,7 +475,7 @@ void FFmpegReader::Open() { */ } else { - ZmqLogger::Instance()->Log("HW decode active: no (falling back to software)"); + Logger::Instance()->Log("HW decode active: no (falling back to software)"); throw InvalidCodec("Hardware device create failed.", path); } } @@ -513,7 +513,7 @@ void FFmpegReader::Open() { pCodecCtx->coded_height < constraints->min_height || pCodecCtx->coded_width > constraints->max_width || pCodecCtx->coded_height > constraints->max_height) { - ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n"); + Logger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n"); hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -524,7 +524,7 @@ void FFmpegReader::Open() { } else { // All is just peachy - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Min width :", constraints->min_width, "Min Height :", constraints->min_height, "MaxWidth :", constraints->max_width, "MaxHeight :", constraints->max_height, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); + Logger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Min width :", constraints->min_width, "Min Height :", constraints->min_height, "MaxWidth :", constraints->max_width, "MaxHeight :", constraints->max_height, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); retry_decode_open = 0; } av_hwframe_constraints_free(&constraints); @@ -538,13 +538,13 @@ void FFmpegReader::Open() { max_h = openshot::Settings::Instance()->DE_LIMIT_HEIGHT_MAX; //max_w = ((getenv( "LIMIT_WIDTH_MAX" )==NULL) ? MAX_SUPPORTED_WIDTH : atoi(getenv( "LIMIT_WIDTH_MAX" ))); max_w = openshot::Settings::Instance()->DE_LIMIT_WIDTH_MAX; - ZmqLogger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n"); + Logger::Instance()->AppendDebugMethod("Constraints could not be found using default limit\n"); //cerr << "Constraints could not be found using default limit\n"; if (pCodecCtx->coded_width < 0 || pCodecCtx->coded_height < 0 || pCodecCtx->coded_width > max_w || pCodecCtx->coded_height > max_h ) { - ZmqLogger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); + Logger::Instance()->AppendDebugMethod("DIMENSIONS ARE TOO LARGE for hardware acceleration\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); hw_de_supported = 0; retry_decode_open = 1; AV_FREE_CONTEXT(pCodecCtx); @@ -554,13 +554,13 @@ void FFmpegReader::Open() { } } else { - ZmqLogger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); + Logger::Instance()->AppendDebugMethod("\nDecode hardware acceleration is used\n", "Max Width :", max_w, "Max Height :", max_h, "Frame width :", pCodecCtx->coded_width, "Frame height :", pCodecCtx->coded_height); retry_decode_open = 0; } } } // if hw_de_on && hw_de_supported else { - ZmqLogger::Instance()->AppendDebugMethod("\nDecode in software is used\n"); + Logger::Instance()->AppendDebugMethod("\nDecode in software is used\n"); } #else retry_decode_open = 0; @@ -616,7 +616,7 @@ void FFmpegReader::Open() { (info.audio_timebase.den <= 0) || (aCodecCtx->sample_fmt == AV_SAMPLE_FMT_NONE); if (invalid_audio_info) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::Open (Disable invalid audio stream)", "channels", info.channels, "sample_rate", info.sample_rate, @@ -638,7 +638,7 @@ void FFmpegReader::Open() { } } else { // Keep decoding video, but disable bad/unsupported audio stream. - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::Open (Audio codec unavailable; disabling audio)", "audioStream", audioStream); info.has_audio = false; @@ -655,7 +655,7 @@ void FFmpegReader::Open() { // Guard invalid frame-rate / timebase values from malformed streams. if (info.fps.num <= 0 || info.fps.den <= 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::Open (Invalid FPS detected; applying fallback)", "fps.num", info.fps.num, "fps.den", info.fps.den); @@ -663,7 +663,7 @@ void FFmpegReader::Open() { info.fps.den = 1; } if (info.video_timebase.num <= 0 || info.video_timebase.den <= 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::Open (Invalid video_timebase detected; applying fallback)", "video_timebase.num", info.video_timebase.num, "video_timebase.den", info.video_timebase.den); @@ -1054,11 +1054,11 @@ void FFmpegReader::UpdateVideoInfo() { info.fps.den = framerate.den; } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::UpdateVideoInfo", "info.fps.num", info.fps.num, "info.fps.den", info.fps.den); + Logger::Instance()->AppendDebugMethod("FFmpegReader::UpdateVideoInfo", "info.fps.num", info.fps.num, "info.fps.den", info.fps.den); // TODO: remove excessive debug info in the next releases // The debug info below is just for comparison and troubleshooting on users side during the transition period - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::UpdateVideoInfo (pStream->avg_frame_rate)", "num", pStream->avg_frame_rate.num, "den", pStream->avg_frame_rate.den); + Logger::Instance()->AppendDebugMethod("FFmpegReader::UpdateVideoInfo (pStream->avg_frame_rate)", "num", pStream->avg_frame_rate.num, "den", pStream->avg_frame_rate.den); if (pStream->sample_aspect_ratio.num != 0) { info.pixel_ratio.num = pStream->sample_aspect_ratio.num; @@ -1260,13 +1260,13 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) { throw InvalidFile("Could not detect the duration of the video or audio stream.", path); // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "requested_frame", requested_frame, "last_frame", last_frame); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "requested_frame", requested_frame, "last_frame", last_frame); // Check the cache for this frame std::shared_ptr frame = final_cache.GetFrame(requested_frame); if (frame) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame", requested_frame); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame", requested_frame); // Return the cached frame return frame; } else { @@ -1278,7 +1278,7 @@ std::shared_ptr FFmpegReader::GetFrame(int64_t requested_frame) { frame = final_cache.GetFrame(requested_frame); if (frame) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame on 2nd look", requested_frame); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetFrame", "returned cached frame on 2nd look", requested_frame); } else { // Frame is not in cache // Reset seek count @@ -1321,7 +1321,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { double prev_video_pts_seconds = video_pts_seconds; // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream", "requested_frame", requested_frame); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream", "requested_frame", requested_frame); // Loop through the stream until the correct frame is found while (true) { @@ -1347,7 +1347,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { } // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (GetNextPacket)", "requested_frame", requested_frame,"packets_read", packet_status.packets_read(), "packets_decoded", packet_status.packets_decoded(), "is_seeking", is_seeking); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (GetNextPacket)", "requested_frame", requested_frame,"packets_read", packet_status.packets_read(), "packets_decoded", packet_status.packets_decoded(), "is_seeking", is_seeking); // Check the status of a seek (if any) if (is_seeking) { @@ -1403,7 +1403,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { if ((packet_status.packets_eof && packet_status.packets_read() == packet_status.packets_decoded()) || packet_status.end_of_file) { // Force EOF (end of file) variables to true, if decoder does not support EOF detection. // If we have no more packets, and all known packets have been decoded - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (force EOF)", "packets_read", packet_status.packets_read(), "packets_decoded", packet_status.packets_decoded(), "packets_eof", packet_status.packets_eof, "video_eof", packet_status.video_eof, "audio_eof", packet_status.audio_eof, "end_of_file", packet_status.end_of_file); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (force EOF)", "packets_read", packet_status.packets_read(), "packets_decoded", packet_status.packets_decoded(), "packets_eof", packet_status.packets_eof, "video_eof", packet_status.video_eof, "audio_eof", packet_status.audio_eof, "end_of_file", packet_status.end_of_file); if (!packet_status.video_eof) { packet_status.video_eof = true; } @@ -1430,7 +1430,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { && packet_status.packets_eof && !packet && !hold_packet) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (force EOF after stall)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (force EOF after stall)", "requested_frame", requested_frame, "no_progress_count", no_progress_count, "packets_read", packet_status.packets_read(), @@ -1450,7 +1450,7 @@ std::shared_ptr FFmpegReader::ReadStream(int64_t requested_frame) { } // end while // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Completed)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ReadStream (Completed)", "packets_read", packet_status.packets_read(), "packets_decoded", packet_status.packets_decoded(), "end_of_file", packet_status.end_of_file, @@ -1555,7 +1555,7 @@ bool FFmpegReader::GetAVFrame() { } if (err == AVERROR_INVALIDDATA && packet_status.video_decoded == 0) { hw_decode_error_count++; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( std::string("FFmpegReader::GetAVFrame (hardware decode failure candidate during ") + stage + ")", "error_count", hw_decode_error_count, "error", err); @@ -1581,7 +1581,7 @@ bool FFmpegReader::GetAVFrame() { if (packet && send_packet_err >= 0) { send_packet_pts = GetPacketPTS(); hold_packet = false; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet succeeded)", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet succeeded)", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts); } } @@ -1591,17 +1591,17 @@ bool FFmpegReader::GetAVFrame() { hw_de_av_device_type = hw_de_av_device_type_global; #endif // USE_HW_ACCEL if (send_packet_err < 0 && send_packet_err != AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: Not sent [" + av_err2string(send_packet_err) + "])", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: Not sent [" + av_err2string(send_packet_err) + "])", "send_packet_err", send_packet_err, "send_packet_pts", send_packet_pts); note_hw_decode_failure(send_packet_err, "send_packet"); if (send_packet_err == AVERROR(EAGAIN)) { hold_packet = true; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EAGAIN): user must read output with avcodec_receive_frame()", "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EAGAIN): user must read output with avcodec_receive_frame()", "send_packet_pts", send_packet_pts); } if (send_packet_err == AVERROR(EINVAL)) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EINVAL): codec not opened, it is an encoder, or requires flush", "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(EINVAL): codec not opened, it is an encoder, or requires flush", "send_packet_pts", send_packet_pts); } if (send_packet_err == AVERROR(ENOMEM)) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(ENOMEM): failed to add packet to internal queue, or legitimate decoding errors", "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (send packet: AVERROR(ENOMEM): failed to add packet to internal queue, or legitimate decoding errors", "send_packet_pts", send_packet_pts); } } @@ -1625,26 +1625,26 @@ bool FFmpegReader::GetAVFrame() { receive_frame_err = avcodec_receive_frame(pCodecCtx, next_frame2); if (receive_frame_err != 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (receive frame: frame not ready yet from decoder [\" + av_err2string(receive_frame_err) + \"])", "receive_frame_err", receive_frame_err, "send_packet_pts", send_packet_pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAVFrame (receive frame: frame not ready yet from decoder [\" + av_err2string(receive_frame_err) + \"])", "receive_frame_err", receive_frame_err, "send_packet_pts", send_packet_pts); note_hw_decode_failure(receive_frame_err, "receive_frame"); if (receive_frame_err == AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (receive frame: AVERROR_EOF: EOF detected from decoder, flushing buffers)", "send_packet_pts", send_packet_pts); avcodec_flush_buffers(pCodecCtx); packet_status.video_eof = true; } if (receive_frame_err == AVERROR(EINVAL)) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (receive frame: AVERROR(EINVAL): invalid frame received, flushing buffers)", "send_packet_pts", send_packet_pts); avcodec_flush_buffers(pCodecCtx); } if (receive_frame_err == AVERROR(EAGAIN)) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (receive frame: AVERROR(EAGAIN): output is not available in this state - user must try to send new input)", "send_packet_pts", send_packet_pts); } if (receive_frame_err == AVERROR_INPUT_CHANGED) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (receive frame: AVERROR_INPUT_CHANGED: current decoded frame has changed parameters with respect to first decoded frame)", "send_packet_pts", send_packet_pts); } @@ -1658,7 +1658,7 @@ bool FFmpegReader::GetAVFrame() { int err; if (next_frame2->format == hw_de_av_pix_fmt) { if ((err = av_hwframe_transfer_data(next_frame, next_frame2, 0)) < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (Failed to transfer data to output frame)", "hw_de_on", hw_de_on, "error", err); @@ -1666,7 +1666,7 @@ bool FFmpegReader::GetAVFrame() { break; } if ((err = av_frame_copy_props(next_frame, next_frame2)) < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (Failed to copy props to output frame)", "hw_de_on", hw_de_on, "error", err); @@ -1695,7 +1695,7 @@ bool FFmpegReader::GetAVFrame() { } if (!decoded_frame->data[0]) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (Decoded frame missing image data)", "format", decoded_frame->format, "width", decoded_frame->width, @@ -1748,7 +1748,7 @@ bool FFmpegReader::GetAVFrame() { video_pts = decoded_frame->pkt_dts; } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::GetAVFrame (Successful frame received)", "video_pts", video_pts, "send_packet_pts", send_packet_pts); // break out of loop after each successful image returned @@ -1788,7 +1788,7 @@ bool FFmpegReader::ReopenWithoutHardwareDecode(int64_t requested_frame) { return false; } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::ReopenWithoutHardwareDecode (falling back to software decode)", "requested_frame", requested_frame, "video_packets_read", packet_status.video_read, @@ -1846,7 +1846,7 @@ bool FFmpegReader::CheckSeek() { // determine if we are "before" the requested frame if (max_seeked_frame >= seeking_frame) { // SEEKED TOO FAR - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Too far, seek again)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Too far, seek again)", "is_video_seek", is_video_seek, "max_seeked_frame", max_seeked_frame, "seeking_frame", seeking_frame, @@ -1866,7 +1866,7 @@ bool FFmpegReader::CheckSeek() { } } else { // SEEK WORKED - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Successful)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckSeek (Successful)", "is_video_seek", is_video_seek, "packet->pts", GetPacketPTS(), "seeking_pts", seeking_pts, @@ -1914,7 +1914,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { working_cache.Add(CreateFrame(requested_frame)); // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessVideoPacket (Before)", "requested_frame", requested_frame, "current_frame", current_frame); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessVideoPacket (Before)", "requested_frame", requested_frame, "current_frame", current_frame); // Init some things local (for OpenMP) AVPixelFormat decoded_pix_fmt = (pFrame && pFrame->format != AV_PIX_FMT_NONE) @@ -2062,7 +2062,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { #if USE_HW_ACCEL if (hw_de_on && hw_de_supported && !force_sw_decode) { hw_decode_failed = true; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::ProcessVideoPacket (Invalid source frame; forcing software fallback)", "requested_frame", requested_frame, "current_frame", current_frame, @@ -2085,7 +2085,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { #if USE_HW_ACCEL if (hw_de_on && hw_de_supported && !force_sw_decode) { hw_decode_failed = true; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::ProcessVideoPacket (sws_scale failed; forcing software fallback)", "requested_frame", requested_frame, "current_frame", current_frame, @@ -2131,7 +2131,7 @@ void FFmpegReader::ProcessVideoPacket(int64_t requested_frame) { video_pts_seconds = (double(video_pts) * info.video_timebase.ToDouble()) + pts_offset_seconds; // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessVideoPacket (After)", "requested_frame", requested_frame, "current_frame", current_frame, "f->number", f->number, "video_pts_seconds", video_pts_seconds); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessVideoPacket (After)", "requested_frame", requested_frame, "current_frame", current_frame, "f->number", f->number, "video_pts_seconds", video_pts_seconds); } // Process an audio packet @@ -2154,7 +2154,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { working_cache.Add(CreateFrame(requested_frame)); // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (Before)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (Before)", "requested_frame", requested_frame, "target_frame", location.frame, "starting_sample", location.sample_start); @@ -2170,7 +2170,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { #if IS_FFMPEG_3_2 int send_packet_err = avcodec_send_packet(aCodecCtx, packet); if (send_packet_err < 0 && send_packet_err != AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (Packet not sent)"); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (Packet not sent)"); } else { int receive_frame_err = avcodec_receive_frame(aCodecCtx, audio_frame); @@ -2178,15 +2178,15 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { frame_finished = 1; } if (receive_frame_err == AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (EOF detected from decoder)"); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (EOF detected from decoder)"); packet_status.audio_eof = true; } if (receive_frame_err == AVERROR(EINVAL) || receive_frame_err == AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (invalid frame received or EOF from decoder)"); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (invalid frame received or EOF from decoder)"); avcodec_flush_buffers(aCodecCtx); } if (receive_frame_err != 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (frame not ready yet from decoder)"); + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (frame not ready yet from decoder)"); } } #else @@ -2228,7 +2228,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { // Bail if no samples found if (pts_remaining_samples == 0) { - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (No samples, bailing)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (No samples, bailing)", "packet_samples", packet_samples, "info.channels", info.channels, "pts_remaining_samples", pts_remaining_samples); @@ -2257,7 +2257,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { } } - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (ReSample)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (ReSample)", "packet_samples", packet_samples, "info.channels", info.channels, "info.sample_rate", info.sample_rate, @@ -2341,7 +2341,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { f->AddAudio(true, channel_filter, start, channel_buffer, samples, 1.0f); // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (f->AddAudio)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (f->AddAudio)", "frame", starting_frame_number, "start", start, "samples", samples, @@ -2375,7 +2375,7 @@ void FFmpegReader::ProcessAudioPacket(int64_t requested_frame) { audio_pts_seconds = (double(audio_pts) * info.audio_timebase.ToDouble()) + pts_offset_seconds; // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (After)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::ProcessAudioPacket (After)", "requested_frame", requested_frame, "starting_frame", location.frame, "end_frame", starting_frame_number - 1, @@ -2397,7 +2397,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { } // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::Seek", + Logger::Instance()->AppendDebugMethod("FFmpegReader::Seek", "requested_frame", requested_frame, "seek_count", seek_count, "last_frame", last_frame); @@ -2457,7 +2457,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { if (!seek_worked && info.has_video && !HasAlbumArt()) { seek_target = ConvertFrameToVideoPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.video_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - ZmqLogger::Instance()->Log(std::string(pFormatCtx->AV_FILENAME) + ": error while seeking video stream"); + Logger::Instance()->Log(std::string(pFormatCtx->AV_FILENAME) + ": error while seeking video stream"); } else { // VIDEO SEEK is_video_seek = true; @@ -2469,7 +2469,7 @@ void FFmpegReader::Seek(int64_t requested_frame) { if (!seek_worked && info.has_audio) { seek_target = ConvertFrameToAudioPTS(requested_frame - buffer_amount); if (av_seek_frame(pFormatCtx, info.audio_stream_index, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { - ZmqLogger::Instance()->Log(std::string(pFormatCtx->AV_FILENAME) + ": error while seeking audio stream"); + Logger::Instance()->Log(std::string(pFormatCtx->AV_FILENAME) + ": error while seeking audio stream"); } else { // AUDIO SEEK is_video_seek = false; @@ -2726,11 +2726,11 @@ AudioLocation FFmpegReader::GetAudioPTSLocation(int64_t pts) { location.frame = previous_packet_location.frame; // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (Audio Gap Detected)", "Source Frame", orig_frame, "Source Audio Sample", orig_start, "Target Frame", location.frame, "Target Audio Sample", location.sample_start, "pts", pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (Audio Gap Detected)", "Source Frame", orig_frame, "Source Audio Sample", orig_start, "Target Frame", location.frame, "Target Audio Sample", location.sample_start, "pts", pts); } else { // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (Audio Gap Ignored - too big)", "Previous location frame", previous_packet_location.frame, "Target Frame", location.frame, "Target Audio Sample", location.sample_start, "pts", pts); + Logger::Instance()->AppendDebugMethod("FFmpegReader::GetAudioPTSLocation (Audio Gap Ignored - too big)", "Previous location frame", previous_packet_location.frame, "Target Frame", location.frame, "Target Audio Sample", location.sample_start, "pts", pts); } } @@ -2820,7 +2820,7 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) { // Video stream is past this frame (so it must be done) // OR video stream is too far behind, missing, or end-of-file is_video_ready = true; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (video ready)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (video ready)", "frame_number", f->number, "frame_pts_seconds", frame_pts_seconds, "video_pts_seconds", video_pts_seconds, @@ -2852,7 +2852,7 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) { // Last-resort fallback if no prior image is available. if (!f->has_image_data) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegReader::CheckWorkingFrames (no previous image found; using black frame)", "frame_number", f->number); f->AddColor("#000000"); @@ -2868,7 +2868,7 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) { // OR audio stream is too far behind, missing, or end-of-file // Adding a bit of margin here, to allow for partial audio packets is_audio_ready = true; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (audio ready)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (audio ready)", "frame_number", f->number, "frame_pts_seconds", frame_pts_seconds, "audio_pts_seconds", audio_pts_seconds, @@ -2882,7 +2882,7 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) { if (!info.has_audio) is_audio_ready = true; // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames", "frame_number", f->number, "is_video_ready", is_video_ready, "is_audio_ready", is_audio_ready, @@ -2944,7 +2944,7 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) { } if ((!packet_status.end_of_file && is_video_ready && is_audio_ready) || packet_status.end_of_file || is_seek_trash) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (mark frame as final)", + Logger::Instance()->AppendDebugMethod("FFmpegReader::CheckWorkingFrames (mark frame as final)", "requested_frame", requested_frame, "f->number", f->number, "is_seek_trash", is_seek_trash, diff --git a/src/FFmpegWriter.cpp b/src/FFmpegWriter.cpp index 4ae40ec50..12c477d09 100644 --- a/src/FFmpegWriter.cpp +++ b/src/FFmpegWriter.cpp @@ -27,7 +27,7 @@ #include "Frame.h" #include "OpenMPUtilities.h" #include "Settings.h" -#include "ZmqLogger.h" +#include "Logger.h" using namespace openshot; @@ -143,7 +143,7 @@ void FFmpegWriter::auto_detect_format() { // initialize streams void FFmpegWriter::initialize_streams() { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::initialize_streams", "oc->oformat->video_codec", oc->oformat->video_codec, "oc->oformat->audio_codec", oc->oformat->audio_codec, @@ -264,7 +264,7 @@ void FFmpegWriter::SetVideoOptions(bool has_video, std::string codec, Fraction f info.display_ratio.num = size.num; info.display_ratio.den = size.den; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::SetVideoOptions (" + codec + ")", "width", width, "height", height, "size.num", size.num, "size.den", size.den, @@ -310,7 +310,7 @@ void FFmpegWriter::SetAudioOptions(bool has_audio, std::string codec, int sample if (original_channels == 0) original_channels = info.channels; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::SetAudioOptions (" + codec + ")", "sample_rate", sample_rate, "channels", channels, @@ -579,7 +579,7 @@ void FFmpegWriter::SetOption(StreamType stream, std::string name, std::string va AV_OPTION_SET(st, c->priv_data, name.c_str(), value.c_str(), c); } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::SetOption (" + (std::string)name + ")", "stream == VIDEO_STREAM", stream == VIDEO_STREAM); @@ -614,7 +614,7 @@ void FFmpegWriter::PrepareStreams() { if (!info.has_audio && !info.has_video) throw InvalidOptions("No video or audio options have been set. You must set has_video or has_audio (or both).", path); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::PrepareStreams [" + path + "]", "info.has_audio", info.has_audio, "info.has_video", info.has_video); @@ -653,7 +653,7 @@ void FFmpegWriter::WriteHeader() { // Write the stream header if (avformat_write_header(oc, &dict) != 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::WriteHeader (avformat_write_header)"); throw InvalidFile("Could not write header to file.", path); }; @@ -665,7 +665,7 @@ void FFmpegWriter::WriteHeader() { // Mark as 'written' write_header = true; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::WriteHeader"); + Logger::Instance()->AppendDebugMethod("FFmpegWriter::WriteHeader"); } // Add a frame to the queue waiting to be encoded. @@ -674,7 +674,7 @@ void FFmpegWriter::WriteFrame(std::shared_ptr frame) { if (!is_open) throw WriterClosed("The FFmpegWriter is closed. Call Open() before calling this method.", path); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::WriteFrame", "frame->number", frame->number, "is_writing", is_writing); @@ -695,7 +695,7 @@ void FFmpegWriter::WriteFrameAt(std::shared_ptr frame, int64_t frame_number = 1; } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::WriteFrameAt", "frame->number", frame->number, "output_frame_number", frame_number, @@ -762,7 +762,7 @@ void FFmpegWriter::write_frame(std::shared_ptr frame) { // Write a block of frames from a reader void FFmpegWriter::WriteFrame(ReaderBase *reader, int64_t start, int64_t length) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::WriteFrame (from Reader)", "start", start, "length", length); @@ -802,7 +802,7 @@ void FFmpegWriter::WriteTrailer() { // Mark as 'written' write_trailer = true; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::WriteTrailer"); + Logger::Instance()->AppendDebugMethod("FFmpegWriter::WriteTrailer"); } // Flush encoders @@ -865,7 +865,7 @@ void FFmpegWriter::flush_encoders() { #endif // IS_FFMPEG_3_2 if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -884,7 +884,7 @@ void FFmpegWriter::flush_encoders() { // Write packet error_code = av_interleaved_write_frame(oc, pkt); if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -915,7 +915,7 @@ void FFmpegWriter::flush_encoders() { } error_code = avcodec_send_frame(audio_codec_ctx, NULL); if (error_code < 0 && error_code != AVERROR_EOF) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -927,7 +927,7 @@ void FFmpegWriter::flush_encoders() { break; } if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -952,7 +952,7 @@ void FFmpegWriter::flush_encoders() { error_code = av_interleaved_write_frame(oc, pkt); if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -965,7 +965,7 @@ void FFmpegWriter::flush_encoders() { #else error_code = avcodec_encode_audio2(audio_codec_ctx, pkt, NULL, &got_packet); if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -991,7 +991,7 @@ void FFmpegWriter::flush_encoders() { // Write packet error_code = av_interleaved_write_frame(oc, pkt); if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::flush_encoders ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -1093,7 +1093,7 @@ void FFmpegWriter::Close() { write_header = false; write_trailer = false; - ZmqLogger::Instance()->AppendDebugMethod("FFmpegWriter::Close"); + Logger::Instance()->AppendDebugMethod("FFmpegWriter::Close"); } // Add an AVFrame to the cache @@ -1233,7 +1233,7 @@ AVStream *FFmpegWriter::add_audio_stream() { channel_layout_label = "c->channel_layout"; #endif - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::add_audio_stream", "c->codec_id", c->codec_id, "c->bit_rate", c->bit_rate, @@ -1429,7 +1429,7 @@ AVStream *FFmpegWriter::add_video_stream() { } AV_COPY_PARAMS_FROM_CONTEXT(st, c); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::add_video_stream (" + (std::string)oc->oformat->name + " : " + (std::string)av_get_pix_fmt_name(c->pix_fmt) + ")", @@ -1508,7 +1508,7 @@ void FFmpegWriter::open_audio(AVFormatContext *oc, AVStream *st) { av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::open_audio", "audio_codec_ctx->thread_count", audio_codec_ctx->thread_count, "audio_input_frame_size", audio_input_frame_size, @@ -1550,19 +1550,19 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { #elif defined(_WIN32) || defined(__APPLE__) if( adapter_ptr != NULL ) { #endif - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Encode Device present using device", "adapter", adapter_num); } else { adapter_ptr = NULL; // use default - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Encode Device not present, using default"); } if (av_hwdevice_ctx_create(&hw_device_ctx, hw_en_av_device_type, adapter_ptr, NULL, 0) < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::open_video ERROR creating hwdevice, Codec name:", info.vcodec.c_str(), -1); throw InvalidCodec("Could not create hwdevice", path); @@ -1626,7 +1626,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { // tested to work with defaults break; default: - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "No codec-specific options defined for this codec. HW encoding may fail", "codec_id", video_codec_ctx->codec_id); break; @@ -1636,7 +1636,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { int err; if ((err = set_hwframe_ctx(video_codec_ctx, hw_device_ctx, info.width, info.height)) < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::open_video (set_hwframe_ctx) ERROR faled to set hwframe context", "width", info.width, "height", info.height, @@ -1671,7 +1671,7 @@ void FFmpegWriter::open_video(AVFormatContext *oc, AVStream *st) { av_dict_set(&st->metadata, iter->first.c_str(), iter->second.c_str(), 0); } - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::open_video", "video_codec_ctx->thread_count", video_codec_ctx->thread_count); @@ -1742,7 +1742,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrAppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets", "is_final", is_final, "total_frame_samples", total_frame_samples, @@ -1764,7 +1764,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrAppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -1804,7 +1804,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrnb_samples = total_frame_samples / channels_in_frame; av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, audio_converted->nb_samples, output_sample_fmt, 0); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets (1st resampling)", "in_sample_fmt", AV_SAMPLE_FMT_S16, "out_sample_fmt", output_sample_fmt, @@ -1873,7 +1873,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrAppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets (Successfully completed 1st resampling)", "nb_samples", nb_samples, "remaining_frame_samples", remaining_frame_samples); @@ -1927,7 +1927,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrsample_fmt)) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets (2nd resampling for Planar formats)", "in_sample_fmt", output_sample_fmt, "out_sample_fmt", audio_codec_ctx->sample_fmt, @@ -2018,7 +2018,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrAppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets (Successfully completed 2nd resampling for Planar formats)", "nb_samples", nb_samples); @@ -2130,7 +2130,7 @@ void FFmpegWriter::write_audio_packets(bool is_final, std::shared_ptrAppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_audio_packets ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -2320,7 +2320,7 @@ void FFmpegWriter::process_video_packet(std::shared_ptr frame) { bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *frame_final) { #if (LIBAVFORMAT_VERSION_MAJOR >= 58) // FFmpeg 4.0+ - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags", oc->oformat->flags); @@ -2330,7 +2330,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra // TODO: Should we have moved away from oc->oformat->flags / AVFMT_RAWPICTURE // on ffmpeg < 4.0 as well? // Does AV_CODEC_ID_RAWVIDEO not work in ffmpeg 3.x? - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet", "frame->number", frame->number, "oc->oformat->flags & AVFMT_RAWPICTURE", oc->oformat->flags & AVFMT_RAWPICTURE); @@ -2359,7 +2359,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra /* write the compressed frame in the media file */ int error_code = av_interleaved_write_frame(oc, pkt); if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); @@ -2419,7 +2419,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra } error_code = ret; if (ret < 0 ) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet (Frame not sent)"); if (ret == AVERROR(EAGAIN) ) { std::clog << "Frame EAGAIN\n"; @@ -2447,13 +2447,13 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra // Write video packet (older than FFmpeg 3.2) error_code = avcodec_encode_video2(video_codec_ctx, pkt, frame_final, &got_packet_ptr); if (error_code != 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); } if (got_packet_ptr == 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet (Frame gotpacket error)"); } #endif // IS_FFMPEG_3_2 @@ -2470,7 +2470,7 @@ bool FFmpegWriter::write_video_packet(std::shared_ptr frame, AVFrame *fra /* write the compressed frame in the media file */ int result = av_interleaved_write_frame(oc, pkt); if (result < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FFmpegWriter::write_video_packet ERROR [" + av_err2string(result) + "]", "result", result); diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index 5080924c0..5405c767f 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -19,7 +19,7 @@ #include "Exceptions.h" #include "Clip.h" #include "MemoryTrim.h" -#include "ZmqLogger.h" +#include "Logger.h" using namespace std; using namespace openshot; @@ -114,7 +114,7 @@ void FrameMapper::Clear() { // whether the frame rate is increasing or decreasing. void FrameMapper::Init() { - ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Init (Calculate frame mappings)"); + Logger::Instance()->AppendDebugMethod("FrameMapper::Init (Calculate frame mappings)"); // Do not initialize anything if just a picture with no audio if (info.has_video and !info.has_audio and info.has_single_image) @@ -378,7 +378,7 @@ MappedFrame FrameMapper::GetMappedFrame(int64_t TargetFrameNumber) TargetFrameNumber = frames.size(); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::GetMappedFrame", "TargetFrameNumber", TargetFrameNumber, "frames.size()", frames.size(), @@ -399,7 +399,7 @@ std::shared_ptr FrameMapper::GetOrCreateFrame(int64_t number) try { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame); @@ -417,7 +417,7 @@ std::shared_ptr FrameMapper::GetOrCreateFrame(int64_t number) } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::GetOrCreateFrame (create blank)", "number", number, "samples_in_frame", samples_in_frame); @@ -494,7 +494,7 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) int minimum_frames = 1; // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::GetFrame (Loop through frames)", "requested_frame", requested_frame, "minimum_frames", minimum_frames); @@ -503,7 +503,7 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame) for (int64_t frame_number = requested_frame; frame_number < requested_frame + minimum_frames; frame_number++) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::GetFrame (inside omp for loop)", "frame_number", frame_number, "minimum_frames", minimum_frames, @@ -727,7 +727,7 @@ void FrameMapper::Open() { if (reader) { - ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Open"); + Logger::Instance()->AppendDebugMethod("FrameMapper::Open"); // Open the reader reader->Open(); @@ -742,7 +742,7 @@ void FrameMapper::Close() // Create a scoped lock, allowing only a single thread to run the following code at one time const std::lock_guard lock(getFrameMutex); - ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Close"); + Logger::Instance()->AppendDebugMethod("FrameMapper::Close"); // Close internal reader reader->Close(); @@ -824,7 +824,7 @@ void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldow // the resampler while this mapping update is in progress. const std::lock_guard lock(getFrameMutex); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::ChangeMapping", "target_fps.num", target_fps.num, "target_fps.den", target_fps.den, @@ -889,7 +889,7 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig int samples_in_frame = frame->GetAudioSamplesCount(); ChannelLayout channel_layout_in_frame = frame->ChannelsLayout(); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::ResampleMappedAudio", "frame->number", frame->number, "original_frame_number", original_frame_number, @@ -942,7 +942,7 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig if (error_code < 0) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::ResampleMappedAudio ERROR [" + av_err2string(error_code) + "]", "error_code", error_code); throw ErrorEncodingVideo("Error while resampling audio in frame mapper", frame->number); @@ -1029,7 +1029,7 @@ void FrameMapper::ResampleMappedAudio(std::shared_ptr frame, int64_t orig int channel_buffer_size = nb_samples; frame->ResizeAudio(info.channels, channel_buffer_size, info.sample_rate, info.channel_layout); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "FrameMapper::ResampleMappedAudio (Audio successfully resampled)", "nb_samples", nb_samples, "total_frame_samples", total_frame_samples, diff --git a/src/ImageWriter.cpp b/src/ImageWriter.cpp index c56aab72f..883e663e1 100644 --- a/src/ImageWriter.cpp +++ b/src/ImageWriter.cpp @@ -20,7 +20,7 @@ #include "Exceptions.h" #include "Frame.h" #include "ReaderBase.h" -#include "ZmqLogger.h" +#include "Logger.h" using namespace openshot; @@ -65,7 +65,7 @@ void ImageWriter::SetVideoOptions( // Set the ratio based on the reduced fraction info.display_ratio = size; - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "ImageWriter::SetVideoOptions (" + format + ")", "width", width, "height", height, @@ -120,7 +120,7 @@ void ImageWriter::WriteFrame(std::shared_ptr frame) // Write a block of frames from a reader void ImageWriter::WriteFrame(ReaderBase* reader, int64_t start, int64_t length) { - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "ImageWriter::WriteFrame (from Reader)", "start", start, "length", length); @@ -147,7 +147,7 @@ void ImageWriter::Close() write_video_count = 0; is_open = false; - ZmqLogger::Instance()->AppendDebugMethod("ImageWriter::Close"); + Logger::Instance()->AppendDebugMethod("ImageWriter::Close"); } #endif //USE_IMAGEMAGICK diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 000000000..77ef01f5b --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,204 @@ +// Copyright (c) 2008-2026 OpenShot Studios, LLC +// SPDX-License-Identifier: LGPL-3.0-or-later +#include "Logger.h" +#include "Settings.h" +#if USE_RESVG == 1 +#include "ResvgQt.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace openshot; +namespace { +int ParseLevel(std::string value) { + std::transform(value.begin(), value.end(), value.begin(), + [](unsigned char c) { return std::toupper(c); }); + if (value == "DEBUG") return Logger::LevelDebug; + if (value == "INFO") return Logger::LevelInfo; + if (value == "WARNING") return Logger::LevelWarning; + if (value == "ERROR") return Logger::LevelError; + if (value == "CRITICAL") return Logger::LevelCritical; + if (value == "OFF") return Logger::LevelOff; + throw std::invalid_argument("Invalid logging level: " + value); +} +const char* LevelName(Logger::Level level) { + switch (level) { + case Logger::LevelDebug: return "DEBUG"; + case Logger::LevelInfo: return "INFO"; + case Logger::LevelWarning: return "WARNING"; + case Logger::LevelError: return "ERROR"; + case Logger::LevelCritical: return "CRITICAL"; + default: return "OFF"; + } +} +std::tm LocalTime(std::time_t now) { + std::tm result{}; +#ifdef _WIN32 + localtime_s(&result, &now); +#else + localtime_r(&now, &result); +#endif + return result; +} +} + +Logger* Logger::Instance() { + // Retain the logger through static destruction, including crash reporting. + static Logger* instance = new Logger; + return instance; +} + +Logger::Logger() : legacy_settings(Settings::Instance()) { + // Parse each variable once; malformed values do not hide valid fallbacks. + for (const char* key : {"OPENSHOT_LOG_LEVEL", "OPENSHOT_LOG_FILE_LEVEL", + "OPENSHOT_LOG_CONSOLE_LEVEL", "LIBOPENSHOT_LOG_LEVEL", + "LIBOPENSHOT_LOG_FILE_LEVEL", "LIBOPENSHOT_LOG_CONSOLE_LEVEL"}) { + const char* value = std::getenv(key); + if (!value) continue; + try { + int level = ParseLevel(value); + std::string name(key); + if (name.find("CONSOLE") == std::string::npos) file_level = level; + if (name.find("FILE") == std::string::npos) { + console_level = level; + console_configured = true; + } + } catch (const std::invalid_argument&) { + std::cerr << "libopenshot: ignoring invalid " << key << "=" << value << '\n'; + } + } +#ifdef _WIN32 + // Windows environment paths are UTF-16; avoid the locale-dependent narrow getenv. + if (const wchar_t* path = _wgetenv(L"LIBOPENSHOT_LOG_FILE")) { + try { Path(std::filesystem::path(path).u8string()); } + catch (const std::exception&) { + std::cerr << "libopenshot: invalid LIBOPENSHOT_LOG_FILE path\n"; + } + } +#else + if (const char* path = std::getenv("LIBOPENSHOT_LOG_FILE")) Path(path); +#endif +#if USE_RESVG == 1 + ResvgRenderer::initLog(); +#endif +} + +int Logger::ConsoleLevel() const { + // Preserve the old explicit Settings API and presence-based environment alias. + if (!console_configured && legacy_settings->DEBUG_TO_STDERR) return LevelDebug; + return console_level; +} +bool Logger::ShouldLog(Level level) const { + return level != LevelOff && ((file_open && level >= file_level) || level >= ConsoleLevel()); +} +void Logger::SetFileLevel(std::string level) { file_level = ParseLevel(level); } +void Logger::SetConsoleLevel(std::string level) { + console_level = ParseLevel(level); + console_configured = true; +} +void Logger::Enable(bool enabled) { file_level = enabled ? LevelDebug : LevelOff; } + +void Logger::Path(std::string path) { + const std::lock_guard lock(loggerMutex); + if (path == file_path && file_open) return; + file_open = false; + if (log_file.is_open()) log_file.close(); + log_file.clear(); + file_path = path; + if (path.empty()) return; + try { + log_file.open(std::filesystem::u8path(path), std::ios::out | std::ios::app); + } catch (const std::exception&) { + std::cerr << "libopenshot: invalid log file path: " << path << '\n'; + return; + } + file_open = log_file.is_open(); + if (!file_open) { + std::cerr << "libopenshot: unable to open log file: " << path << '\n'; + return; + } + auto now = LocalTime(std::time(nullptr)); + // Keep these markers intact for openshot-qt's existing crash recovery scanner. + log_file << "------------------------------------------\n" + << "libopenshot logging: " << std::put_time(&now, "%a %b %d %H:%M:%S %Y") << '\n' + << "------------------------------------------" << std::endl; +} +void Logger::LogToFile(std::string message) { + // Deliberately retain the existing crash-write path (no level filter or new lock). + if (log_file.is_open()) log_file << message << std::flush; +} +void Logger::Log(std::string message, Level level) { + if (!ShouldLog(level)) return; + const std::lock_guard lock(loggerMutex); + auto now = LocalTime(std::time(nullptr)); + std::ostringstream record; + record << std::put_time(&now, "%Y-%m-%d %H:%M:%S") << ' ' + << LevelName(level) << " [" << std::this_thread::get_id() << "] " << message; + if (message.empty() || message.back() != '\n') record << '\n'; + const auto text = record.str(); + if (level >= file_level) LogToFile(text); + if (level >= ConsoleLevel()) std::clog << text << std::flush; +} +void Logger::Close() { + const std::lock_guard lock(loggerMutex); + file_level = LevelOff; + console_level = LevelOff; + console_configured = true; + file_open = false; + if (log_file.is_open()) log_file.close(); +} + +// Append debug information +void Logger::AppendDebugMethod(std::string method_name, + std::string arg1_name, float arg1_value, + std::string arg2_name, float arg2_value, + std::string arg3_name, float arg3_value, + std::string arg4_name, float arg4_value, + std::string arg5_name, float arg5_value, + std::string arg6_name, float arg6_value) +{ + if (!ShouldLog(LevelDebug)) + // Don't do anything + return; + + { + // Create a scoped lock, allowing only a single thread to run the following code at one time + const std::lock_guard lock(loggerMutex); + + std::stringstream message; + message << std::fixed << std::setprecision(4); + + // Construct message + message << method_name << " ("; + + if (arg1_name.length() > 0) + message << arg1_name << "=" << arg1_value; + + if (arg2_name.length() > 0) + message << ", " << arg2_name << "=" << arg2_value; + + if (arg3_name.length() > 0) + message << ", " << arg3_name << "=" << arg3_value; + + if (arg4_name.length() > 0) + message << ", " << arg4_name << "=" << arg4_value; + + if (arg5_name.length() > 0) + message << ", " << arg5_name << "=" << arg5_value; + + if (arg6_name.length() > 0) + message << ", " << arg6_name << "=" << arg6_value; + + message << ")" << std::endl; + + Log(message.str(), LevelDebug); + } +} diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 000000000..b93de8975 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,53 @@ +// Copyright (c) 2008-2026 OpenShot Studios, LLC +// SPDX-License-Identifier: LGPL-3.0-or-later +#ifndef OPENSHOT_LOGGER_H +#define OPENSHOT_LOGGER_H + +#include +#include +#include +#include + +namespace openshot { +class Settings; +/// Independent file and stderr logging. No networking or worker thread. +class Logger { +public: + enum Level { LevelDebug = 10, LevelInfo = 20, LevelWarning = 30, LevelError = 40, LevelCritical = 50, LevelOff = 100 }; + static Logger* Instance(); + /// Explicit configuration overrides environment defaults. Invalid levels throw. + void SetFileLevel(std::string level); + void SetConsoleLevel(std::string level); + bool ShouldLog(Level level = LevelDebug) const; + void Path(std::string path); + void Close(); + void Log(std::string message, Level level = LevelDebug); + /// Unfiltered raw output, retained for the existing crash handler. + void LogToFile(std::string message); + /// Compatibility: enable debug file output, or disable ordinary file output. + void Enable(bool enabled); + /// Deprecated compatibility no-op; logging no longer uses connections. + void Connection(std::string connection) {} + void AppendDebugMethod(std::string method_name, + std::string arg1_name="", float arg1_value=-1.0, + std::string arg2_name="", float arg2_value=-1.0, + std::string arg3_name="", float arg3_value=-1.0, + std::string arg4_name="", float arg4_value=-1.0, + std::string arg5_name="", float arg5_value=-1.0, + std::string arg6_name="", float arg6_value=-1.0); +private: + Logger(); + Settings* legacy_settings; + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + std::recursive_mutex loggerMutex; + std::ofstream log_file; + std::string file_path; + std::atomic file_level{LevelInfo}; + std::atomic console_level{LevelInfo}; + std::atomic file_open{false}; + std::atomic console_configured{false}; + int ConsoleLevel() const; +}; +} +#endif diff --git a/src/OpenShot.h b/src/OpenShot.h index 1a753377e..e7f8935b1 100644 --- a/src/OpenShot.h +++ b/src/OpenShot.h @@ -101,6 +101,8 @@ // Include the version number of OpenShot Library #include "OpenShotVersion.h" +#include "Logger.h" +#include "ZmqLogger.h" // Deprecated source compatibility alias // Include all other classes #include "AudioBufferSource.h" diff --git a/src/Qt/AudioPlaybackThread.cpp b/src/Qt/AudioPlaybackThread.cpp index dd7c5f7d5..9d39e7e44 100644 --- a/src/Qt/AudioPlaybackThread.cpp +++ b/src/Qt/AudioPlaybackThread.cpp @@ -19,7 +19,7 @@ #include "../AudioReaderSource.h" #include "../AudioDevices.h" #include "../Settings.h" -#include "../ZmqLogger.h" +#include "../Logger.h" #include #include // for std::this_thread::sleep_for @@ -61,7 +61,7 @@ namespace openshot constructor_title << "AudioDeviceManagerSingleton::Instance (default audio device type: " << Settings::Instance()->PLAYBACK_AUDIO_DEVICE_TYPE << ", default audio device name: " << Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME << ")"; - ZmqLogger::Instance()->AppendDebugMethod(constructor_title.str(), "channels", channels, "buffer", Settings::Instance()->PLAYBACK_AUDIO_BUFFER_SIZE); + Logger::Instance()->AppendDebugMethod(constructor_title.str(), "channels", channels, "buffer", Settings::Instance()->PLAYBACK_AUDIO_BUFFER_SIZE); // Get preferred audio device type and name (if any - these can be blank) openshot::AudioDeviceInfo requested_device = {Settings::Instance()->PLAYBACK_AUDIO_DEVICE_TYPE, @@ -85,7 +85,7 @@ namespace openshot for (const auto t : mgr->getAvailableDeviceTypes()) { std::stringstream type_debug; type_debug << "AudioDeviceManagerSingleton::Instance (iterate audio device type: " << t->getTypeName() << ")"; - ZmqLogger::Instance()->AppendDebugMethod(type_debug.str(), "rate", rate, "channels", channels); + Logger::Instance()->AppendDebugMethod(type_debug.str(), "rate", rate, "channels", channels); t->scanForDevices(); for (const auto n : t->getDeviceNames()) { @@ -93,7 +93,7 @@ namespace openshot devices.push_back(device); std::stringstream device_debug; device_debug << "AudioDeviceManagerSingleton::Instance (iterate audio device name: " << device.name << ", type: " << t->getTypeName() << ")"; - ZmqLogger::Instance()->AppendDebugMethod(device_debug.str(), "rate", rate, "channels", channels); + Logger::Instance()->AppendDebugMethod(device_debug.str(), "rate", rate, "channels", channels); } } @@ -122,7 +122,7 @@ namespace openshot for(int attempt_rate : possible_rates) { std::stringstream title_rate; title_rate << "AudioDeviceManagerSingleton::Instance (attempt audio device name: " << attempt_device.name << ")"; - ZmqLogger::Instance()->AppendDebugMethod(title_rate.str(), "rate", attempt_rate, "channels", channels); + Logger::Instance()->AppendDebugMethod(title_rate.str(), "rate", attempt_rate, "channels", channels); // Update the audio device setup for the current sample rate m_pInstance->defaultSampleRate = attempt_rate; @@ -147,7 +147,7 @@ namespace openshot std::stringstream title_error; title_error << "AudioDeviceManagerSingleton::Instance (audio device error: " << m_pInstance->initialise_error << ")"; - ZmqLogger::Instance()->AppendDebugMethod(title_error.str(), "rate", attempt_rate, "channels", channels); + Logger::Instance()->AppendDebugMethod(title_error.str(), "rate", attempt_rate, "channels", channels); } // Determine if audio device was opened successfully, and matches the attempted sample rate @@ -158,7 +158,7 @@ namespace openshot std::stringstream title_found; title_found << "AudioDeviceManagerSingleton::Instance (successful audio device found: " << foundAudioIODevice->getTypeName() << ", name: " << foundAudioIODevice->getName() << ")"; - ZmqLogger::Instance()->AppendDebugMethod(title_found.str(), "rate", attempt_rate, "channels", channels); + Logger::Instance()->AppendDebugMethod(title_found.str(), "rate", attempt_rate, "channels", channels); break; } } @@ -169,7 +169,7 @@ namespace openshot } } - ZmqLogger::Instance()->AppendDebugMethod("AudioDeviceManagerSingleton::Instance (audio device initialization completed)"); + Logger::Instance()->AppendDebugMethod("AudioDeviceManagerSingleton::Instance (audio device initialization completed)"); } return m_pInstance; } @@ -220,7 +220,7 @@ namespace openshot sampleRate = reader->info.sample_rate; numChannels = reader->info.channels; - ZmqLogger::Instance()->AppendDebugMethod("AudioPlaybackThread::Reader", "rate", sampleRate, "channel", numChannels); + Logger::Instance()->AppendDebugMethod("AudioPlaybackThread::Reader", "rate", sampleRate, "channel", numChannels); // Set video cache thread source->setVideoCache(videoCache); diff --git a/src/Qt/VideoPlaybackThread.cpp b/src/Qt/VideoPlaybackThread.cpp index b8c53e583..e716244c9 100644 --- a/src/Qt/VideoPlaybackThread.cpp +++ b/src/Qt/VideoPlaybackThread.cpp @@ -12,11 +12,11 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "VideoPlaybackThread.h" -#include "ZmqLogger.h" +#include "Logger.h" #include "Frame.h" #include "RendererBase.h" -#include "ZmqLogger.h" +#include "Logger.h" namespace openshot { @@ -51,7 +51,7 @@ namespace openshot if (need_render && frame) { // Debug - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "VideoPlaybackThread::run (before render)", "frame->number", frame->number, "need_render", need_render); diff --git a/src/Settings.h b/src/Settings.h index 7a013c9aa..2ae646d03 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -122,7 +122,7 @@ namespace openshot { /// paths depend on the location of OpenShot transitions and files) std::string PATH_OPENSHOT_INSTALL = ""; - /// Whether to dump ZeroMQ debug messages to stderr + /// Legacy native debug-to-stderr switch (explicit Logger levels take precedence) bool DEBUG_TO_STDERR = false; /// Return the effective OpenMP worker budget used by libopenshot heuristics diff --git a/src/Timeline.cpp b/src/Timeline.cpp index fec00f649..544c910ad 100644 --- a/src/Timeline.cpp +++ b/src/Timeline.cpp @@ -561,7 +561,7 @@ double Timeline::calculate_time(int64_t number, Fraction rate) std::shared_ptr Timeline::apply_effects(std::shared_ptr frame, int64_t timeline_frame_number, int layer, TimelineInfoStruct* options) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::apply_effects", "frame->number", frame->number, "timeline_frame_number", timeline_frame_number, @@ -591,7 +591,7 @@ std::shared_ptr Timeline::apply_effects(std::shared_ptr frame, int continue; // skip effect, if this filter does not match // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::apply_effects (Process Effect)", "effect_frame_number", effect_frame_number, "does_effect_intersect", does_effect_intersect); @@ -616,7 +616,7 @@ std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr backgro try { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetOrCreateFrame (from reader)", "number", number, "samples_in_frame", samples_in_frame); @@ -634,7 +634,7 @@ std::shared_ptr Timeline::GetOrCreateFrame(std::shared_ptr backgro } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetOrCreateFrame (create blank)", "number", number, "samples_in_frame", samples_in_frame); @@ -660,7 +660,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in return; // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::add_layer", "new_frame->number", new_frame->number, "clip_frame_number", clip_frame_number); @@ -668,7 +668,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in /* COPY AUDIO - with correct volume */ if (source_clip->Reader()->info.has_audio) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::add_layer (Copy Audio)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, "source_frame->GetAudioChannelsCount()", source_frame->GetAudioChannelsCount(), @@ -730,7 +730,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in } else // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::add_layer (No Audio Copied - Wrong # of Channels)", "source_clip->Reader()->info.has_audio", source_clip->Reader()->info.has_audio, @@ -741,7 +741,7 @@ void Timeline::add_layer(std::shared_ptr new_frame, Clip* source_clip, in } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::add_layer (Transform: Composite Image Layer: Completed)", "source_frame->number", source_frame->number, "new_frame->GetImage()->width()", new_frame->GetWidth(), @@ -754,7 +754,7 @@ void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect) // Get lock (prevent getting frames while this happens) const std::lock_guard guard(getFrameMutex); - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::update_open_clips (before)", "does_clip_intersect", does_clip_intersect, "closing_clips.size()", closing_clips.size(), @@ -807,7 +807,7 @@ void Timeline::update_open_clips(Clip *clip, bool does_clip_intersect) } // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::update_open_clips (after)", "does_clip_intersect", does_clip_intersect, "clip_found", clip_found, @@ -870,7 +870,7 @@ void Timeline::sort_clips() const std::lock_guard guard(getFrameMutex); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::SortClips", "clips.size()", clips.size()); @@ -897,7 +897,7 @@ void Timeline::sort_effects() // Clear all clips from timeline void Timeline::Clear() { - ZmqLogger::Instance()->AppendDebugMethod("Timeline::Clear"); + Logger::Instance()->AppendDebugMethod("Timeline::Clear"); // Get lock (prevent getting frames while this happens) const std::lock_guard guard(getFrameMutex); @@ -943,7 +943,7 @@ void Timeline::Clear() // Close the reader (and any resources it was consuming) void Timeline::Close() { - ZmqLogger::Instance()->AppendDebugMethod("Timeline::Close"); + Logger::Instance()->AppendDebugMethod("Timeline::Close"); // Get lock (prevent getting frames while this happens) const std::lock_guard guard(getFrameMutex); @@ -989,7 +989,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) frame = final_cache->GetFrame(requested_frame); if (frame) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Cached frame found)", "requested_frame", requested_frame); @@ -1007,7 +1007,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) frame = final_cache->GetFrame(requested_frame); if (frame) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Cached frame found on 2nd check)", "requested_frame", requested_frame); @@ -1020,7 +1020,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) nearby_clips = find_intersecting_clips(requested_frame, 1, true); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (processing frame)", "requested_frame", requested_frame, "omp_get_thread_num()", omp_get_thread_num()); @@ -1035,7 +1035,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) new_frame->ChannelsLayout(info.channel_layout); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Adding solid color)", "requested_frame", requested_frame, "info.width", info.width, @@ -1048,7 +1048,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) new_frame->AddColor(preview_width, preview_height, color.GetColorHex(requested_frame)); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Loop through clips)", "requested_frame", requested_frame, "clips.size()", clips.size(), @@ -1102,7 +1102,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) // Compose intersecting clips in a single pass for (const auto& ci : clip_infos) { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Does clip intersect)", "requested_frame", requested_frame, "clip->Position()", ci.clip->Position(), @@ -1122,7 +1122,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) int64_t clip_frame_number = ci.frame_number; // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Calculate clip's frame #)", "clip->Position()", ci.clip->Position(), "clip->Start()", ci.clip->Start(), @@ -1134,7 +1134,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) } else { // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (clip does not intersect)", "requested_frame", requested_frame, "does_clip_intersect", ci.intersects); @@ -1143,7 +1143,7 @@ std::shared_ptr Timeline::GetFrame(int64_t requested_frame) } // end clip loop // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::GetFrame (Add frame to cache)", "requested_frame", requested_frame, "info.width", info.width, @@ -1186,7 +1186,7 @@ std::vector Timeline::find_intersecting_clips(int64_t requested_frame, in (clip_end_position >= min_requested_frame || clip_end_position >= max_requested_frame); // Debug output - ZmqLogger::Instance()->AppendDebugMethod( + Logger::Instance()->AppendDebugMethod( "Timeline::find_intersecting_clips (Is clip near or intersecting)", "requested_frame", requested_frame, "min_requested_frame", min_requested_frame, diff --git a/src/WaylandScreenCaptureReader.cpp b/src/WaylandScreenCaptureReader.cpp index 71f28db57..31097dcf0 100644 --- a/src/WaylandScreenCaptureReader.cpp +++ b/src/WaylandScreenCaptureReader.cpp @@ -36,7 +36,7 @@ #include "Exceptions.h" #include "Frame.h" #include "WaylandBufferUtilities.h" -#include "ZmqLogger.h" +#include "Logger.h" using namespace openshot; @@ -312,7 +312,7 @@ class WaylandScreenCaptureReader final : public ScreenCaptureReader::CaptureBack SelectPortalSources(session_handle); stream_info = StartPortalSession(session_handle); ApplyPortalStreamInfo(); - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( "Wayland portal stream selected: node_id=" + std::to_string(stream_info.node_id) + " pipewire_serial=" + std::to_string(stream_info.pipewire_serial) + " source_type=" + std::to_string(stream_info.source_type) + @@ -684,7 +684,7 @@ class WaylandScreenCaptureReader final : public ScreenCaptureReader::CaptureBack self->info.video_timebase = self->info.fps.Reciprocal(); } if (self->stream_width > 0 && self->stream_height > 0) { - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( "Wayland PipeWire stream format: " + std::to_string(self->stream_width) + "x" + std::to_string(self->stream_height) + " format=" + std::to_string(self->video_format) + @@ -790,7 +790,7 @@ class WaylandScreenCaptureReader final : public ScreenCaptureReader::CaptureBack crop_width = std::min(static_cast(crop->region.size.width), stream_width - crop_x); crop_height = std::min(static_cast(crop->region.size.height), stream_height - crop_y); if (!crop_logged) { - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( "Wayland PipeWire video crop: x=" + std::to_string(crop_x) + " y=" + std::to_string(crop_y) + " width=" + std::to_string(crop_width) + @@ -915,7 +915,7 @@ class WaylandScreenCaptureReader final : public ScreenCaptureReader::CaptureBack if (header_drop_log_count >= 5) { return; } - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( "Wayland PipeWire dropped non-monotonic frame: reason=" + reason + " flags=" + std::to_string(header.flags) + " seq=" + std::to_string(header.seq) + diff --git a/src/ZmqLogger.cpp b/src/ZmqLogger.cpp deleted file mode 100644 index 5607eae55..000000000 --- a/src/ZmqLogger.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/** - * @file - * @brief Source file for ZeroMQ-based Logger class - * @author Jonathan Thomas - * - * @ref License - */ - -// Copyright (c) 2008-2019 OpenShot Studios, LLC -// -// SPDX-License-Identifier: LGPL-3.0-or-later - -#include "ZmqLogger.h" -#include "Exceptions.h" -#include "Settings.h" - -#if USE_RESVG == 1 - #include "ResvgQt.h" -#endif - -using namespace openshot; - -#include -#include -#include -#include -#include // for std::this_thread::sleep_for -#include // for std::duration::microseconds - - -// Global reference to logger -ZmqLogger *ZmqLogger::m_pInstance = NULL; - -// Create or Get an instance of the logger singleton -ZmqLogger *ZmqLogger::Instance() -{ - if (!m_pInstance) { - // Create the actual instance of logger only once - m_pInstance = new ZmqLogger; - - // init ZMQ variables - m_pInstance->context = NULL; - m_pInstance->publisher = NULL; - m_pInstance->connection = ""; - - // Default connection - m_pInstance->Connection("tcp://*:5556"); - - // Init enabled to False (force user to call Enable()) - m_pInstance->enabled = false; - - #if USE_RESVG == 1 - // Init resvg logging (if needed) - // This can only happen 1 time or it will crash - ResvgRenderer::initLog(); - #endif - } - - return m_pInstance; -} - -// Set the connection for this logger -void ZmqLogger::Connection(std::string new_connection) -{ - // Create a scoped lock, allowing only a single thread to run the following code at one time - const std::lock_guard lock(loggerMutex); - - // Does anything need to happen? - if (new_connection == connection) - return; - else - // Set new connection - connection = new_connection; - - if (context == NULL) { - // Create ZMQ Context - context = new zmq::context_t(1); - } - - if (publisher != NULL) { - // Close an existing bound publisher socket - publisher->close(); - publisher = NULL; - } - - // Create new publisher instance - publisher = new zmq::socket_t(*context, ZMQ_PUB); - - // Bind to the socket - try { - publisher->bind(connection.c_str()); - - } catch (zmq::error_t &e) { - std::cout << "ZmqLogger::Connection - Error binding to " << connection << ". Switching to an available port." << std::endl; - connection = "tcp://*:*"; - publisher->bind(connection.c_str()); - } - - // Sleeping to allow connection to wake up (0.25 seconds) - std::this_thread::sleep_for(std::chrono::milliseconds(250)); -} - -void ZmqLogger::Log(std::string message) -{ - if (!enabled) - // Don't do anything - return; - - // Create a scoped lock, allowing only a single thread to run the following code at one time - const std::lock_guard lock(loggerMutex); - - // Send message over socket (ZeroMQ) - zmq::message_t reply (message.length()); - std::memcpy (reply.data(), message.c_str(), message.length()); - -#if ZMQ_VERSION > ZMQ_MAKE_VERSION(4, 3, 1) - // Set flags for immediate delivery (new API) - publisher->send(reply, zmq::send_flags::dontwait); -#else - publisher->send(reply); -#endif - - // Also log to file, if open - LogToFile(message); -} - -// Log message to a file (if path set) -void ZmqLogger::LogToFile(std::string message) -{ - // Write to log file (if opened, and force it to write to disk in case of a crash) - if (log_file.is_open()) - log_file << message << std::flush; -} - -void ZmqLogger::Path(std::string new_path) -{ - // Update path - file_path = new_path; - - // Close file (if already open) - if (log_file.is_open()) - log_file.close(); - - // Open file (write + append) - log_file.open (file_path.c_str(), std::ios::out | std::ios::app); - - // Get current time and log first message - std::time_t now = std::time(0); - std::tm* localtm = std::localtime(&now); - log_file << "------------------------------------------" << std::endl; - log_file << "libopenshot logging: " << std::asctime(localtm); - log_file << "------------------------------------------" << std::endl; -} - -void ZmqLogger::Close() -{ - // Disable logger as it no longer needed - enabled = false; - - // Close file (if already open) - if (log_file.is_open()) - log_file.close(); - - // Close socket (if any) - if (publisher != NULL) { - // Close an existing bound publisher socket - publisher->close(); - publisher = NULL; - } - - // Terminate zmq threads - if (context != NULL) { - context->close(); - } -} - -// Append debug information -void ZmqLogger::AppendDebugMethod(std::string method_name, - std::string arg1_name, float arg1_value, - std::string arg2_name, float arg2_value, - std::string arg3_name, float arg3_value, - std::string arg4_name, float arg4_value, - std::string arg5_name, float arg5_value, - std::string arg6_name, float arg6_value) -{ - if (!enabled && !openshot::Settings::Instance()->DEBUG_TO_STDERR) - // Don't do anything - return; - - { - // Create a scoped lock, allowing only a single thread to run the following code at one time - const std::lock_guard lock(loggerMutex); - - std::stringstream message; - message << std::fixed << std::setprecision(4); - - // Construct message - message << method_name << " ("; - - if (arg1_name.length() > 0) - message << arg1_name << "=" << arg1_value; - - if (arg2_name.length() > 0) - message << ", " << arg2_name << "=" << arg2_value; - - if (arg3_name.length() > 0) - message << ", " << arg3_name << "=" << arg3_value; - - if (arg4_name.length() > 0) - message << ", " << arg4_name << "=" << arg4_value; - - if (arg5_name.length() > 0) - message << ", " << arg5_name << "=" << arg5_value; - - if (arg6_name.length() > 0) - message << ", " << arg6_name << "=" << arg6_value; - - message << ")" << std::endl; - - if (openshot::Settings::Instance()->DEBUG_TO_STDERR) { - // Print message to stderr - std::clog << message.str(); - } - - if (enabled) { - // Send message through ZMQ - Log(message.str()); - } - } -} diff --git a/src/ZmqLogger.h b/src/ZmqLogger.h index e2be22e54..4fca7f0bc 100644 --- a/src/ZmqLogger.h +++ b/src/ZmqLogger.h @@ -1,104 +1,9 @@ -/** - * @file - * @brief Header file for ZeroMQ-based Logger class - * @author Jonathan Thomas - * - * @ref License - */ - -// Copyright (c) 2008-2019 OpenShot Studios, LLC -// // SPDX-License-Identifier: LGPL-3.0-or-later - -#ifndef OPENSHOT_LOGGER_H -#define OPENSHOT_LOGGER_H - - -#include -#include -#include -#include - -#include - +#ifndef OPENSHOT_ZMQLOGGER_COMPAT_H +#define OPENSHOT_ZMQLOGGER_COMPAT_H +#include "Logger.h" namespace openshot { - - /** - * @brief This class is used for logging and sending those logs over a ZemoMQ socket to a listener - * - * OpenShot desktop editor listens to this port, to receive libopenshot debug output. It both logs to - * a file and sends the stdout over a socket. - */ - class ZmqLogger { - private: - std::recursive_mutex loggerMutex; - std::string connection; - - // Logfile related vars - std::string file_path; - std::ofstream log_file; - bool enabled; - - /// ZMQ Context - zmq::context_t *context; - - /// ZMQ Socket - zmq::socket_t *publisher; - - /// Default constructor - ZmqLogger(){}; // Don't allow user to create an instance of this singleton - -#if __GNUC__ >=7 - /// Default copy method - ZmqLogger(ZmqLogger const&) = delete; // Don't allow the user to assign this instance - - /// Default assignment operator - ZmqLogger & operator=(ZmqLogger const&) = delete; // Don't allow the user to assign this instance -#else - /// Default copy method - ZmqLogger(ZmqLogger const&) {}; // Don't allow the user to assign this instance - - /// Default assignment operator - ZmqLogger & operator=(ZmqLogger const&); // Don't allow the user to assign this instance -#endif - - /// Private variable to keep track of singleton instance - static ZmqLogger * m_pInstance; - - public: - /// Create or get an instance of this logger singleton (invoke the class with this method) - static ZmqLogger * Instance(); - - /// Append debug information - void AppendDebugMethod( - std::string method_name, - std::string arg1_name="", float arg1_value=-1.0, - std::string arg2_name="", float arg2_value=-1.0, - std::string arg3_name="", float arg3_value=-1.0, - std::string arg4_name="", float arg4_value=-1.0, - std::string arg5_name="", float arg5_value=-1.0, - std::string arg6_name="", float arg6_value=-1.0 - ); - - /// Close logger (sockets and/or files) - void Close(); - - /// Set or change connection info for logger (i.e. tcp://*:5556) - void Connection(std::string new_connection); - - /// Enable/Disable logging - void Enable(bool is_enabled) { enabled = is_enabled;}; - - /// Set or change the file path (optional) - void Path(std::string new_path); - - /// Log message to all subscribers of this logger (if any) - void Log(std::string message); - - /// Log message to a file (if path set) - void LogToFile(std::string message); - }; - +/// Deprecated source compatibility alias. Rebuild clients against this release. +using ZmqLogger = Logger; } - #endif diff --git a/src/effects/Displace.cpp b/src/effects/Displace.cpp index b4b0520ac..cf571fb52 100644 --- a/src/effects/Displace.cpp +++ b/src/effects/Displace.cpp @@ -15,7 +15,7 @@ #include "Exceptions.h" #include "ReaderBase.h" #include "Timeline.h" -#include "ZmqLogger.h" +#include "Logger.h" #include #include @@ -135,7 +135,7 @@ std::shared_ptr Displace::GetMapImage(std::shared_ptr target_ima source_map = std::make_shared(*source_frame->GetImage()); } } catch (const std::exception& e) { - ZmqLogger::Instance()->Log( + Logger::Instance()->Log( std::string("Displace::GetMapImage unable to read displacement frame: ") + e.what()); source_map.reset(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee6ae8d97..4d5c72e34 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -44,6 +44,7 @@ set(OPENSHOT_TESTS FrameScope FrameMapper KeyFrame + Logger Point Profiles QtPlayer diff --git a/tests/Logger.cpp b/tests/Logger.cpp new file mode 100644 index 000000000..ccfe0568d --- /dev/null +++ b/tests/Logger.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2026 OpenShot Studios, LLC +// SPDX-License-Identifier: LGPL-3.0-or-later +#include "openshot_catch.h" +#include "Logger.h" +#include "ZmqLogger.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace openshot; + +TEST_CASE("Independent logging destinations and crash writes", "[logger]") { + auto* logger = Logger::Instance(); + CHECK(logger == ZmqLogger::Instance()); + auto path = std::filesystem::temp_directory_path() / + std::filesystem::u8path("openshot-logging-\xc3\xa9-" + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()) + ".log"); + logger->Path(path.u8string()); + logger->SetFileLevel("warning"); + logger->SetConsoleLevel("debug"); + CHECK(logger->ShouldLog(Logger::LevelDebug)); + std::ostringstream console; + auto* previous = std::clog.rdbuf(console.rdbuf()); + logger->Log("console-only-debug", Logger::LevelDebug); + logger->Log("both-warning", Logger::LevelWarning); + std::clog.rdbuf(previous); + logger->SetFileLevel("off"); + logger->SetConsoleLevel("off"); + CHECK_FALSE(logger->ShouldLog(Logger::LevelCritical)); + logger->Log("filtered-error", Logger::LevelError); + logger->LogToFile("---- Unhandled Exception: Stack Trace ----\ncrash-evidence\n---- End of Stack Trace ----\n"); + CHECK_THROWS_AS(logger->SetFileLevel("not-a-level"), std::invalid_argument); + logger->Close(); + std::ifstream file(path); + std::string content((std::istreambuf_iterator(file)), {}); + CHECK(content.find("console-only-debug") == std::string::npos); + CHECK(console.str().find("console-only-debug") != std::string::npos); + CHECK(content.find("both-warning") != std::string::npos); + CHECK(content.find("filtered-error") == std::string::npos); + CHECK(content.find("crash-evidence") != std::string::npos); + CHECK(content.find("libopenshot logging:") != std::string::npos); + file.close(); + std::filesystem::remove(path); +} + +TEST_CASE("Concurrent records remain complete and path can reopen", "[logger]") { + auto* logger = Logger::Instance(); + auto path = std::filesystem::temp_directory_path() / + ("openshot-logging-threads-" + std::to_string( + std::chrono::steady_clock::now().time_since_epoch().count()) + ".log"); + logger->Path(path.u8string()); + logger->SetFileLevel("debug"); + logger->SetConsoleLevel("off"); + std::vector workers; + for (int i = 0; i < 4; ++i) workers.emplace_back([logger, i]() { + for (int j = 0; j < 100; ++j) + logger->Log("record-" + std::to_string(i) + "-" + std::to_string(j)); + }); + for (auto& worker : workers) worker.join(); + logger->Close(); + logger->Path(path.u8string()); + logger->Enable(true); + logger->AppendDebugMethod("reopened", "frame", 42); + logger->Close(); + std::ifstream file(path); + std::string line; + int records = 0; + bool reopened = false; + while (std::getline(file, line)) { + if (line.find("record-") != std::string::npos) { + ++records; + CHECK(line.find("record-", line.find("record-") + 1) == std::string::npos); + } + if (line.find("reopened (frame=42.0000)") != std::string::npos) reopened = true; + } + CHECK(records == 400); + CHECK(reopened); + file.close(); + std::filesystem::remove(path); +} From 8e4bc29c30e6073bdf7fd2e652be4138f90d916f Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Sep 2026 13:18:19 -0500 Subject: [PATCH 2/6] Fix logger compatibility with older macOS and Windows test DLL loading --- bindings/python/CMakeLists.txt | 2 ++ bindings/python/test_logger.py | 18 +++++++++++++++-- src/Logger.cpp | 8 ++++++++ tests/Logger.cpp | 36 +++++++++++++++++----------------- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index a2bbdc06d..ef43d543f 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -145,6 +145,8 @@ if(BUILD_TESTING) "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}" "OPENSHOT_TEST_MODULE_DIR=$" "OPENSHOT_TEST_DLL_DIR=$" + # The Windows build copies libopenshot-audio.dll into the tests directory. + "OPENSHOT_TEST_AUDIO_DLL_DIR=${PROJECT_BINARY_DIR}/tests" ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/test_logger.py") set_tests_properties(Logger:PythonEnvironment PROPERTIES LABELS Logger) endif() diff --git a/bindings/python/test_logger.py b/bindings/python/test_logger.py index 423fd5d26..16884ef32 100644 --- a/bindings/python/test_logger.py +++ b/bindings/python/test_logger.py @@ -21,8 +21,22 @@ def run_logger(self, variables, body=''): result = subprocess.run([sys.executable, '-c', ''' import os _dll_handles = [] -if os.name == 'nt' and os.environ.get('OPENSHOT_TEST_DLL_DIR'): - _dll_handles.append(os.add_dll_directory(os.environ['OPENSHOT_TEST_DLL_DIR'])) +if os.name == 'nt' and hasattr(os, 'add_dll_directory'): + # Python 3.8+ does not search PATH for extension-module dependencies. + # Include the project DLLs and the dependency directories supplied by CI. + _dll_dirs = [os.environ.get('OPENSHOT_TEST_DLL_DIR', ''), + os.environ.get('OPENSHOT_TEST_AUDIO_DLL_DIR', '')] + _dll_dirs.extend(os.environ.get('PATH', '').split(os.pathsep)) + _seen = set() + for _directory in _dll_dirs: + _directory = _directory.strip('"') + if not _directory or not os.path.isdir(_directory): + continue + _directory = os.path.abspath(_directory) + _key = os.path.normcase(_directory) + if _key not in _seen: + _dll_handles.append(os.add_dll_directory(_directory)) + _seen.add(_key) import openshot logger = openshot.Logger.Instance() assert openshot.ZmqLogger is openshot.Logger diff --git a/src/Logger.cpp b/src/Logger.cpp index 77ef01f5b..6a51112f8 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -9,7 +9,9 @@ #include #include #include +#ifdef _WIN32 #include +#endif #include #include #include @@ -115,7 +117,13 @@ void Logger::Path(std::string path) { file_path = path; if (path.empty()) return; try { +#ifdef _WIN32 log_file.open(std::filesystem::u8path(path), std::ios::out | std::ios::app); +#else + // POSIX paths already use UTF-8 bytes. Avoid std::filesystem, which + // requires macOS 10.15 even when compiling with C++17 enabled. + log_file.open(path, std::ios::out | std::ios::app); +#endif } catch (const std::exception&) { std::cerr << "libopenshot: invalid log file path: " << path << '\n'; return; diff --git a/tests/Logger.cpp b/tests/Logger.cpp index ccfe0568d..a039f4eb3 100644 --- a/tests/Logger.cpp +++ b/tests/Logger.cpp @@ -3,23 +3,22 @@ #include "openshot_catch.h" #include "Logger.h" #include "ZmqLogger.h" -#include -#include +#include +#include #include #include #include #include -#include using namespace openshot; TEST_CASE("Independent logging destinations and crash writes", "[logger]") { auto* logger = Logger::Instance(); CHECK(logger == ZmqLogger::Instance()); - auto path = std::filesystem::temp_directory_path() / - std::filesystem::u8path("openshot-logging-\xc3\xa9-" + std::to_string( - std::chrono::steady_clock::now().time_since_epoch().count()) + ".log"); - logger->Path(path.u8string()); + QTemporaryDir directory; + REQUIRE(directory.isValid()); + auto path = directory.filePath(QString::fromUtf8("openshot-logging-\xc3\xa9.log")); + logger->Path(path.toUtf8().toStdString()); logger->SetFileLevel("warning"); logger->SetConsoleLevel("debug"); CHECK(logger->ShouldLog(Logger::LevelDebug)); @@ -35,8 +34,9 @@ TEST_CASE("Independent logging destinations and crash writes", "[logger]") { logger->LogToFile("---- Unhandled Exception: Stack Trace ----\ncrash-evidence\n---- End of Stack Trace ----\n"); CHECK_THROWS_AS(logger->SetFileLevel("not-a-level"), std::invalid_argument); logger->Close(); - std::ifstream file(path); - std::string content((std::istreambuf_iterator(file)), {}); + QFile file(path); + REQUIRE(file.open(QIODevice::ReadOnly)); + std::string content = file.readAll().toStdString(); CHECK(content.find("console-only-debug") == std::string::npos); CHECK(console.str().find("console-only-debug") != std::string::npos); CHECK(content.find("both-warning") != std::string::npos); @@ -44,15 +44,14 @@ TEST_CASE("Independent logging destinations and crash writes", "[logger]") { CHECK(content.find("crash-evidence") != std::string::npos); CHECK(content.find("libopenshot logging:") != std::string::npos); file.close(); - std::filesystem::remove(path); } TEST_CASE("Concurrent records remain complete and path can reopen", "[logger]") { auto* logger = Logger::Instance(); - auto path = std::filesystem::temp_directory_path() / - ("openshot-logging-threads-" + std::to_string( - std::chrono::steady_clock::now().time_since_epoch().count()) + ".log"); - logger->Path(path.u8string()); + QTemporaryDir directory; + REQUIRE(directory.isValid()); + auto path = directory.filePath("openshot-logging-threads.log"); + logger->Path(path.toUtf8().toStdString()); logger->SetFileLevel("debug"); logger->SetConsoleLevel("off"); std::vector workers; @@ -62,15 +61,17 @@ TEST_CASE("Concurrent records remain complete and path can reopen", "[logger]") }); for (auto& worker : workers) worker.join(); logger->Close(); - logger->Path(path.u8string()); + logger->Path(path.toUtf8().toStdString()); logger->Enable(true); logger->AppendDebugMethod("reopened", "frame", 42); logger->Close(); - std::ifstream file(path); + QFile file(path); + REQUIRE(file.open(QIODevice::ReadOnly)); + std::istringstream content(file.readAll().toStdString()); std::string line; int records = 0; bool reopened = false; - while (std::getline(file, line)) { + while (std::getline(content, line)) { if (line.find("record-") != std::string::npos) { ++records; CHECK(line.find("record-", line.find("record-") + 1) == std::string::npos); @@ -80,5 +81,4 @@ TEST_CASE("Concurrent records remain complete and path can reopen", "[logger]") CHECK(records == 400); CHECK(reopened); file.close(); - std::filesystem::remove(path); } From bb5b08212404dbb7b178e1065d7a61852a4284cb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Sep 2026 14:48:22 -0500 Subject: [PATCH 3/6] Fix OpenCV runtime DLL path on Windows x64 CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8ad920bc..97fce6565 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -106,7 +106,7 @@ windows-builder-x64: - Expand-Archive -Path artifacts.zip -DestinationPath . - $env:OPENCV_ROOT = "C:\msys64\mingw64\opencv-4.13.0" - $env:OpenCV_DIR = "$env:OPENCV_ROOT\lib\cmake\opencv4" - - $env:Path = "$env:OPENCV_ROOT\bin;C:\msys64\mingw64\bin;C:\msys64\usr\bin;C:\msys64\usr\local\bin;" + $env:Path; + - $env:Path = "$env:OPENCV_ROOT\x64\mingw\bin;$env:OPENCV_ROOT\bin;C:\msys64\mingw64\bin;C:\msys64\usr\bin;C:\msys64\usr\local\bin;" + $env:Path; - $env:MSYSTEM = "MINGW64" - ffmpeg -hide_banner -devices | findstr /I "gdigrab" - ffmpeg -hide_banner -devices | findstr /I "dshow" From 387cf5cabe85dc80ad536b5aedd832e3fad663e2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Sep 2026 15:17:35 -0500 Subject: [PATCH 4/6] Use portable temporary files in ObjectMask tests --- tests/ObjectMask.cpp | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/tests/ObjectMask.cpp b/tests/ObjectMask.cpp index 8b6432e5a..62f4f7509 100644 --- a/tests/ObjectMask.cpp +++ b/tests/ObjectMask.cpp @@ -21,13 +21,12 @@ #endif #include +#include #include +#include -#include #include -#include #include -#include using namespace openshot; @@ -37,15 +36,6 @@ static std::shared_ptr make_object_mask_frame(int64_t number, int width, return frame; } -static std::string temp_object_mask_path() { - char path[] = "/tmp/libopenshot_object_mask_XXXXXX"; - int fd = mkstemp(path); - REQUIRE(fd != -1); - close(fd); - std::remove(path); - return std::string(path) + ".data"; -} - static void append_varint(std::string& output, uint64_t value) { while (value >= 0x80) { output.push_back(static_cast((value & 0x7f) | 0x80)); @@ -67,9 +57,7 @@ static void append_length_delimited(std::string& output, uint32_t field_number, output.append(value); } -static std::string create_object_mask_data() { - const std::string path = temp_object_mask_path(); - +static void create_object_mask_data(const QString& path) { std::string mask; append_varint(mask, 8); append_varint(mask, 4); @@ -106,10 +94,10 @@ static std::string create_object_mask_data() { append_length_delimited(data, 1, frame); append_length_delimited(data, 3, "object mask"); - std::ofstream output(path, std::ios::out | std::ios::binary); - output.write(data.data(), static_cast(data.size())); - REQUIRE(output.good()); - return path; + QFile output(path); + REQUIRE(output.open(QIODevice::WriteOnly)); + REQUIRE(output.write(data.data(), static_cast(data.size())) == static_cast(data.size())); + REQUIRE(output.flush()); } TEST_CASE("ObjectMask effect is registered", "[effect][object_mask]") { @@ -121,11 +109,14 @@ TEST_CASE("ObjectMask effect is registered", "[effect][object_mask]") { } TEST_CASE("ObjectMask loads protobuf masks and exposes style controls", "[effect][object_mask]") { - const std::string protobuf_path = create_object_mask_data(); + QTemporaryDir directory; + REQUIRE(directory.isValid()); + const QString protobuf_path = directory.filePath("object_mask.data"); + create_object_mask_data(protobuf_path); ObjectMask effect; Json::Value config; - config["protobuf_data_path"] = protobuf_path; + config["protobuf_data_path"] = protobuf_path.toUtf8().toStdString(); config["mask_alpha"] = Keyframe(0.5).JsonValue(); config["stroke_width"] = Keyframe(2.0).JsonValue(); effect.SetJsonValue(config); @@ -151,8 +142,6 @@ TEST_CASE("ObjectMask loads protobuf masks and exposes style controls", "[effect auto output = effect.GetFrame(frame, 1)->GetImage(); CHECK(output->pixelColor(0, 0) != QColor(64, 64, 64, 255)); CHECK(output->pixelColor(3, 3) == QColor(64, 64, 64, 255)); - - std::remove(protobuf_path.c_str()); } #ifdef USE_OPENCV From 961775aa3de54a2c1dcdf977332248cd28c3a944 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Sep 2026 15:19:40 -0500 Subject: [PATCH 5/6] Support legacy MinGW Python DLL lookup in logger tests --- bindings/python/test_logger.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bindings/python/test_logger.py b/bindings/python/test_logger.py index 16884ef32..7b24fcf3b 100644 --- a/bindings/python/test_logger.py +++ b/bindings/python/test_logger.py @@ -18,11 +18,17 @@ def run_logger(self, variables, body=''): module_dir = env.get('OPENSHOT_TEST_MODULE_DIR') if module_dir: env['PYTHONPATH'] = module_dir + os.pathsep + env.get('PYTHONPATH', '') + if os.name == 'nt': + # Older MinGW Python uses PATH and ignores add_dll_directory(). + dll_dirs = [env[key] for key in ( + 'OPENSHOT_TEST_DLL_DIR', 'OPENSHOT_TEST_AUDIO_DLL_DIR') + if env.get(key)] + env['PATH'] = os.pathsep.join(dll_dirs + [env.get('PATH', '')]) result = subprocess.run([sys.executable, '-c', ''' import os _dll_handles = [] if os.name == 'nt' and hasattr(os, 'add_dll_directory'): - # Python 3.8+ does not search PATH for extension-module dependencies. + # Modern Python does not search PATH for extension-module dependencies. # Include the project DLLs and the dependency directories supplied by CI. _dll_dirs = [os.environ.get('OPENSHOT_TEST_DLL_DIR', ''), os.environ.get('OPENSHOT_TEST_AUDIO_DLL_DIR', '')] From 68018f2d4908875cf8c86658111880d70dcc6ccb Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Tue, 15 Sep 2026 15:58:33 -0500 Subject: [PATCH 6/6] Add missing REUSE licensing headers for logging files --- bindings/python/test_logger.py | 3 +++ doc/logging.rst | 3 +++ src/ZmqLogger.h | 1 + 3 files changed, 7 insertions(+) diff --git a/bindings/python/test_logger.py b/bindings/python/test_logger.py index 7b24fcf3b..a5b0ecbae 100644 --- a/bindings/python/test_logger.py +++ b/bindings/python/test_logger.py @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC +# SPDX-License-Identifier: LGPL-3.0-or-later + """Exercise native logger configuration in fresh processes through SWIG.""" import os from pathlib import Path diff --git a/doc/logging.rst b/doc/logging.rst index b1901e7f6..bf56796d6 100644 --- a/doc/logging.rst +++ b/doc/logging.rst @@ -1,3 +1,6 @@ +.. SPDX-FileCopyrightText: 2026 OpenShot Studios, LLC +.. SPDX-License-Identifier: LGPL-3.0-or-later + Logging ========= diff --git a/src/ZmqLogger.h b/src/ZmqLogger.h index 4fca7f0bc..31f4d2188 100644 --- a/src/ZmqLogger.h +++ b/src/ZmqLogger.h @@ -1,3 +1,4 @@ +// SPDX-FileCopyrightText: 2008-2026 OpenShot Studios, LLC // SPDX-License-Identifier: LGPL-3.0-or-later #ifndef OPENSHOT_ZMQLOGGER_COMPAT_H #define OPENSHOT_ZMQLOGGER_COMPAT_H