diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2061085..c69802b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,7 +31,8 @@ jobs: libvulkan-dev \ glslang-tools \ libomp-dev \ - libopencv-dev + libopencv-dev \ + libboost-program-options-dev - name: Build Video2X run: | mkdir -p /tmp/build /tmp/install @@ -59,6 +60,7 @@ jobs: with: vulkan-query-version: 1.3.204.0 vulkan-components: Vulkan-Headers, Vulkan-Loader, Glslang, SPIRV-Tools, SPIRV-Headers + vulkan-use-cache: true - name: Install dependencies shell: pwsh run: | @@ -83,7 +85,7 @@ jobs: shell: pwsh run: | cmake -S . -B build ` - -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DUSE_SYSTEM_OPENCV=OFF ` + -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DUSE_SYSTEM_OPENCV=OFF -DUSE_SYSTEM_BOOST=OFF ` -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=build/video2x_install cmake --build build --config Debug --parallel --target install - name: Upload artifacts diff --git a/.gitmodules b/.gitmodules index 963f9c9..857e1f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third_party/opencv"] path = third_party/opencv url = https://github.com/opencv/opencv.git +[submodule "third_party/boost"] + path = third_party/boost + url = https://github.com/boostorg/boost.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f03ec58..17d676c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(video2x VERSION 6.0.0 LANGUAGES C CXX) - -# Set the C standard -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) +project(video2x VERSION 6.0.0 LANGUAGES CXX) # Set the C++ standard set(CMAKE_CXX_STANDARD 17) @@ -18,10 +14,8 @@ endif() # Set the default optimization flags for Release builds if(CMAKE_BUILD_TYPE STREQUAL "Release") if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Ox /GL /LTCG /MD /DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Ox /GL /LTCG /MD /DNDEBUG") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -march=native -flto") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -march=native -flto") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} -s") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -s") @@ -38,9 +32,10 @@ endif() # Build options option(BUILD_SHARED_LIBS "Build libvideo2x as a shared library" ON) option(BUILD_VIDEO2X_CLI "Build the video2x executable" ON) -option(USE_SYSTEM_SPDLOG "Use system spdlog library" ON) option(USE_SYSTEM_OPENCV "Use system OpenCV library" ON) option(USE_SYSTEM_NCNN "Use system ncnn library" ON) +option(USE_SYSTEM_SPDLOG "Use system spdlog library" ON) +option(USE_SYSTEM_BOOST "Use system Boost library" ON) # Generate the version header file configure_file( @@ -53,16 +48,6 @@ configure_file( set(ALL_INCLUDE_DIRS) set(ALL_LIBRARIES) -# spdlog -if (USE_SYSTEM_SPDLOG) - find_package(spdlog REQUIRED) - list(APPEND ALL_INCLUDE_DIRS ${spdlog_INCLUDE_DIRS}) - 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 @@ -71,7 +56,7 @@ if(WIN32) set(OPENCV_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/opencv-shared) # FFmpeg - list(APPEND ALL_LIBRARIES + set(FFMPEG_LIB ${FFMPEG_BASE_PATH}/lib/avcodec.lib ${FFMPEG_BASE_PATH}/lib/avdevice.lib ${FFMPEG_BASE_PATH}/lib/avfilter.lib @@ -79,6 +64,7 @@ if(WIN32) ${FFMPEG_BASE_PATH}/lib/avutil.lib ${FFMPEG_BASE_PATH}/lib/swscale.lib ) + list(APPEND ALL_LIBRARIES ${FFMPEG_LIB}) list(APPEND ALL_INCLUDE_DIRS ${FFMPEG_BASE_PATH}/include) # ncnn @@ -99,7 +85,7 @@ if(WIN32) list(APPEND ALL_INCLUDE_DIRS ${NCNN_BASE_PATH}/include/ncnn) # OpenCV - list(APPEND ALL_LIBRARIES ${OPENCV_BASE_PATH}/build/x64/vc16/lib/opencv_world4100.lib) + # list(APPEND ALL_LIBRARIES ${OPENCV_BASE_PATH}/build/x64/vc16/lib/opencv_world4100.lib) list(APPEND ALL_INCLUDE_DIRS ${OPENCV_BASE_PATH}/build/include) else() # FFmpeg @@ -114,17 +100,19 @@ else() ) # Loop through each package to find and collect include dirs and libraries + set(FFMPEG_LIB) foreach(PKG ${FFMPEG_REQUIRED_PKGS}) pkg_check_modules(${PKG} REQUIRED ${PKG}) list(APPEND ALL_INCLUDE_DIRS ${${PKG}_INCLUDE_DIRS}) - list(APPEND ALL_LIBRARIES ${${PKG}_LIBRARIES}) + list(APPEND FFMPEG_LIB ${${PKG}_LIBRARIES}) endforeach() + list(APPEND ALL_LIBRARIES ${FFMPEG_LIB}) # OpenCV if (USE_SYSTEM_OPENCV) find_package(OpenCV REQUIRED) list(APPEND ALL_INCLUDE_DIRS ${OpenCV_INCLUDE_DIRS}/opencv2) - list(APPEND ALL_LIBRARIES opencv_core opencv_videoio) + # list(APPEND ALL_LIBRARIES opencv_core opencv_videoio) else() option(BUILD_opencv_calib3d "" OFF) option(BUILD_opencv_core "" ON) @@ -165,7 +153,7 @@ else() ${PROJECT_SOURCE_DIR}/third_party/opencv/modules/core/include ${PROJECT_SOURCE_DIR}/third_party/opencv/modules/videoio/include ) - list(APPEND ALL_LIBRARIES opencv_core opencv_videoio) + # list(APPEND ALL_LIBRARIES opencv_core opencv_videoio) endif() # USE_SYSTEM_OPENCV endif() # WIN32 @@ -268,6 +256,32 @@ else() add_subdirectory(third_party/ncnn) endif() +# spdlog +if (USE_SYSTEM_SPDLOG) + find_package(spdlog REQUIRED) + list(APPEND ALL_INCLUDE_DIRS ${spdlog_INCLUDE_DIRS}) + set(SPDLOG_LIB spdlog::spdlog) +else() + add_subdirectory(third_party/spdlog) + set(SPDLOG_LIB spdlog::spdlog_header_only) +endif() +list(APPEND ALL_LIBRARIES ${SPDLOG_LIB}) + +# Boost +if (USE_SYSTEM_BOOST) + find_package(Boost REQUIRED COMPONENTS program_options) + list(APPEND ALL_INCLUDE_DIRS ${Boost_INCLUDE_DIRS}) +else() + option(Boost_USE_STATIC_LIBS "" ON) + option(Boost_USE_STATIC_RUNTIME "" ON) + option(Boost_COMPONENTS "program_options") + + add_subdirectory(third_party/boost) + include_directories(${PROJECT_SOURCE_DIR}/third_party/boost/libs/program_options/include) + set(BOOST_BASE_PATH ${CMAKE_BINARY_DIR}/third_party/boost/libs/program_options/${CMAKE_BUILD_TYPE}) +endif() +set(BOOST_LIB Boost::program_options) + # Include ExternalProject module include(ExternalProject) @@ -288,11 +302,17 @@ ExternalProject_Add( list(REMOVE_DUPLICATES ALL_INCLUDE_DIRS) list(REMOVE_DUPLICATES ALL_LIBRARIES) -# Add all source files for libvideo2x -file(GLOB LIBVIDEO2X_SOURCES src/*.cpp) - # Create the shared library 'libvideo2x' -add_library(libvideo2x ${LIBVIDEO2X_SOURCES}) +add_library(libvideo2x + src/conversions.cpp + src/decoder.cpp + src/encoder.cpp + src/fsutils.cpp + src/libplacebo_filter.cpp + src/libplacebo.cpp + src/libvideo2x.cpp + src/realesrgan_filter.cpp +) target_compile_definitions(libvideo2x PRIVATE LIBVIDEO2X_EXPORTS) if(WIN32) set_target_properties(libvideo2x PROPERTIES OUTPUT_NAME libvideo2x) @@ -321,13 +341,13 @@ target_compile_options(libvideo2x PRIVATE # Define the path to the built libresrgan-ncnn-vulkan library if(WIN32) - set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.lib) + list(APPEND ALL_LIBRARIES ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.lib) else() - set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.so) + list(APPEND ALL_LIBRARIES ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.so) endif() # Link the shared library with the dependencies -target_link_libraries(libvideo2x PRIVATE ${ALL_LIBRARIES} ${REALESRGAN_LIB}) +target_link_libraries(libvideo2x PRIVATE ${ALL_LIBRARIES}) if(NOT WIN32) if (USE_SYSTEM_NCNN) @@ -339,7 +359,7 @@ endif() # Create the executable 'video2x' if (BUILD_VIDEO2X_CLI) - add_executable(video2x src/video2x.c src/getopt.c) + add_executable(video2x src/video2x.cpp) set_target_properties(video2x PROPERTIES OUTPUT_NAME video2x) # Include directories for the executable @@ -353,7 +373,7 @@ if (BUILD_VIDEO2X_CLI) target_compile_options(video2x PRIVATE $<$:-g -DDEBUG>) # Link the executable with the shared library - target_link_libraries(video2x PRIVATE ${ALL_LIBRARIES} libvideo2x) + target_link_libraries(video2x PRIVATE libvideo2x ${FFMPEG_LIB} ${SPDLOG_LIB} ${BOOST_LIB}) endif() # Define the default installation directories @@ -408,33 +428,26 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h # Platform-specific installation rules if(WIN32) # Install Windows-specific dependencies - install(FILES ${CMAKE_BINARY_DIR}/realesrgan_install/bin/librealesrgan-ncnn-vulkan.dll - DESTINATION ${INSTALL_BIN_DESTINATION} - PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE - ) - install(FILES ${FFMPEG_BASE_PATH}/bin/swscale-8.dll - ${FFMPEG_BASE_PATH}/bin/avcodec-61.dll - ${FFMPEG_BASE_PATH}/bin/avdevice-61.dll - ${FFMPEG_BASE_PATH}/bin/avfilter-10.dll - ${FFMPEG_BASE_PATH}/bin/avformat-61.dll - ${FFMPEG_BASE_PATH}/bin/avutil-59.dll - ${FFMPEG_BASE_PATH}/bin/postproc-58.dll - ${FFMPEG_BASE_PATH}/bin/swresample-5.dll - DESTINATION ${INSTALL_BIN_DESTINATION} - PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE - ) - install(FILES ${OPENCV_BASE_PATH}/build/x64/vc16/bin/opencv_world4100.dll - ${OPENCV_BASE_PATH}/build/x64/vc16/bin/opencv_videoio_msmf4100_64.dll - DESTINATION ${INSTALL_BIN_DESTINATION} - PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE - GROUP_READ GROUP_EXECUTE - WORLD_READ WORLD_EXECUTE - ) - install(FILES ${NCNN_BASE_PATH}/bin/ncnn.dll + if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(BOOST_DLL_PATH ${BOOST_BASE_PATH}/boost_program_options-vc143-mt-x64-1_86.dll) + else() + set(BOOST_DLL_PATH ${BOOST_BASE_PATH}/boost_program_options-vc143-mt-gd-x64-1_86.dll) + endif() + + install(FILES + ${CMAKE_BINARY_DIR}/realesrgan_install/bin/librealesrgan-ncnn-vulkan.dll + ${FFMPEG_BASE_PATH}/bin/swscale-8.dll + ${FFMPEG_BASE_PATH}/bin/avcodec-61.dll + ${FFMPEG_BASE_PATH}/bin/avdevice-61.dll + ${FFMPEG_BASE_PATH}/bin/avfilter-10.dll + ${FFMPEG_BASE_PATH}/bin/avformat-61.dll + ${FFMPEG_BASE_PATH}/bin/avutil-59.dll + ${FFMPEG_BASE_PATH}/bin/postproc-58.dll + ${FFMPEG_BASE_PATH}/bin/swresample-5.dll + # ${OPENCV_BASE_PATH}/build/x64/vc16/bin/opencv_world4100.dll + # ${OPENCV_BASE_PATH}/build/x64/vc16/bin/opencv_videoio_msmf4100_64.dll + ${NCNN_BASE_PATH}/bin/ncnn.dll + ${BOOST_DLL_PATH} DESTINATION ${INSTALL_BIN_DESTINATION} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE diff --git a/Makefile b/Makefile index 16f89fc..c13dc64 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,8 @@ debian: glslang-tools \ libomp-dev \ libspdlog-dev \ - libopencv-dev + libopencv-dev \ + libboost-program-options-dev cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \ -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/install \ @@ -74,7 +75,8 @@ ubuntu: libvulkan-dev \ glslang-tools \ libomp-dev \ - libopencv-dev + libopencv-dev \ + libboost-program-options-dev cmake -B build -S . -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DSPDLOG_NO_EXCEPTIONS=ON \ -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/video2x_package/usr diff --git a/include/getopt.h b/include/getopt.h deleted file mode 100644 index be8b5ac..0000000 --- a/include/getopt.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef __GETOPT_H__ -#define __GETOPT_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -extern int opterr; /* if error message should be printed */ -extern int optind; /* index into parent argv vector */ -extern int optopt; /* character checked for validity */ -extern int optreset; /* reset getopt */ -extern char *optarg; /* argument associated with option */ - -struct option { - const char *name; - int has_arg; - int *flag; - int val; -}; - -#define no_argument 0 -#define required_argument 1 -#define optional_argument 2 - -// int getopt(int, char **, const char *); -int getopt_long(int, char **, const char *, const struct option *, int *); - -#ifdef __cplusplus -} -#endif - -#endif /* __GETOPT_H__ */ diff --git a/include/libvideo2x/decoder.h b/include/libvideo2x/decoder.h index bd0312b..dc90c3e 100644 --- a/include/libvideo2x/decoder.h +++ b/include/libvideo2x/decoder.h @@ -1,6 +1,8 @@ #ifndef DECODER_H #define DECODER_H +#include + extern "C" { #include #include @@ -9,7 +11,7 @@ extern "C" { int init_decoder( AVHWDeviceType hw_type, AVBufferRef *hw_ctx, - const char *in_fname, + std::filesystem::path in_fpath, AVFormatContext **fmt_ctx, AVCodecContext **dec_ctx, int *vstream_idx diff --git a/include/libvideo2x/encoder.h b/include/libvideo2x/encoder.h index 090ceb8..65d39a4 100644 --- a/include/libvideo2x/encoder.h +++ b/include/libvideo2x/encoder.h @@ -1,6 +1,8 @@ #ifndef ENCODER_H #define ENCODER_H +#include + extern "C" { #include #include @@ -11,7 +13,7 @@ extern "C" { int init_encoder( AVBufferRef *hw_ctx, - const char *out_fname, + std::filesystem::path out_fpath, AVFormatContext *ifmt_ctx, AVFormatContext **ofmt_ctx, AVCodecContext **enc_ctx, diff --git a/include/libvideo2x/libvideo2x.h b/include/libvideo2x/libvideo2x.h index 38addab..5b315e6 100644 --- a/include/libvideo2x/libvideo2x.h +++ b/include/libvideo2x/libvideo2x.h @@ -43,7 +43,11 @@ enum Libvideo2xLogLevel { struct LibplaceboConfig { int out_width; int out_height; +#ifdef _WIN32 + const wchar_t *shader_path; +#else const char *shader_path; +#endif }; // Configuration for RealESRGAN filter @@ -51,7 +55,11 @@ struct RealESRGANConfig { int gpuid; bool tta_mode; int scaling_factor; - const char *model; +#ifdef _WIN32 + const wchar_t *model_path; +#else + const char *model_path; +#endif }; // Unified filter configuration @@ -87,8 +95,13 @@ struct VideoProcessingContext { // C-compatible process_video function LIBVIDEO2X_API int process_video( +#ifdef _WIN32 + const wchar_t *in_fname, + const wchar_t *out_fname, +#else const char *in_fname, const char *out_fname, +#endif enum Libvideo2xLogLevel log_level, bool benchmark, enum AVHWDeviceType hw_device_type, diff --git a/include/libvideo2x/realesrgan_filter.h b/include/libvideo2x/realesrgan_filter.h index e046c1f..88e3695 100644 --- a/include/libvideo2x/realesrgan_filter.h +++ b/include/libvideo2x/realesrgan_filter.h @@ -17,7 +17,7 @@ class RealesrganFilter : public Filter { int gpuid; bool tta_mode; int scaling_factor; - const char *model; + const std::filesystem::path model_path; const std::filesystem::path custom_model_param_path; const std::filesystem::path custom_model_bin_path; AVRational in_time_base; @@ -30,7 +30,7 @@ class RealesrganFilter : public Filter { int gpuid = 0, bool tta_mode = false, int scaling_factor = 4, - const char *model = "realesr-animevideov3", + const std::filesystem::path model = std::filesystem::path("realesr-animevideov3"), const std::filesystem::path custom_model_param_path = std::filesystem::path(), const std::filesystem::path custom_model_bin_path = std::filesystem::path() ); diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index a0b8dce..646f755 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -1,12 +1,12 @@ pkgname=video2x -pkgver=r843.e09f348 +pkgver=r874.66c623f pkgrel=1 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' 'opencv' 'spdlog') -makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp') +depends=('ffmpeg' 'ncnn' 'vulkan-driver' 'opencv' 'spdlog' 'boost-libs') +makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp' 'boost') pkgver() { printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" diff --git a/packaging/debian/control b/packaging/debian/control index 91bf591..a64c236 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -4,5 +4,5 @@ Section: video Priority: optional Architecture: amd64 Maintainer: K4YT3X -Depends: ffmpeg, libopencv-videoio406t64 +Depends: ffmpeg, libboost-program-options1.83.0 Description: A machine learning-based lossless video super resolution framework. diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index 5583cdc..d0396d7 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -1,7 +1,7 @@ # Name: Video2X Dockerfile # Creator: K4YT3X # Date Created: February 3, 2022 -# Last Modified: October 30, 2024 +# Last Modified: November 1, 2024 # stage 1: build the python components into wheels FROM docker.io/archlinux:latest AS builder @@ -9,7 +9,7 @@ FROM docker.io/archlinux:latest AS builder # Install dependencies and create a non-root user RUN pacman -Syy --noconfirm \ base-devel git cmake make clang pkgconf sudo \ - ffmpeg ncnn vulkan-headers openmp spdlog opencv \ + ffmpeg ncnn vulkan-headers openmp spdlog opencv boost \ nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \ && useradd -m builder \ && echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder @@ -38,7 +38,7 @@ ENV VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json\ COPY --from=builder /tmp/video2x.pkg.tar.zst /video2x.pkg.tar.zst RUN pacman -Sy --noconfirm nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \ - ffmpeg ncnn spdlog opencv \ + ffmpeg ncnn spdlog opencv boost-libs \ && pacman -U --noconfirm /video2x.pkg.tar.zst \ && rm -rf /video2x.pkg.tar.zst /var/cache/pacman/pkg/* diff --git a/src/decoder.cpp b/src/decoder.cpp index 48ae246..d5a05ee 100644 --- a/src/decoder.cpp +++ b/src/decoder.cpp @@ -22,7 +22,7 @@ static enum AVPixelFormat get_hw_format(AVCodecContext *_, const enum AVPixelFor int init_decoder( AVHWDeviceType hw_type, AVBufferRef *hw_ctx, - const char *in_fname, + std::filesystem::path in_fpath, AVFormatContext **fmt_ctx, AVCodecContext **dec_ctx, int *vstream_idx @@ -31,8 +31,8 @@ int init_decoder( AVCodecContext *codec_ctx = NULL; int ret; - if ((ret = avformat_open_input(&ifmt_ctx, in_fname, NULL, NULL)) < 0) { - spdlog::error("Could not open input file '{}'", in_fname); + if ((ret = avformat_open_input(&ifmt_ctx, in_fpath.u8string().c_str(), NULL, NULL)) < 0) { + spdlog::error("Could not open input file '{}'", in_fpath.u8string().c_str()); return ret; } diff --git a/src/encoder.cpp b/src/encoder.cpp index 53d879c..d5fe5ce 100644 --- a/src/encoder.cpp +++ b/src/encoder.cpp @@ -19,7 +19,7 @@ static enum AVPixelFormat get_encoder_default_pix_fmt(const AVCodec *encoder) { int init_encoder( AVBufferRef *hw_ctx, - const char *out_fname, + std::filesystem::path out_fpath, AVFormatContext *ifmt_ctx, AVFormatContext **ofmt_ctx, AVCodecContext **enc_ctx, @@ -33,7 +33,7 @@ int init_encoder( int stream_index = 0; int ret; - avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_fname); + avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_fpath.u8string().c_str()); if (!fmt_ctx) { spdlog::error("Could not create output context"); return AVERROR_UNKNOWN; @@ -174,9 +174,9 @@ int init_encoder( // Open the output file if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) { - ret = avio_open(&fmt_ctx->pb, out_fname, AVIO_FLAG_WRITE); + ret = avio_open(&fmt_ctx->pb, out_fpath.u8string().c_str(), AVIO_FLAG_WRITE); if (ret < 0) { - spdlog::error("Could not open output file '{}'", out_fname); + spdlog::error("Could not open output file '{}'", out_fpath.u8string().c_str()); return ret; } } diff --git a/src/getopt.c b/src/getopt.c deleted file mode 100644 index 8924c93..0000000 --- a/src/getopt.c +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 1987, 1993, 1994, 1996 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS - * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include -#include - -#include "getopt.h" - -/* -extern int opterr; -extern int optind; -extern int optopt; -extern int optreset; -extern char *optarg; -*/ - -int opterr = 1; /* if error message should be printed */ -int optind = 1; /* index into parent argv vector */ -int optopt = 0; /* character checked for validity */ -int optreset = 0; /* reset getopt */ -char *optarg = NULL; /* argument associated with option */ - -#ifndef __P -#define __P(x) x -#endif -#define _DIAGASSERT(x) assert(x) - -static char *__progname __P((char *)); -int getopt_internal __P((int, char *const *, const char *)); - -static char *__progname(char *nargv0) { - char *tmp; - - _DIAGASSERT(nargv0 != NULL); - - tmp = strrchr(nargv0, '/'); - if (tmp) { - tmp++; - } else { - tmp = nargv0; - } - return (tmp); -} - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int getopt_internal(int nargc, char *const *nargv, const char *ostr) { - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - - _DIAGASSERT(nargv != NULL); - _DIAGASSERT(ostr != NULL); - - if (optreset || !*place) { /* update scanning pointer */ - optreset = 0; - if (optind >= nargc || *(place = nargv[optind]) != '-') { - place = EMSG; - return (-1); - } - if (place[1] && *++place == '-') { /* found "--" */ - /* ++optind; */ - place = EMSG; - return (-2); - } - } /* option letter okay? */ - if ((optopt = (int)*place++) == (int)':' || !(oli = strchr(ostr, optopt))) { - /* - * if the user didn't specify '-' as an option, - * assume it means -1. - */ - if (optopt == (int)'-') { - return (-1); - } - if (!*place) { - ++optind; - } - if (opterr && *ostr != ':') { - (void)fprintf(stderr, "%s: illegal option -- %c\n", __progname(nargv[0]), optopt); - } - return (BADCH); - } - if (*++oli != ':') { /* don't need argument */ - optarg = NULL; - if (!*place) { - ++optind; - } - } else { /* need an argument */ - if (*place) { /* no white space */ - optarg = place; - } else if (nargc <= ++optind) { /* no arg */ - place = EMSG; - if ((opterr) && (*ostr != ':')) { - (void)fprintf( - stderr, "%s: option requires an argument -- %c\n", __progname(nargv[0]), optopt - ); - } - return (BADARG); - } else { /* white space */ - optarg = nargv[optind]; - } - place = EMSG; - ++optind; - } - return (optopt); /* dump back option letter */ -} - -#if 0 -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt2(nargc, nargv, ostr) - int nargc; - char * const *nargv; - const char *ostr; -{ - int retval; - - if ((retval = getopt_internal(nargc, nargv, ostr)) == -2) { - retval = -1; - ++optind; - } - return(retval); -} -#endif - -/* - * getopt_long -- - * Parse argc/argv argument vector. - */ -int getopt_long( - int nargc, - char **nargv, - const char *options, - const struct option *long_options, - int *index -) { - int retval; - - _DIAGASSERT(nargv != NULL); - _DIAGASSERT(options != NULL); - _DIAGASSERT(long_options != NULL); - /* index may be NULL */ - - if ((retval = getopt_internal(nargc, nargv, options)) == -2) { - char *current_argv = nargv[optind++] + 2, *has_equal; - int i, match = -1; - size_t current_argv_len; - - if (*current_argv == '\0') { - return (-1); - } - if ((has_equal = strchr(current_argv, '=')) != NULL) { - current_argv_len = (size_t)(has_equal - current_argv); - has_equal++; - } else { - current_argv_len = strlen(current_argv); - } - - for (i = 0; long_options[i].name; i++) { - if (strncmp(current_argv, long_options[i].name, current_argv_len)) { - continue; - } - - if (strlen(long_options[i].name) == current_argv_len) { - match = i; - break; - } - if (match == -1) { - match = i; - } - } - if (match != -1) { - if (long_options[match].has_arg == required_argument || - long_options[match].has_arg == optional_argument) { - if (has_equal) { - optarg = has_equal; - } else { - optarg = nargv[optind++]; - } - } - if ((long_options[match].has_arg == required_argument) && (optarg == NULL)) { - /* - * Missing argument, leading : - * indicates no error should be generated - */ - if ((opterr) && (*options != ':')) { - (void)fprintf( - stderr, - "%s: option requires an argument -- %s\n", - __progname(nargv[0]), - current_argv - ); - } - return (BADARG); - } - } else { /* No matching argument */ - if ((opterr) && (*options != ':')) { - (void - )fprintf(stderr, "%s: illegal option -- %s\n", __progname(nargv[0]), current_argv); - } - return (BADCH); - } - if (long_options[match].flag) { - *long_options[match].flag = long_options[match].val; - retval = 0; - } else { - retval = long_options[match].val; - } - if (index) { - *index = match; - } - } - return (retval); -} diff --git a/src/libvideo2x.cpp b/src/libvideo2x.cpp index ae7eaf3..ee1438f 100644 --- a/src/libvideo2x.cpp +++ b/src/libvideo2x.cpp @@ -5,8 +5,11 @@ #include #include +extern "C" { +#include +} + #include -#include #include "decoder.h" #include "encoder.h" @@ -46,28 +49,46 @@ static int process_frames( std::vector flushed_frames; // Get the total number of frames in the video with OpenCV - spdlog::debug("Reading total number of frames with OpenCV"); - cv::VideoCapture cap(ifmt_ctx->url); - if (!cap.isOpened()) { - spdlog::error("Failed to open video file with OpenCV"); - return -1; + spdlog::debug("Reading total number of frames"); + proc_ctx->total_frames = ifmt_ctx->streams[vstream_idx]->nb_frames; + if (proc_ctx->total_frames > 0) { + spdlog::debug("Read total number of frames from 'nb_frames': {}", proc_ctx->total_frames); + } else { + spdlog::warn("Estimating the total number of frames from duration * fps"); + // Calculate duration in seconds + double duration_secs = static_cast(ifmt_ctx->streams[vstream_idx]->duration) * + av_q2d(ifmt_ctx->streams[vstream_idx]->time_base); + spdlog::debug("Video duration: {}s", duration_secs); + + // Calculate average FPS + double fps = av_q2d(ifmt_ctx->streams[vstream_idx]->avg_frame_rate); + if (fps <= 0) { + spdlog::debug("Unable to read the average frame rate from 'avg_frame_rate'"); + fps = av_q2d(ifmt_ctx->streams[vstream_idx]->r_frame_rate); + } + if (fps <= 0) { + spdlog::debug("Unable to read the average frame rate from 'r_frame_rate'"); + fps = av_q2d(av_guess_frame_rate(ifmt_ctx, ifmt_ctx->streams[vstream_idx], nullptr)); + } + if (fps <= 0) { + spdlog::debug("Unable to estimate the average frame rate with 'av_guess_frame_rate'"); + fps = av_q2d(ifmt_ctx->streams[vstream_idx]->time_base); + } + if (fps <= 0) { + spdlog::debug("Unable to estimate the video's average frame rate"); + } else { + // Calculate total frames + proc_ctx->total_frames = static_cast(duration_secs * fps); + } } - proc_ctx->total_frames = static_cast(cap.get(cv::CAP_PROP_FRAME_COUNT)); - cap.release(); // Check if the total number of frames is still 0 if (proc_ctx->total_frames == 0) { - spdlog::warn("Unable to determine total number of frames"); + spdlog::warn("Unable to determine the total number of frames"); } else { spdlog::debug("{} frames to process", proc_ctx->total_frames); } - // Get start time - proc_ctx->start_time = time(NULL); - if (proc_ctx->start_time == -1) { - perror("time"); - } - AVFrame *frame = av_frame_alloc(); if (frame == nullptr) { ret = AVERROR(ENOMEM); @@ -236,8 +257,13 @@ static int process_frames( * @return int 0 on success, non-zero value on error */ extern "C" int process_video( +#ifdef _WIN32 + const wchar_t *in_fname, + const wchar_t *out_fname, +#else const char *in_fname, const char *out_fname, +#endif Libvideo2xLogLevel log_level, bool benchmark, AVHWDeviceType hw_type, @@ -328,6 +354,10 @@ extern "C" int process_video( break; } + // Convert the file names to std::filesystem::path + std::filesystem::path in_fpath(in_fname); + std::filesystem::path out_fpath(out_fname); + // Initialize hardware device context if (hw_type != AV_HWDEVICE_TYPE_NONE) { ret = av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0); @@ -340,7 +370,7 @@ extern "C" int process_video( } // Initialize input - ret = init_decoder(hw_type, hw_ctx, in_fname, &ifmt_ctx, &dec_ctx, &vstream_idx); + ret = init_decoder(hw_type, hw_ctx, in_fpath, &ifmt_ctx, &dec_ctx, &vstream_idx); if (ret < 0) { av_strerror(ret, errbuf, sizeof(errbuf)); spdlog::error("Failed to initialize decoder: {}", errbuf); @@ -371,7 +401,7 @@ extern "C" int process_video( encoder_config->out_height = output_height; ret = init_encoder( hw_ctx, - out_fname, + out_fpath, ifmt_ctx, &ofmt_ctx, &enc_ctx, @@ -409,13 +439,13 @@ extern "C" int process_video( }; } else if (filter_config->filter_type == FILTER_REALESRGAN) { const auto &config = filter_config->config.realesrgan; - if (!config.model) { + if (!config.model_path) { spdlog::error("Model name must be provided for the RealESRGAN filter"); cleanup(); return -1; } filter = new RealesrganFilter{ - config.gpuid, config.tta_mode, config.scaling_factor, config.model + config.gpuid, config.tta_mode, config.scaling_factor, config.model_path }; } else { spdlog::error("Unknown filter type"); diff --git a/src/realesrgan_filter.cpp b/src/realesrgan_filter.cpp index 419e571..2699851 100644 --- a/src/realesrgan_filter.cpp +++ b/src/realesrgan_filter.cpp @@ -13,7 +13,7 @@ RealesrganFilter::RealesrganFilter( int gpuid, bool tta_mode, int scaling_factor, - const char *model, + const std::filesystem::path model_path, const std::filesystem::path custom_model_param_path, const std::filesystem::path custom_model_bin_path ) @@ -21,7 +21,7 @@ RealesrganFilter::RealesrganFilter( gpuid(gpuid), tta_mode(tta_mode), scaling_factor(scaling_factor), - model(model), + model_path(std::move(model_path)), custom_model_param_path(std::move(custom_model_param_path)), custom_model_bin_path(std::move(custom_model_bin_path)) {} @@ -37,12 +37,13 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB std::filesystem::path model_param_path; std::filesystem::path model_bin_path; - if (model) { + if (!model_path.empty()) { // Find the model paths by model name if provided + // TODO: ensure this works with wide strings on Windows model_param_path = std::filesystem::path("models") / "realesrgan" / - (std::string(model) + "-x" + std::to_string(scaling_factor) + ".param"); + (model_path.string() + "-x" + std::to_string(scaling_factor) + ".param"); model_bin_path = std::filesystem::path("models") / "realesrgan" / - (std::string(model) + "-x" + std::to_string(scaling_factor) + ".bin"); + (model_path.string() + "-x" + std::to_string(scaling_factor) + ".bin"); } else if (!custom_model_param_path.empty() && !custom_model_bin_path.empty()) { // Use the custom model paths if provided model_param_path = custom_model_param_path; diff --git a/src/video2x.c b/src/video2x.c deleted file mode 100644 index 5f08da6..0000000 --- a/src/video2x.c +++ /dev/null @@ -1,599 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#else -#include -#include -#include -#endif - -#include -#include -#include - -#include -#include - -#include "getopt.h" - -// 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}, - - // General options - {"input", required_argument, NULL, 'i'}, - {"output", required_argument, NULL, 'o'}, - {"filter", required_argument, NULL, 'f'}, - {"hwaccel", required_argument, NULL, 'a'}, - {"nocopystreams", no_argument, NULL, 0}, - {"benchmark", no_argument, NULL, 0}, - - // Encoder options - {"codec", required_argument, NULL, 'c'}, - {"preset", required_argument, NULL, 'p'}, - {"pixfmt", required_argument, NULL, 'x'}, - {"bitrate", required_argument, NULL, 'b'}, - {"crf", required_argument, NULL, 'q'}, - - // libplacebo options - {"shader", required_argument, NULL, 's'}, - {"width", required_argument, NULL, 'w'}, - {"height", required_argument, NULL, 'h'}, - - // RealESRGAN options - {"gpuid", required_argument, NULL, 'g'}, - {"model", required_argument, NULL, 'm'}, - {"scale", required_argument, NULL, 'r'}, - {0, 0, 0, 0} -}; - -// List of valid RealESRGAN models -const char *valid_realesrgan_models[] = { - "realesrgan-plus", - "realesrgan-plus-anime", - "realesr-animevideov3", -}; - -// Indicate if a newline needs to be printed before the next output -bool newline_required = false; - -// Structure to hold parsed arguments -struct arguments { - // General options - const char *loglevel; - bool noprogress; - const char *in_fname; - const char *out_fname; - const char *filter_type; - const char *hwaccel; - bool nocopystreams; - bool benchmark; - - // Encoder options - const char *codec; - const char *pix_fmt; - const char *preset; - int64_t bitrate; - float crf; - - // libplacebo options - const char *shader_path; - int out_width; - int out_height; - - // RealESRGAN options - int gpuid; - const char *model; - int scaling_factor; -}; - -struct ProcessVideoThreadArguments { - struct arguments *arguments; - enum AVHWDeviceType hw_device_type; - struct FilterConfig *filter_config; - struct EncoderConfig *encoder_config; - struct VideoProcessingContext *proc_ctx; -}; - -// Set UNIX terminal input to non-blocking mode -#ifndef _WIN32 -void set_nonblocking_input(bool enable) { - static struct termios oldt, newt; - if (enable) { - tcgetattr(STDIN_FILENO, &oldt); - newt = oldt; - newt.c_lflag &= ~(tcflag_t)(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &newt); - fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); - } else { - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); - fcntl(STDIN_FILENO, F_SETFL, 0); - } -} -#endif - -// Newline-safe log callback for FFmpeg -void newline_safe_ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) { - if (level <= av_log_get_level() && newline_required) { - putchar('\n'); - newline_required = false; - } - av_log_default_callback(ptr, level, fmt, vl); -} - -int is_valid_realesrgan_model(const char *model) { - if (!model) { - return 0; - } - for (unsigned long i = 0; - i < sizeof(valid_realesrgan_models) / sizeof(valid_realesrgan_models[0]); - i++) { - if (strcmp(model, valid_realesrgan_models[i]) == 0) { - return 1; - } - } - return 0; -} - -void print_help(void) { - printf("Usage: video2x [OPTIONS]\n"); - printf("\nOptions:\n"); - printf(" --loglevel Set log level \n"); - printf(" (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"); - printf(" -i, --input Input video file path\n"); - printf(" -o, --output Output video file path\n"); - printf(" -f, --filter Filter to use: 'libplacebo' or 'realesrgan'\n"); - printf(" -a, --hwaccel Hardware acceleration method (default: none)\n"); - printf(" --nocopystreams Do not copy audio and subtitle streams\n"); - printf(" --benchmark Discard processed frames and calculate average FPS\n"); - - printf("\nEncoder Options (Optional):\n"); - printf(" -c, --codec Output codec (default: libx264)\n"); - printf(" -p, --preset Encoder preset (default: slow)\n"); - printf(" -x, --pixfmt Output pixel format (default: auto)\n"); - printf(" -b, --bitrate Bitrate in bits per second (default: 0 (VBR))\n"); - printf(" -q, --crf Constant Rate Factor (default: 20.0)\n"); - - printf("\nlibplacebo Options:\n"); - printf(" -s, --shader Name or path of the GLSL shader file to use \n"); - printf(" (built-in: 'anime4k-a', 'anime4k-b', 'anime4k-c',\n"); - printf(" 'anime4k-a+a', 'anime4k-b+b', 'anime4k-c+a')\n"); - printf(" -w, --width Output width\n"); - printf(" -h, --height Output height\n"); - - printf("\nRealESRGAN Options:\n"); - printf(" -g, --gpuid Vulkan GPU ID (default: 0)\n"); - printf(" -m, --model Name of the model to use\n"); - printf(" -r, --scale Scaling factor (2, 3, or 4)\n"); - - printf("\nExamples Usage:\n"); - printf(" video2x -i in.mp4 -o out.mp4 -f libplacebo -s anime4k-v4-a+a -w 3840 -h 2160\n"); - printf(" video2x -i in.mp4 -o out.mp4 -f realesrgan -m realesr-animevideov3 -r 4\n"); -} - -void parse_arguments(int argc, char **argv, struct arguments *arguments) { - int option_index = 0; - int c; - - // Default argument values - arguments->loglevel = "info"; - arguments->noprogress = false; - arguments->in_fname = NULL; - arguments->out_fname = NULL; - arguments->filter_type = NULL; - arguments->hwaccel = "none"; - arguments->nocopystreams = false; - arguments->benchmark = false; - - // Encoder options - arguments->codec = "libx264"; - arguments->preset = "slow"; - arguments->pix_fmt = NULL; - arguments->bitrate = 0; - arguments->crf = 20.0; - - // libplacebo options - arguments->shader_path = NULL; - arguments->out_width = 0; - arguments->out_height = 0; - - // RealESRGAN options - arguments->gpuid = 0; - arguments->model = NULL; - arguments->scaling_factor = 0; - - while ((c = getopt_long( - argc, argv, "i:o:f:a:c:x:p:b:q:s:w:h:g:m:r:v", long_options, &option_index - )) != -1) { - switch (c) { - case 'i': - arguments->in_fname = optarg; - break; - case 'o': - arguments->out_fname = optarg; - break; - case 'f': - arguments->filter_type = optarg; - break; - case 'a': - arguments->hwaccel = optarg; - break; - case 'c': - arguments->codec = optarg; - break; - case 'x': - arguments->pix_fmt = optarg; - break; - case 'p': - arguments->preset = optarg; - break; - case 'b': - arguments->bitrate = strtoll(optarg, NULL, 10); - if (arguments->bitrate <= 0) { - fprintf(stderr, "Error: Invalid bitrate specified.\n"); - exit(1); - } - break; - case 'q': - arguments->crf = (float)atof(optarg); - if (arguments->crf < 0.0 || arguments->crf > 51.0) { - fprintf(stderr, "Error: CRF must be between 0 and 51.\n"); - exit(1); - } - break; - case 's': - arguments->shader_path = optarg; - break; - case 'w': - arguments->out_width = atoi(optarg); - if (arguments->out_width <= 0) { - fprintf(stderr, "Error: Output width must be greater than 0.\n"); - exit(1); - } - break; - case 'h': - arguments->out_height = atoi(optarg); - if (arguments->out_height <= 0) { - fprintf(stderr, "Error: Output height must be greater than 0.\n"); - exit(1); - } - break; - case 'g': - arguments->gpuid = atoi(optarg); - break; - case 'm': - arguments->model = optarg; - if (!is_valid_realesrgan_model(arguments->model)) { - fprintf( - stderr, - "Error: Invalid model specified. Must be 'realesrgan-plus', " - "'realesrgan-plus-anime', or 'realesr-animevideov3'.\n" - ); - exit(1); - } - break; - case 'r': - arguments->scaling_factor = atoi(optarg); - if (arguments->scaling_factor != 2 && arguments->scaling_factor != 3 && - arguments->scaling_factor != 4) { - fprintf(stderr, "Error: Scaling factor must be 2, 3, or 4.\n"); - exit(1); - } - break; - case 'v': - 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, "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) { - arguments->nocopystreams = true; - } else if (strcmp(long_options[option_index].name, "benchmark") == 0) { - arguments->benchmark = true; - } - break; - default: - fprintf(stderr, "Invalid options provided.\n"); - exit(1); - } - } - - // Check for required arguments - if (!arguments->in_fname) { - fprintf(stderr, "Error: Input file path is required.\n"); - exit(1); - } - - if (!arguments->out_fname && !arguments->benchmark) { - fprintf(stderr, "Error: Output file path is required.\n"); - exit(1); - } - - if (!arguments->filter_type) { - fprintf(stderr, "Error: Filter type is required (libplacebo or realesrgan).\n"); - exit(1); - } - - if (strcmp(arguments->filter_type, "libplacebo") == 0) { - if (!arguments->shader_path || arguments->out_width == 0 || arguments->out_height == 0) { - fprintf( - stderr, - "Error: For libplacebo, shader name/path (-s), width (-w), " - "and height (-h) are required.\n" - ); - exit(1); - } - } else if (strcmp(arguments->filter_type, "realesrgan") == 0) { - if (arguments->scaling_factor == 0 || !arguments->model) { - fprintf( - stderr, "Error: For realesrgan, scaling factor (-r) and model (-m) are required.\n" - ); - exit(1); - } - } -} - -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; - struct EncoderConfig *encoder_config = thread_args->encoder_config; - struct VideoProcessingContext *proc_ctx = thread_args->proc_ctx; - - // Call the process_video function - int result = process_video( - arguments->in_fname, - arguments->out_fname, - log_level, - arguments->benchmark, - hw_device_type, - filter_config, - encoder_config, - proc_ctx - ); - - proc_ctx->completed = true; - return result; -} - -int main(int argc, char **argv) { - // Print help if no arguments are provided - if (argc < 2) { - print_help(); - return 1; - } - - // Parse command line arguments - struct arguments arguments; - parse_arguments(argc, argv, &arguments); - - // Setup filter configurations based on the parsed arguments - struct FilterConfig filter_config; - if (strcmp(arguments.filter_type, "libplacebo") == 0) { - filter_config.filter_type = FILTER_LIBPLACEBO; - filter_config.config.libplacebo.out_width = arguments.out_width; - filter_config.config.libplacebo.out_height = arguments.out_height; - filter_config.config.libplacebo.shader_path = arguments.shader_path; - } else if (strcmp(arguments.filter_type, "realesrgan") == 0) { - filter_config.filter_type = FILTER_REALESRGAN; - filter_config.config.realesrgan.gpuid = arguments.gpuid; - filter_config.config.realesrgan.tta_mode = 0; - filter_config.config.realesrgan.scaling_factor = arguments.scaling_factor; - filter_config.config.realesrgan.model = arguments.model; - } else { - fprintf(stderr, "Error: Invalid filter type specified.\n"); - return 1; - } - - // Parse codec to AVCodec - const AVCodec *codec = avcodec_find_encoder_by_name(arguments.codec); - if (!codec) { - fprintf(stderr, "Error: Codec '%s' not found.\n", arguments.codec); - return 1; - } - - // Parse pixel format to AVPixelFormat - enum AVPixelFormat pix_fmt = AV_PIX_FMT_NONE; - if (arguments.pix_fmt) { - pix_fmt = av_get_pix_fmt(arguments.pix_fmt); - if (pix_fmt == AV_PIX_FMT_NONE) { - fprintf(stderr, "Error: Invalid pixel format '%s'.\n", arguments.pix_fmt); - return 1; - } - } - - // Setup encoder configuration - struct EncoderConfig encoder_config = { - .out_width = 0, // To be filled by libvideo2x - .out_height = 0, // To be filled by libvideo2x - .copy_streams = !arguments.nocopystreams, - .codec = codec->id, - .pix_fmt = pix_fmt, - .preset = arguments.preset, - .bit_rate = arguments.bitrate, - .crf = arguments.crf, - }; - - // Parse hardware acceleration method - enum AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE; - if (strcmp(arguments.hwaccel, "none") != 0) { - hw_device_type = av_hwdevice_find_type_by_name(arguments.hwaccel); - if (hw_device_type == AV_HWDEVICE_TYPE_NONE) { - fprintf(stderr, "Error: Invalid hardware device type '%s'.\n", arguments.hwaccel); - return 1; - } - } - - // Setup struct to store processing context - struct VideoProcessingContext proc_ctx = { - .processed_frames = 0, - .total_frames = 0, - .start_time = time(NULL), - .pause = false, - .abort = false, - .completed = false - }; - - // Create a ThreadArguments struct to hold all the arguments for the thread - struct ProcessVideoThreadArguments thread_args = { - .arguments = &arguments, - .hw_device_type = hw_device_type, - .filter_config = &filter_config, - .encoder_config = &encoder_config, - .proc_ctx = &proc_ctx - }; - - // Register a newline-safe log callback for FFmpeg - // This will ensure that log messages are printed on a new line after the progress bar - av_log_set_callback(newline_safe_ffmpeg_log_callback); - - // 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("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) { - // Check for key presses - int ch = -1; - - // Check for key press -#ifdef _WIN32 - if (_kbhit()) { - ch = _getch(); - } -#else - ch = getchar(); -#endif - - if (ch == ' ' || ch == '\n') { - // Toggle pause state - proc_ctx.pause = !proc_ctx.pause; - if (proc_ctx.pause) { - printf("\nProcessing paused. Press SPACE to resume, 'q' to abort.\n"); - } else { - printf("Resuming processing...\n"); - } - } else if (ch == 'q' || ch == 'Q') { - // Abort processing - printf("\nAborting processing...\n"); - proc_ctx.abort = true; - newline_required = false; - break; - } - - // Display progress - if (!arguments.noprogress && !proc_ctx.pause && proc_ctx.total_frames > 0) { - printf( - "\rProcessing frame %ld/%ld (%.2f%%); time elapsed: %lds", - proc_ctx.processed_frames, - proc_ctx.total_frames, - proc_ctx.total_frames > 0 - ? (double)proc_ctx.processed_frames * 100.0 / (double)proc_ctx.total_frames - : 0.0, - time(NULL) - proc_ctx.start_time - ); - fflush(stdout); - newline_required = true; - } - - // Sleep for 50ms - thrd_sleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 100000000}, NULL); - } - -// Restore terminal to blocking mode -#ifndef _WIN32 - set_nonblocking_input(false); -#endif - - // Join the processing thread to ensure it completes before exiting - int process_result; - thrd_join(processing_thread, &process_result); - - // Print a newline if progress bar was displayed - if (newline_required) { - putchar('\n'); - } - - // Print final message based on processing result - if (proc_ctx.abort) { - fprintf(stderr, "Video processing aborted\n"); - return 2; - } else if (process_result != 0) { - fprintf(stderr, "Video processing failed\n"); - return process_result; - } else { - printf("Video processing completed successfully\n"); - } - - // Calculate statistics - time_t time_elapsed = time(NULL) - proc_ctx.start_time; - float average_speed_fps = - (float)proc_ctx.processed_frames / (time_elapsed > 0 ? (float)time_elapsed : 1); - - // Print processing summary - printf("====== Video2X %s summary ======\n", arguments.benchmark ? "Benchmark" : "Processing"); - printf("Video file processed: %s\n", arguments.in_fname); - printf("Total frames processed: %ld\n", proc_ctx.processed_frames); - printf("Total time taken: %lds\n", time_elapsed); - printf("Average processing speed: %.2f FPS\n", average_speed_fps); - - // Print additional information if not in benchmark mode - if (!arguments.benchmark) { - printf("Output written to: %s\n", arguments.out_fname); - } - - return 0; -} diff --git a/src/video2x.cpp b/src/video2x.cpp new file mode 100644 index 0000000..487b50c --- /dev/null +++ b/src/video2x.cpp @@ -0,0 +1,704 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +extern "C" { +#include +#include +#include +#include +#include + +#include +#include +} + +#include + +#ifdef _WIN32 +#define BOOST_PROGRAM_OPTIONS_WCHAR_T +#endif +#include +namespace po = boost::program_options; + +// Indicate if a newline needs to be printed before the next output +std::atomic newline_required = false; + +// Structure to hold parsed arguments +struct Arguments { + // General options +#ifdef _WIN32 + std::wstring loglevel = L"info"; + std::wstring filter_type; + std::wstring hwaccel = L"none"; +#else + std::string loglevel = "info"; + std::string filter_type; + std::string hwaccel = "none"; +#endif + bool noprogress = false; + std::filesystem::path in_fname; + std::filesystem::path out_fname; + bool nocopystreams = false; + bool benchmark = false; + + // Encoder options +#ifdef _WIN32 + std::wstring codec = L"libx264"; + std::wstring pix_fmt; + std::wstring preset = L"slow"; +#else + std::string codec = "libx264"; + std::string pix_fmt; + std::string preset = "slow"; +#endif + int64_t bitrate = 0; + float crf = 20.0f; + + // libplacebo options + std::filesystem::path shader_path; + int out_width = 0; + int out_height = 0; + + // RealESRGAN options + int gpuid = 0; + std::filesystem::path model_path; + int scaling_factor = 0; +}; + +// Set UNIX terminal input to non-blocking mode +#ifndef _WIN32 +void set_nonblocking_input(bool enable) { + static struct termios oldt, newt; + if (enable) { + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= static_cast(~(ICANON | ECHO)); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); + } else { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, 0); + } +} +#endif + +// Convert a wide string to UTF-8 string +#ifdef _WIN32 +std::string wstring_to_utf8(const std::wstring &wstr) { + if (wstr.empty()) { + return std::string(); + } + int size_needed = WideCharToMultiByte( + CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr + ); + std::string converted_str(size_needed, 0); + WideCharToMultiByte( + CP_UTF8, + 0, + wstr.data(), + static_cast(wstr.size()), + &converted_str[0], + size_needed, + nullptr, + nullptr + ); + return converted_str; +} +#else +std::string wstring_to_utf8(const std::string &str) { + return str; +} +#endif + +// Newline-safe log callback for FFmpeg +void newline_safe_ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) { + if (level <= av_log_get_level() && newline_required) { + putchar('\n'); + newline_required = false; + } + av_log_default_callback(ptr, level, fmt, vl); +} + +bool is_valid_realesrgan_model(const std::string &model) { + static const std::unordered_set valid_realesrgan_models = { + "realesrgan-plus", "realesrgan-plus-anime", "realesr-animevideov3" + }; + return valid_realesrgan_models.count(model) > 0; +} + +enum Libvideo2xLogLevel parse_log_level(const std::string &level_name) { + if (level_name == "trace") { + return LIBVIDEO2X_LOG_LEVEL_TRACE; + } else if (level_name == "debug") { + return LIBVIDEO2X_LOG_LEVEL_DEBUG; + } else if (level_name == "info") { + return LIBVIDEO2X_LOG_LEVEL_INFO; + } else if (level_name == "warning" || level_name == "warn") { + return LIBVIDEO2X_LOG_LEVEL_WARNING; + } else if (level_name == "error") { + return LIBVIDEO2X_LOG_LEVEL_ERROR; + } else if (level_name == "critical") { + return LIBVIDEO2X_LOG_LEVEL_CRITICAL; + } else if (level_name == "off" || level_name == "none") { + return LIBVIDEO2X_LOG_LEVEL_OFF; + } else { + spdlog::warn("Invalid log level specified. Defaulting to 'info'."); + return LIBVIDEO2X_LOG_LEVEL_INFO; + } +} + +// Mutex for synchronizing access to VideoProcessingContext +std::mutex proc_ctx_mutex; + +// Wrapper function for video processing thread +void process_video_thread( + Arguments *arguments, + AVHWDeviceType hw_device_type, + FilterConfig *filter_config, + EncoderConfig *encoder_config, + VideoProcessingContext *proc_ctx +) { + enum Libvideo2xLogLevel log_level = parse_log_level(wstring_to_utf8(arguments->loglevel)); + +#ifdef _WIN32 + const wchar_t *in_fname = arguments->in_fname.c_str(); + const wchar_t *out_fname = arguments->out_fname.c_str(); +#else + const char *in_fname = arguments->in_fname.c_str(); + const char *out_fname = arguments->out_fname.c_str(); +#endif + + int result = process_video( + in_fname, + out_fname, + log_level, + arguments->benchmark, + hw_device_type, + filter_config, + encoder_config, + proc_ctx + ); + + { + std::lock_guard lock(proc_ctx_mutex); + proc_ctx->completed = true; + } + + if (result != 0) { + spdlog::error("Video processing failed with error code: {}", result); + } +} + +#ifdef _WIN32 +int wmain(int argc, wchar_t *argv[]) { + SetConsoleOutputCP(CP_UTF8); +#else +int main(int argc, char **argv) { +#endif + // Initialize arguments structure + Arguments arguments; + + // Parse command line arguments using Boost.Program_options + try { + po::options_description desc("Allowed options"); + +#ifdef _WIN32 + desc.add_options() + ("help", "Display this help page") + ("version,v", "Print program version") + ("loglevel", po::wvalue(&arguments.loglevel), "Set log level (trace, debug, info, warn, error, critical, none)") + ("noprogress", po::bool_switch(&arguments.noprogress), "Do not display the progress bar") + + // General Processing Options + ("input,i", po::wvalue(), "Input video file path") + ("output,o", po::wvalue(), "Output video file path") + ("filter,f", po::wvalue(&arguments.filter_type), "Filter to use: 'libplacebo' or 'realesrgan'") + ("hwaccel,a", po::wvalue(&arguments.hwaccel), "Hardware acceleration method (default: none)") + ("nocopystreams", po::bool_switch(&arguments.nocopystreams), "Do not copy audio and subtitle streams") + ("benchmark", po::bool_switch(&arguments.benchmark), "Discard processed frames and calculate average FPS") + + // Encoder options + ("codec,c", po::wvalue(&arguments.codec), "Output codec (default: libx264)") + ("preset,p", po::wvalue(&arguments.preset), "Encoder preset (default: slow)") + ("pixfmt,x", po::wvalue(&arguments.pix_fmt), "Output pixel format (default: auto)") + ("bitrate,b", po::value(&arguments.bitrate)->default_value(0), "Bitrate in bits per second (default: 0 (VBR))") + ("crf,q", po::value(&arguments.crf)->default_value(20.0f), "Constant Rate Factor (default: 20.0)") + + // libplacebo options + ("shader,s", po::wvalue(), "Name or path of the GLSL shader file to use") + ("width,w", po::value(&arguments.out_width), "Output width") + ("height,h", po::value(&arguments.out_height), "Output height") + + // RealESRGAN options + ("gpuid,g", po::value(&arguments.gpuid)->default_value(0), "Vulkan GPU ID (default: 0)") + ("model,m", po::wvalue(), "Name of the model to use") + ("scale,r", po::value(&arguments.scaling_factor), "Scaling factor (2, 3, or 4)") + ; +#else + desc.add_options() + ("help", "Display this help page") + ("version,v", "Print program version") + ("loglevel", po::value(&arguments.loglevel)->default_value("info"), "Set log level (trace, debug, info, warn, error, critical, none)") + ("noprogress", po::bool_switch(&arguments.noprogress), "Do not display the progress bar") + + // General Processing Options + ("input,i", po::value(), "Input video file path") + ("output,o", po::value(), "Output video file path") + ("filter,f", po::value(&arguments.filter_type), "Filter to use: 'libplacebo' or 'realesrgan'") + ("hwaccel,a", po::value(&arguments.hwaccel)->default_value("none"), "Hardware acceleration method (default: none)") + ("nocopystreams", po::bool_switch(&arguments.nocopystreams), "Do not copy audio and subtitle streams") + ("benchmark", po::bool_switch(&arguments.benchmark), "Discard processed frames and calculate average FPS") + + // Encoder options + ("codec,c", po::value(&arguments.codec)->default_value("libx264"), "Output codec (default: libx264)") + ("preset,p", po::value(&arguments.preset)->default_value("slow"), "Encoder preset (default: slow)") + ("pixfmt,x", po::value(&arguments.pix_fmt), "Output pixel format (default: auto)") + ("bitrate,b", po::value(&arguments.bitrate)->default_value(0), "Bitrate in bits per second (default: 0 (VBR))") + ("crf,q", po::value(&arguments.crf)->default_value(20.0f), "Constant Rate Factor (default: 20.0)") + + // libplacebo options + ("shader,s", po::value(), "Name or path of the GLSL shader file to use (built-in: 'anime4k-a', 'anime4k-b', 'anime4k-c', 'anime4k-a+a', 'anime4k-b+b', 'anime4k-c+a')") + ("width,w", po::value(&arguments.out_width), "Output width") + ("height,h", po::value(&arguments.out_height), "Output height") + + // RealESRGAN options + ("gpuid,g", po::value(&arguments.gpuid)->default_value(0), "Vulkan GPU ID (default: 0)") + ("model,m", po::value(), "Name of the model to use") + ("scale,r", po::value(&arguments.scaling_factor), "Scaling factor (2, 3, or 4)") + ; +#endif + + // Positional arguments + po::positional_options_description p; + p.add("input", 1).add("output", 1).add("filter", 1); + +#ifdef _WIN32 + po::variables_map vm; + po::store(po::wcommand_line_parser(argc, argv).options(desc).positional(p).run(), vm); +#else + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); +#endif + po::notify(vm); + + // Set default values for optional arguments +#ifdef _WIN32 + if (!vm.count("loglevel")) { + arguments.loglevel = L"info"; + } + if (!vm.count("hwaccel")) { + arguments.hwaccel = L"none"; + } + if (!vm.count("codec")) { + arguments.codec = L"libx264"; + } + if (!vm.count("preset")) { + arguments.preset = L"slow"; + } +#endif + + if (vm.count("help")) { + std::cout << desc << std::endl; + return 0; + } + + if (vm.count("version")) { + std::cout << "Video2X version " << LIBVIDEO2X_VERSION_STRING << std::endl; + return 0; + } + + // Assign positional arguments + if (vm.count("input")) { +#ifdef _WIN32 + arguments.in_fname = std::filesystem::path(vm["input"].as()); +#else + arguments.in_fname = std::filesystem::path(vm["input"].as()); +#endif + } else { + spdlog::error("Error: Input file path is required."); + return 1; + } + + if (vm.count("output")) { +#ifdef _WIN32 + arguments.out_fname = std::filesystem::path(vm["output"].as()); +#else + arguments.out_fname = std::filesystem::path(vm["output"].as()); +#endif + } else if (!arguments.benchmark) { + spdlog::error("Error: Output file path is required."); + return 1; + } + + if (!vm.count("filter")) { + spdlog::error("Error: Filter type is required (libplacebo or realesrgan)."); + return 1; + } + + if (vm.count("shader")) { +#ifdef _WIN32 + arguments.shader_path = std::filesystem::path(vm["shader"].as()); +#else + arguments.shader_path = std::filesystem::path(vm["shader"].as()); +#endif + } + + if (vm.count("model")) { +#ifdef _WIN32 + arguments.model_path = std::filesystem::path(vm["model"].as()); +#else + arguments.model_path = vm["model"].as(); +#endif + if (!is_valid_realesrgan_model(vm["model"].as())) { + spdlog::error( + "Error: Invalid model specified. Must be 'realesrgan-plus', " + "'realesrgan-plus-anime', or 'realesr-animevideov3'." + ); + return 1; + } + } + } catch (const po::error &e) { + spdlog::error("Error parsing options: {}", e.what()); + return 1; + } catch (const std::exception &e) { + spdlog::error("Exception caught: {}", e.what()); + return 1; + } + +// Additional validations +#ifdef _WIN32 + if (arguments.filter_type == L"libplacebo") { +#else + if (arguments.filter_type == "libplacebo") { +#endif + if (arguments.shader_path.empty() || arguments.out_width == 0 || + arguments.out_height == 0) { + spdlog::error( + "Error: For libplacebo, shader name/path (-s), width (-w), " + "and height (-h) are required." + ); + return 1; + } +#ifdef _WIN32 + } else if (arguments.filter_type == L"realesrgan") { +#else + } else if (arguments.filter_type == "realesrgan") { +#endif + if (arguments.scaling_factor == 0 || arguments.model_path.empty()) { + spdlog::error("Error: For realesrgan, scaling factor (-r) and model (-m) are required." + ); + return 1; + } + if (arguments.scaling_factor != 2 && arguments.scaling_factor != 3 && + arguments.scaling_factor != 4) { + spdlog::error("Error: Scaling factor must be 2, 3, or 4."); + return 1; + } + } else { + spdlog::error("Error: Invalid filter type specified. Must be 'libplacebo' or 'realesrgan'." + ); + return 1; + } + + // Validate bitrate + if (arguments.bitrate < 0) { + spdlog::error("Error: Invalid bitrate specified."); + return 1; + } + + // Validate CRF + if (arguments.crf < 0.0f || arguments.crf > 51.0f) { + spdlog::error("Error: CRF must be between 0 and 51."); + return 1; + } + + // Parse codec to AVCodec + const AVCodec *codec = avcodec_find_encoder_by_name(wstring_to_utf8(arguments.codec).c_str()); + if (!codec) { + spdlog::error("Error: Codec '{}' not found.", wstring_to_utf8(arguments.codec)); + return 1; + } + + // Parse pixel format to AVPixelFormat + enum AVPixelFormat pix_fmt = AV_PIX_FMT_NONE; + if (!arguments.pix_fmt.empty()) { + pix_fmt = av_get_pix_fmt(wstring_to_utf8(arguments.pix_fmt).c_str()); + if (pix_fmt == AV_PIX_FMT_NONE) { + spdlog::error("Error: Invalid pixel format '{}'.", wstring_to_utf8(arguments.pix_fmt)); + return 1; + } + } + + // Set spdlog log level + auto log_level = parse_log_level(wstring_to_utf8(arguments.loglevel)); + switch (log_level) { + case LIBVIDEO2X_LOG_LEVEL_TRACE: + spdlog::set_level(spdlog::level::trace); + break; + case LIBVIDEO2X_LOG_LEVEL_DEBUG: + spdlog::set_level(spdlog::level::debug); + break; + case LIBVIDEO2X_LOG_LEVEL_INFO: + spdlog::set_level(spdlog::level::info); + break; + case LIBVIDEO2X_LOG_LEVEL_WARNING: + spdlog::set_level(spdlog::level::warn); + break; + case LIBVIDEO2X_LOG_LEVEL_ERROR: + spdlog::set_level(spdlog::level::err); + break; + case LIBVIDEO2X_LOG_LEVEL_CRITICAL: + spdlog::set_level(spdlog::level::critical); + break; + case LIBVIDEO2X_LOG_LEVEL_OFF: + spdlog::set_level(spdlog::level::off); + break; + default: + spdlog::set_level(spdlog::level::info); + break; + } + + // Setup filter configurations based on the parsed arguments + FilterConfig filter_config; +#ifdef _WIN32 + if (arguments.filter_type == L"libplacebo") { +#else + if (arguments.filter_type == "libplacebo") { +#endif + filter_config.filter_type = FILTER_LIBPLACEBO; + filter_config.config.libplacebo.out_width = arguments.out_width; + filter_config.config.libplacebo.out_height = arguments.out_height; +#ifdef _WIN32 + filter_config.config.libplacebo.shader_path = arguments.shader_path.c_str(); +#else + filter_config.config.libplacebo.shader_path = arguments.shader_path.c_str(); +#endif +#ifdef _WIN32 + } else if (arguments.filter_type == L"realesrgan") { +#else + } else if (arguments.filter_type == "realesrgan") { +#endif + filter_config.filter_type = FILTER_REALESRGAN; + filter_config.config.realesrgan.gpuid = arguments.gpuid; + filter_config.config.realesrgan.tta_mode = false; + filter_config.config.realesrgan.scaling_factor = arguments.scaling_factor; +#ifdef _WIN32 + filter_config.config.realesrgan.model_path = arguments.model_path.c_str(); +#else + filter_config.config.realesrgan.model_path = arguments.model_path.c_str(); +#endif + } + + // Convert arguments to UTF-8 encoded strings + std::string preset_str = wstring_to_utf8(arguments.preset); + + // Setup encoder configuration + EncoderConfig encoder_config; + encoder_config.out_width = 0; + encoder_config.out_height = 0; + encoder_config.copy_streams = !arguments.nocopystreams; + encoder_config.codec = codec->id; + encoder_config.pix_fmt = pix_fmt; + encoder_config.preset = preset_str.c_str(); + encoder_config.bit_rate = arguments.bitrate; + encoder_config.crf = arguments.crf; + + // Parse hardware acceleration method + enum AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE; +#ifdef _WIN32 + if (arguments.hwaccel != L"none") { +#else + if (arguments.hwaccel != "none") { +#endif + hw_device_type = av_hwdevice_find_type_by_name(wstring_to_utf8(arguments.hwaccel).c_str()); + if (hw_device_type == AV_HWDEVICE_TYPE_NONE) { + spdlog::error( + "Error: Invalid hardware device type '{}'.", wstring_to_utf8(arguments.hwaccel) + ); + return 1; + } + } + + // Setup struct to store processing context + VideoProcessingContext proc_ctx; + proc_ctx.processed_frames = 0; + proc_ctx.total_frames = 0; + proc_ctx.pause = false; + proc_ctx.abort = false; + proc_ctx.completed = false; + + // Register a newline-safe log callback for FFmpeg + av_log_set_callback(newline_safe_ffmpeg_log_callback); + + // Create a thread for video processing + std::thread processing_thread( + process_video_thread, &arguments, hw_device_type, &filter_config, &encoder_config, &proc_ctx + ); + spdlog::info("Press SPACE to pause/resume, 'q' to abort."); + + // Setup variables to track processing time + auto start_time = std::chrono::steady_clock::now(); + auto paused_start = std::chrono::steady_clock::time_point(); + std::chrono::seconds total_paused_duration(0); + long long time_elapsed = 0; + + // Enable non-blocking input +#ifndef _WIN32 + set_nonblocking_input(true); +#endif + + // Main thread loop to display progress and handle input + while (true) { + bool completed; + { + std::lock_guard lock(proc_ctx_mutex); + completed = proc_ctx.completed; + } + if (completed) { + break; + } + + // Check for key presses + int ch = -1; + + // Check for key press +#ifdef _WIN32 + if (_kbhit()) { + ch = _getch(); + } +#else + ch = getchar(); +#endif + + if (ch == ' ' || ch == '\n') { + // Toggle pause state + { + std::lock_guard lock(proc_ctx_mutex); + proc_ctx.pause = !proc_ctx.pause; + if (proc_ctx.pause) { + putchar('\n'); + spdlog::info("Processing paused. Press SPACE to resume, 'q' to abort."); + paused_start = std::chrono::steady_clock::now(); + } else { + spdlog::info("Resuming processing..."); + total_paused_duration += std::chrono::duration_cast( + std::chrono::steady_clock::now() - paused_start + ); + } + } + } else if (ch == 'q' || ch == 'Q') { + // Abort processing + putchar('\n'); + spdlog::info("Aborting processing..."); + { + std::lock_guard lock(proc_ctx_mutex); + proc_ctx.abort = true; + newline_required = false; + } + break; + } + + // Display progress + if (!arguments.noprogress) { + int64_t processed_frames, total_frames; + bool pause; + { + std::lock_guard lock(proc_ctx_mutex); + processed_frames = proc_ctx.processed_frames; + total_frames = proc_ctx.total_frames; + pause = proc_ctx.pause; + } + if (!pause && total_frames > 0) { + double percentage = total_frames > 0 ? static_cast(processed_frames) * + 100.0 / static_cast(total_frames) + : 0.0; + auto now = std::chrono::steady_clock::now(); + time_elapsed = std::chrono::duration_cast( + now - start_time - total_paused_duration + ) + .count(); + + std::cout << "\rProcessing frame " << processed_frames << "/" << total_frames + << " (" << percentage << "%); time elapsed: " << time_elapsed << "s"; + std::cout.flush(); + newline_required = true; + } + } + + // Sleep for 100ms + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Restore terminal to blocking mode +#ifndef _WIN32 + set_nonblocking_input(false); +#endif + + // Join the processing thread to ensure it completes before exiting + processing_thread.join(); + + // Print a newline if progress bar was displayed + if (newline_required) { + std::cout << '\n'; + } + + // Print final message based on processing result + bool aborted, completed; + { + std::lock_guard lock(proc_ctx_mutex); + aborted = proc_ctx.abort; + completed = proc_ctx.completed; + } + if (aborted) { + spdlog::warn("Video processing aborted"); + return 2; + } else if (!completed) { + spdlog::error("Video processing failed"); + return 1; + } else { + spdlog::info("Video processed successfully"); + } + + // Calculate statistics + int64_t processed_frames; + { + std::lock_guard lock(proc_ctx_mutex); + processed_frames = proc_ctx.processed_frames; + } + float average_speed_fps = static_cast(processed_frames) / + (time_elapsed > 0 ? static_cast(time_elapsed) : 1); + + // Print processing summary + printf("====== Video2X %s summary ======\n", arguments.benchmark ? "Benchmark" : "Processing"); + printf("Video file processed: %s\n", arguments.in_fname.u8string().c_str()); + printf("Total frames processed: %ld\n", proc_ctx.processed_frames); + printf("Total time taken: %llds\n", time_elapsed); + printf("Average processing speed: %.2f FPS\n", average_speed_fps); + + // Print additional information if not in benchmark mode + if (!arguments.benchmark) { + printf("Output written to: %s\n", arguments.out_fname.u8string().c_str()); + } + + return 0; +} diff --git a/third_party/boost b/third_party/boost new file mode 160000 index 0000000..65c1319 --- /dev/null +++ b/third_party/boost @@ -0,0 +1 @@ +Subproject commit 65c1319bb92fe7a9a4abd588eff5818d9c2bccf9