39 Commits

Author SHA1 Message Date
k4yt3x
7a7558e1a8 ci(release): disabled Ubuntu 24.04 build in the release pipeline
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-05 00:00:00 +00:00
k4yt3x
ec4b51064a feat(libplacebo): added Vulkan device selection for libplacebo
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-04 00:00:00 +00:00
k4yt3x
77a06e7d92 ci(release): added Ubuntu and Windows builds to the release pipeline
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-04 00:00:00 +00:00
k4yt3x
8eda3fa2d2 build(cmake): fixed the broken RealESRGAN installation rules
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-04 00:00:00 +00:00
k4yt3x
bcbe33d5dc feat(video2x): added the --listgpus option and GPU ID validation
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-04 00:00:00 +00:00
k4yt3x
850e0fde9c build(cmake): install the char_defs.h header file 2024-11-04 03:50:41 -05:00
k4yt3x
a63b8d85b0 build(makefile): added make targets for Windows 2024-11-04 03:49:20 -05:00
k4yt3x
1d1792d10f style: added platform-dependent type aliases for char and string
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-04 00:00:00 +00:00
k4yt3x
851f13bd4d fix(libvideo2x): fixed errors in estimating the total number of frames
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-03 00:00:00 +00:00
k4yt3x
fbe3b44139 feat(video2x): improved the timer
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-03 00:00:00 +00:00
k4yt3x
d6f27b3f22 fix(libplacebo): fixed wide char string path support for GLSL shader path 2024-11-02 23:13:03 -04:00
k4yt3x
fb0e3a040d fix(video2x): fixed RealESRGAN model name parsing
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
bb74144070 fix(realesrgan): fixed RealESRGAN model name variable
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
406a97f360 fix(video2x): fixed return code fetching
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
9c491d4277 feat: improved file not found messages
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
K4YT3X
27c76189d9 Merge pull request #1208 from k4yt3x/dev
feat(*): fixed wide character string paths and rewritten the CLI
2024-11-02 01:35:27 -04:00
k4yt3x
77b3df22c1 ci(build): enabled caching for Vulkan SDK installation
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
b333f88c2f build(cmake): fixed boost build path and removed redundant librife DLL
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
8cafb29f70 build(cmake): cleaned up install directives
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
afd7c8050f chore: added missing boost lib into Makefile, Debian, PKGBUILD
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
66c623ff49 chore: fixed missing boost submodule
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
0633b3fd62 ci(build): fixed build pipeline boost dependency
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
0588908dd6 fix(video2x): fixed string conversions on Linux
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-11-02 00:00:00 +00:00
k4yt3x
b309bec1cb feat(video2x): rewritten the CLI with C++ 2024-11-01 22:20:32 -04:00
k4yt3x
94e69f9f62 fix(libvideo2x): fixed wide character string paths on Windows 2024-11-01 22:19:01 -04:00
k4yt3x
a8b952c3ad feat(libplacebo): auto-detect the buffer filter's supported options
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-31 00:00:00 +00:00
k4yt3x
c912bfaffc build(makefile): added make target for ubuntu deb package
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-31 00:00:00 +00:00
k4yt3x
f5eb195d79 chore: reorganized packaging related files
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-30 00:00:00 +00:00
k4yt3x
5156d4a825 fix(video2x): fixed Anime4K shader name in the help message
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-30 00:00:00 +00:00
k4yt3x
cbffef2b23 docs(readme): updated README for 6.0.0
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-29 00:00:00 +00:00
k4yt3x
31e606bd4c chore(librealesrgan): updated librealesrgan submodule path
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-29 00:00:00 +00:00
k4yt3x
d50cf54f2a style(libvideo2x): improved resource cleaning and error handling
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-29 00:00:00 +00:00
k4yt3x
613b75ffec fix(encoder): fixed incorrect encoded video frame rate
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-28 00:00:00 +00:00
k4yt3x
9d342c51a2 fix(libplacebo): fixed libplacebo shader path formatting on Windows
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-28 00:00:00 +00:00
k4yt3x
48119a30eb feat(anime4k): added version name to Anime4K GLSL shader files
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-27 00:00:00 +00:00
k4yt3x
9d09d8570f feat(anime4k): added Anime4K GAN GLSL shaders
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-27 00:00:00 +00:00
Neo_Chen (BU4AK)
ebef5f54cb fix(video2x): fixed -g option in src/video2x.c and help message for libplacebo (#1197)
* fixed -g option in src/video2x.c and help message for libplacebo
* style: sorted getopt options

---------

Signed-off-by: Neo_Chen <chenkolei@gmail.com>
Co-authored-by: K4YT3X <i@k4yt3x.com>
2024-10-26 19:16:55 +00:00
k4yt3x
747d85cf9b docs(readme): updated links for 6.0.0-beta.5
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
k4yt3x
f89b263487 ci(release): corrected release name variable
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
43 changed files with 10790 additions and 1340 deletions

2
.gitattributes vendored
View File

@@ -1 +1 @@
models/* linguist-vendored
models/** linguist-vendored

View File

@@ -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
@@ -91,3 +93,17 @@ jobs:
with:
name: video2x-nightly-windows-amd64
path: build/video2x_install
container:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: mr-smithers-excellent/docker-build-push@v5
name: Build the Docker image
with:
registry: ghcr.io
dockerfile: packaging/docker/Dockerfile
image: video2x
tags: latest
pushImage: false

View File

@@ -9,8 +9,8 @@ permissions:
jobs:
setup:
if: github.event.base_ref == 'refs/heads/master'
name: Setup
if: github.event.base_ref == 'refs/heads/master'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
@@ -19,6 +19,103 @@ jobs:
id: get_version
run: echo version=${GITHUB_REF/refs\/tags\//} >> $GITHUB_OUTPUT
ubuntu-2404:
# GitHub Actions' ubuntu-24.04 runner does not have enough RAM
if: false
name: Build Ubuntu 24.04 release
needs:
- setup
runs-on: ubuntu-24.04
env:
DEBIAN_FRONTEND: noninteractive
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
git submodule update --init --recursive
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libavcodec-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libswscale-dev \
libvulkan-dev \
glslang-tools \
libomp-dev \
libopencv-dev \
libboost-program-options-dev
- name: Build Video2X
run: |
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-linux-ubuntu-2404-amd64/usr
cmake --build build --config Release --target install --parallel
mkdir -p build/video2x-linux-ubuntu-2404-amd64/DEBIAN
cp packaging/debian/control build/video2x-linux-ubuntu-2404-amd64/DEBIAN/control
dpkg-deb --build build/video2x-linux-ubuntu-2404-amd64
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: video2x-linux-ubuntu-2404-amd64
path: build/video2x-linux-ubuntu-2404-amd64.deb
windows:
name: Build Windows release
needs:
- setup
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Vulkan SDK
uses: humbletim/setup-vulkan-sdk@v1.2.0
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: |
$ffmpegVersion = "7.1"
$ncnnVersion = "20240820"
$opencvVersion = "4.10.0"
git submodule update --init --recursive
curl -Lo ffmpeg-shared.zip "https://github.com/GyanD/codexffmpeg/releases/download/$ffmpegVersion/ffmpeg-$ffmpegVersion-full_build-shared.zip"
Expand-Archive -Path ffmpeg-shared.zip -DestinationPath third_party
Rename-Item -Path "third_party/ffmpeg-$ffmpegVersion-full_build-shared" -NewName ffmpeg-shared
curl -Lo ncnn-shared.zip "https://github.com/Tencent/ncnn/releases/download/$ncnnVersion/ncnn-$ncnnVersion-windows-vs2022-shared.zip"
Expand-Archive -Path ncnn-shared.zip -DestinationPath third_party
Rename-Item -Path "third_party/ncnn-$ncnnVersion-windows-vs2022-shared" -NewName ncnn-shared
curl -Lo opencv-shared.exe "https://github.com/opencv/opencv/releases/download/$opencvVersion/opencv-$opencvVersion-windows.exe"
7z x opencv-shared.exe -y
Move-Item -Path opencv -Destination third_party\opencv-shared
- name: Build Video2X
shell: pwsh
run: |
cmake -S . -B build `
-DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DUSE_SYSTEM_OPENCV=OFF -DUSE_SYSTEM_BOOST=OFF `
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=build/video2x_install
cmake --build build --config Release --parallel --target install
- name: Compress artifacts
run: |
Compress-Archive -Path build/video2x_install/* -DestinationPath build/video2x-windows-amd64.zip
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: video2x-windows-amd64
path: build/video2x-windows-amd64.zip
container:
name: Build and upload container
needs:
@@ -33,7 +130,7 @@ jobs:
registry: ghcr.io
username: ${{ secrets.GHCR_USER }}
password: ${{ secrets.GHCR_TOKEN }}
dockerfile: Dockerfile
dockerfile: packaging/docker/Dockerfile
image: video2x
tags: latest, ${{ needs.setup.outputs.version }}
@@ -41,17 +138,23 @@ jobs:
name: Create release
needs:
- setup
# - ubuntu-2404
- windows
- container
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Create release
id: create_release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ needs.setup.outputs.version }}
release_name: Video2X ${{ needs.setup.outputs.version }}
name: ${{ needs.setup.outputs.version }}
draft: true
prerelease: false
files: |
video2x-windows-amd64.zip

9
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "third_party/libreal_esrgan_ncnn_vulkan"]
path = third_party/libreal_esrgan_ncnn_vulkan
url = https://github.com/k4yt3x/libreal-esrgan-ncnn-vulkan.git
[submodule "third_party/librealesrgan_ncnn_vulkan"]
path = third_party/librealesrgan_ncnn_vulkan
url = https://github.com/k4yt3x/librealesrgan-ncnn-vulkan.git
[submodule "third_party/ncnn"]
path = third_party/ncnn
url = https://github.com/Tencent/ncnn.git
@@ -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

View File

@@ -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.1.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,12 +85,12 @@ 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
find_package(PkgConfig REQUIRED)
set(REQUIRED_PKGS
set(FFMPEG_REQUIRED_PKGS REQUIRED
libavcodec
libavdevice
libavfilter
@@ -114,17 +100,19 @@ else()
)
# Loop through each package to find and collect include dirs and libraries
foreach(PKG ${REQUIRED_PKGS})
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,13 +256,44 @@ 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)
if (BUILD_VIDEO2X_CLI)
find_package(Vulkan REQUIRED)
set(VULKAN_LIB Vulkan::Vulkan)
endif()
# Include ExternalProject module
include(ExternalProject)
# Add libreal-esrgan-ncnn-vulkan as an external project
# Add librealesrgan-ncnn-vulkan as an external project
ExternalProject_Add(
realesrgan
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/libreal_esrgan_ncnn_vulkan/src
SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/librealesrgan_ncnn_vulkan/src
CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/realesrgan_install
@@ -288,11 +307,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)
@@ -309,7 +334,7 @@ target_include_directories(libvideo2x PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/include/libvideo2x
${PROJECT_SOURCE_DIR}/third_party/libreal_esrgan_ncnn_vulkan/src
${PROJECT_SOURCE_DIR}/third_party/librealesrgan_ncnn_vulkan/src
)
# Compile options for the shared library
@@ -322,12 +347,14 @@ 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 ${REALESRGAN_LIB})
else()
set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.so)
list(APPEND ALL_LIBRARIES ${REALESRGAN_LIB})
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 +366,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 src/timer.cpp)
set_target_properties(video2x PROPERTIES OUTPUT_NAME video2x)
# Include directories for the executable
@@ -347,13 +374,20 @@ if (BUILD_VIDEO2X_CLI)
${ALL_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/include/libvideo2x
)
# Compile options for the executable
target_compile_options(video2x PRIVATE $<$<CONFIG:Debug>:-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}
${VULKAN_LIB}
)
endif()
# Define the default installation directories
@@ -397,44 +431,37 @@ if(BUILD_VIDEO2X_CLI)
install(TARGETS video2x RUNTIME DESTINATION ${INSTALL_BIN_DESTINATION})
endif()
# Install the header file
install(FILES ${PROJECT_SOURCE_DIR}/include/libvideo2x/libvideo2x.h
DESTINATION ${INSTALL_INCLUDE_DESTINATION}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h
# Install the header files
install(FILES
${PROJECT_SOURCE_DIR}/include/libvideo2x/libvideo2x.h
${PROJECT_SOURCE_DIR}/include/libvideo2x/char_defs.h
${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h
DESTINATION ${INSTALL_INCLUDE_DESTINATION}
)
# 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

View File

@@ -1,4 +1,4 @@
.PHONY: build static debug clean \
.PHONY: build static debug windows windows-debug debian ubuntu clean \
test-realesrgan test-libplacebo \
memcheck-realesrgan memcheck-libplacebo \
heaptrack-realesrgan heaptrack-libplacebo
@@ -38,6 +38,26 @@ debug:
cmake --build $(BINDIR) --config Debug --parallel
cp $(BINDIR)/compile_commands.json .
windows:
cmake -S . -B $(BINDIR) \
-DUSE_SYSTEM_NCNN=OFF \
-DUSE_SYSTEM_OPENCV=OFF \
-DUSE_SYSTEM_SPDLOG=OFF \
-DUSE_SYSTEM_BOOST=OFF \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=build/libvideo2x-shared
cmake --build $(BINDIR) --config Release --parallel --target install
windows-debug:
cmake -S . -B $(BINDIR) \
-DUSE_SYSTEM_NCNN=OFF \
-DUSE_SYSTEM_OPENCV=OFF \
-DUSE_SYSTEM_SPDLOG=OFF \
-DUSE_SYSTEM_BOOST=OFF \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_INSTALL_PREFIX=build/libvideo2x-shared
cmake --build $(BINDIR) --config Debug --parallel --target install
debian:
apt-get update
apt-get install -y --no-install-recommends \
@@ -52,7 +72,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 \
@@ -60,8 +81,32 @@ debian:
-DINSTALL_LIB_DESTINATION=. -DINSTALL_MODEL_DESTINATION=.
cmake --build /tmp/build --config Release --target install --parallel
ubuntu:
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends \
build-essential cmake pkg-config \
libavcodec-dev \
libavdevice-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libswscale-dev \
libvulkan-dev \
glslang-tools \
libomp-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
cmake --build build --config Release --target install --parallel
mkdir -p build/video2x_package/DEBIAN
cp packaging/debian/control build/video2x_package/DEBIAN/control
dpkg-deb --build build/video2x_package
clean:
rm -rf $(BINDIR)
rm -vrf $(BINDIR) data/output*.* heaptrack*.zst valgrind.log
test-realesrgan:
LD_LIBRARY_PATH=$(BINDIR) $(BINDIR)/video2x -i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
@@ -69,7 +114,7 @@ test-realesrgan:
test-libplacebo:
LD_LIBRARY_PATH=$(BINDIR) $(BINDIR)/video2x -i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f libplacebo -w 1920 -h 1080 -s anime4k-mode-a
-f libplacebo -w 1920 -h 1080 -s anime4k-v4-a
memcheck-realesrgan:
LD_LIBRARY_PATH=$(BINDIR) valgrind \
@@ -94,7 +139,7 @@ memcheck-libplacebo:
--verbose --log-file="valgrind.log" \
$(BINDIR)/video2x \
-i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f libplacebo -w 1920 -h 1080 -s anime4k-mode-a \
-f libplacebo -w 1920 -h 1080 -s anime4k-v4-a \
-p veryfast -b 1000000 -q 30
heaptrack-realesrgan:
@@ -108,5 +153,5 @@ heaptrack-libplacebo:
LD_LIBRARY_PATH=$(BINDIR) HEAPTRACK_ENABLE_DEBUGINFOD=1 heaptrack \
$(BINDIR)/video2x \
-i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f libplacebo -w 1920 -h 1080 -s anime4k-mode-a \
-f libplacebo -w 1920 -h 1080 -s anime4k-v4-a \
-p veryfast -b 1000000 -q 30

View File

@@ -11,65 +11,65 @@
> [!IMPORTANT]
> Versions 4 and 5 have reached end-of-life (EOL) status. Due to limited development resources, issues related to any version earlier than 6 will no longer be addressed.
## 🌟 Version 6.0.0 Preview
## 🌟 Version 6.0.0
**[Direct download link for Windows (Installer)](https://github.com/k4yt3x/video2x/releases/download/6.0.0-beta.4/video2x-qt6-windows-amd64-installer.exe)**
**[Download Windows Installer](https://github.com/k4yt3x/video2x/releases/download/6.0.0/video2x-qt6-windows-amd64-installer.exe)**
![6.0.0-beta-screenshot](https://github.com/user-attachments/assets/bd9e0b82-67ee-4b62-a830-a694db6f5de3)
**TL;DR: Version 6.0.0 is a complete rewrite of the Video2X project in C/C++, featuring a faster, more efficient architecture, cross-platform support, vastly improved output quality, and a new GUI and installer for easy setup on Windows.**
![6.0.0-screenshot](https://github.com/user-attachments/assets/7c6f4cd9-2cca-4deb-a886-f9f225962866)
Version 6.0.0 is a complete rewrite of this project in C/C++. It:
- actually works this time, with less pain (in comparison to 5.0.0 beta);
- is blazing fast, thanks to the redesigned efficient pipeline and the speed of C/C++;
- is cross-platform, available right now for both Windows and Linux;
- provides much better output quality with Anime4K v4 and RealESRGAN;
- supports Anime4K v4 and all other custom MPV-compatible GLSL shaders;
- supports RealESRGAN (all three models) via ncnn and Vulkan;
- requires 0 disk space for processing the video, just space for storing the final output; and
- exports a standard C function that can be easily integrated in your own projects!
- genuinely works this time, with much less hassle compared to the 5.0.0 beta;
- is blazing fast, thanks to the new optimized pipeline and the efficiency of C/C++;
- is cross-platform, available now for both Windows and Linux;
- offers significantly better output quality with Anime4K v4 and RealESRGAN;
- supports Anime4K v4 and all custom MPV-compatible GLSL shaders;
- includes support for RealESRGAN (all three models) via ncnn and Vulkan;
- requires zero additional disk space during processing, just space for the final output; and
- exports a standard C function for easy integration into other projects! (documentations are on the way)
These are available for download now:
Support for RealCUGAN and frame interpolation with RIFE are coming soon.
- **6.0.0 beta Qt6-based GUI for Windows** is on the [releases page](https://github.com/k4yt3x/video2x/releases).
- **6.0.0 beta CLI preview builds for Windows and Linux** are on the [releases page](https://github.com/k4yt3x/video2x/releases).
- You will need to install the dependencies and set `LD_LIBRARY_PATH` for the Linux build to work. Refer to the [PKGBUILD](PKGBUILD) file to see what needs to be installed.
- Alternatively, you can build it from source. Take a look at the [Makefile](Makefile).
- 6.0.0 beta AUR package for Arch Linux (`video2x-git`).
- 6.0.0 beta [container image](https://github.com/k4yt3x/video2x/pkgs/container/video2x).
- A new Colab will be made for 6.0.0 at a later time.
## [🪟 Download for Windows](https://github.com/k4yt3x/video2x/releases/latest)
There is still much to be done and optimize. Stay tuned for more updates. As for why the 5.0.0 branch was abandoned, here are some of the reasons:
You can download the latest Windows release from the [releases page](https://github.com/k4yt3x/video2x/releases/latest). For basic GUI usage, refer to the [GUI wiki page](https://github.com/k4yt3x/video2x/wiki/GUI). If you're unable to download directly from GitHub, try the [mirror](https://files.k4yt3x.com/Projects/Video2X/latest). The GUI currently supports the following languages:
- Wrapped C++ libraries for Python are too painful to build for cross-platform distribution.
- Some wrapped C++ libraires exhibited unexpected behaviors.
- Running FFmpeg via commands and piping data through stdin/stdout are inefficient.
- C/C++ native binaries are much smaller and much more efficient.
- English (United States)
- 简体中文(中国)
- 日本語(日本)
- Português (Portugal)
## [💬 Telegram Discussion Group](https://t.me/video2x)
## [🐧 Install on Linux](https://aur.archlinux.org/packages/video2x-git)
Join our Telegram discussion group to ask any questions you have about Video2X, chat directly with the developers, or discuss about super resolution technologies and the future of Video2X in general.
## [🪟 Download Windows Releases](https://github.com/k4yt3x/video2x/releases/tag/4.8.1)
The latest Windows release build based on version 4.8.1. Go to the [GUI](https://github.com/k4yt3x/video2x/wiki/GUI) page to see the basic usages of the GUI. Try the [mirror](https://files.k4yt3x.com/Projects/Video2X/latest) if you can't download releases directly from GitHub.
## [📔 Google Colab](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo)
You can use Video2X on [Google Colab](https://colab.research.google.com/) **for free** if you don't have a powerful GPU of your own. You can borrow a powerful GPU (Tesla K80, T4, P4, or P100) on Google's server for free for a maximum of 12 hours per session. **Please use the free resource fairly** and do not create sessions back-to-back and run upscaling 24/7. This might result in you getting banned. You can get [Colab Pro/Pro+](https://colab.research.google.com/signup/pricing) if you'd like to use better GPUs and get longer runtimes. Usage instructions are embedded in the [Colab Notebook](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo).
You can install Video2X on Arch Linux using the [video2x-git](https://aur.archlinux.org/packages/video2x-git) package or download pre-compiled binaries from the [releases page](https://github.com/k4yt3x/video2x/releases/latest). If you'd like to build from source, refer to the [PKGBUILD](packaging/arch/PKGBUILD) file for a general overview of the required packages and commands. If you'd prefer not to compile the program from source, consider using the container image below.
## [📦 Container Image](https://github.com/k4yt3x/video2x/pkgs/container/video2x)
Video2X container images are available on the GitHub Container Registry for easy deployment on Linux and macOS. If you already have Docker/Podman installed, only one command is needed to start upscaling a video. For more information on how to use Video2X's Docker image, please refer to the [documentations](https://github.com/K4YT3X/video2x/wiki/Container).
## [📔 Google Colab](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo) (Outdated: 5.0.0-beta7)
You can use Video2X on [Google Colab](https://colab.research.google.com/) **for free** if you don't have a powerful GPU of your own. You can borrow a powerful GPU (NVIDIA T4, L4, or A100) on Google's server for free for a maximum of 12 hours per session. **Please use the free resource fairly** and do not create sessions back-to-back and run upscaling 24/7. This might result in you getting banned. You can get [Colab Pro/Pro+](https://colab.research.google.com/signup/pricing) if you'd like to use better GPUs and get longer runtimes. Usage instructions are embedded in the [Colab Notebook](https://colab.research.google.com/drive/1gWEwcA9y57EsxwOjmLNmNMXPsafw0kGo).
## [💬 Telegram Discussion Group](https://t.me/video2x)
Join our Telegram discussion group to ask any questions you have about Video2X, chat directly with the developers, or discuss about super resolution technologies and the future of Video2X in general.
## [📖 Documentations](https://github.com/k4yt3x/video2x/wiki)
Video2X's documentations are hosted on this repository's [Wiki page](https://github.com/k4yt3x/video2x/wiki). It includes comprehensive explanations for how to use the [GUI](https://github.com/k4yt3x/video2x/wiki/GUI), the [CLI](https://github.com/k4yt3x/video2x/wiki/CLI), the [container image](https://github.com/K4YT3X/video2x/wiki/Container), the [library](https://github.com/k4yt3x/video2x/wiki/Library), and more. The Wiki is open to edits by the community, so you, yes you, can also correct errors or add new contents to the documentations.
## Introduction
Video2X is a video/GIF/image upscaling and frame interpolation software written in Python. It can use these following state-of-the-art algorithms to increase the resolution and frame rate of your video/GIF/image. More information about the algorithms that it supports can be found in [the documentations](https://github.com/k4yt3x/video2x/wiki/Algorithms).
Video2X is a machine-learning-powered framework for video upscaling and frame interpolation, built around three main components:
### Video Upscaling
- [libvideo2x](https://github.com/k4yt3x/video2x/blob/master/src/libvideo2x.cpp): The core C++ library providing upscaling and frame interpolation capabilities.
- [Video2X CLI](https://github.com/k4yt3x/video2x/blob/master/src/video2x.c): A command-line interface that utilizes `libvideo2x` for video processing.
- [Video2X Qt6](https://github.com/k4yt3x/video2x-qt6): A Qt6-based graphical interface that utilizes `libvideo2x` for video processing.
### Video Demos
![Spirited Away Demo](https://user-images.githubusercontent.com/21986859/49412428-65083280-f73a-11e8-8237-bb34158a545e.png)\
_Upscale demo: Spirited Away's movie trailer_
@@ -78,10 +78,10 @@ _Upscale demo: Spirited Away's movie trailer_
- 360P to 4K
- The [original video](https://www.youtube.com/watch?v=ByXuk9QqQkk)'s copyright belongs to 株式会社スタジオジブリ
- **Bad Apple!!**: [YouTube](https://youtu.be/A81rW_FI3cw) | [Bilibili](https://www.bilibili.com/video/BV16K411K7ue)
- 384P to 4K 120FPS
- 384P 30 FPS to 4K 120 FPS with waifu2x and DAIN
- The [original video](https://www.nicovideo.jp/watch/sm8628149)'s copyright belongs to あにら
- **The Pet Girl of Sakurasou**: [YouTube](https://youtu.be/M0vDI1HH2_Y) | [Bilibili](https://www.bilibili.com/video/BV14k4y167KP/)
- 240P to 1080P 60FPS
- 240P 29.97 to 1080P 60 FPS with waifu2x and DAIN
- The original video's copyright belongs to ASCII Media Works
### Standard Test Clip
@@ -97,25 +97,25 @@ Copyright of this clip belongs to 株式会社アニプレックス.
## License
This project is licensed under the [GNU Affero General Public License Version 3 (GNU AGPL v3)](https://www.gnu.org/licenses/agpl-3.0.txt)\
This project is licensed under [GNU AGPL version 3](https://www.gnu.org/licenses/agpl-3.0.txt).\
Copyright (C) 2018-2024 K4YT3X and [contributors](https://github.com/k4yt3x/video2x/graphs/contributors).
![AGPLv3](https://www.gnu.org/graphics/agplv3-155x51.png)
This project (`libvideo2x`) includes or depends on these following projects:
This project includes or depends on these following projects:
| Project | License |
| ----------------------------------------------------------------------------- | --------------- |
| [Anime4K](https://github.com/bloc97/Anime4K) | MIT License |
| [FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
| [Real-ESRGAN ncnn Vulkan](https://github.com/xinntao/Real-ESRGAN-ncnn-vulkan) | MIT License |
| [ncnn](https://github.com/Tencent/ncnn) | BSD 3-Clause |
| Project | License |
| ------------------------------------------------------------------------------------- | --------------- |
| [bloc97/Anime4K](https://github.com/bloc97/Anime4K) | MIT License |
| [FFmpeg/FFmpeg](https://www.ffmpeg.org/) | LGPLv2.1, GPLv2 |
| [xinntao/Real-ESRGAN-ncnn-vulkan](https://github.com/xinntao/Real-ESRGAN-ncnn-vulkan) | MIT License |
| [Tencent/ncnn](https://github.com/Tencent/ncnn) | BSD 3-Clause |
More licensing information can be found in the [NOTICE](NOTICE) file.
## Special Thanks
Appreciations are given to the following personnel who have contributed significantly to the project.
Special thanks to the following individuals for their significant contributions to the project, listed in alphabetical order.
- [@ArchieMeng](https://github.com/archiemeng)
- [@BrianPetkovsek](https://github.com/BrianPetkovsek)
@@ -123,8 +123,3 @@ Appreciations are given to the following personnel who have contributed signific
- [@lhanjian](https://github.com/lhanjian)
- [@nihui](https://github.com/nihui)
- [@sat3ll](https://github.com/sat3ll)
## Similar Projects
- [Dandere2x](https://github.com/CardinalPanda/dandere2x): A lossy video upscaler also built around `waifu2x`, but with video compression techniques to shorten the time needed to process a video.
- [Waifu2x-Extension-GUI](https://github.com/AaronFeng753/Waifu2x-Extension-GUI): A similar project that focuses more and only on building a better graphical user interface. It is built using C++ and Qt5, and currently only supports the Windows platform.

View File

@@ -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__ */

View File

@@ -0,0 +1,22 @@
#ifndef CHAR_DEFS_H
#define CHAR_DEFS_H
#ifdef _WIN32
typedef wchar_t CharType;
#define STR(x) L##x
#else
typedef char CharType;
#define STR(x) x
#endif
#ifdef __cplusplus
#include <string>
#ifdef _WIN32
typedef std::wstring StringType;
#else
typedef std::string StringType;
#endif
#endif // __cplusplus
#endif // CHAR_DEFS_H

View File

@@ -1,6 +1,8 @@
#ifndef DECODER_H
#define DECODER_H
#include <filesystem>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
@@ -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

View File

@@ -1,6 +1,8 @@
#ifndef ENCODER_H
#define ENCODER_H
#include <filesystem>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
@@ -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,
@@ -21,7 +23,7 @@ int init_encoder(
int **stream_map
);
int encode_and_write_frame(
int write_frame(
AVFrame *frame,
AVCodecContext *enc_ctx,
AVFormatContext *ofmt_ctx,

View File

@@ -15,7 +15,7 @@ class Filter {
virtual ~Filter() = default;
virtual int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) = 0;
virtual int process_frame(AVFrame *in_frame, AVFrame **out_frame) = 0;
virtual int flush(std::vector<AVFrame *> &flushed_frames) { return 0; }
virtual int flush(std::vector<AVFrame *> &_) { return 0; }
};
#endif // FILTER_H

View File

@@ -2,11 +2,18 @@
#define FSUTILS_H
#include <filesystem>
#include <string>
#include "char_defs.h"
bool filepath_is_readable(const std::filesystem::path &path);
std::filesystem::path find_resource_file(const std::filesystem::path &path);
std::string path_to_string(const std::filesystem::path& path);
std::string path_to_u8string(const std::filesystem::path &path);
StringType path_to_string_type(const std::filesystem::path &path);
StringType to_string_type(int value);
#endif // FSUTILS_H

View File

@@ -9,13 +9,13 @@ extern "C" {
}
int init_libplacebo(
AVBufferRef *hw_ctx,
AVFilterGraph **filter_graph,
AVFilterContext **buffersrc_ctx,
AVFilterContext **buffersink_ctx,
AVCodecContext *dec_ctx,
int out_width,
int out_height,
uint32_t vk_device_index,
const std::filesystem::path &shader_path
);

View File

@@ -17,15 +17,21 @@ class LibplaceboFilter : public Filter {
AVFilterGraph *filter_graph;
AVFilterContext *buffersrc_ctx;
AVFilterContext *buffersink_ctx;
uint32_t vk_device_index;
const std::filesystem::path shader_path;
int out_width;
int out_height;
const std::filesystem::path shader_path;
AVRational in_time_base;
AVRational out_time_base;
public:
// Constructor
LibplaceboFilter(int width, int height, const std::filesystem::path &shader_path);
LibplaceboFilter(
uint32_t vk_device_index,
const std::filesystem::path &shader_path,
int width,
int height
);
// Destructor
virtual ~LibplaceboFilter() override;

View File

@@ -5,6 +5,17 @@
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
}
#endif
#include "char_defs.h"
#ifdef _WIN32
#ifdef LIBVIDEO2X_EXPORTS
#define LIBVIDEO2X_API __declspec(dllexport)
@@ -19,9 +30,6 @@
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
// Enum to specify filter type
enum FilterType {
FILTER_LIBPLACEBO,
@@ -43,15 +51,14 @@ enum Libvideo2xLogLevel {
struct LibplaceboConfig {
int out_width;
int out_height;
const char *shader_path;
const CharType *shader_path;
};
// Configuration for RealESRGAN filter
struct RealESRGANConfig {
int gpuid;
bool tta_mode;
int scaling_factor;
const char *model;
const CharType *model_name;
};
// Unified filter configuration
@@ -85,12 +92,26 @@ struct VideoProcessingContext {
bool completed;
};
// C-compatible process_video function
/**
* @brief Process a video file using the selected filter and encoder settings.
*
* @param[in] in_fname Path to the input video file
* @param[in] out_fname Path to the output video file
* @param[in] log_level Log level
* @param[in] benchmark Flag to enable benchmarking mode
* @param[in] vk_device_index Vulkan device index
* @param[in] hw_type Hardware device type
* @param[in] filter_config Filter configurations
* @param[in] encoder_config Encoder configurations
* @param[in,out] proc_ctx Video processing context
* @return int 0 on success, non-zero value on error
*/
LIBVIDEO2X_API int process_video(
const char *in_fname,
const char *out_fname,
const CharType *in_fname,
const CharType *out_fname,
enum Libvideo2xLogLevel log_level,
bool benchmark,
uint32_t vk_device_index,
enum AVHWDeviceType hw_device_type,
const struct FilterConfig *filter_config,
struct EncoderConfig *encoder_config,

View File

@@ -1,12 +1,11 @@
#ifndef REALSRGAN_FILTER_H
#define REALSRGAN_FILTER_H
#include <filesystem>
extern "C" {
#include <libavcodec/avcodec.h>
}
#include "char_defs.h"
#include "filter.h"
#include "realesrgan.h"
@@ -17,9 +16,7 @@ class RealesrganFilter : public Filter {
int gpuid;
bool tta_mode;
int scaling_factor;
const char *model;
const std::filesystem::path custom_model_param_path;
const std::filesystem::path custom_model_bin_path;
const StringType model_name;
AVRational in_time_base;
AVRational out_time_base;
AVPixelFormat out_pix_fmt;
@@ -30,9 +27,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 custom_model_param_path = std::filesystem::path(),
const std::filesystem::path custom_model_bin_path = std::filesystem::path()
const StringType model_name = STR("realesr-animevideov3")
);
// Destructor

View File

@@ -0,0 +1,34 @@
#ifndef TIMER_H
#define TIMER_H
#include <atomic>
#include <chrono>
#include <cstdint>
#include <thread>
class Timer {
public:
Timer();
~Timer();
void start();
void pause();
void resume();
void stop();
bool is_running() const;
bool is_paused() const;
int64_t get_elapsed_time() const;
private:
std::atomic<bool> running;
std::atomic<bool> paused;
std::thread timer_thread;
int64_t elapsed_time;
std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point pause_start_time;
void update_elapsed_time();
};
#endif // TIMER_H

9173
models/libplacebo/anime4k-v4.1-gan.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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)"

8
packaging/debian/control Normal file
View File

@@ -0,0 +1,8 @@
Package: video2x
Version: 6.1.0
Section: video
Priority: optional
Architecture: amd64
Maintainer: K4YT3X <i@k4yt3x.com>
Depends: ffmpeg, libboost-program-options1.83.0
Description: A machine learning-based lossless video super resolution framework.

View File

@@ -1,7 +1,7 @@
# Name: Video2X Dockerfile
# Creator: K4YT3X
# Date Created: February 3, 2022
# Last Modified: October 21, 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
@@ -20,7 +20,8 @@ COPY --chown=builder:builder . /video2x
WORKDIR /video2x
# Build the package
RUN makepkg -s --noconfirm \
RUN cp packaging/arch/PKGBUILD . \
&& makepkg -s --noconfirm \
&& find /video2x -maxdepth 1 -name 'video2x-*.pkg.tar.zst' ! -name '*-debug-*' | head -n 1 | \
xargs -I {} cp {} /tmp/video2x.pkg.tar.zst
@@ -37,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/*

View File

@@ -63,6 +63,12 @@ def download_and_combine_files():
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_M.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"gan": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_GAN_UUL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_GAN_x4_UUL.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_Soft_M.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
}
for mode in modes:
@@ -72,7 +78,13 @@ def download_and_combine_files():
response.raise_for_status()
file_contents += response.text + "\n"
with (SHADERS_DIR / Path(f"anime4k-{mode}.glsl")).open("w") as output_file:
version = "v4"
if mode == "gan":
version = "v4.1"
with (SHADERS_DIR / Path(f"anime4k-{version}-{mode}.glsl")).open(
"w"
) as output_file:
output_file.write(file_contents)

View File

@@ -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;
}

View File

@@ -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;
@@ -85,16 +85,23 @@ int init_encoder(
}
}
// Set the time base
codec_ctx->time_base = av_inv_q(dec_ctx->framerate);
if (codec_ctx->time_base.num == 0 || codec_ctx->time_base.den == 0) {
// Set the output video's time base
if (dec_ctx->time_base.num > 0 && dec_ctx->time_base.den > 0) {
codec_ctx->time_base = dec_ctx->time_base;
} else {
codec_ctx->time_base = av_inv_q(av_guess_frame_rate(ifmt_ctx, out_stream, NULL));
}
// Set the output video's frame rate
if (dec_ctx->framerate.num > 0 && dec_ctx->framerate.den > 0) {
codec_ctx->framerate = dec_ctx->framerate;
} else {
codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, out_stream, NULL);
}
// Set the CRF and preset for any codecs that support it
char crf_str[16];
snprintf(crf_str, sizeof(crf_str), "%.f", static_cast<double>(encoder_config->crf));
av_opt_set(codec_ctx->priv_data, "crf", crf_str, 0);
std::string crf_str = std::to_string(encoder_config->crf);
av_opt_set(codec_ctx->priv_data, "crf", crf_str.c_str(), 0);
av_opt_set(codec_ctx->priv_data, "preset", encoder_config->preset, 0);
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
@@ -113,6 +120,8 @@ int init_encoder(
}
out_stream->time_base = codec_ctx->time_base;
out_stream->avg_frame_rate = codec_ctx->framerate;
out_stream->r_frame_rate = codec_ctx->framerate;
if (encoder_config->copy_streams) {
// Allocate the stream map
@@ -165,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;
}
}
@@ -178,7 +187,7 @@ int init_encoder(
return 0;
}
int encode_and_write_frame(
int write_frame(
AVFrame *frame,
AVCodecContext *enc_ctx,
AVFormatContext *ofmt_ctx,

View File

@@ -75,7 +75,7 @@ std::filesystem::path find_resource_file(const std::filesystem::path &path) {
return get_executable_directory() / path;
}
std::string path_to_string(const std::filesystem::path &path) {
std::string path_to_u8string(const std::filesystem::path &path) {
#if _WIN32
std::wstring wide_path = path.wstring();
int buffer_size =
@@ -92,3 +92,19 @@ std::string path_to_string(const std::filesystem::path &path) {
return path.string();
#endif
}
StringType path_to_string_type(const std::filesystem::path &path) {
#if _WIN32
return path.wstring();
#else
return path.string();
#endif
}
StringType to_string_type(int value) {
#if _WIN32
return std::to_wstring(value);
#else
return std::to_string(value);
#endif
}

View File

@@ -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 <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 = 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);
}

View File

@@ -2,24 +2,37 @@
#include <stdio.h>
#include <stdlib.h>
#include <string>
extern "C" {
#include <libavutil/dict.h>
#include <libavutil/opt.h>
}
#include <spdlog/spdlog.h>
#include "fsutils.h"
int init_libplacebo(
AVBufferRef *hw_ctx,
AVFilterGraph **filter_graph,
AVFilterContext **buffersrc_ctx,
AVFilterContext **buffersink_ctx,
AVCodecContext *dec_ctx,
int out_width,
int out_height,
uint32_t vk_device_index,
const std::filesystem::path &shader_path
) {
char args[512];
int ret;
// Create the Vulkan hardware device context
AVBufferRef *vk_hw_device_ctx = nullptr;
ret = av_hwdevice_ctx_create(
&vk_hw_device_ctx, AV_HWDEVICE_TYPE_VULKAN, std::to_string(vk_device_index).c_str(), NULL, 0
);
if (ret < 0) {
spdlog::error("Failed to create Vulkan hardware device context.");
return ret;
}
AVFilterGraph *graph = avfilter_graph_alloc();
if (!graph) {
spdlog::error("Unable to create filter graph.");
@@ -28,25 +41,43 @@ int init_libplacebo(
// Create buffer source
const AVFilter *buffersrc = avfilter_get_by_name("buffer");
snprintf(
args,
sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:"
"pixel_aspect=%d/%d:colorspace=%d:range=%d",
dec_ctx->width,
dec_ctx->height,
dec_ctx->pix_fmt,
dec_ctx->time_base.num,
dec_ctx->time_base.den,
dec_ctx->framerate.num,
dec_ctx->framerate.den,
dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den,
dec_ctx->colorspace,
dec_ctx->color_range
);
if (!buffersrc) {
spdlog::error("Filter 'buffer' not found.");
avfilter_graph_free(&graph);
return AVERROR_FILTER_NOT_FOUND;
}
ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args, NULL, graph);
// Start building the arguments string
std::string args = "video_size=" + std::to_string(dec_ctx->width) + "x" +
std::to_string(dec_ctx->height) +
":pix_fmt=" + std::to_string(dec_ctx->pix_fmt) +
":time_base=" + std::to_string(dec_ctx->time_base.num) + "/" +
std::to_string(dec_ctx->time_base.den) +
":frame_rate=" + std::to_string(dec_ctx->framerate.num) + "/" +
std::to_string(dec_ctx->framerate.den) +
":pixel_aspect=" + std::to_string(dec_ctx->sample_aspect_ratio.num) + "/" +
std::to_string(dec_ctx->sample_aspect_ratio.den);
// Make a copy of the AVClass on the stack
AVClass priv_class_copy = *buffersrc->priv_class;
AVClass *priv_class_copy_ptr = &priv_class_copy;
// Check if the colorspace option is supported
if (av_opt_find(&priv_class_copy_ptr, "colorspace", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) {
args += ":colorspace=" + std::to_string(dec_ctx->colorspace);
} else {
spdlog::warn("Option 'colorspace' is not supported by the buffer filter.");
}
// Check if the range option is supported
if (av_opt_find(&priv_class_copy_ptr, "range", NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)) {
args += ":range=" + std::to_string(dec_ctx->color_range);
} else {
spdlog::warn("Option 'range' is not supported by the buffer filter.");
}
spdlog::debug("Buffer source args: {}", args);
ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args.c_str(), NULL, graph);
if (ret < 0) {
spdlog::error("Cannot create buffer source.");
avfilter_graph_free(&graph);
@@ -64,7 +95,7 @@ int init_libplacebo(
}
// Convert the shader path to a string since filter args is const char *
std::string shader_path_string = path_to_string(shader_path);
std::string shader_path_string = shader_path.u8string();
#ifdef _WIN32
// libplacebo does not recognize the Windows '\\' path separator
@@ -72,19 +103,13 @@ int init_libplacebo(
#endif
// Prepare the filter arguments
char filter_args[512];
snprintf(
filter_args,
sizeof(filter_args),
"w=%d:h=%d:upscaler=ewa_lanczos:custom_shader_path=%s",
out_width,
out_height,
shader_path_string.c_str()
);
std::string filter_args = "w=" + std::to_string(out_width) +
":h=" + std::to_string(out_height) + ":custom_shader_path='" +
shader_path_string + "'";
AVFilterContext *libplacebo_ctx;
ret = avfilter_graph_create_filter(
&libplacebo_ctx, libplacebo_filter, "libplacebo", filter_args, NULL, graph
&libplacebo_ctx, libplacebo_filter, "libplacebo", filter_args.c_str(), NULL, graph
);
if (ret < 0) {
spdlog::error("Cannot create libplacebo filter.");
@@ -93,9 +118,8 @@ int init_libplacebo(
}
// Set the hardware device context to Vulkan
if (hw_ctx != nullptr) {
libplacebo_ctx->hw_device_ctx = av_buffer_ref(hw_ctx);
}
libplacebo_ctx->hw_device_ctx = av_buffer_ref(vk_hw_device_ctx);
av_buffer_unref(&vk_hw_device_ctx);
// Link buffersrc to libplacebo
ret = avfilter_link(last_filter, 0, libplacebo_ctx, 0);

View File

@@ -4,20 +4,23 @@
#include <spdlog/spdlog.h>
#include "char_defs.h"
#include "fsutils.h"
#include "libplacebo.h"
LibplaceboFilter::LibplaceboFilter(
uint32_t vk_device_index,
const std::filesystem::path &shader_path,
int out_width,
int out_height,
const std::filesystem::path &shader_path
int out_height
)
: filter_graph(nullptr),
buffersrc_ctx(nullptr),
buffersink_ctx(nullptr),
vk_device_index(vk_device_index),
shader_path(std::move(shader_path)),
out_width(out_width),
out_height(out_height),
shader_path(std::move(shader_path)) {}
out_height(out_height) {}
LibplaceboFilter::~LibplaceboFilter() {
if (buffersrc_ctx) {
@@ -34,7 +37,7 @@ LibplaceboFilter::~LibplaceboFilter() {
}
}
int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) {
int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *_) {
// Construct the shader path
std::filesystem::path shader_full_path;
if (filepath_is_readable(shader_path)) {
@@ -43,13 +46,14 @@ int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
} else {
// Construct the fallback path using std::filesystem
shader_full_path = find_resource_file(
std::filesystem::path("models") / "libplacebo" / (shader_path.string() + ".glsl")
std::filesystem::path(STR("models")) / STR("libplacebo") /
(path_to_string_type(shader_path) + STR(".glsl"))
);
}
// Check if the shader file exists
if (!std::filesystem::exists(shader_full_path)) {
spdlog::error("libplacebo shader file not found: {}", shader_full_path.string());
spdlog::error("libplacebo shader file not found: '{}'", shader_path.u8string());
return -1;
}
@@ -59,13 +63,13 @@ int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
// Initialize the libplacebo filter
int ret = init_libplacebo(
hw_ctx,
&filter_graph,
&buffersrc_ctx,
&buffersink_ctx,
dec_ctx,
out_width,
out_height,
vk_device_index,
shader_full_path
);
@@ -92,6 +96,7 @@ int LibplaceboFilter::process_frame(AVFrame *in_frame, AVFrame **out_frame) {
ret = av_buffersrc_add_frame(buffersrc_ctx, in_frame);
if (ret < 0) {
spdlog::error("Error while feeding the filter graph");
av_frame_free(out_frame);
return ret;
}

View File

@@ -5,8 +5,11 @@
#include <string.h>
#include <thread>
extern "C" {
#include <libavutil/avutil.h>
}
#include <spdlog/spdlog.h>
#include <opencv2/videoio.hpp>
#include "decoder.h"
#include "encoder.h"
@@ -14,21 +17,7 @@
#include "libplacebo_filter.h"
#include "realesrgan_filter.h"
/**
* @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] 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] vstream_idx Index of the video stream in the input format context
* @param[in] stream_map 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
*/
// Process frames using the selected filter.
static int process_frames(
EncoderConfig *encoder_config,
VideoProcessingContext *proc_ctx,
@@ -42,65 +31,109 @@ static int process_frames(
bool benchmark = false
) {
int ret;
AVPacket packet;
std::vector<AVFrame *> flushed_frames;
char errbuf[AV_ERROR_MAX_STRING_SIZE];
std::vector<AVFrame *> flushed_frames;
// Get the total number of frames in the video with OpenCV
spdlog::debug("Unable to estimate total number of frames; reading 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");
// Get the duration of the video
double duration_secs = 0.0;
if (ifmt_ctx->duration != AV_NOPTS_VALUE) {
duration_secs =
static_cast<double>(ifmt_ctx->duration) / static_cast<double>(AV_TIME_BASE);
} else if (ifmt_ctx->streams[vstream_idx]->duration != AV_NOPTS_VALUE) {
duration_secs = static_cast<double>(ifmt_ctx->streams[vstream_idx]->duration) *
av_q2d(ifmt_ctx->streams[vstream_idx]->time_base);
} else {
spdlog::warn("Unable to determine video duration");
}
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 || duration_secs <= 0) {
spdlog::warn("Unable to estimate the video's average frame rate");
} else {
// Calculate total frames
proc_ctx->total_frames = static_cast<int64_t>(duration_secs * fps);
}
}
proc_ctx->total_frames = static_cast<int64_t>(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");
if (proc_ctx->total_frames <= 0) {
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);
goto end;
return ret;
}
AVPacket *packet = av_packet_alloc();
if (packet == nullptr) {
spdlog::critical("Could not allocate AVPacket");
av_frame_free(&frame);
return AVERROR(ENOMEM);
}
// Lambda function for cleaning up resources
auto cleanup = [&]() {
av_frame_free(&frame);
av_packet_free(&packet);
for (AVFrame *&flushed_frame : flushed_frames) {
if (flushed_frame) {
av_frame_free(&flushed_frame);
flushed_frame = nullptr;
}
}
};
// Read frames from the input file
while (!proc_ctx->abort) {
ret = av_read_frame(ifmt_ctx, &packet);
ret = av_read_frame(ifmt_ctx, packet);
if (ret < 0) {
if (ret == AVERROR_EOF) {
spdlog::debug("Reached end of file");
break;
}
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error reading packet: {}", errbuf);
goto end;
spdlog::critical("Error reading packet: {}", errbuf);
cleanup();
return ret;
}
if (packet.stream_index == vstream_idx) {
// Send the packet to the decoder
ret = avcodec_send_packet(dec_ctx, &packet);
if (packet->stream_index == vstream_idx) {
ret = avcodec_send_packet(dec_ctx, packet);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error sending packet to decoder: {}", errbuf);
av_packet_unref(&packet);
goto end;
spdlog::critical("Error sending packet to decoder: {}", errbuf);
av_packet_unref(packet);
cleanup();
return ret;
}
// Receive and process frames from the decoder
while (!proc_ctx->abort) {
// Check if the processing is paused
if (proc_ctx->pause) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
@@ -112,31 +145,34 @@ static int process_frames(
break;
} else if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error decoding video frame: {}", errbuf);
goto end;
spdlog::critical("Error decoding video frame: {}", errbuf);
av_packet_unref(packet);
cleanup();
return ret;
}
// Process the frame using the selected filter
AVFrame *processed_frame = nullptr;
ret = filter->process_frame(frame, &processed_frame);
if (ret == 0 && processed_frame != nullptr) {
// Encode and write the processed frame
if (ret < 0 && ret != AVERROR(EAGAIN)) {
av_strerror(ret, errbuf, sizeof(errbuf));
av_frame_free(&processed_frame);
av_packet_unref(packet);
cleanup();
return ret;
} else if (ret == 0 && processed_frame != nullptr) {
if (!benchmark) {
ret =
encode_and_write_frame(processed_frame, enc_ctx, ofmt_ctx, vstream_idx);
ret = write_frame(processed_frame, enc_ctx, ofmt_ctx, vstream_idx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error encoding/writing frame: {}", errbuf);
spdlog::critical("Error encoding/writing frame: {}", errbuf);
av_frame_free(&processed_frame);
goto end;
av_packet_unref(packet);
cleanup();
return ret;
}
}
av_frame_free(&processed_frame);
proc_ctx->processed_frames++;
} else if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
spdlog::error("Filter returned an error");
goto end;
}
av_frame_unref(frame);
@@ -144,130 +180,70 @@ static int process_frames(
"Processed frame {}/{}", proc_ctx->processed_frames, proc_ctx->total_frames
);
}
} else if (encoder_config->copy_streams && stream_map[packet.stream_index] >= 0) {
AVStream *in_stream = ifmt_ctx->streams[packet.stream_index];
int out_stream_index = stream_map[packet.stream_index];
} else if (encoder_config->copy_streams && stream_map[packet->stream_index] >= 0) {
AVStream *in_stream = ifmt_ctx->streams[packet->stream_index];
int out_stream_index = stream_map[packet->stream_index];
AVStream *out_stream = ofmt_ctx->streams[out_stream_index];
// Rescale packet timestamps
av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
packet.stream_index = out_stream_index;
av_packet_rescale_ts(packet, in_stream->time_base, out_stream->time_base);
packet->stream_index = out_stream_index;
// If copy streams is enabled, copy the packet to the output
ret = av_interleaved_write_frame(ofmt_ctx, &packet);
ret = av_interleaved_write_frame(ofmt_ctx, packet);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error muxing packet: {}", errbuf);
av_packet_unref(&packet);
av_packet_unref(packet);
cleanup();
return ret;
}
}
av_packet_unref(&packet);
av_packet_unref(packet);
}
// Flush the filter
ret = filter->flush(flushed_frames);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error flushing filter: {}", errbuf);
goto end;
spdlog::critical("Error flushing filter: {}", errbuf);
cleanup();
return ret;
}
// Encode and write all flushed frames
for (AVFrame *&flushed_frame : flushed_frames) {
ret = encode_and_write_frame(flushed_frame, enc_ctx, ofmt_ctx, vstream_idx);
ret = write_frame(flushed_frame, enc_ctx, ofmt_ctx, vstream_idx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error encoding/writing flushed frame: {}", errbuf);
spdlog::critical("Error encoding/writing flushed frame: {}", errbuf);
av_frame_free(&flushed_frame);
flushed_frame = nullptr;
goto end;
cleanup();
return ret;
}
av_frame_free(&flushed_frame);
flushed_frame = nullptr;
proc_ctx->processed_frames++;
}
// Flush the encoder
ret = flush_encoder(enc_ctx, ofmt_ctx);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error flushing encoder: {}", errbuf);
goto end;
spdlog::critical("Error flushing encoder: {}", errbuf);
cleanup();
return ret;
}
end:
av_frame_free(&frame);
// Free any flushed frames not yet freed
for (AVFrame *flushed_frame : flushed_frames) {
if (flushed_frame) {
av_frame_free(&flushed_frame);
}
}
cleanup();
return ret;
}
// Cleanup resources after processing the video
static void cleanup(
AVFormatContext *ifmt_ctx,
AVFormatContext *ofmt_ctx,
AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx,
AVBufferRef *hw_ctx,
int *stream_map,
Filter *filter
) {
if (ifmt_ctx) {
avformat_close_input(&ifmt_ctx);
ifmt_ctx = nullptr;
}
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
ofmt_ctx->pb = nullptr;
}
if (ofmt_ctx) {
avformat_free_context(ofmt_ctx);
ofmt_ctx = nullptr;
}
if (dec_ctx) {
avcodec_free_context(&dec_ctx);
dec_ctx = nullptr;
}
if (enc_ctx) {
avcodec_free_context(&enc_ctx);
enc_ctx = nullptr;
}
if (hw_ctx) {
av_buffer_unref(&hw_ctx);
hw_ctx = nullptr;
}
if (stream_map) {
av_free(stream_map);
stream_map = nullptr;
}
if (filter) {
delete filter;
filter = nullptr;
}
}
/**
* @brief Process a video file using the selected filter and encoder settings.
*
* @param[in] in_fname Path to the input video file
* @param[in] out_fname 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
* @param[in,out] proc_ctx Video processing context
* @return int 0 on success, non-zero value on error
*/
extern "C" int process_video(
const char *in_fname,
const char *out_fname,
const CharType *in_fname,
const CharType *out_fname,
Libvideo2xLogLevel log_level,
bool benchmark,
uint32_t vk_device_index,
AVHWDeviceType hw_type,
const FilterConfig *filter_config,
EncoderConfig *encoder_config,
@@ -284,6 +260,42 @@ extern "C" int process_video(
char errbuf[AV_ERROR_MAX_STRING_SIZE];
int ret = 0;
// Lambda function for cleaning up resources
auto cleanup = [&]() {
if (ifmt_ctx) {
avformat_close_input(&ifmt_ctx);
ifmt_ctx = nullptr;
}
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
ofmt_ctx->pb = nullptr;
}
if (ofmt_ctx) {
avformat_free_context(ofmt_ctx);
ofmt_ctx = nullptr;
}
if (dec_ctx) {
avcodec_free_context(&dec_ctx);
dec_ctx = nullptr;
}
if (enc_ctx) {
avcodec_free_context(&enc_ctx);
enc_ctx = nullptr;
}
if (hw_ctx) {
av_buffer_unref(&hw_ctx);
hw_ctx = nullptr;
}
if (stream_map) {
av_free(stream_map);
stream_map = nullptr;
}
if (filter) {
delete filter;
filter = nullptr;
}
};
// Set the log level for FFmpeg and spdlog (libvideo2x)
switch (log_level) {
case LIBVIDEO2X_LOG_LEVEL_TRACE:
@@ -320,26 +332,31 @@ 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);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error initializing hardware device context: {}", errbuf);
spdlog::critical("Error initializing hardware device context: {}", errbuf);
cleanup();
return ret;
}
}
// 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);
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
spdlog::critical("Failed to initialize decoder: {}", errbuf);
cleanup();
return ret;
}
// Initialize output based on Libplacebo or RealESRGAN configuration
// Initialize output dimensions based on filter configuration
int output_width = 0, output_height = 0;
switch (filter_config->filter_type) {
case FILTER_LIBPLACEBO:
@@ -347,13 +364,12 @@ extern "C" int process_video(
output_height = filter_config->config.libplacebo.out_height;
break;
case FILTER_REALESRGAN:
// Calculate the output dimensions based on the scaling factor
output_width = dec_ctx->width * filter_config->config.realesrgan.scaling_factor;
output_height = dec_ctx->height * filter_config->config.realesrgan.scaling_factor;
break;
default:
spdlog::error("Unknown filter type");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
spdlog::critical("Unknown filter type");
cleanup();
return -1;
}
spdlog::info("Output video dimensions: {}x{}", output_width, output_height);
@@ -363,7 +379,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,
@@ -374,8 +390,8 @@ extern "C" int process_video(
);
if (ret < 0) {
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_map, filter);
spdlog::critical("Failed to initialize encoder: {}", errbuf);
cleanup();
return ret;
}
@@ -383,68 +399,56 @@ extern "C" int process_video(
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
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_map, filter);
spdlog::critical("Error occurred when opening output file: {}", errbuf);
cleanup();
return ret;
}
// Create and initialize the appropriate filter
switch (filter_config->filter_type) {
case FILTER_LIBPLACEBO: {
const auto &config = filter_config->config.libplacebo;
// Validate shader path
if (!config.shader_path) {
spdlog::error("Shader path must be provided for the libplacebo filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
return -1;
}
// Validate output dimensions
if (config.out_width <= 0 || config.out_height <= 0) {
spdlog::error("Output dimensions must be provided for the libplacebo filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
return -1;
}
filter = new LibplaceboFilter{
config.out_width, config.out_height, std::filesystem::path(config.shader_path)
};
break;
}
case FILTER_REALESRGAN: {
const auto &config = filter_config->config.realesrgan;
// Validate model name
if (!config.model) {
spdlog::error("Model name must be provided for the RealESRGAN filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
return -1;
}
// Validate scaling factor
if (config.scaling_factor <= 0) {
spdlog::error("Scaling factor must be provided for the RealESRGAN filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
return -1;
}
filter = new RealesrganFilter{
config.gpuid, config.tta_mode, config.scaling_factor, config.model
};
break;
}
default:
spdlog::error("Unknown filter type");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
if (filter_config->filter_type == FILTER_LIBPLACEBO) {
const auto &config = filter_config->config.libplacebo;
if (!config.shader_path) {
spdlog::critical("Shader path must be provided for the libplacebo filter");
cleanup();
return -1;
}
filter = new LibplaceboFilter{
vk_device_index,
std::filesystem::path(config.shader_path),
config.out_width,
config.out_height
};
} else if (filter_config->filter_type == FILTER_REALESRGAN) {
const auto &config = filter_config->config.realesrgan;
if (!config.model_name) {
spdlog::critical("Model name must be provided for the RealESRGAN filter");
cleanup();
return -1;
}
filter = new RealesrganFilter{
static_cast<int>(vk_device_index),
config.tta_mode,
config.scaling_factor,
config.model_name
};
} else {
spdlog::critical("Unknown filter type");
cleanup();
return -1;
}
// Check if the filter instance was created successfully
if (filter == nullptr) {
spdlog::critical("Failed to create filter instance");
cleanup();
return -1;
}
// Initialize the filter
ret = filter->init(dec_ctx, enc_ctx, hw_ctx);
if (ret < 0) {
spdlog::error("Failed to initialize filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
spdlog::critical("Failed to initialize filter");
cleanup();
return ret;
}
@@ -463,8 +467,8 @@ extern "C" int process_video(
);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error processing frames: {}", errbuf);
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
spdlog::critical("Error processing frames: {}", errbuf);
cleanup();
return ret;
}
@@ -472,11 +476,11 @@ extern "C" int process_video(
av_write_trailer(ofmt_ctx);
// Cleanup before returning
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_map, filter);
cleanup();
if (ret < 0 && ret != AVERROR_EOF) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error occurred: {}", errbuf);
spdlog::critical("Error occurred: {}", errbuf);
return ret;
}
return 0;

View File

@@ -2,7 +2,7 @@
#include <cstdint>
#include <cstdio>
#include <string>
#include <filesystem>
#include <spdlog/spdlog.h>
@@ -13,17 +13,13 @@ RealesrganFilter::RealesrganFilter(
int gpuid,
bool tta_mode,
int scaling_factor,
const char *model,
const std::filesystem::path custom_model_param_path,
const std::filesystem::path custom_model_bin_path
const StringType model_name
)
: realesrgan(nullptr),
gpuid(gpuid),
tta_mode(tta_mode),
scaling_factor(scaling_factor),
model(model),
custom_model_param_path(std::move(custom_model_param_path)),
custom_model_bin_path(std::move(custom_model_bin_path)) {}
model_name(std::move(model_name)) {}
RealesrganFilter::~RealesrganFilter() {
if (realesrgan) {
@@ -37,21 +33,14 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
std::filesystem::path model_param_path;
std::filesystem::path model_bin_path;
if (model) {
// Find the model paths by model name if provided
model_param_path = std::filesystem::path("models") / "realesrgan" /
(std::string(model) + "-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");
} 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;
model_bin_path = custom_model_bin_path;
} else {
// Neither model name nor custom model paths provided
spdlog::error("Model or model paths must be provided for RealESRGAN filter");
return -1;
}
StringType param_file_name =
model_name + STR("-x") + to_string_type(scaling_factor) + STR(".param");
StringType bin_file_name =
model_name + STR("-x") + to_string_type(scaling_factor) + STR(".bin");
// Find the model paths by model name if provided
model_param_path = std::filesystem::path(STR("models")) / STR("realesrgan") / param_file_name;
model_bin_path = std::filesystem::path(STR("models")) / STR("realesrgan") / bin_file_name;
// Get the full paths using a function that possibly modifies or validates the path
std::filesystem::path model_param_full_path = find_resource_file(model_param_path);
@@ -59,11 +48,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)) {
spdlog::error("RealESRGAN model param file not found: {}", model_param_full_path.string());
spdlog::error("RealESRGAN model param file not found: {}", model_param_path.u8string());
return -1;
}
if (!std::filesystem::exists(model_bin_full_path)) {
spdlog::error("RealESRGAN model bin file not found: {}", model_bin_full_path.string());
spdlog::error("RealESRGAN model bin file not found: {}", model_bin_path.u8string());
return -1;
}

76
src/timer.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "timer.h"
#include <sys/types.h>
Timer::Timer() : running(false), paused(false), elapsed_time(0) {}
Timer::~Timer() {
stop();
}
void Timer::start() {
if (running) {
return;
}
running = true;
paused = false;
elapsed_time = 0;
start_time = std::chrono::steady_clock::now();
timer_thread = std::thread([this]() {
while (running) {
if (!paused) {
update_elapsed_time();
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
}
void Timer::pause() {
if (running && !paused) {
paused = true;
pause_start_time = std::chrono::steady_clock::now();
}
}
void Timer::resume() {
if (running && paused) {
paused = false;
auto pause_end_time = std::chrono::steady_clock::now();
auto pause_duration =
std::chrono::duration_cast<std::chrono::milliseconds>(pause_end_time - pause_start_time)
.count();
start_time += std::chrono::milliseconds(pause_duration);
}
}
void Timer::stop() {
running = false;
if (timer_thread.joinable()) {
timer_thread.join();
}
update_elapsed_time();
}
bool Timer::is_running() const {
return running;
}
bool Timer::is_paused() const {
return paused;
}
int64_t Timer::get_elapsed_time() const {
return elapsed_time;
}
void Timer::update_elapsed_time() {
if (running && !paused) {
auto current_time = std::chrono::steady_clock::now();
elapsed_time =
std::chrono::duration_cast<std::chrono::milliseconds>(current_time - start_time)
.count();
}
}

View File

@@ -1,599 +0,0 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <time.h>
#ifdef _WIN32
#include <conio.h>
#else
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif
#include <libavutil/hwcontext.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include <libvideo2x/libvideo2x.h>
#include <libvideo2x/version.h>
#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-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:r:m: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 (-e) 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;
}

734
src/video2x.cpp Normal file
View File

@@ -0,0 +1,734 @@
#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_set>
#ifdef _WIN32
#include <Windows.h>
#include <conio.h>
#else
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#endif
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/hwcontext.h>
#include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h>
#include <libvideo2x/libvideo2x.h>
#include <libvideo2x/version.h>
}
#include <spdlog/spdlog.h>
#include <vulkan/vulkan.h>
#ifdef _WIN32
#define BOOST_PROGRAM_OPTIONS_WCHAR_T
#define PO_STR_VALUE po::wvalue
#else
#define PO_STR_VALUE po::value
#endif
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#include "libvideo2x/char_defs.h"
#include "libvideo2x/timer.h"
// Indicate if a newline needs to be printed before the next output
std::atomic<bool> newline_required = false;
// Mutex for synchronizing access to VideoProcessingContext
std::mutex proc_ctx_mutex;
// Structure to hold parsed arguments
struct Arguments {
StringType loglevel = STR("info");
bool noprogress = false;
// General options
std::filesystem::path in_fname;
std::filesystem::path out_fname;
StringType filter_type;
uint32_t gpuid = 0;
StringType hwaccel = STR("none");
bool nocopystreams = false;
bool benchmark = false;
// Encoder options
StringType codec = STR("libx264");
StringType preset = STR("slow");
StringType pix_fmt;
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
StringType model_name;
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<unsigned int>(~(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<int>(wstr.size()), nullptr, 0, nullptr, nullptr
);
std::string converted_str(size_needed, 0);
WideCharToMultiByte(
CP_UTF8,
0,
wstr.data(),
static_cast<int>(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 StringType &model) {
static const std::unordered_set<StringType> valid_realesrgan_models = {
STR("realesrgan-plus"), STR("realesrgan-plus-anime"), STR("realesr-animevideov3")
};
return valid_realesrgan_models.count(model) > 0;
}
enum Libvideo2xLogLevel parse_log_level(const StringType &level_name) {
if (level_name == STR("trace")) {
return LIBVIDEO2X_LOG_LEVEL_TRACE;
} else if (level_name == STR("debug")) {
return LIBVIDEO2X_LOG_LEVEL_DEBUG;
} else if (level_name == STR("info")) {
return LIBVIDEO2X_LOG_LEVEL_INFO;
} else if (level_name == STR("warning") || level_name == STR("warn")) {
return LIBVIDEO2X_LOG_LEVEL_WARNING;
} else if (level_name == STR("error")) {
return LIBVIDEO2X_LOG_LEVEL_ERROR;
} else if (level_name == STR("critical")) {
return LIBVIDEO2X_LOG_LEVEL_CRITICAL;
} else if (level_name == STR("off") || level_name == STR("none")) {
return LIBVIDEO2X_LOG_LEVEL_OFF;
} else {
spdlog::warn("Invalid log level specified. Defaulting to 'info'.");
return LIBVIDEO2X_LOG_LEVEL_INFO;
}
}
int list_gpus() {
// Create a Vulkan instance
VkInstance instance;
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) {
spdlog::critical("Failed to create Vulkan instance.");
return -1;
}
// Enumerate physical devices
uint32_t device_count = 0;
VkResult result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (result != VK_SUCCESS) {
spdlog::critical("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Check if any devices are found
if (device_count == 0) {
spdlog::critical("No Vulkan physical devices found.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Get physical device properties
std::vector<VkPhysicalDevice> physical_devices(device_count);
result = vkEnumeratePhysicalDevices(instance, &device_count, physical_devices.data());
if (result != VK_SUCCESS) {
spdlog::critical("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// List GPU information
for (uint32_t i = 0; i < device_count; i++) {
VkPhysicalDevice device = physical_devices[i];
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties(device, &device_properties);
// Print GPU ID and name
std::cout << i << ". " << device_properties.deviceName << std::endl;
std::cout << "\tType: ";
switch (device_properties.deviceType) {
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
std::cout << "Integrated GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
std::cout << "Discrete GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
std::cout << "Virtual GPU";
break;
case VK_PHYSICAL_DEVICE_TYPE_CPU:
std::cout << "CPU";
break;
default:
std::cout << "Unknown";
break;
}
std::cout << std::endl;
// Print Vulkan API version
std::cout << "\tVulkan API Version: " << VK_VERSION_MAJOR(device_properties.apiVersion)
<< "." << VK_VERSION_MINOR(device_properties.apiVersion) << "."
<< VK_VERSION_PATCH(device_properties.apiVersion) << std::endl;
// Print driver version
std::cout << "\tDriver Version: " << VK_VERSION_MAJOR(device_properties.driverVersion)
<< "." << VK_VERSION_MINOR(device_properties.driverVersion) << "."
<< VK_VERSION_PATCH(device_properties.driverVersion) << std::endl;
}
// Clean up Vulkan instance
vkDestroyInstance(instance, nullptr);
return 0;
}
int is_valid_gpu_id(uint32_t gpu_id) {
// Create a Vulkan instance
VkInstance instance;
VkInstanceCreateInfo create_info{};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
if (vkCreateInstance(&create_info, nullptr, &instance) != VK_SUCCESS) {
spdlog::error("Failed to create Vulkan instance.");
return -1;
}
// Enumerate physical devices
uint32_t device_count = 0;
VkResult result = vkEnumeratePhysicalDevices(instance, &device_count, nullptr);
if (result != VK_SUCCESS) {
spdlog::error("Failed to enumerate Vulkan physical devices.");
vkDestroyInstance(instance, nullptr);
return -1;
}
// Clean up Vulkan instance
vkDestroyInstance(instance, nullptr);
if (gpu_id >= device_count) {
return 0;
}
return 1;
}
// Wrapper function for video processing thread
void process_video_thread(
Arguments *arguments,
int *proc_ret,
AVHWDeviceType hw_device_type,
FilterConfig *filter_config,
EncoderConfig *encoder_config,
VideoProcessingContext *proc_ctx
) {
enum Libvideo2xLogLevel log_level = parse_log_level(arguments->loglevel);
StringType in_fname_string;
StringType out_fname_string;
#ifdef _WIN32
in_fname_string = StringType(arguments->in_fname.wstring());
out_fname_string = StringType(arguments->out_fname.wstring());
#else
in_fname_string = StringType(arguments->in_fname.string());
out_fname_string = StringType(arguments->out_fname.string());
#endif
const CharType *in_fname = in_fname_string.c_str();
const CharType *out_fname = out_fname_string.c_str();
*proc_ret = process_video(
in_fname,
out_fname,
log_level,
arguments->benchmark,
arguments->gpuid,
hw_device_type,
filter_config,
encoder_config,
proc_ctx
);
{
std::lock_guard<std::mutex> lock(proc_ctx_mutex);
proc_ctx->completed = true;
}
}
#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");
desc.add_options()
("help", "Display this help page")
("version,v", "Print program version")
("loglevel", PO_STR_VALUE<StringType>(&arguments.loglevel)->default_value(STR("info"), "info"), "Set log level (trace, debug, info, warn, error, critical, none)")
("noprogress", po::bool_switch(&arguments.noprogress), "Do not display the progress bar")
("listgpus", "List the available GPUs")
// General Processing Options
("input,i", PO_STR_VALUE<StringType>(), "Input video file path")
("output,o", PO_STR_VALUE<StringType>(), "Output video file path")
("filter,f", PO_STR_VALUE<StringType>(&arguments.filter_type), "Filter to use: 'libplacebo' or 'realesrgan'")
("gpuid,g", po::value<uint32_t>(&arguments.gpuid)->default_value(0), "Vulkan GPU ID (default: 0)")
("hwaccel,a", PO_STR_VALUE<StringType>(&arguments.hwaccel)->default_value(STR("none"), "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_STR_VALUE<StringType>(&arguments.codec)->default_value(STR("libx264"), "libx264"), "Output codec (default: libx264)")
("preset,p", PO_STR_VALUE<StringType>(&arguments.preset)->default_value(STR("slow"), "slow"), "Encoder preset (default: slow)")
("pixfmt,x", PO_STR_VALUE<StringType>(&arguments.pix_fmt), "Output pixel format (default: auto)")
("bitrate,b", po::value<int64_t>(&arguments.bitrate)->default_value(0), "Bitrate in bits per second (default: 0 (VBR))")
("crf,q", po::value<float>(&arguments.crf)->default_value(20.0f), "Constant Rate Factor (default: 20.0)")
// libplacebo options
("shader,s", PO_STR_VALUE<StringType>(), "Name or path of the GLSL shader file to use")
("width,w", po::value<int>(&arguments.out_width), "Output width")
("height,h", po::value<int>(&arguments.out_height), "Output height")
// RealESRGAN options
("model,m", PO_STR_VALUE<StringType>(&arguments.model_name), "Name of the model to use")
("scale,r", po::value<int>(&arguments.scaling_factor), "Scaling factor (2, 3, or 4)")
;
// 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);
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;
}
if (vm.count("listgpus")) {
return list_gpus();
}
// Assign positional arguments
if (vm.count("input")) {
arguments.in_fname = std::filesystem::path(vm["input"].as<StringType>());
} else {
spdlog::critical("Input file path is required.");
return 1;
}
if (vm.count("output")) {
arguments.out_fname = std::filesystem::path(vm["output"].as<StringType>());
} else if (!arguments.benchmark) {
spdlog::critical("Output file path is required.");
return 1;
}
if (!vm.count("filter")) {
spdlog::critical("Filter type is required (libplacebo or realesrgan).");
return 1;
}
if (vm.count("shader")) {
arguments.shader_path = std::filesystem::path(vm["shader"].as<StringType>());
}
if (vm.count("model")) {
if (!is_valid_realesrgan_model(vm["model"].as<StringType>())) {
spdlog::critical(
"Invalid model specified. Must be 'realesrgan-plus', "
"'realesrgan-plus-anime', or 'realesr-animevideov3'."
);
return 1;
}
}
} catch (const po::error &e) {
spdlog::critical("Error parsing options: {}", e.what());
return 1;
} catch (const std::exception &e) {
spdlog::critical("Unexpected exception caught while parsing options: {}", e.what());
return 1;
}
// Additional validations
if (arguments.filter_type == STR("libplacebo")) {
if (arguments.shader_path.empty() || arguments.out_width == 0 ||
arguments.out_height == 0) {
spdlog::critical(
"For libplacebo, shader name/path (-s), width (-w), "
"and height (-h) are required."
);
return 1;
}
} else if (arguments.filter_type == STR("realesrgan")) {
if (arguments.scaling_factor == 0 || arguments.model_name.empty()) {
spdlog::critical("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::critical("Scaling factor must be 2, 3, or 4.");
return 1;
}
} else {
spdlog::critical("Invalid filter type specified. Must be 'libplacebo' or 'realesrgan'.");
return 1;
}
// Validate GPU ID
int gpu_status = is_valid_gpu_id(arguments.gpuid);
if (gpu_status < 0) {
spdlog::warn("Unable to validate GPU ID.");
} else if (arguments.gpuid > 0 && gpu_status == 0) {
spdlog::critical("Invalid GPU ID specified.");
return 1;
}
// Validate bitrate
if (arguments.bitrate < 0) {
spdlog::critical("Invalid bitrate specified.");
return 1;
}
// Validate CRF
if (arguments.crf < 0.0f || arguments.crf > 51.0f) {
spdlog::critical("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::critical("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::critical("Invalid pixel format '{}'.", wstring_to_utf8(arguments.pix_fmt));
return 1;
}
}
// Set spdlog log level
auto log_level = parse_log_level(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;
}
// Print program version and processing information
spdlog::info("Video2X version {}", LIBVIDEO2X_VERSION_STRING);
spdlog::info("Processing file: {}", arguments.in_fname.u8string());
#ifdef _WIN32
std::wstring shader_path_str = arguments.shader_path.wstring();
#else
std::string shader_path_str = arguments.shader_path.string();
#endif
// Setup filter configurations based on the parsed arguments
FilterConfig filter_config;
if (arguments.filter_type == STR("libplacebo")) {
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 = shader_path_str.c_str();
} else if (arguments.filter_type == STR("realesrgan")) {
filter_config.filter_type = FILTER_REALESRGAN;
filter_config.config.realesrgan.tta_mode = false;
filter_config.config.realesrgan.scaling_factor = arguments.scaling_factor;
filter_config.config.realesrgan.model_name = arguments.model_name.c_str();
}
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;
if (arguments.hwaccel != STR("none")) {
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::critical(
"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
int proc_ret = 0;
std::thread processing_thread(
process_video_thread,
&arguments,
&proc_ret,
hw_device_type,
&filter_config,
&encoder_config,
&proc_ctx
);
spdlog::info("Press SPACE to pause/resume, 'q' to abort.");
// Setup timer
Timer timer;
timer.start();
// 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<std::mutex> 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<std::mutex> 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.");
timer.pause();
} else {
spdlog::info("Resuming processing...");
timer.resume();
}
}
} else if (ch == 'q' || ch == 'Q') {
// Abort processing
putchar('\n');
spdlog::info("Aborting processing...");
{
std::lock_guard<std::mutex> 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<std::mutex> 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<double>(processed_frames) *
100.0 / static_cast<double>(total_frames)
: 0.0;
int64_t time_elapsed = timer.get_elapsed_time() / 1000;
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;
{
std::lock_guard<std::mutex> lock(proc_ctx_mutex);
aborted = proc_ctx.abort;
}
if (aborted) {
spdlog::warn("Video processing aborted");
return 2;
} else if (proc_ret != 0) {
spdlog::critical("Video processing failed with error code {}", proc_ret);
return 1;
} else {
spdlog::info("Video processed successfully");
}
// Calculate statistics
int64_t processed_frames;
{
std::lock_guard<std::mutex> lock(proc_ctx_mutex);
processed_frames = proc_ctx.processed_frames;
}
int64_t time_elapsed = timer.get_elapsed_time() / 1000;
float average_speed_fps = static_cast<float>(processed_frames) /
(time_elapsed > 0 ? static_cast<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.u8string().c_str());
printf("Total frames processed: %ld\n", proc_ctx.processed_frames);
printf("Total time taken: %ld s\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;
}

1
third_party/boost vendored Submodule

Submodule third_party/boost added at 65c1319bb9