diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 280985fe..f17d029e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - dev pull_request: {} workflow_dispatch: {} + jobs: ubuntu: runs-on: ubuntu-latest @@ -33,7 +34,7 @@ jobs: - name: Build Video2X run: | mkdir -p /tmp/build /tmp/install - cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \ + cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DINSTALL_BIN_DESTINATION=. -DINSTALL_INCLUDE_DESTINATION=include \ @@ -44,6 +45,7 @@ jobs: with: name: video2x-nightly-linux-amd64 path: /tmp/install + windows: runs-on: windows-latest steps: @@ -73,7 +75,7 @@ jobs: Rename-Item -Path "third_party/ncnn-$ncnnVersion-windows-vs2022-shared" -NewName ncnn-shared - name: Build Video2X run: | - cmake -S . -B build -DUSE_SYSTEM_NCNN=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=build/video2x_install + cmake -S . -B build -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=build/video2x_install cmake --build build --config Debug --parallel --target install - name: Upload artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6bda32a..c9a4e209 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,17 +4,20 @@ on: tags: - "*" +permissions: + contents: write + jobs: setup: if: github.event.base_ref == 'refs/heads/master' name: Setup runs-on: ubuntu-latest outputs: - tag: ${{ steps.get_tag.outputs.tag }} + version: ${{ steps.get_version.outputs.version }} steps: - - name: Get tag - id: get_tag - run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//} + - name: Get version + id: get_version + run: echo version=${GITHUB_REF/refs\/tags\//} >> $GITHUB_OUTPUT container: name: Build and upload container @@ -32,7 +35,7 @@ jobs: password: ${{ secrets.GHCR_TOKEN }} dockerfile: Dockerfile image: video2x - tags: latest, ${{ needs.setup.outputs.tag }} + tags: latest, ${{ needs.setup.outputs.version }} create-release: name: Create release @@ -45,11 +48,10 @@ jobs: steps: - name: Create release id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: softprops/action-gh-release@v2 with: - tag_name: ${{ needs.setup.outputs.tag }} - release_name: Video2X ${{ needs.setup.outputs.tag }} + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ needs.setup.outputs.version }} + release_name: Video2X ${{ needs.setup.outputs.version }} draft: true prerelease: false diff --git a/.gitmodules b/.gitmodules index a3df0c64..48b2ab0b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "third_party/ncnn"] path = third_party/ncnn url = https://github.com/Tencent/ncnn.git +[submodule "third_party/spdlog"] + path = third_party/spdlog + url = https://github.com/gabime/spdlog.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 742f2082..b2d398e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,12 +30,31 @@ endif() # Build options option(BUILD_SHARED_LIBS "Build libvideo2x as a shared library" ON) -option(BUILD_VIDEO2X "Build the video2x executable" ON) +option(BUILD_VIDEO2X_CLI "Build the video2x executable" ON) +option(USE_SYSTEM_SPDLOG "Use system spdlog library" ON) option(USE_SYSTEM_NCNN "Use system ncnn library" ON) +# Generate the version header file +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/include/libvideo2x/version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h" + @ONLY +) + +# Find the required packages set(ALL_INCLUDE_DIRS) set(ALL_LIBRARIES) +# spdlog +if (USE_SYSTEM_SPDLOG) + find_package(spdlog REQUIRED) + list(APPEND ALL_LIBRARIES spdlog::spdlog) +else() + add_subdirectory(third_party/spdlog) + list(APPEND ALL_LIBRARIES spdlog::spdlog_header_only) +endif() + +# Platform-specific dependencies if(WIN32) # Define base paths for FFmpeg and ncnn set(FFMPEG_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/ffmpeg-shared) @@ -223,7 +242,9 @@ add_dependencies(libvideo2x realesrgan) # Include directories for the shared library target_include_directories(libvideo2x PRIVATE ${ALL_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/include/libvideo2x ${PROJECT_SOURCE_DIR}/third_party/libreal_esrgan_ncnn_vulkan/src ) @@ -254,13 +275,14 @@ if(NOT WIN32) endif() # Create the executable 'video2x' -if (BUILD_VIDEO2X) +if (BUILD_VIDEO2X_CLI) add_executable(video2x src/video2x.c src/getopt.c) set_target_properties(video2x PROPERTIES OUTPUT_NAME video2x) # Include directories for the executable target_include_directories(video2x PRIVATE ${ALL_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/include ) @@ -277,12 +299,12 @@ endif() # Define the default installation directories if(WIN32) set(BIN_DESTINATION_DEFAULT ".") - set(INCLUDE_DESTINATION_DEFAULT "include") + set(INCLUDE_DESTINATION_DEFAULT "include/libvideo2x") set(LIB_DESTINATION_DEFAULT ".") set(MODEL_DESTINATION_DEFAULT ".") else() set(BIN_DESTINATION_DEFAULT "bin") - set(INCLUDE_DESTINATION_DEFAULT "include") + set(INCLUDE_DESTINATION_DEFAULT "include/libvideo2x") set(LIB_DESTINATION_DEFAULT "lib") set(MODEL_DESTINATION_DEFAULT "share/video2x") endif() @@ -306,13 +328,18 @@ install(TARGETS libvideo2x # Install model files install(DIRECTORY ${CMAKE_SOURCE_DIR}/models DESTINATION ${INSTALL_MODEL_DESTINATION}) -# Install the executable if BUILD_VIDEO2X is enabled -if(BUILD_VIDEO2X) +# Install the executable if BUILD_VIDEO2X_CLI is enabled +if(BUILD_VIDEO2X_CLI) install(TARGETS video2x RUNTIME DESTINATION ${INSTALL_BIN_DESTINATION}) endif() # Install the header file -install(FILES ${PROJECT_SOURCE_DIR}/include/libvideo2x.h DESTINATION ${INSTALL_INCLUDE_DESTINATION}) +install(FILES ${PROJECT_SOURCE_DIR}/include/libvideo2x/libvideo2x.h + DESTINATION ${INSTALL_INCLUDE_DESTINATION} +) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h + DESTINATION ${INSTALL_INCLUDE_DESTINATION} +) # Platform-specific installation rules if(WIN32) diff --git a/Makefile b/Makefile index 3ea71a33..378830d8 100644 --- a/Makefile +++ b/Makefile @@ -19,8 +19,7 @@ static: -DCMAKE_C_COMPILER=$(CC) \ -DCMAKE_CXX_COMPILER=$(CXX) \ -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=OFF \ - -DUSE_SYSTEM_NCNN=OFF + -DBUILD_SHARED_LIBS=OFF cmake --build $(BINDIR) --config Release --parallel cp $(BINDIR)/compile_commands.json . @@ -45,7 +44,8 @@ debian: libswscale-dev \ libvulkan-dev \ glslang-tools \ - libomp-dev + libomp-dev \ + libspdlog-dev cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/install \ diff --git a/PKGBUILD b/PKGBUILD index 8d30935d..b4bf9a4e 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,12 +1,12 @@ pkgname=video2x -pkgver=r862.f590ead +pkgver=r840.ecbc512 pkgrel=1 -pkgdesc="A lossless video super resolution framework" +pkgdesc="A machine learning-based lossless video super resolution framework" arch=('x86_64') url="https://github.com/k4yt3x/video2x" license=('AGPL3') depends=('ffmpeg' 'ncnn' 'vulkan-driver') -makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp') +makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp' 'spdlog') pkgver() { printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" diff --git a/include/conversions.h b/include/libvideo2x/conversions.h similarity index 100% rename from include/conversions.h rename to include/libvideo2x/conversions.h diff --git a/include/decoder.h b/include/libvideo2x/decoder.h similarity index 100% rename from include/decoder.h rename to include/libvideo2x/decoder.h diff --git a/include/encoder.h b/include/libvideo2x/encoder.h similarity index 100% rename from include/encoder.h rename to include/libvideo2x/encoder.h diff --git a/include/filter.h b/include/libvideo2x/filter.h similarity index 100% rename from include/filter.h rename to include/libvideo2x/filter.h diff --git a/include/fsutils.h b/include/libvideo2x/fsutils.h similarity index 100% rename from include/fsutils.h rename to include/libvideo2x/fsutils.h diff --git a/include/libplacebo.h b/include/libvideo2x/libplacebo.h similarity index 100% rename from include/libplacebo.h rename to include/libvideo2x/libplacebo.h diff --git a/include/libplacebo_filter.h b/include/libvideo2x/libplacebo_filter.h similarity index 100% rename from include/libplacebo_filter.h rename to include/libvideo2x/libplacebo_filter.h diff --git a/include/libvideo2x.h b/include/libvideo2x/libvideo2x.h similarity index 84% rename from include/libvideo2x.h rename to include/libvideo2x/libvideo2x.h index df0eb256..7c7a5eee 100644 --- a/include/libvideo2x.h +++ b/include/libvideo2x/libvideo2x.h @@ -28,6 +28,17 @@ enum FilterType { FILTER_REALESRGAN }; +// Enum to specify log level +enum Libvideo2xLogLevel { + LIBVIDEO2X_LOG_LEVEL_TRACE, + LIBVIDEO2X_LOG_LEVEL_DEBUG, + LIBVIDEO2X_LOG_LEVEL_INFO, + LIBVIDEO2X_LOG_LEVEL_WARNING, + LIBVIDEO2X_LOG_LEVEL_ERROR, + LIBVIDEO2X_LOG_LEVEL_CRITICAL, + LIBVIDEO2X_LOG_LEVEL_OFF +}; + // Configuration for Libplacebo filter struct LibplaceboConfig { int output_width; @@ -78,6 +89,7 @@ struct VideoProcessingContext { LIBVIDEO2X_API int process_video( const char *input_filename, const char *output_filename, + enum Libvideo2xLogLevel log_level, bool benchmark, enum AVHWDeviceType hw_device_type, const struct FilterConfig *filter_config, diff --git a/include/realesrgan_filter.h b/include/libvideo2x/realesrgan_filter.h similarity index 100% rename from include/realesrgan_filter.h rename to include/libvideo2x/realesrgan_filter.h diff --git a/include/libvideo2x/version.h.in b/include/libvideo2x/version.h.in new file mode 100644 index 00000000..5cd7f54e --- /dev/null +++ b/include/libvideo2x/version.h.in @@ -0,0 +1,6 @@ +#ifndef VERSION_H +#define VERSION_H + +#define LIBVIDEO2X_VERSION_STRING "@PROJECT_VERSION@" + +#endif // VERSION_H diff --git a/src/conversions.cpp b/src/conversions.cpp index 6f3e5f18..147e9dc7 100644 --- a/src/conversions.cpp +++ b/src/conversions.cpp @@ -2,11 +2,13 @@ #include +#include + // Convert AVFrame format AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) { AVFrame *dst_frame = av_frame_alloc(); if (dst_frame == nullptr) { - fprintf(stderr, "Failed to allocate destination AVFrame.\n"); + spdlog::error("Failed to allocate destination AVFrame."); return nullptr; } @@ -16,7 +18,7 @@ AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) { // Allocate memory for the converted frame if (av_frame_get_buffer(dst_frame, 32) < 0) { - fprintf(stderr, "Failed to allocate memory for AVFrame.\n"); + spdlog::error("Failed to allocate memory for AVFrame."); av_frame_free(&dst_frame); return nullptr; } @@ -36,7 +38,7 @@ AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) { ); if (sws_ctx == nullptr) { - fprintf(stderr, "Failed to initialize swscale context.\n"); + spdlog::error("Failed to initialize swscale context."); av_frame_free(&dst_frame); return nullptr; } @@ -66,7 +68,7 @@ ncnn::Mat avframe_to_ncnn_mat(AVFrame *frame) { if (frame->format != AV_PIX_FMT_BGR24) { converted_frame = convert_avframe_pix_fmt(frame, AV_PIX_FMT_BGR24); if (!converted_frame) { - fprintf(stderr, "Failed to convert AVFrame to BGR24.\n"); + spdlog::error("Failed to convert AVFrame to BGR24."); return ncnn::Mat(); } } else { @@ -102,7 +104,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { // Step 1: Allocate a destination AVFrame for the specified pixel format AVFrame *dst_frame = av_frame_alloc(); if (!dst_frame) { - fprintf(stderr, "Failed to allocate destination AVFrame.\n"); + spdlog::error("Failed to allocate destination AVFrame."); return nullptr; } @@ -112,7 +114,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { // Allocate memory for the frame buffer if (av_frame_get_buffer(dst_frame, 32) < 0) { - fprintf(stderr, "Failed to allocate memory for destination AVFrame.\n"); + spdlog::error("Failed to allocate memory for destination AVFrame."); av_frame_free(&dst_frame); return nullptr; } @@ -120,7 +122,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { // Step 2: Convert ncnn::Mat to BGR AVFrame AVFrame *bgr_frame = av_frame_alloc(); if (!bgr_frame) { - fprintf(stderr, "Failed to allocate intermediate BGR AVFrame.\n"); + spdlog::error("Failed to allocate intermediate BGR AVFrame."); av_frame_free(&dst_frame); return nullptr; } @@ -131,7 +133,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { // Allocate memory for the intermediate BGR frame if (av_frame_get_buffer(bgr_frame, 32) < 0) { - fprintf(stderr, "Failed to allocate memory for BGR AVFrame.\n"); + spdlog::error("Failed to allocate memory for BGR AVFrame."); av_frame_free(&dst_frame); av_frame_free(&bgr_frame); return nullptr; @@ -159,7 +161,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { ); if (sws_ctx == nullptr) { - fprintf(stderr, "Failed to initialize swscale context.\n"); + spdlog::error("Failed to initialize swscale context."); av_frame_free(&bgr_frame); av_frame_free(&dst_frame); return nullptr; @@ -181,7 +183,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) { av_frame_free(&bgr_frame); if (ret != dst_frame->height) { - fprintf(stderr, "Failed to convert BGR AVFrame to destination pixel format.\n"); + spdlog::error("Failed to convert BGR AVFrame to destination pixel format."); av_frame_free(&dst_frame); return nullptr; } diff --git a/src/decoder.cpp b/src/decoder.cpp index b2be5843..eb6d281c 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -4,6 +4,8 @@ #include #include +#include + static enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; // Callback function to choose the hardware-accelerated pixel format @@ -13,7 +15,7 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelF return *p; } } - fprintf(stderr, "Failed to get HW surface format.\n"); + spdlog::error("Failed to get HW surface format."); return AV_PIX_FMT_NONE; } @@ -30,19 +32,19 @@ int init_decoder( int ret; if ((ret = avformat_open_input(&ifmt_ctx, input_filename, NULL, NULL)) < 0) { - fprintf(stderr, "Could not open input file '%s'\n", input_filename); + spdlog::error("Could not open input file '{}'", input_filename); return ret; } if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { - fprintf(stderr, "Failed to retrieve input stream information\n"); + spdlog::error("Failed to retrieve input stream information"); return ret; } // Find the first video stream ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (ret < 0) { - fprintf(stderr, "Could not find video stream in the input, aborting\n"); + spdlog::error("Could not find video stream in the input file"); return ret; } @@ -52,13 +54,15 @@ int init_decoder( // Set up the decoder const AVCodec *decoder = avcodec_find_decoder(video_stream->codecpar->codec_id); if (!decoder) { - fprintf(stderr, "Failed to find decoder for stream #%u\n", stream_index); + spdlog::error( + "Failed to find decoder for codec ID {}", (int)video_stream->codecpar->codec_id + ); return AVERROR_DECODER_NOT_FOUND; } codec_ctx = avcodec_alloc_context3(decoder); if (!codec_ctx) { - fprintf(stderr, "Failed to allocate the decoder context\n"); + spdlog::error("Failed to allocate the decoder context"); return AVERROR(ENOMEM); } @@ -71,9 +75,8 @@ int init_decoder( for (int i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); if (config == nullptr) { - fprintf( - stderr, - "Decoder %s does not support device type %s.\n", + spdlog::error( + "Decoder {} does not support device type {}.", decoder->name, av_hwdevice_get_type_name(hw_type) ); @@ -90,7 +93,7 @@ int init_decoder( } if ((ret = avcodec_parameters_to_context(codec_ctx, video_stream->codecpar)) < 0) { - fprintf(stderr, "Failed to copy decoder parameters to input decoder context\n"); + spdlog::error("Failed to copy decoder parameters to input decoder context"); return ret; } @@ -100,7 +103,7 @@ int init_decoder( codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, video_stream, NULL); if ((ret = avcodec_open2(codec_ctx, decoder, NULL)) < 0) { - fprintf(stderr, "Failed to open decoder for stream #%u\n", stream_index); + spdlog::error("Failed to open decoder for stream #{}", stream_index); return ret; } diff --git a/src/encoder.cpp b/src/encoder.cpp index f5e424a6..cdb6ad00 100644 --- a/src/encoder.cpp +++ b/src/encoder.cpp @@ -4,12 +4,14 @@ #include #include +#include + #include "conversions.h" static enum AVPixelFormat get_encoder_default_pix_fmt(const AVCodec *encoder) { const enum AVPixelFormat *p = encoder->pix_fmts; if (!p) { - fprintf(stderr, "No pixel formats supported by encoder\n"); + spdlog::error("No pixel formats supported by encoder"); return AV_PIX_FMT_NONE; } return *p; @@ -33,15 +35,14 @@ int init_encoder( avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, output_filename); if (!fmt_ctx) { - fprintf(stderr, "Could not create output context\n"); + spdlog::error("Could not create output context"); return AVERROR_UNKNOWN; } const AVCodec *encoder = avcodec_find_encoder(encoder_config->codec); if (!encoder) { - fprintf( - stderr, - "Required video encoder not found for vcodec %s\n", + spdlog::error( + "Required video encoder not found for vcodec {}", avcodec_get_name(encoder_config->codec) ); return AVERROR_ENCODER_NOT_FOUND; @@ -50,13 +51,13 @@ int init_encoder( // Create a new video stream in the output file AVStream *out_stream = avformat_new_stream(fmt_ctx, NULL); if (!out_stream) { - fprintf(stderr, "Failed to allocate the output video stream\n"); + spdlog::error("Failed to allocate the output video stream"); return AVERROR_UNKNOWN; } codec_ctx = avcodec_alloc_context3(encoder); if (!codec_ctx) { - fprintf(stderr, "Failed to allocate the encoder context\n"); + spdlog::error("Failed to allocate the encoder context"); return AVERROR(ENOMEM); } @@ -79,7 +80,7 @@ int init_encoder( // Fall back to the default pixel format codec_ctx->pix_fmt = get_encoder_default_pix_fmt(encoder); if (codec_ctx->pix_fmt == AV_PIX_FMT_NONE) { - fprintf(stderr, "Could not get the default pixel format for the encoder\n"); + spdlog::error("Could not get the default pixel format for the encoder"); return AVERROR(EINVAL); } } @@ -101,13 +102,13 @@ int init_encoder( } if ((ret = avcodec_open2(codec_ctx, encoder, NULL)) < 0) { - fprintf(stderr, "Cannot open video encoder\n"); + spdlog::error("Cannot open video encoder"); return ret; } ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx); if (ret < 0) { - fprintf(stderr, "Failed to copy encoder parameters to output video stream\n"); + spdlog::error("Failed to copy encoder parameters to output video stream"); return ret; } @@ -117,7 +118,7 @@ int init_encoder( // Allocate the stream map *stream_mapping = (int *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(**stream_mapping)); if (!*stream_mapping) { - fprintf(stderr, "Could not allocate stream mapping\n"); + spdlog::error("Could not allocate stream mapping"); return AVERROR(ENOMEM); } @@ -143,13 +144,13 @@ int init_encoder( // Create corresponding output stream AVStream *out_stream = avformat_new_stream(fmt_ctx, NULL); if (!out_stream) { - fprintf(stderr, "Failed allocating output stream\n"); + spdlog::error("Failed allocating output stream"); return AVERROR_UNKNOWN; } ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar); if (ret < 0) { - fprintf(stderr, "Failed to copy codec parameters\n"); + spdlog::error("Failed to copy codec parameters"); return ret; } out_stream->codecpar->codec_tag = 0; @@ -165,7 +166,7 @@ int init_encoder( if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE); if (ret < 0) { - fprintf(stderr, "Could not open output file '%s'\n", output_filename); + spdlog::error("Could not open output file '{}'", output_filename); return ret; } } @@ -188,7 +189,7 @@ int encode_and_write_frame( if (frame->format != enc_ctx->pix_fmt) { AVFrame *converted_frame = convert_avframe_pix_fmt(frame, enc_ctx->pix_fmt); if (!converted_frame) { - fprintf(stderr, "Error converting frame to encoder's pixel format\n"); + spdlog::error("Error converting frame to encoder's pixel format"); return AVERROR_EXTERNAL; } @@ -198,13 +199,13 @@ int encode_and_write_frame( AVPacket *enc_pkt = av_packet_alloc(); if (!enc_pkt) { - fprintf(stderr, "Could not allocate AVPacket\n"); + spdlog::error("Could not allocate AVPacket"); return AVERROR(ENOMEM); } ret = avcodec_send_frame(enc_ctx, frame); if (ret < 0) { - fprintf(stderr, "Error sending frame to encoder\n"); + spdlog::error("Error sending frame to encoder"); av_packet_free(&enc_pkt); return ret; } @@ -215,7 +216,7 @@ int encode_and_write_frame( av_packet_unref(enc_pkt); break; } else if (ret < 0) { - fprintf(stderr, "Error encoding frame\n"); + spdlog::error("Error encoding frame"); av_packet_free(&enc_pkt); return ret; } @@ -230,7 +231,7 @@ int encode_and_write_frame( ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); av_packet_unref(enc_pkt); if (ret < 0) { - fprintf(stderr, "Error muxing packet\n"); + spdlog::error("Error muxing packet"); av_packet_free(&enc_pkt); return ret; } @@ -244,7 +245,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) { int ret; AVPacket *enc_pkt = av_packet_alloc(); if (!enc_pkt) { - fprintf(stderr, "Could not allocate AVPacket\n"); + spdlog::error("Could not allocate AVPacket"); return AVERROR(ENOMEM); } @@ -255,7 +256,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) { av_packet_unref(enc_pkt); break; } else if (ret < 0) { - fprintf(stderr, "Error encoding frame\n"); + spdlog::error("Error encoding frame"); av_packet_free(&enc_pkt); return ret; } @@ -268,7 +269,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) { ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); av_packet_unref(enc_pkt); if (ret < 0) { - fprintf(stderr, "Error muxing packet\n"); + spdlog::error("Error muxing packet"); av_packet_free(&enc_pkt); return ret; } diff --git a/src/fsutils.cpp b/src/fsutils.cpp index 025a0c24..a0c74471 100644 --- a/src/fsutils.cpp +++ b/src/fsutils.cpp @@ -8,6 +8,8 @@ #include #endif +#include + #if _WIN32 std::filesystem::path get_executable_directory() { std::vector filepath(MAX_PATH); @@ -15,7 +17,7 @@ std::filesystem::path get_executable_directory() { // Get the executable path, expanding the buffer if necessary DWORD size = GetModuleFileNameW(NULL, filepath.data(), static_cast(filepath.size())); if (size == 0) { - fprintf(stderr, "Error getting executable path: %lu\n", GetLastError()); + spdlog::error("Error getting executable path: {}", GetLastError()); return std::filesystem::path(); } @@ -24,7 +26,7 @@ std::filesystem::path get_executable_directory() { filepath.resize(filepath.size() * 2); size = GetModuleFileNameW(NULL, filepath.data(), static_cast(filepath.size())); if (size == 0) { - fprintf(stderr, "Error getting executable path: %lu\n", GetLastError()); + spdlog::error("Error getting executable path: {}", GetLastError()); return std::filesystem::path(); } } @@ -39,7 +41,7 @@ std::filesystem::path get_executable_directory() { std::filesystem::path filepath = std::filesystem::read_symlink("/proc/self/exe", ec); if (ec) { - fprintf(stderr, "Error reading /proc/self/exe: %s\n", ec.message().c_str()); + spdlog::error("Error reading /proc/self/exe: {}", ec.message()); return std::filesystem::path(); } diff --git a/src/libplacebo.cpp b/src/libplacebo.cpp index 6e4e85ea..69b2e4cf 100644 --- a/src/libplacebo.cpp +++ b/src/libplacebo.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "fsutils.h" int init_libplacebo( @@ -20,7 +22,7 @@ int init_libplacebo( AVFilterGraph *graph = avfilter_graph_alloc(); if (!graph) { - fprintf(stderr, "Unable to create filter graph.\n"); + spdlog::error("Unable to create filter graph."); return AVERROR(ENOMEM); } @@ -30,7 +32,7 @@ int init_libplacebo( args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:" - "pixel_aspect=%d/%d:colorspace=%d", + "pixel_aspect=%d/%d:colorspace=%d:range=%d", dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt, @@ -40,12 +42,13 @@ int init_libplacebo( dec_ctx->framerate.den, dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den, - dec_ctx->colorspace + dec_ctx->colorspace, + dec_ctx->color_range ); ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args, NULL, graph); if (ret < 0) { - fprintf(stderr, "Cannot create buffer source\n"); + spdlog::error("Cannot create buffer source."); avfilter_graph_free(&graph); return ret; } @@ -55,7 +58,7 @@ int init_libplacebo( // Create the libplacebo filter const AVFilter *libplacebo_filter = avfilter_get_by_name("libplacebo"); if (!libplacebo_filter) { - fprintf(stderr, "Filter 'libplacebo' not found\n"); + spdlog::error("Filter 'libplacebo' not found."); avfilter_graph_free(&graph); return AVERROR_FILTER_NOT_FOUND; } @@ -84,7 +87,7 @@ int init_libplacebo( &libplacebo_ctx, libplacebo_filter, "libplacebo", filter_args, NULL, graph ); if (ret < 0) { - fprintf(stderr, "Cannot create libplacebo filter\n"); + spdlog::error("Cannot create libplacebo filter."); avfilter_graph_free(&graph); return ret; } @@ -97,7 +100,7 @@ int init_libplacebo( // Link buffersrc to libplacebo ret = avfilter_link(last_filter, 0, libplacebo_ctx, 0); if (ret < 0) { - fprintf(stderr, "Error connecting buffersrc to libplacebo filter\n"); + spdlog::error("Error connecting buffersrc to libplacebo filter."); avfilter_graph_free(&graph); return ret; } @@ -108,7 +111,7 @@ int init_libplacebo( const AVFilter *buffersink = avfilter_get_by_name("buffersink"); ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out", NULL, NULL, graph); if (ret < 0) { - fprintf(stderr, "Cannot create buffer sink\n"); + spdlog::error("Cannot create buffer sink."); avfilter_graph_free(&graph); return ret; } @@ -116,7 +119,7 @@ int init_libplacebo( // Link libplacebo to buffersink ret = avfilter_link(last_filter, 0, *buffersink_ctx, 0); if (ret < 0) { - fprintf(stderr, "Error connecting libplacebo filter to buffersink\n"); + spdlog::error("Error connecting libplacebo filter to buffersink."); avfilter_graph_free(&graph); return ret; } @@ -124,7 +127,7 @@ int init_libplacebo( // Configure the filter graph ret = avfilter_graph_config(graph, NULL); if (ret < 0) { - fprintf(stderr, "Error configuring the filter graph\n"); + spdlog::error("Error configuring the filter graph."); avfilter_graph_free(&graph); return ret; } diff --git a/src/libplacebo_filter.cpp b/src/libplacebo_filter.cpp index a2ce8fe3..4bbf5177 100644 --- a/src/libplacebo_filter.cpp +++ b/src/libplacebo_filter.cpp @@ -2,6 +2,8 @@ #include +#include + #include "fsutils.h" #include "libplacebo.h" @@ -42,7 +44,7 @@ int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB // Check if the shader file exists if (!std::filesystem::exists(shader_full_path)) { - fprintf(stderr, "libplacebo shader file not found: %s\n", shader_full_path.c_str()); + spdlog::error("libplacebo shader file not found: {}", shader_full_path.string()); return -1; } @@ -67,14 +69,14 @@ int LibplaceboFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame // Get the filtered frame *output_frame = av_frame_alloc(); if (*output_frame == nullptr) { - fprintf(stderr, "Failed to allocate output frame\n"); + spdlog::error("Failed to allocate output frame"); return -1; } // Feed the frame to the filter graph ret = av_buffersrc_add_frame(buffersrc_ctx, input_frame); if (ret < 0) { - fprintf(stderr, "Error while feeding the filter graph\n"); + spdlog::error("Error while feeding the filter graph"); return ret; } @@ -95,7 +97,7 @@ int LibplaceboFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame int LibplaceboFilter::flush(std::vector &processed_frames) { int ret = av_buffersrc_add_frame(buffersrc_ctx, nullptr); if (ret < 0) { - fprintf(stderr, "Error while flushing filter graph\n"); + spdlog::error("Error while flushing filter graph"); return ret; } diff --git a/src/libvideo2x.cpp b/src/libvideo2x.cpp index aa7e5dce..33725b35 100644 --- a/src/libvideo2x.cpp +++ b/src/libvideo2x.cpp @@ -1,12 +1,13 @@ #include "libvideo2x.h" -#include #include #include #include #include #include +#include + #include "decoder.h" #include "encoder.h" #include "filter.h" @@ -16,13 +17,16 @@ /** * @brief Process frames using the selected filter. * + * @param[in] encoder_config Encoder configurations * @param[in,out] proc_ctx Struct containing the processing context - * @param[in] fmt_ctx Input format context + * @param[in] ifmt_ctx Input format context * @param[in] ofmt_ctx Output format context * @param[in] dec_ctx Decoder context * @param[in] enc_ctx Encoder context * @param[in] filter Filter instance * @param[in] video_stream_index Index of the video stream in the input format context + * @param[in] stream_mapping Array mapping input stream indexes to output stream indexes + * @param[in] benchmark Flag to enable benchmarking mode * @return int 0 on success, negative value on error */ int process_frames( @@ -79,7 +83,7 @@ int process_frames( ret = avcodec_send_packet(dec_ctx, &packet); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error sending packet to decoder: %s\n", errbuf); + spdlog::error("Error sending packet to decoder: {}", errbuf); av_packet_unref(&packet); goto end; } @@ -97,7 +101,7 @@ int process_frames( break; } else if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error decoding video frame: %s\n", errbuf); + spdlog::error("Error decoding video frame: {}", errbuf); goto end; } @@ -112,7 +116,7 @@ int process_frames( ); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error encoding/writing frame: %s\n", errbuf); + spdlog::error("Error encoding/writing frame: {}", errbuf); av_frame_free(&processed_frame); goto end; } @@ -121,12 +125,14 @@ int process_frames( av_frame_free(&processed_frame); proc_ctx->processed_frames++; } else if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { - fprintf(stderr, "Filter returned an error\n"); + spdlog::error("Filter returned an error"); goto end; } av_frame_unref(frame); - // TODO: Print the debug processing status + spdlog::debug( + "Processed frame {}/{}", proc_ctx->processed_frames, proc_ctx->total_frames + ); } } else if (encoder_config->copy_streams && stream_mapping[packet.stream_index] >= 0) { AVStream *in_stream = ifmt_ctx->streams[packet.stream_index]; @@ -140,7 +146,8 @@ int process_frames( // If copy streams is enabled, copy the packet to the output ret = av_interleaved_write_frame(ofmt_ctx, &packet); if (ret < 0) { - fprintf(stderr, "Error muxing packet\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Error muxing packet: {}", errbuf); av_packet_unref(&packet); return ret; } @@ -152,7 +159,7 @@ int process_frames( ret = filter->flush(flushed_frames); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error flushing filter: %s\n", errbuf); + spdlog::error("Error flushing filter: {}", errbuf); goto end; } @@ -161,7 +168,7 @@ int process_frames( ret = encode_and_write_frame(flushed_frame, enc_ctx, ofmt_ctx, video_stream_index); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error encoding/writing flushed frame: %s\n", errbuf); + spdlog::error("Error encoding/writing flushed frame: {}", errbuf); av_frame_free(&flushed_frame); flushed_frame = nullptr; goto end; @@ -174,7 +181,7 @@ int process_frames( ret = flush_encoder(enc_ctx, ofmt_ctx); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error flushing encoder: %s\n", errbuf); + spdlog::error("Error flushing encoder: {}", errbuf); goto end; } @@ -230,6 +237,8 @@ void cleanup( * * @param[in] input_filename Path to the input video file * @param[in] output_filename Path to the output video file + * @param[in] log_level Log level + * @param[in] benchmark Flag to enable benchmarking mode * @param[in] hw_type Hardware device type * @param[in] filter_config Filter configurations * @param[in] encoder_config Encoder configurations @@ -239,6 +248,7 @@ void cleanup( extern "C" int process_video( const char *input_filename, const char *output_filename, + Libvideo2xLogLevel log_level, bool benchmark, AVHWDeviceType hw_type, const FilterConfig *filter_config, @@ -253,13 +263,51 @@ extern "C" int process_video( int *stream_mapping = nullptr; Filter *filter = nullptr; int video_stream_index = -1; + char errbuf[AV_ERROR_MAX_STRING_SIZE]; int ret = 0; + // Set the log level for FFmpeg and spdlog (libvideo2x) + switch (log_level) { + case LIBVIDEO2X_LOG_LEVEL_TRACE: + av_log_set_level(AV_LOG_TRACE); + spdlog::set_level(spdlog::level::trace); + break; + case LIBVIDEO2X_LOG_LEVEL_DEBUG: + av_log_set_level(AV_LOG_DEBUG); + spdlog::set_level(spdlog::level::debug); + break; + case LIBVIDEO2X_LOG_LEVEL_INFO: + av_log_set_level(AV_LOG_INFO); + spdlog::set_level(spdlog::level::info); + break; + case LIBVIDEO2X_LOG_LEVEL_WARNING: + av_log_set_level(AV_LOG_WARNING); + spdlog::set_level(spdlog::level::warn); + break; + case LIBVIDEO2X_LOG_LEVEL_ERROR: + av_log_set_level(AV_LOG_ERROR); + spdlog::set_level(spdlog::level::err); + break; + case LIBVIDEO2X_LOG_LEVEL_CRITICAL: + av_log_set_level(AV_LOG_FATAL); + spdlog::set_level(spdlog::level::critical); + break; + case LIBVIDEO2X_LOG_LEVEL_OFF: + av_log_set_level(AV_LOG_QUIET); + spdlog::set_level(spdlog::level::off); + break; + default: + av_log_set_level(AV_LOG_INFO); + spdlog::set_level(spdlog::level::info); + break; + } + // Initialize hardware device context if (hw_type != AV_HWDEVICE_TYPE_NONE) { ret = av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0); if (ret < 0) { - fprintf(stderr, "Unable to initialize hardware device context\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Error initializing hardware device context: {}", errbuf); return ret; } } @@ -267,7 +315,8 @@ extern "C" int process_video( // Initialize input ret = init_decoder(hw_type, hw_ctx, input_filename, &ifmt_ctx, &dec_ctx, &video_stream_index); if (ret < 0) { - fprintf(stderr, "Failed to initialize decoder\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Failed to initialize decoder: {}", errbuf); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return ret; } @@ -300,7 +349,8 @@ extern "C" int process_video( &stream_mapping ); if (ret < 0) { - fprintf(stderr, "Failed to initialize encoder\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Failed to initialize encoder: {}", errbuf); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return ret; } @@ -308,7 +358,8 @@ extern "C" int process_video( // Write the output file header ret = avformat_write_header(ofmt_ctx, NULL); if (ret < 0) { - fprintf(stderr, "Error occurred when opening output file\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Error occurred when opening output file: {}", errbuf); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return ret; } @@ -320,14 +371,14 @@ extern "C" int process_video( // Validate shader path if (!config.shader_path) { - fprintf(stderr, "Shader path must be provided for the libplacebo filter\n"); + spdlog::error("Shader path must be provided for the libplacebo filter"); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return -1; } // Validate output dimensions if (config.output_width <= 0 || config.output_height <= 0) { - fprintf(stderr, "Output dimensions must be provided for the libplacebo filter\n"); + spdlog::error("Output dimensions must be provided for the libplacebo filter"); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return -1; } @@ -342,14 +393,14 @@ extern "C" int process_video( // Validate model name if (!config.model) { - fprintf(stderr, "Model name must be provided for the RealESRGAN filter\n"); + spdlog::error("Model name must be provided for the RealESRGAN filter"); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return -1; } // Validate scaling factor if (config.scaling_factor <= 0) { - fprintf(stderr, "Scaling factor must be provided for the RealESRGAN filter\n"); + spdlog::error("Scaling factor must be provided for the RealESRGAN filter"); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return -1; } @@ -360,7 +411,7 @@ extern "C" int process_video( break; } default: - fprintf(stderr, "Unknown filter type\n"); + spdlog::error("Unknown filter type"); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return -1; } @@ -368,7 +419,8 @@ extern "C" int process_video( // Initialize the filter ret = filter->init(dec_ctx, enc_ctx, hw_ctx); if (ret < 0) { - fprintf(stderr, "Failed to initialize filter\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Failed to initialize filter: {}", errbuf); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return ret; } @@ -387,7 +439,8 @@ extern "C" int process_video( benchmark ); if (ret < 0) { - fprintf(stderr, "Error processing frames\n"); + av_strerror(ret, errbuf, sizeof(errbuf)); + spdlog::error("Error processing frames: {}", errbuf); cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); return ret; } @@ -399,9 +452,8 @@ extern "C" int process_video( cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); if (ret < 0 && ret != AVERROR_EOF) { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, errbuf, sizeof(errbuf)); - fprintf(stderr, "Error occurred: %s\n", errbuf); + spdlog::error("Error occurred: {}", errbuf); return ret; } return 0; diff --git a/src/realesrgan_filter.cpp b/src/realesrgan_filter.cpp index 6cbe09fd..5c265bb9 100644 --- a/src/realesrgan_filter.cpp +++ b/src/realesrgan_filter.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "conversions.h" #include "fsutils.h" @@ -47,7 +49,7 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB model_bin_path = custom_model_bin_path; } else { // Neither model name nor custom model paths provided - fprintf(stderr, "Model or model paths must be provided for RealESRGAN filter\n"); + spdlog::error("Model or model paths must be provided for RealESRGAN filter"); return -1; } @@ -57,13 +59,11 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB // Check if the model files exist if (!std::filesystem::exists(model_param_full_path)) { - fprintf( - stderr, "RealESRGAN model param file not found: %s\n", model_param_full_path.c_str() - ); + spdlog::error("RealESRGAN model param file not found: {}", model_param_full_path.string()); return -1; } if (!std::filesystem::exists(model_bin_full_path)) { - fprintf(stderr, "RealESRGAN model bin file not found: %s\n", model_bin_full_path.c_str()); + spdlog::error("RealESRGAN model bin file not found: {}", model_bin_full_path.string()); return -1; } @@ -77,7 +77,7 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB // Load the model if (realesrgan->load(model_param_full_path, model_bin_full_path) != 0) { - fprintf(stderr, "Failed to load RealESRGAN model\n"); + spdlog::error("Failed to load RealESRGAN model"); return -1; } @@ -106,7 +106,7 @@ int RealesrganFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame // Convert the input frame to RGB24 ncnn::Mat input_mat = avframe_to_ncnn_mat(input_frame); if (input_mat.empty()) { - fprintf(stderr, "Failed to convert AVFrame to ncnn::Mat\n"); + spdlog::error("Failed to convert AVFrame to ncnn::Mat"); return -1; } @@ -117,7 +117,7 @@ int RealesrganFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame ret = realesrgan->process(input_mat, output_mat); if (ret != 0) { - fprintf(stderr, "RealESRGAN processing failed\n"); + spdlog::error("RealESRGAN processing failed"); return ret; } diff --git a/src/video2x.c b/src/video2x.c index 0ae7b656..04552c46 100644 --- a/src/video2x.c +++ b/src/video2x.c @@ -17,12 +17,11 @@ #include #include -#include +#include +#include #include "getopt.h" -const char *VIDEO2X_VERSION = "6.0.0"; - // Set UNIX terminal input to non-blocking mode #ifndef _WIN32 void set_nonblocking_input(bool enable) { @@ -42,6 +41,8 @@ void set_nonblocking_input(bool enable) { // Define command line options static struct option long_options[] = { + {"loglevel", required_argument, NULL, 0}, + {"noprogress", no_argument, NULL, 0}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 0}, @@ -75,6 +76,8 @@ static struct option long_options[] = { // Structure to hold parsed arguments struct arguments { // General options + const char *loglevel; + bool noprogress; const char *input_filename; const char *output_filename; const char *filter_type; @@ -129,6 +132,10 @@ int is_valid_realesrgan_model(const char *model) { void print_help() { printf("Usage: video2x [OPTIONS]\n"); printf("\nOptions:\n"); + printf( + " --loglevel Set log level (trace, debug, info, warn, error, critical, none)\n" + ); + printf(" --noprogress Do not display the progress bar\n"); printf(" -v, --version Print program version\n"); printf(" -?, --help Display this help page\n"); printf("\nGeneral Processing Options:\n"); @@ -166,6 +173,8 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) { int c; // Default argument values + arguments->loglevel = "info"; + arguments->noprogress = false; arguments->input_filename = NULL; arguments->output_filename = NULL; arguments->filter_type = NULL; @@ -269,10 +278,14 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) { } break; case 'v': - printf("Video2X v%s\n", VIDEO2X_VERSION); + printf("Video2X version %s\n", LIBVIDEO2X_VERSION_STRING); exit(0); case 0: // Long-only options without short equivalents - if (strcmp(long_options[option_index].name, "help") == 0) { + if (strcmp(long_options[option_index].name, "loglevel") == 0) { + arguments->loglevel = optarg; + } else if (strcmp(long_options[option_index].name, "noprogress") == 0) { + arguments->noprogress = true; + } else if (strcmp(long_options[option_index].name, "help") == 0) { print_help(); exit(0); } else if (strcmp(long_options[option_index].name, "nocopystreams") == 0) { @@ -323,11 +336,33 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) { } } +enum Libvideo2xLogLevel parse_log_level(const char *level_name) { + if (strcmp(level_name, "trace") == 0) { + return LIBVIDEO2X_LOG_LEVEL_TRACE; + } else if (strcmp(level_name, "debug") == 0) { + return LIBVIDEO2X_LOG_LEVEL_DEBUG; + } else if (strcmp(level_name, "info") == 0) { + return LIBVIDEO2X_LOG_LEVEL_INFO; + } else if (strcmp(level_name, "warning") == 0) { + return LIBVIDEO2X_LOG_LEVEL_WARNING; + } else if (strcmp(level_name, "error") == 0) { + return LIBVIDEO2X_LOG_LEVEL_ERROR; + } else if (strcmp(level_name, "critical") == 0) { + return LIBVIDEO2X_LOG_LEVEL_CRITICAL; + } else if (strcmp(level_name, "off") == 0) { + return LIBVIDEO2X_LOG_LEVEL_OFF; + } else { + fprintf(stderr, "Warning: Invalid log level specified. Defaulting to 'info'.\n"); + return LIBVIDEO2X_LOG_LEVEL_INFO; + } +} + // Wrapper function for video processing thread int process_video_thread(void *arg) { struct ProcessVideoThreadArguments *thread_args = (struct ProcessVideoThreadArguments *)arg; // Extract individual arguments + enum Libvideo2xLogLevel log_level = parse_log_level(thread_args->arguments->loglevel); struct arguments *arguments = thread_args->arguments; enum AVHWDeviceType hw_device_type = thread_args->hw_device_type; struct FilterConfig *filter_config = thread_args->filter_config; @@ -338,6 +373,7 @@ int process_video_thread(void *arg) { int result = process_video( arguments->input_filename, arguments->output_filename, + log_level, arguments->benchmark, hw_device_type, filter_config, @@ -436,19 +472,18 @@ int main(int argc, char **argv) { .proc_ctx = &proc_ctx }; -// Enable non-blocking input -#ifndef _WIN32 - set_nonblocking_input(true); -#endif - // Create a thread for video processing thrd_t processing_thread; if (thrd_create(&processing_thread, process_video_thread, &thread_args) != thrd_success) { fprintf(stderr, "Failed to create processing thread\n"); return 1; } - printf("[Video2X] Video processing started.\n"); - printf("[Video2X] Press SPACE to pause/resume, 'q' to abort.\n"); + printf("Video processing started; press SPACE to pause/resume, 'q' to abort.\n"); + +// Enable non-blocking input +#ifndef _WIN32 + set_nonblocking_input(true); +#endif // Main thread loop to display progress and handle input while (!proc_ctx.completed) { @@ -468,23 +503,21 @@ int main(int argc, char **argv) { // Toggle pause state proc_ctx.pause = !proc_ctx.pause; if (proc_ctx.pause) { - printf("\n[Video2X] Processing paused. Press SPACE to resume, 'q' to abort."); + printf("\nProcessing paused. Press SPACE to resume, 'q' to abort.\n"); } else { - printf("\n[Video2X] Resuming processing..."); + printf("Resuming processing...\n"); } - fflush(stdout); } else if (ch == 'q' || ch == 'Q') { // Abort processing - printf("\n[Video2X] Aborting processing..."); - fflush(stdout); + printf("Aborting processing...\n"); proc_ctx.abort = true; break; } // Display progress - if (!proc_ctx.pause && proc_ctx.total_frames > 0) { + if (!arguments.noprogress && !proc_ctx.pause && proc_ctx.total_frames > 0) { printf( - "\r[Video2X] Processing frame %ld/%ld (%.2f%%); time elapsed: %lds", + "\rProcessing frame %ld/%ld (%.2f%%); time elapsed: %lds", proc_ctx.processed_frames, proc_ctx.total_frames, proc_ctx.total_frames > 0 @@ -495,10 +528,9 @@ int main(int argc, char **argv) { fflush(stdout); } - // Sleep for a short duration - thrd_sleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 100000000}, NULL); // Sleep for 100ms + // Sleep for 50ms + thrd_sleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 50000000}, NULL); } - puts(""); // Print newline after progress bar is complete // Restore terminal to blocking mode #ifndef _WIN32 @@ -509,6 +541,11 @@ int main(int argc, char **argv) { int process_result; thrd_join(processing_thread, &process_result); + // Print a newline if progress bar was displayed + if (!arguments.noprogress && process_result == 0) { + puts(""); + } + if (proc_ctx.abort) { fprintf(stderr, "Video processing aborted\n"); return 2; diff --git a/third_party/spdlog b/third_party/spdlog new file mode 160000 index 00000000..e593f669 --- /dev/null +++ b/third_party/spdlog @@ -0,0 +1 @@ +Subproject commit e593f6695c6065e6b345fe2862f04a519ed484e0