28 Commits

Author SHA1 Message Date
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
k4yt3x
ebd5e8eb3d fix(realesrgan): fixed incorrect type for ncnn::Mat elemsize
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
k4yt3x
50d5cb16d1 docs(video2x): updated command line help messages
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
k4yt3x
8eac1a7393 feat(libplacebo): added more modes for Anime4K v4
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
k4yt3x
f0f3166d92 chore(models): moved model files into subdirectories
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-26 00:00:00 +00:00
k4yt3x
482e82f9c4 style(*): reduced implicit casting and other styling issues
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-25 00:00:00 +00:00
k4yt3x
58ea9e4b35 feat(cmake): removed CMake policy CMP0146 for compatibility
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-22 00:00:00 +00:00
k4yt3x
e077849a2c docs(readme): updated links for 6.0.0-beta.4
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-22 00:00:00 +00:00
k4yt3x
0fb547589d feat(*): updated Dockerfile and PKGBUILD for 6.0.0-beta.4
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-22 00:00:00 +00:00
k4yt3x
1f18b8507d feat(cmake): added installation rules for FFmpeg and OpenCV DLLs 2024-10-21 21:43:11 -04:00
k4yt3x
eb7d411f49 perf(libvideo2x): removed unnecessary read of nb_frames
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-22 00:00:00 +00:00
k4yt3x
640d9cd52b fix(encoder): fixed memory leak in the encoder
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-21 00:00:00 +00:00
K4YT3X
bc168d11ab feat(libvideo2x): use OpenCV to retrieve total frame count (#1194) 2024-10-21 23:54:22 +00:00
Owen Quinlan
e09f348890 Fix missing spdlog in docker build [Fix for #1185] (#1186)
* Fix missing spdlog in docker build
* fix: added the spdlog dependency for the Dockerfile building stage

---------

Co-authored-by: K4YT3X <i@k4yt3x.com>
2024-10-14 16:51:40 +00:00
k4yt3x
f3caf22765 docs(readme): updated links for 6.0.0-beta.3
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-14 00:00:00 +00:00
K4YT3X
cd2006b4d9 feat(*): switched to spdlog for logging and organized headers (#1183)
* feat: updated PKGBUILD description
* feat: updated workflow syntax and dependencies
* feat: switched logging to spdlog
* chore: adjusted library defaults
* ci: fixed spdlog format string issues
* docs: fixed docs for libvideo2x functions
* feat: organized header files
* fix: fixed header installation directory
* feat: link spdlog statically if compiled from source
* feat: adjusted libvideo2x log level enum names
* feat: added version.h header

Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-14 02:46:59 +00:00
k4yt3x
ecbc512711 ci(build): fixed librealesrgan library name
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-11 00:00:00 +00:00
k4yt3x
3fe1f6d544 docs(readme): updated Windows download link
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-11 00:00:00 +00:00
k4yt3x
ee4d6f360e chore(librealesrgan): updated librealesrgan submodule
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-10 00:00:00 +00:00
k4yt3x
7ae1943754 docs(readme): added EOL notice for versions 4 and 5
Signed-off-by: k4yt3x <i@k4yt3x.com>
2024-10-10 00:00:00 +00:00
53 changed files with 20777 additions and 586 deletions

2
.gitattributes vendored
View File

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

10
.github/FUNDING.yml vendored
View File

@@ -1,12 +1,2 @@
# These are supported funding model platforms
github: k4yt3x github: k4yt3x
patreon: k4yt3x patreon: k4yt3x
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -6,6 +6,7 @@ on:
- dev - dev
pull_request: {} pull_request: {}
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
ubuntu: ubuntu:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -29,11 +30,12 @@ jobs:
libswscale-dev \ libswscale-dev \
libvulkan-dev \ libvulkan-dev \
glslang-tools \ glslang-tools \
libomp-dev libomp-dev \
libopencv-dev
- name: Build Video2X - name: Build Video2X
run: | run: |
mkdir -p /tmp/build /tmp/install mkdir -p /tmp/build /tmp/install
cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \ cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/tmp/install \
-DINSTALL_BIN_DESTINATION=. -DINSTALL_INCLUDE_DESTINATION=include \ -DINSTALL_BIN_DESTINATION=. -DINSTALL_INCLUDE_DESTINATION=include \
@@ -44,6 +46,7 @@ jobs:
with: with:
name: video2x-nightly-linux-amd64 name: video2x-nightly-linux-amd64
path: /tmp/install path: /tmp/install
windows: windows:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
@@ -61,6 +64,7 @@ jobs:
run: | run: |
$ffmpegVersion = "7.1" $ffmpegVersion = "7.1"
$ncnnVersion = "20240820" $ncnnVersion = "20240820"
$opencvVersion = "4.10.0"
git submodule update --init --recursive git submodule update --init --recursive
@@ -71,9 +75,16 @@ jobs:
curl -Lo ncnn-shared.zip "https://github.com/Tencent/ncnn/releases/download/$ncnnVersion/ncnn-$ncnnVersion-windows-vs2022-shared.zip" 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 Expand-Archive -Path ncnn-shared.zip -DestinationPath third_party
Rename-Item -Path "third_party/ncnn-$ncnnVersion-windows-vs2022-shared" -NewName ncnn-shared 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 - name: Build Video2X
shell: pwsh
run: | run: |
cmake -S . -B build -DUSE_SYSTEM_NCNN=OFF -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=build/video2x_install cmake -S . -B build `
-DUSE_SYSTEM_NCNN=OFF -DUSE_SYSTEM_SPDLOG=OFF -DUSE_SYSTEM_OPENCV=OFF `
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=build/video2x_install
cmake --build build --config Debug --parallel --target install cmake --build build --config Debug --parallel --target install
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@@ -4,17 +4,20 @@ on:
tags: tags:
- "*" - "*"
permissions:
contents: write
jobs: jobs:
setup: setup:
if: github.event.base_ref == 'refs/heads/master' if: github.event.base_ref == 'refs/heads/master'
name: Setup name: Setup
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
tag: ${{ steps.get_tag.outputs.tag }} version: ${{ steps.get_version.outputs.version }}
steps: steps:
- name: Get tag - name: Get version
id: get_tag id: get_version
run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//} run: echo version=${GITHUB_REF/refs\/tags\//} >> $GITHUB_OUTPUT
container: container:
name: Build and upload container name: Build and upload container
@@ -32,7 +35,7 @@ jobs:
password: ${{ secrets.GHCR_TOKEN }} password: ${{ secrets.GHCR_TOKEN }}
dockerfile: Dockerfile dockerfile: Dockerfile
image: video2x image: video2x
tags: latest, ${{ needs.setup.outputs.tag }} tags: latest, ${{ needs.setup.outputs.version }}
create-release: create-release:
name: Create release name: Create release
@@ -45,11 +48,10 @@ jobs:
steps: steps:
- name: Create release - name: Create release
id: create_release id: create_release
uses: actions/create-release@v1 uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ needs.setup.outputs.tag }} token: ${{ secrets.GITHUB_TOKEN }}
release_name: Video2X ${{ needs.setup.outputs.tag }} tag_name: ${{ needs.setup.outputs.version }}
name: ${{ needs.setup.outputs.version }}
draft: true draft: true
prerelease: false prerelease: false

12
.gitmodules vendored
View File

@@ -1,6 +1,12 @@
[submodule "third_party/libreal_esrgan_ncnn_vulkan"] [submodule "third_party/librealesrgan_ncnn_vulkan"]
path = third_party/libreal_esrgan_ncnn_vulkan path = third_party/librealesrgan_ncnn_vulkan
url = https://github.com/k4yt3x/libreal-esrgan-ncnn-vulkan.git url = https://github.com/k4yt3x/librealesrgan-ncnn-vulkan.git
[submodule "third_party/ncnn"] [submodule "third_party/ncnn"]
path = third_party/ncnn path = third_party/ncnn
url = https://github.com/Tencent/ncnn.git url = https://github.com/Tencent/ncnn.git
[submodule "third_party/spdlog"]
path = third_party/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "third_party/opencv"]
path = third_party/opencv
url = https://github.com/opencv/opencv.git

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(video2x VERSION 6.0.0 LANGUAGES CXX C) project(video2x VERSION 6.0.0 LANGUAGES C CXX)
# Set the C standard # Set the C standard
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
@@ -28,18 +28,47 @@ if(CMAKE_BUILD_TYPE STREQUAL "Release")
endif() endif()
endif() endif()
# Set global compile options for all targets
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
add_compile_options(/W4 /permissive-)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic -Wconversion -Wshadow)
endif()
# Build options # Build options
option(BUILD_SHARED_LIBS "Build libvideo2x as a shared library" ON) option(BUILD_SHARED_LIBS "Build libvideo2x as a shared library" ON)
option(BUILD_VIDEO2X "Build the video2x executable" ON) option(BUILD_VIDEO2X_CLI "Build the video2x executable" ON)
option(USE_SYSTEM_SPDLOG "Use system spdlog library" ON)
option(USE_SYSTEM_OPENCV "Use system OpenCV library" ON)
option(USE_SYSTEM_NCNN "Use system ncnn library" ON) option(USE_SYSTEM_NCNN "Use system ncnn library" ON)
# Generate the version header file
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/include/libvideo2x/version.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h"
@ONLY
)
# Find the required packages
set(ALL_INCLUDE_DIRS) set(ALL_INCLUDE_DIRS)
set(ALL_LIBRARIES) 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) if(WIN32)
# Define base paths for FFmpeg and ncnn # Define base paths for FFmpeg and ncnn
set(FFMPEG_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/ffmpeg-shared) set(FFMPEG_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/ffmpeg-shared)
set(NCNN_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/ncnn-shared/x64) set(NCNN_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/ncnn-shared/x64)
set(OPENCV_BASE_PATH ${PROJECT_SOURCE_DIR}/third_party/opencv-shared)
# FFmpeg # FFmpeg
list(APPEND ALL_LIBRARIES list(APPEND ALL_LIBRARIES
@@ -54,7 +83,9 @@ if(WIN32)
# ncnn # ncnn
# TODO: Figure out why this file is not being copied to the install directory # TODO: Figure out why this file is not being copied to the install directory
set(SPIRV_BUILD_PATH ${CMAKE_BINARY_DIR}/realesrgan-prefix/src/realesrgan-build/ncnn/glslang/SPIRV) set(SPIRV_BUILD_PATH
${CMAKE_BINARY_DIR}/realesrgan-prefix/src/realesrgan-build/ncnn/glslang/SPIRV
)
if (CMAKE_BUILD_TYPE STREQUAL "Release") if (CMAKE_BUILD_TYPE STREQUAL "Release")
set(SPIRV_LIB ${SPIRV_BUILD_PATH}/Release/SPIRV.lib) set(SPIRV_LIB ${SPIRV_BUILD_PATH}/Release/SPIRV.lib)
else() else()
@@ -66,8 +97,12 @@ if(WIN32)
${SPIRV_LIB} ${SPIRV_LIB}
) )
list(APPEND ALL_INCLUDE_DIRS ${NCNN_BASE_PATH}/include/ncnn) 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_INCLUDE_DIRS ${OPENCV_BASE_PATH}/build/include)
else() else()
# Find the required packages using pkg-config # FFmpeg
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
set(REQUIRED_PKGS set(REQUIRED_PKGS
libavcodec libavcodec
@@ -84,11 +119,55 @@ else()
list(APPEND ALL_INCLUDE_DIRS ${${PKG}_INCLUDE_DIRS}) list(APPEND ALL_INCLUDE_DIRS ${${PKG}_INCLUDE_DIRS})
list(APPEND ALL_LIBRARIES ${${PKG}_LIBRARIES}) list(APPEND ALL_LIBRARIES ${${PKG}_LIBRARIES})
endforeach() endforeach()
endif()
# Remove duplicate entries # OpenCV
list(REMOVE_DUPLICATES ALL_INCLUDE_DIRS) if (USE_SYSTEM_OPENCV)
list(REMOVE_DUPLICATES ALL_LIBRARIES) find_package(OpenCV REQUIRED)
list(APPEND ALL_INCLUDE_DIRS ${OpenCV_INCLUDE_DIRS}/opencv2)
list(APPEND ALL_LIBRARIES opencv_core opencv_videoio)
else()
option(BUILD_opencv_calib3d "" OFF)
option(BUILD_opencv_core "" ON)
option(BUILD_opencv_dnn "" OFF)
option(BUILD_opencv_features2d "" OFF)
option(BUILD_opencv_flann "" OFF)
option(BUILD_opencv_gapi "" OFF)
option(BUILD_opencv_highgui "" OFF)
option(BUILD_opencv_imgcodecs "" OFF)
option(BUILD_opencv_imgproc "" OFF)
option(BUILD_opencv_java "" OFF)
option(BUILD_opencv_js "" OFF)
option(BUILD_opencv_ml "" OFF)
option(BUILD_opencv_objc "" OFF)
option(BUILD_opencv_objdetect "" OFF)
option(BUILD_opencv_photo "" OFF)
option(BUILD_opencv_python "" OFF)
option(BUILD_opencv_python2 "" OFF)
option(BUILD_opencv_python3 "" OFF)
option(BUILD_opencv_stitching "" OFF)
option(BUILD_opencv_ts "" OFF)
option(BUILD_opencv_video "" OFF)
option(BUILD_opencv_videoio "" ON)
option(BUILD_opencv_world "" OFF)
option(WITH_PNG "" OFF)
option(WITH_JPEG "" OFF)
option(WITH_TIFF "" OFF)
option(WITH_WEBP "" OFF)
option(WITH_OPENJPEG "" OFF)
option(WITH_JASPER "" OFF)
option(WITH_OPENEXR "" OFF)
option(WITH_IMGCODEC "" OFF)
add_subdirectory(third_party/opencv)
list(APPEND ALL_INCLUDE_DIRS
${PROJECT_SOURCE_DIR}/third_party/opencv/include
${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)
endif() # USE_SYSTEM_OPENCV
endif() # WIN32
# Find ncnn package # Find ncnn package
if(USE_SYSTEM_NCNN) if(USE_SYSTEM_NCNN)
@@ -192,10 +271,10 @@ endif()
# Include ExternalProject module # Include ExternalProject module
include(ExternalProject) include(ExternalProject)
# Add libreal-esrgan-ncnn-vulkan as an external project # Add librealesrgan-ncnn-vulkan as an external project
ExternalProject_Add( ExternalProject_Add(
realesrgan 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 CMAKE_ARGS
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/realesrgan_install -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/realesrgan_install
@@ -205,6 +284,10 @@ ExternalProject_Add(
INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}
) )
# Remove duplicate entries
list(REMOVE_DUPLICATES ALL_INCLUDE_DIRS)
list(REMOVE_DUPLICATES ALL_LIBRARIES)
# Add all source files for libvideo2x # Add all source files for libvideo2x
file(GLOB LIBVIDEO2X_SOURCES src/*.cpp) file(GLOB LIBVIDEO2X_SOURCES src/*.cpp)
@@ -223,13 +306,14 @@ add_dependencies(libvideo2x realesrgan)
# Include directories for the shared library # Include directories for the shared library
target_include_directories(libvideo2x PRIVATE target_include_directories(libvideo2x PRIVATE
${ALL_INCLUDE_DIRS} ${ALL_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/third_party/libreal_esrgan_ncnn_vulkan/src ${PROJECT_SOURCE_DIR}/include/libvideo2x
${PROJECT_SOURCE_DIR}/third_party/librealesrgan_ncnn_vulkan/src
) )
# Compile options for the shared library # Compile options for the shared library
target_compile_options(libvideo2x PRIVATE target_compile_options(libvideo2x PRIVATE
-Wall
-fPIC -fPIC
$<$<CONFIG:Release>:-Ofast> $<$<CONFIG:Release>:-Ofast>
$<$<CONFIG:Debug>:-g -DDEBUG> $<$<CONFIG:Debug>:-g -DDEBUG>
@@ -237,7 +321,7 @@ target_compile_options(libvideo2x PRIVATE
# Define the path to the built libresrgan-ncnn-vulkan library # Define the path to the built libresrgan-ncnn-vulkan library
if(WIN32) if(WIN32)
set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/realesrgan-ncnn-vulkan.lib) set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.lib)
else() else()
set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.so) set(REALESRGAN_LIB ${CMAKE_BINARY_DIR}/realesrgan_install/lib/librealesrgan-ncnn-vulkan.so)
endif() endif()
@@ -254,21 +338,19 @@ if(NOT WIN32)
endif() endif()
# Create the executable 'video2x' # Create the executable 'video2x'
if (BUILD_VIDEO2X) if (BUILD_VIDEO2X_CLI)
add_executable(video2x src/video2x.c src/getopt.c) add_executable(video2x src/video2x.c src/getopt.c)
set_target_properties(video2x PROPERTIES OUTPUT_NAME video2x) set_target_properties(video2x PROPERTIES OUTPUT_NAME video2x)
# Include directories for the executable # Include directories for the executable
target_include_directories(video2x PRIVATE target_include_directories(video2x PRIVATE
${ALL_INCLUDE_DIRS} ${ALL_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
) )
# Compile options for the executable # Compile options for the executable
target_compile_options(video2x PRIVATE target_compile_options(video2x PRIVATE $<$<CONFIG:Debug>:-g -DDEBUG>)
-Wall
$<$<CONFIG:Debug>:-g -DDEBUG>
)
# Link the executable with the shared library # Link the executable with the shared library
target_link_libraries(video2x PRIVATE ${ALL_LIBRARIES} libvideo2x) target_link_libraries(video2x PRIVATE ${ALL_LIBRARIES} libvideo2x)
@@ -277,16 +359,20 @@ endif()
# Define the default installation directories # Define the default installation directories
if(WIN32) if(WIN32)
set(BIN_DESTINATION_DEFAULT ".") set(BIN_DESTINATION_DEFAULT ".")
set(INCLUDE_DESTINATION_DEFAULT "include") set(INCLUDE_DESTINATION_DEFAULT "include/libvideo2x")
set(LIB_DESTINATION_DEFAULT ".") set(LIB_DESTINATION_DEFAULT ".")
set(MODEL_DESTINATION_DEFAULT ".") set(MODEL_DESTINATION_DEFAULT ".")
else() else()
set(BIN_DESTINATION_DEFAULT "bin") set(BIN_DESTINATION_DEFAULT "bin")
set(INCLUDE_DESTINATION_DEFAULT "include") set(INCLUDE_DESTINATION_DEFAULT "include/libvideo2x")
set(LIB_DESTINATION_DEFAULT "lib") set(LIB_DESTINATION_DEFAULT "lib")
set(MODEL_DESTINATION_DEFAULT "share/video2x") set(MODEL_DESTINATION_DEFAULT "share/video2x")
endif() endif()
# Explicitly set the output paths since OpenCV changes these variables
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR})
# Set the installation directories # Set the installation directories
set(INSTALL_BIN_DESTINATION ${BIN_DESTINATION_DEFAULT} CACHE STRING "") set(INSTALL_BIN_DESTINATION ${BIN_DESTINATION_DEFAULT} CACHE STRING "")
set(INSTALL_INCLUDE_DESTINATION ${INCLUDE_DESTINATION_DEFAULT} CACHE STRING "") set(INSTALL_INCLUDE_DESTINATION ${INCLUDE_DESTINATION_DEFAULT} CACHE STRING "")
@@ -306,31 +392,58 @@ install(TARGETS libvideo2x
# Install model files # Install model files
install(DIRECTORY ${CMAKE_SOURCE_DIR}/models DESTINATION ${INSTALL_MODEL_DESTINATION}) install(DIRECTORY ${CMAKE_SOURCE_DIR}/models DESTINATION ${INSTALL_MODEL_DESTINATION})
# Install the executable if BUILD_VIDEO2X is enabled # Install the executable if BUILD_VIDEO2X_CLI is enabled
if(BUILD_VIDEO2X) if(BUILD_VIDEO2X_CLI)
install(TARGETS video2x RUNTIME DESTINATION ${INSTALL_BIN_DESTINATION}) install(TARGETS video2x RUNTIME DESTINATION ${INSTALL_BIN_DESTINATION})
endif() endif()
# Install the header file # Install the header file
install(FILES ${PROJECT_SOURCE_DIR}/include/libvideo2x.h DESTINATION ${INSTALL_INCLUDE_DESTINATION}) install(FILES ${PROJECT_SOURCE_DIR}/include/libvideo2x/libvideo2x.h
DESTINATION ${INSTALL_INCLUDE_DESTINATION}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libvideo2x/version.h
DESTINATION ${INSTALL_INCLUDE_DESTINATION}
)
# Platform-specific installation rules # Platform-specific installation rules
if(WIN32) if(WIN32)
# Install Windows-specific dependencies # Install Windows-specific dependencies
install(FILES ${CMAKE_BINARY_DIR}/realesrgan_install/bin/realesrgan-ncnn-vulkan.dll install(FILES ${CMAKE_BINARY_DIR}/realesrgan_install/bin/librealesrgan-ncnn-vulkan.dll
DESTINATION ${INSTALL_BIN_DESTINATION} DESTINATION ${INSTALL_BIN_DESTINATION}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE WORLD_READ WORLD_EXECUTE
) )
install(FILES ${NCNN_BASE_PATH}/bin/ncnn.dll DESTINATION ${INSTALL_BIN_DESTINATION} 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
DESTINATION ${INSTALL_BIN_DESTINATION}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE WORLD_READ WORLD_EXECUTE
) )
else() else()
# Install Unix-specific dependencies # Install Unix-specific dependencies
install(FILES ${REALESRGAN_LIB} DESTINATION ${INSTALL_LIB_DESTINATION} install(FILES ${REALESRGAN_LIB}
DESTINATION ${INSTALL_LIB_DESTINATION}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE WORLD_READ WORLD_EXECUTE

View File

@@ -1,14 +1,15 @@
# Name: Video2X Dockerfile # Name: Video2X Dockerfile
# Creator: K4YT3X # Creator: K4YT3X
# Date Created: February 3, 2022 # Date Created: February 3, 2022
# Last Modified: October 7, 2024 # Last Modified: October 21, 2024
# stage 1: build the python components into wheels # stage 1: build the python components into wheels
FROM docker.io/archlinux:latest AS builder FROM docker.io/archlinux:latest AS builder
# Install dependencies and create a non-root user # Install dependencies and create a non-root user
RUN pacman -Syy --noconfirm \ RUN pacman -Syy --noconfirm \
base-devel ffmpeg ncnn git cmake make clang pkgconf vulkan-headers openmp sudo \ base-devel git cmake make clang pkgconf sudo \
ffmpeg ncnn vulkan-headers openmp spdlog opencv \
nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \ nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \
&& useradd -m builder \ && useradd -m builder \
&& echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder && echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder
@@ -31,11 +32,12 @@ LABEL maintainer="K4YT3X <i@k4yt3x.com>" \
ENV VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json\ ENV VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json\
:/usr/share/vulkan/icd.d/radeon_icd.x86_64.json\ :/usr/share/vulkan/icd.d/radeon_icd.x86_64.json\
:/usr/share/vulkan/icd.d/intel_icd.x86_64.json :/usr/share/vulkan/icd.d/intel_icd.x86_64.json\
:/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
COPY --from=builder /tmp/video2x.pkg.tar.zst /video2x.pkg.tar.zst COPY --from=builder /tmp/video2x.pkg.tar.zst /video2x.pkg.tar.zst
RUN pacman -Sy --noconfirm ffmpeg ncnn \ RUN pacman -Sy --noconfirm nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \
nvidia-utils vulkan-radeon vulkan-intel vulkan-swrast \ ffmpeg ncnn spdlog opencv \
&& pacman -U --noconfirm /video2x.pkg.tar.zst \ && pacman -U --noconfirm /video2x.pkg.tar.zst \
&& rm -rf /video2x.pkg.tar.zst /var/cache/pacman/pkg/* && rm -rf /video2x.pkg.tar.zst /var/cache/pacman/pkg/*

View File

@@ -1,9 +1,15 @@
.PHONY: build static debug windows test-realesrgan test-libplacebo leakcheck clean .PHONY: build static debug clean \
test-realesrgan test-libplacebo \
memcheck-realesrgan memcheck-libplacebo \
heaptrack-realesrgan heaptrack-libplacebo
BINDIR=build BINDIR=build
CC=clang CC=clang
CXX=clang++ CXX=clang++
TEST_VIDEO=data/standard-test.mp4
TEST_OUTPUT=data/output.mp4
build: build:
cmake -S . -B $(BINDIR) \ cmake -S . -B $(BINDIR) \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
@@ -19,8 +25,7 @@ static:
-DCMAKE_C_COMPILER=$(CC) \ -DCMAKE_C_COMPILER=$(CC) \
-DCMAKE_CXX_COMPILER=$(CXX) \ -DCMAKE_CXX_COMPILER=$(CXX) \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \ -DBUILD_SHARED_LIBS=OFF
-DUSE_SYSTEM_NCNN=OFF
cmake --build $(BINDIR) --config Release --parallel cmake --build $(BINDIR) --config Release --parallel
cp $(BINDIR)/compile_commands.json . cp $(BINDIR)/compile_commands.json .
@@ -45,23 +50,28 @@ debian:
libswscale-dev \ libswscale-dev \
libvulkan-dev \ libvulkan-dev \
glslang-tools \ glslang-tools \
libomp-dev libomp-dev \
libspdlog-dev \
libopencv-dev
cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \ cmake -B /tmp/build -S . -DUSE_SYSTEM_NCNN=OFF \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/install \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/install \
-DINSTALL_BIN_DESTINATION=. -DINSTALL_INCLUDE_DESTINATION=include \ -DINSTALL_BIN_DESTINATION=. -DINSTALL_INCLUDE_DESTINATION=include \
-DINSTALL_LIB_DESTINATION=. -DINSTALL_MODEL_DESTINATION=. -DINSTALL_LIB_DESTINATION=. -DINSTALL_MODEL_DESTINATION=.
cmake --build /tmp/build --config Release --target install --parallel cmake --build /tmp/build --config Release --target install --parallel
clean:
rm -vrf $(BINDIR) data/output*.* heaptrack*.zst valgrind.log
test-realesrgan: test-realesrgan:
LD_LIBRARY_PATH=$(BINDIR) $(BINDIR)/video2x -i data/standard-test.mp4 -o data/output.mp4 \ LD_LIBRARY_PATH=$(BINDIR) $(BINDIR)/video2x -i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f realesrgan -r 4 --model realesr-animevideov3 -f realesrgan -r 4 -m realesr-animevideov3
test-libplacebo: test-libplacebo:
LD_LIBRARY_PATH=$(BINDIR) $(BINDIR)/video2x -i data/standard-test.mp4 -o data/output.mp4 \ 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
leakcheck-realesrgan: memcheck-realesrgan:
LD_LIBRARY_PATH=$(BINDIR) valgrind \ LD_LIBRARY_PATH=$(BINDIR) valgrind \
--tool=memcheck \ --tool=memcheck \
--leak-check=full \ --leak-check=full \
@@ -70,11 +80,11 @@ leakcheck-realesrgan:
--show-reachable=yes \ --show-reachable=yes \
--verbose --log-file="valgrind.log" \ --verbose --log-file="valgrind.log" \
$(BINDIR)/video2x \ $(BINDIR)/video2x \
-i data/standard-test.mp4 -o data/output.mp4 \ -i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f realesrgan -r 2 --model realesr-animevideov3 \ -f realesrgan -r 2 -m realesr-animevideov3 \
-p veryfast -b 1000000 -q 30 -p veryfast -b 1000000 -q 30
leakcheck-libplacebo: memcheck-libplacebo:
LD_LIBRARY_PATH=$(BINDIR) valgrind \ LD_LIBRARY_PATH=$(BINDIR) valgrind \
--tool=memcheck \ --tool=memcheck \
--leak-check=full \ --leak-check=full \
@@ -83,9 +93,20 @@ leakcheck-libplacebo:
--show-reachable=yes \ --show-reachable=yes \
--verbose --log-file="valgrind.log" \ --verbose --log-file="valgrind.log" \
$(BINDIR)/video2x \ $(BINDIR)/video2x \
-i data/standard-test.mp4 -o data/output.mp4 \ -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 -p veryfast -b 1000000 -q 30
clean: heaptrack-realesrgan:
rm -rf $(BINDIR) LD_LIBRARY_PATH=$(BINDIR) HEAPTRACK_ENABLE_DEBUGINFOD=1 heaptrack \
$(BINDIR)/video2x \
-i $(TEST_VIDEO) -o $(TEST_OUTPUT) \
-f realesrgan -r 4 -m realesr-animevideov3 \
-p veryfast -b 1000000 -q 30
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-v4-a \
-p veryfast -b 1000000 -q 30

View File

@@ -1,11 +1,11 @@
pkgname=video2x pkgname=video2x
pkgver=r862.f590ead pkgver=r843.e09f348
pkgrel=1 pkgrel=1
pkgdesc="A lossless video super resolution framework" pkgdesc="A machine learning-based lossless video super resolution framework"
arch=('x86_64') arch=('x86_64')
url="https://github.com/k4yt3x/video2x" url="https://github.com/k4yt3x/video2x"
license=('AGPL3') license=('AGPL3')
depends=('ffmpeg' 'ncnn' 'vulkan-driver') depends=('ffmpeg' 'ncnn' 'vulkan-driver' 'opencv' 'spdlog')
makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp') makedepends=('git' 'cmake' 'make' 'clang' 'pkgconf' 'vulkan-headers' 'openmp')
pkgver() { pkgver() {

View File

@@ -8,11 +8,14 @@
<img src="https://img.shields.io/badge/dynamic/json?color=%23e85b46&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F4507807&style=flat-square"/> <img src="https://img.shields.io/badge/dynamic/json?color=%23e85b46&label=Patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F4507807&style=flat-square"/>
</p> </p>
> [!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 Preview
**[Direct download link for Windows](https://github.com/k4yt3x/video2x/releases/download/6.0.0-beta.2/video2x-qt6-windows-amd64.zip)** **[Direct download link for Windows (Installer)](https://github.com/k4yt3x/video2x/releases/download/6.0.0-beta.5/video2x-qt6-windows-amd64-installer.exe)**
![6.0.0-beta-screenshot](https://github.com/user-attachments/assets/bde4e4e2-2f97-412f-8e34-848f384be720) ![6.0.0-beta-screenshot](https://github.com/user-attachments/assets/e4318eaf-f726-4193-885f-030593a5078a)
Version 6.0.0 is a complete rewrite of this project in C/C++. It: Version 6.0.0 is a complete rewrite of this project in C/C++. It:

View File

@@ -9,10 +9,10 @@ extern "C" {
int init_decoder( int init_decoder(
AVHWDeviceType hw_type, AVHWDeviceType hw_type,
AVBufferRef *hw_ctx, AVBufferRef *hw_ctx,
const char *input_filename, const char *in_fname,
AVFormatContext **fmt_ctx, AVFormatContext **fmt_ctx,
AVCodecContext **dec_ctx, AVCodecContext **dec_ctx,
int *video_stream_index int *vstream_idx
); );
#endif // DECODER_H #endif // DECODER_H

View File

@@ -11,21 +11,21 @@ extern "C" {
int init_encoder( int init_encoder(
AVBufferRef *hw_ctx, AVBufferRef *hw_ctx,
const char *output_filename, const char *out_fname,
AVFormatContext *ifmt_ctx, AVFormatContext *ifmt_ctx,
AVFormatContext **ofmt_ctx, AVFormatContext **ofmt_ctx,
AVCodecContext **enc_ctx, AVCodecContext **enc_ctx,
AVCodecContext *dec_ctx, AVCodecContext *dec_ctx,
EncoderConfig *encoder_config, EncoderConfig *encoder_config,
int video_stream_index, int vstream_idx,
int **stream_mapping int **stream_map
); );
int encode_and_write_frame( int write_frame(
AVFrame *frame, AVFrame *frame,
AVCodecContext *enc_ctx, AVCodecContext *enc_ctx,
AVFormatContext *ofmt_ctx, AVFormatContext *ofmt_ctx,
int video_stream_index int vstream_idx
); );
int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx); int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx);

View File

@@ -12,10 +12,10 @@ extern "C" {
// Abstract base class for filters // Abstract base class for filters
class Filter { class Filter {
public: public:
virtual ~Filter() {} virtual ~Filter() = default;
virtual int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) = 0; virtual int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) = 0;
virtual int process_frame(AVFrame *input_frame, AVFrame **output_frame) = 0; virtual int process_frame(AVFrame *in_frame, AVFrame **out_frame) = 0;
virtual int flush(std::vector<AVFrame *> &processed_frames) = 0; virtual int flush(std::vector<AVFrame *> &_) { return 0; }
}; };
#endif // FILTER_H #endif // FILTER_H

View File

@@ -14,8 +14,8 @@ int init_libplacebo(
AVFilterContext **buffersrc_ctx, AVFilterContext **buffersrc_ctx,
AVFilterContext **buffersink_ctx, AVFilterContext **buffersink_ctx,
AVCodecContext *dec_ctx, AVCodecContext *dec_ctx,
int output_width, int out_width,
int output_height, int out_height,
const std::filesystem::path &shader_path const std::filesystem::path &shader_path
); );

View File

@@ -17,26 +17,27 @@ class LibplaceboFilter : public Filter {
AVFilterGraph *filter_graph; AVFilterGraph *filter_graph;
AVFilterContext *buffersrc_ctx; AVFilterContext *buffersrc_ctx;
AVFilterContext *buffersink_ctx; AVFilterContext *buffersink_ctx;
int output_width; int out_width;
int output_height; int out_height;
const std::filesystem::path shader_path; const std::filesystem::path shader_path;
AVRational output_time_base; AVRational in_time_base;
AVRational out_time_base;
public: public:
// Constructor // Constructor
LibplaceboFilter(int width, int height, const std::filesystem::path &shader_path); LibplaceboFilter(int width, int height, const std::filesystem::path &shader_path);
// Destructor // Destructor
virtual ~LibplaceboFilter(); virtual ~LibplaceboFilter() override;
// Initializes the filter with decoder and encoder contexts // Initializes the filter with decoder and encoder contexts
int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) override; int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) override;
// Processes an input frame and returns the processed frame // Processes an input frame and returns the processed frame
int process_frame(AVFrame *input_frame, AVFrame **output_frame) override; int process_frame(AVFrame *in_frame, AVFrame **out_frame) override;
// Flushes any remaining frames // Flushes any remaining frames
int flush(std::vector<AVFrame *> &processed_frames) override; int flush(std::vector<AVFrame *> &flushed_frames) override;
}; };
#endif // LIBPLACEBO_FILTER_H #endif // LIBPLACEBO_FILTER_H

View File

@@ -28,10 +28,21 @@ enum FilterType {
FILTER_REALESRGAN FILTER_REALESRGAN
}; };
// Enum to specify log level
enum Libvideo2xLogLevel {
LIBVIDEO2X_LOG_LEVEL_TRACE,
LIBVIDEO2X_LOG_LEVEL_DEBUG,
LIBVIDEO2X_LOG_LEVEL_INFO,
LIBVIDEO2X_LOG_LEVEL_WARNING,
LIBVIDEO2X_LOG_LEVEL_ERROR,
LIBVIDEO2X_LOG_LEVEL_CRITICAL,
LIBVIDEO2X_LOG_LEVEL_OFF
};
// Configuration for Libplacebo filter // Configuration for Libplacebo filter
struct LibplaceboConfig { struct LibplaceboConfig {
int output_width; int out_width;
int output_height; int out_height;
const char *shader_path; const char *shader_path;
}; };
@@ -54,8 +65,8 @@ struct FilterConfig {
// Encoder configuration // Encoder configuration
struct EncoderConfig { struct EncoderConfig {
int output_width; int out_width;
int output_height; int out_height;
bool copy_streams; bool copy_streams;
enum AVCodecID codec; enum AVCodecID codec;
enum AVPixelFormat pix_fmt; enum AVPixelFormat pix_fmt;
@@ -76,8 +87,9 @@ struct VideoProcessingContext {
// C-compatible process_video function // C-compatible process_video function
LIBVIDEO2X_API int process_video( LIBVIDEO2X_API int process_video(
const char *input_filename, const char *in_fname,
const char *output_filename, const char *out_fname,
enum Libvideo2xLogLevel log_level,
bool benchmark, bool benchmark,
enum AVHWDeviceType hw_device_type, enum AVHWDeviceType hw_device_type,
const struct FilterConfig *filter_config, const struct FilterConfig *filter_config,

View File

@@ -20,9 +20,9 @@ class RealesrganFilter : public Filter {
const char *model; const char *model;
const std::filesystem::path custom_model_param_path; const std::filesystem::path custom_model_param_path;
const std::filesystem::path custom_model_bin_path; const std::filesystem::path custom_model_bin_path;
AVRational input_time_base; AVRational in_time_base;
AVRational output_time_base; AVRational out_time_base;
AVPixelFormat output_pix_fmt; AVPixelFormat out_pix_fmt;
public: public:
// Constructor // Constructor
@@ -31,21 +31,18 @@ class RealesrganFilter : public Filter {
bool tta_mode = false, bool tta_mode = false,
int scaling_factor = 4, int scaling_factor = 4,
const char *model = "realesr-animevideov3", const char *model = "realesr-animevideov3",
const std::filesystem::path custom_model_bin_pathmodel_param_path = std::filesystem::path(), const std::filesystem::path custom_model_param_path = std::filesystem::path(),
const std::filesystem::path custom_model_bin_pathmodel_bin_path = std::filesystem::path() const std::filesystem::path custom_model_bin_path = std::filesystem::path()
); );
// Destructor // Destructor
virtual ~RealesrganFilter(); virtual ~RealesrganFilter() override;
// Initializes the filter with decoder and encoder contexts // Initializes the filter with decoder and encoder contexts
int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) override; int init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) override;
// Processes an input frame and returns the processed frame // Processes an input frame and returns the processed frame
int process_frame(AVFrame *input_frame, AVFrame **output_frame) override; int process_frame(AVFrame *in_frame, AVFrame **out_frame) override;
// Flushes any remaining frames (if necessary)
int flush(std::vector<AVFrame *> &processed_frames) override;
}; };
#endif #endif

View File

@@ -0,0 +1,6 @@
#ifndef VERSION_H
#define VERSION_H
#define LIBVIDEO2X_VERSION_STRING "@PROJECT_VERSION@"
#endif // VERSION_H

2585
models/libplacebo/anime4k-v4-a+a.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

2585
models/libplacebo/anime4k-v4-b+b.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

2309
models/libplacebo/anime4k-v4-b.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

1711
models/libplacebo/anime4k-v4-c+a.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

1435
models/libplacebo/anime4k-v4-c.glsl vendored Normal file

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import shutil
from pathlib import Path
import requests
ANIME4K_COMMIT = "master"
GITHUB_GLSL_ROOT = (
f"https://raw.githubusercontent.com/bloc97/Anime4K/{ANIME4K_COMMIT}/glsl"
)
SHADERS_DIR = Path(__file__).parent.parent / "data"
def download_and_combine_files():
modes = {
"ModeA": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
]
}
for mode in modes:
file_contents = ""
for file in modes[mode]:
response = requests.get(file, timeout=5)
response.raise_for_status()
file_contents += response.text + "\n"
with (SHADERS_DIR / Path(f"Anime4K_{mode}.glsl")).open("w") as output_file:
output_file.write(file_contents)
if __name__ == "__main__":
# clear shaders directory
if SHADERS_DIR.exists():
shutil.rmtree(SHADERS_DIR)
SHADERS_DIR.mkdir(exist_ok=True)
# download and combine shaders
download_and_combine_files()

View File

@@ -0,0 +1,98 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import shutil
from pathlib import Path
import requests
ANIME4K_COMMIT = "master"
GITHUB_GLSL_ROOT = (
f"https://raw.githubusercontent.com/bloc97/Anime4K/{ANIME4K_COMMIT}/glsl"
)
SHADERS_DIR = Path(__file__).parent.parent / "models" / "libplacebo"
def download_and_combine_files():
modes = {
"a": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"b": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_Soft_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"c": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale+Denoise/Anime4K_Upscale_Denoise_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"a+a": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_M.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"b+b": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_Soft_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Restore_CNN_Soft_M.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_Upscale_CNN_x2_M.glsl",
],
"c+a": [
f"{GITHUB_GLSL_ROOT}/Restore/Anime4K_Clamp_Highlights.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale+Denoise/Anime4K_Upscale_Denoise_CNN_x2_VL.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x2.glsl",
f"{GITHUB_GLSL_ROOT}/Upscale/Anime4K_AutoDownscalePre_x4.glsl",
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:
file_contents = ""
for file in modes[mode]:
response = requests.get(file, timeout=5)
response.raise_for_status()
file_contents += response.text + "\n"
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)
if __name__ == "__main__":
# clear shaders directory
if SHADERS_DIR.exists():
shutil.rmtree(SHADERS_DIR)
SHADERS_DIR.mkdir(exist_ok=True)
# download and combine shaders
download_and_combine_files()

View File

@@ -1,12 +1,15 @@
#include "conversions.h" #include "conversions.h"
#include <cstddef>
#include <cstdio> #include <cstdio>
#include <spdlog/spdlog.h>
// Convert AVFrame format // Convert AVFrame format
AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) { AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) {
AVFrame *dst_frame = av_frame_alloc(); AVFrame *dst_frame = av_frame_alloc();
if (dst_frame == nullptr) { if (dst_frame == nullptr) {
fprintf(stderr, "Failed to allocate destination AVFrame.\n"); spdlog::error("Failed to allocate destination AVFrame.");
return nullptr; return nullptr;
} }
@@ -16,7 +19,7 @@ AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) {
// Allocate memory for the converted frame // Allocate memory for the converted frame
if (av_frame_get_buffer(dst_frame, 32) < 0) { if (av_frame_get_buffer(dst_frame, 32) < 0) {
fprintf(stderr, "Failed to allocate memory for AVFrame.\n"); spdlog::error("Failed to allocate memory for AVFrame.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
} }
@@ -36,7 +39,7 @@ AVFrame *convert_avframe_pix_fmt(AVFrame *src_frame, AVPixelFormat pix_fmt) {
); );
if (sws_ctx == nullptr) { if (sws_ctx == nullptr) {
fprintf(stderr, "Failed to initialize swscale context.\n"); spdlog::error("Failed to initialize swscale context.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
} }
@@ -66,7 +69,7 @@ ncnn::Mat avframe_to_ncnn_mat(AVFrame *frame) {
if (frame->format != AV_PIX_FMT_BGR24) { if (frame->format != AV_PIX_FMT_BGR24) {
converted_frame = convert_avframe_pix_fmt(frame, AV_PIX_FMT_BGR24); converted_frame = convert_avframe_pix_fmt(frame, AV_PIX_FMT_BGR24);
if (!converted_frame) { if (!converted_frame) {
fprintf(stderr, "Failed to convert AVFrame to BGR24.\n"); spdlog::error("Failed to convert AVFrame to BGR24.");
return ncnn::Mat(); return ncnn::Mat();
} }
} else { } else {
@@ -77,14 +80,16 @@ ncnn::Mat avframe_to_ncnn_mat(AVFrame *frame) {
// Allocate a new ncnn::Mat and copy the data // Allocate a new ncnn::Mat and copy the data
int width = converted_frame->width; int width = converted_frame->width;
int height = converted_frame->height; int height = converted_frame->height;
ncnn::Mat ncnn_image = ncnn::Mat(width, height, (size_t)3, 3); // BGR has 3 channels ncnn::Mat ncnn_image = ncnn::Mat(width, height, static_cast<size_t>(3), 3);
// Manually copy the pixel data from AVFrame to the new ncnn::Mat // Manually copy the pixel data from AVFrame to the new ncnn::Mat
const uint8_t *src_data = converted_frame->data[0]; const uint8_t *src_data = converted_frame->data[0];
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
uint8_t *dst_row = ncnn_image.row<uint8_t>(y); uint8_t *dst_row = ncnn_image.row<uint8_t>(y);
const uint8_t *src_row = src_data + y * converted_frame->linesize[0]; const uint8_t *src_row = src_data + y * converted_frame->linesize[0];
memcpy(dst_row, src_row, width * 3); // Copy 3 channels (BGR) per pixel
// Copy 3 channels (BGR) per pixel
memcpy(dst_row, src_row, static_cast<size_t>(width) * 3);
} }
// If we allocated a converted frame, free it // If we allocated a converted frame, free it
@@ -102,7 +107,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
// Step 1: Allocate a destination AVFrame for the specified pixel format // Step 1: Allocate a destination AVFrame for the specified pixel format
AVFrame *dst_frame = av_frame_alloc(); AVFrame *dst_frame = av_frame_alloc();
if (!dst_frame) { if (!dst_frame) {
fprintf(stderr, "Failed to allocate destination AVFrame.\n"); spdlog::error("Failed to allocate destination AVFrame.");
return nullptr; return nullptr;
} }
@@ -112,7 +117,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
// Allocate memory for the frame buffer // Allocate memory for the frame buffer
if (av_frame_get_buffer(dst_frame, 32) < 0) { if (av_frame_get_buffer(dst_frame, 32) < 0) {
fprintf(stderr, "Failed to allocate memory for destination AVFrame.\n"); spdlog::error("Failed to allocate memory for destination AVFrame.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
} }
@@ -120,7 +125,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
// Step 2: Convert ncnn::Mat to BGR AVFrame // Step 2: Convert ncnn::Mat to BGR AVFrame
AVFrame *bgr_frame = av_frame_alloc(); AVFrame *bgr_frame = av_frame_alloc();
if (!bgr_frame) { if (!bgr_frame) {
fprintf(stderr, "Failed to allocate intermediate BGR AVFrame.\n"); spdlog::error("Failed to allocate intermediate BGR AVFrame.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
} }
@@ -131,7 +136,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
// Allocate memory for the intermediate BGR frame // Allocate memory for the intermediate BGR frame
if (av_frame_get_buffer(bgr_frame, 32) < 0) { if (av_frame_get_buffer(bgr_frame, 32) < 0) {
fprintf(stderr, "Failed to allocate memory for BGR AVFrame.\n"); spdlog::error("Failed to allocate memory for BGR AVFrame.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
av_frame_free(&bgr_frame); av_frame_free(&bgr_frame);
return nullptr; return nullptr;
@@ -141,7 +146,9 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
for (int y = 0; y < mat.h; y++) { for (int y = 0; y < mat.h; y++) {
uint8_t *dst_row = bgr_frame->data[0] + y * bgr_frame->linesize[0]; uint8_t *dst_row = bgr_frame->data[0] + y * bgr_frame->linesize[0];
const uint8_t *src_row = mat.row<const uint8_t>(y); const uint8_t *src_row = mat.row<const uint8_t>(y);
memcpy(dst_row, src_row, mat.w * 3); // Copy 3 channels (BGR) per pixel
// Copy 3 channels (BGR) per pixel
memcpy(dst_row, src_row, static_cast<size_t>(mat.w) * 3);
} }
// Step 3: Convert the BGR frame to the desired pixel format // Step 3: Convert the BGR frame to the desired pixel format
@@ -159,7 +166,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
); );
if (sws_ctx == nullptr) { if (sws_ctx == nullptr) {
fprintf(stderr, "Failed to initialize swscale context.\n"); spdlog::error("Failed to initialize swscale context.");
av_frame_free(&bgr_frame); av_frame_free(&bgr_frame);
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
@@ -181,7 +188,7 @@ AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt) {
av_frame_free(&bgr_frame); av_frame_free(&bgr_frame);
if (ret != dst_frame->height) { if (ret != dst_frame->height) {
fprintf(stderr, "Failed to convert BGR AVFrame to destination pixel format.\n"); spdlog::error("Failed to convert BGR AVFrame to destination pixel format.");
av_frame_free(&dst_frame); av_frame_free(&dst_frame);
return nullptr; return nullptr;
} }

View File

@@ -4,45 +4,47 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <spdlog/spdlog.h>
static enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE; static enum AVPixelFormat hw_pix_fmt = AV_PIX_FMT_NONE;
// Callback function to choose the hardware-accelerated pixel format // Callback function to choose the hardware-accelerated pixel format
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) { static enum AVPixelFormat get_hw_format(AVCodecContext *_, const enum AVPixelFormat *pix_fmts) {
for (const enum AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { for (const enum AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) {
if (*p == hw_pix_fmt) { if (*p == hw_pix_fmt) {
return *p; return *p;
} }
} }
fprintf(stderr, "Failed to get HW surface format.\n"); spdlog::error("Failed to get HW surface format.");
return AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE;
} }
int init_decoder( int init_decoder(
AVHWDeviceType hw_type, AVHWDeviceType hw_type,
AVBufferRef *hw_ctx, AVBufferRef *hw_ctx,
const char *input_filename, const char *in_fname,
AVFormatContext **fmt_ctx, AVFormatContext **fmt_ctx,
AVCodecContext **dec_ctx, AVCodecContext **dec_ctx,
int *video_stream_index int *vstream_idx
) { ) {
AVFormatContext *ifmt_ctx = NULL; AVFormatContext *ifmt_ctx = NULL;
AVCodecContext *codec_ctx = NULL; AVCodecContext *codec_ctx = NULL;
int ret; int ret;
if ((ret = avformat_open_input(&ifmt_ctx, input_filename, NULL, NULL)) < 0) { if ((ret = avformat_open_input(&ifmt_ctx, in_fname, NULL, NULL)) < 0) {
fprintf(stderr, "Could not open input file '%s'\n", input_filename); spdlog::error("Could not open input file '{}'", in_fname);
return ret; return ret;
} }
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information\n"); spdlog::error("Failed to retrieve input stream information");
return ret; return ret;
} }
// Find the first video stream // Find the first video stream
ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Could not find video stream in the input, aborting\n"); spdlog::error("Could not find video stream in the input file");
return ret; return ret;
} }
@@ -52,13 +54,16 @@ int init_decoder(
// Set up the decoder // Set up the decoder
const AVCodec *decoder = avcodec_find_decoder(video_stream->codecpar->codec_id); const AVCodec *decoder = avcodec_find_decoder(video_stream->codecpar->codec_id);
if (!decoder) { if (!decoder) {
fprintf(stderr, "Failed to find decoder for stream #%u\n", stream_index); spdlog::error(
"Failed to find decoder for codec ID {}",
static_cast<int>(video_stream->codecpar->codec_id)
);
return AVERROR_DECODER_NOT_FOUND; return AVERROR_DECODER_NOT_FOUND;
} }
codec_ctx = avcodec_alloc_context3(decoder); codec_ctx = avcodec_alloc_context3(decoder);
if (!codec_ctx) { if (!codec_ctx) {
fprintf(stderr, "Failed to allocate the decoder context\n"); spdlog::error("Failed to allocate the decoder context");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
@@ -71,9 +76,8 @@ int init_decoder(
for (int i = 0;; i++) { for (int i = 0;; i++) {
const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i); const AVCodecHWConfig *config = avcodec_get_hw_config(decoder, i);
if (config == nullptr) { if (config == nullptr) {
fprintf( spdlog::error(
stderr, "Decoder {} does not support device type {}.",
"Decoder %s does not support device type %s.\n",
decoder->name, decoder->name,
av_hwdevice_get_type_name(hw_type) av_hwdevice_get_type_name(hw_type)
); );
@@ -90,7 +94,7 @@ int init_decoder(
} }
if ((ret = avcodec_parameters_to_context(codec_ctx, video_stream->codecpar)) < 0) { if ((ret = avcodec_parameters_to_context(codec_ctx, video_stream->codecpar)) < 0) {
fprintf(stderr, "Failed to copy decoder parameters to input decoder context\n"); spdlog::error("Failed to copy decoder parameters to input decoder context");
return ret; return ret;
} }
@@ -100,13 +104,13 @@ int init_decoder(
codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, video_stream, NULL); codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, video_stream, NULL);
if ((ret = avcodec_open2(codec_ctx, decoder, NULL)) < 0) { if ((ret = avcodec_open2(codec_ctx, decoder, NULL)) < 0) {
fprintf(stderr, "Failed to open decoder for stream #%u\n", stream_index); spdlog::error("Failed to open decoder for stream #{}", stream_index);
return ret; return ret;
} }
*fmt_ctx = ifmt_ctx; *fmt_ctx = ifmt_ctx;
*dec_ctx = codec_ctx; *dec_ctx = codec_ctx;
*video_stream_index = stream_index; *vstream_idx = stream_index;
return 0; return 0;
} }

View File

@@ -4,12 +4,14 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <spdlog/spdlog.h>
#include "conversions.h" #include "conversions.h"
static enum AVPixelFormat get_encoder_default_pix_fmt(const AVCodec *encoder) { static enum AVPixelFormat get_encoder_default_pix_fmt(const AVCodec *encoder) {
const enum AVPixelFormat *p = encoder->pix_fmts; const enum AVPixelFormat *p = encoder->pix_fmts;
if (!p) { if (!p) {
fprintf(stderr, "No pixel formats supported by encoder\n"); spdlog::error("No pixel formats supported by encoder");
return AV_PIX_FMT_NONE; return AV_PIX_FMT_NONE;
} }
return *p; return *p;
@@ -17,31 +19,30 @@ static enum AVPixelFormat get_encoder_default_pix_fmt(const AVCodec *encoder) {
int init_encoder( int init_encoder(
AVBufferRef *hw_ctx, AVBufferRef *hw_ctx,
const char *output_filename, const char *out_fname,
AVFormatContext *ifmt_ctx, AVFormatContext *ifmt_ctx,
AVFormatContext **ofmt_ctx, AVFormatContext **ofmt_ctx,
AVCodecContext **enc_ctx, AVCodecContext **enc_ctx,
AVCodecContext *dec_ctx, AVCodecContext *dec_ctx,
EncoderConfig *encoder_config, EncoderConfig *encoder_config,
int video_stream_index, int vstream_idx,
int **stream_mapping int **stream_map
) { ) {
AVFormatContext *fmt_ctx = NULL; AVFormatContext *fmt_ctx = NULL;
AVCodecContext *codec_ctx = NULL; AVCodecContext *codec_ctx = NULL;
int stream_index = 0; int stream_index = 0;
int ret; int ret;
avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, output_filename); avformat_alloc_output_context2(&fmt_ctx, NULL, NULL, out_fname);
if (!fmt_ctx) { if (!fmt_ctx) {
fprintf(stderr, "Could not create output context\n"); spdlog::error("Could not create output context");
return AVERROR_UNKNOWN; return AVERROR_UNKNOWN;
} }
const AVCodec *encoder = avcodec_find_encoder(encoder_config->codec); const AVCodec *encoder = avcodec_find_encoder(encoder_config->codec);
if (!encoder) { if (!encoder) {
fprintf( spdlog::error(
stderr, "Required video encoder not found for vcodec {}",
"Required video encoder not found for vcodec %s\n",
avcodec_get_name(encoder_config->codec) avcodec_get_name(encoder_config->codec)
); );
return AVERROR_ENCODER_NOT_FOUND; return AVERROR_ENCODER_NOT_FOUND;
@@ -50,13 +51,13 @@ int init_encoder(
// Create a new video stream in the output file // Create a new video stream in the output file
AVStream *out_stream = avformat_new_stream(fmt_ctx, NULL); AVStream *out_stream = avformat_new_stream(fmt_ctx, NULL);
if (!out_stream) { if (!out_stream) {
fprintf(stderr, "Failed to allocate the output video stream\n"); spdlog::error("Failed to allocate the output video stream");
return AVERROR_UNKNOWN; return AVERROR_UNKNOWN;
} }
codec_ctx = avcodec_alloc_context3(encoder); codec_ctx = avcodec_alloc_context3(encoder);
if (!codec_ctx) { if (!codec_ctx) {
fprintf(stderr, "Failed to allocate the encoder context\n"); spdlog::error("Failed to allocate the encoder context");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
@@ -66,8 +67,8 @@ int init_encoder(
} }
// Set encoding parameters // Set encoding parameters
codec_ctx->height = encoder_config->output_height; codec_ctx->height = encoder_config->out_height;
codec_ctx->width = encoder_config->output_width; codec_ctx->width = encoder_config->out_width;
codec_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; codec_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
codec_ctx->bit_rate = encoder_config->bit_rate; codec_ctx->bit_rate = encoder_config->bit_rate;
@@ -79,20 +80,28 @@ int init_encoder(
// Fall back to the default pixel format // Fall back to the default pixel format
codec_ctx->pix_fmt = get_encoder_default_pix_fmt(encoder); codec_ctx->pix_fmt = get_encoder_default_pix_fmt(encoder);
if (codec_ctx->pix_fmt == AV_PIX_FMT_NONE) { if (codec_ctx->pix_fmt == AV_PIX_FMT_NONE) {
fprintf(stderr, "Could not get the default pixel format for the encoder\n"); spdlog::error("Could not get the default pixel format for the encoder");
return AVERROR(EINVAL); return AVERROR(EINVAL);
} }
} }
// Set the time base // Set the output video's time base
codec_ctx->time_base = av_inv_q(dec_ctx->framerate); if (dec_ctx->time_base.num > 0 && dec_ctx->time_base.den > 0) {
if (codec_ctx->time_base.num == 0 || codec_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)); 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 // Set the CRF and preset for any codecs that support it
char crf_str[16]; char crf_str[16];
snprintf(crf_str, sizeof(crf_str), "%.f", encoder_config->crf); snprintf(crf_str, sizeof(crf_str), "%.f", static_cast<double>(encoder_config->crf));
av_opt_set(codec_ctx->priv_data, "crf", crf_str, 0); av_opt_set(codec_ctx->priv_data, "crf", crf_str, 0);
av_opt_set(codec_ctx->priv_data, "preset", encoder_config->preset, 0); av_opt_set(codec_ctx->priv_data, "preset", encoder_config->preset, 0);
@@ -101,71 +110,74 @@ int init_encoder(
} }
if ((ret = avcodec_open2(codec_ctx, encoder, NULL)) < 0) { if ((ret = avcodec_open2(codec_ctx, encoder, NULL)) < 0) {
fprintf(stderr, "Cannot open video encoder\n"); spdlog::error("Cannot open video encoder");
return ret; return ret;
} }
ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx); ret = avcodec_parameters_from_context(out_stream->codecpar, codec_ctx);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to copy encoder parameters to output video stream\n"); spdlog::error("Failed to copy encoder parameters to output video stream");
return ret; return ret;
} }
out_stream->time_base = codec_ctx->time_base; 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) { if (encoder_config->copy_streams) {
// Allocate the stream map // Allocate the stream map
*stream_mapping = (int *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(**stream_mapping)); *stream_map =
if (!*stream_mapping) { reinterpret_cast<int *>(av_malloc_array(ifmt_ctx->nb_streams, sizeof(**stream_map)));
fprintf(stderr, "Could not allocate stream mapping\n"); if (!*stream_map) {
spdlog::error("Could not allocate stream mapping");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
// Map the video stream // Map the video stream
(*stream_mapping)[video_stream_index] = stream_index++; (*stream_map)[vstream_idx] = stream_index++;
// Loop through each stream in the input file // Loop through each stream in the input file
for (int i = 0; i < ifmt_ctx->nb_streams; i++) { for (int i = 0; i < static_cast<int>(ifmt_ctx->nb_streams); i++) {
AVStream *in_stream = ifmt_ctx->streams[i]; AVStream *in_stream = ifmt_ctx->streams[i];
AVCodecParameters *in_codecpar = in_stream->codecpar; AVCodecParameters *in_codecpar = in_stream->codecpar;
if (i == video_stream_index) { if (i == vstream_idx) {
// Video stream is already handled // Video stream is already handled
continue; continue;
} }
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
(*stream_mapping)[i] = -1; (*stream_map)[i] = -1;
continue; continue;
} }
// Create corresponding output stream // Create corresponding output stream
AVStream *out_stream = avformat_new_stream(fmt_ctx, NULL); AVStream *out_copied_stream = avformat_new_stream(fmt_ctx, NULL);
if (!out_stream) { if (!out_copied_stream) {
fprintf(stderr, "Failed allocating output stream\n"); spdlog::error("Failed allocating output stream");
return AVERROR_UNKNOWN; return AVERROR_UNKNOWN;
} }
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar); ret = avcodec_parameters_copy(out_copied_stream->codecpar, in_codecpar);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n"); spdlog::error("Failed to copy codec parameters");
return ret; return ret;
} }
out_stream->codecpar->codec_tag = 0; out_copied_stream->codecpar->codec_tag = 0;
// Copy time base // Copy time base
out_stream->time_base = in_stream->time_base; out_copied_stream->time_base = in_stream->time_base;
(*stream_mapping)[i] = stream_index++; (*stream_map)[i] = stream_index++;
} }
} }
// Open the output file // Open the output file
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) { if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&fmt_ctx->pb, output_filename, AVIO_FLAG_WRITE); ret = avio_open(&fmt_ctx->pb, out_fname, AVIO_FLAG_WRITE);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'\n", output_filename); spdlog::error("Could not open output file '{}'", out_fname);
return ret; return ret;
} }
} }
@@ -176,35 +188,40 @@ int init_encoder(
return 0; return 0;
} }
int encode_and_write_frame( int write_frame(
AVFrame *frame, AVFrame *frame,
AVCodecContext *enc_ctx, AVCodecContext *enc_ctx,
AVFormatContext *ofmt_ctx, AVFormatContext *ofmt_ctx,
int video_stream_index int vstream_idx
) { ) {
AVFrame *converted_frame = nullptr;
int ret; int ret;
// Convert the frame to the encoder's pixel format if needed // Convert the frame to the encoder's pixel format if needed
if (frame->format != enc_ctx->pix_fmt) { if (frame->format != enc_ctx->pix_fmt) {
AVFrame *converted_frame = convert_avframe_pix_fmt(frame, enc_ctx->pix_fmt); converted_frame = convert_avframe_pix_fmt(frame, enc_ctx->pix_fmt);
if (!converted_frame) { if (!converted_frame) {
fprintf(stderr, "Error converting frame to encoder's pixel format\n"); spdlog::error("Error converting frame to encoder's pixel format");
return AVERROR_EXTERNAL; return AVERROR_EXTERNAL;
} }
converted_frame->pts = frame->pts; converted_frame->pts = frame->pts;
frame = converted_frame;
} }
AVPacket *enc_pkt = av_packet_alloc(); AVPacket *enc_pkt = av_packet_alloc();
if (!enc_pkt) { if (!enc_pkt) {
fprintf(stderr, "Could not allocate AVPacket\n"); spdlog::error("Could not allocate AVPacket");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
ret = avcodec_send_frame(enc_ctx, frame); if (converted_frame != nullptr) {
ret = avcodec_send_frame(enc_ctx, converted_frame);
av_frame_free(&converted_frame);
} else {
ret = avcodec_send_frame(enc_ctx, frame);
}
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error sending frame to encoder\n"); spdlog::error("Error sending frame to encoder");
av_packet_free(&enc_pkt); av_packet_free(&enc_pkt);
return ret; return ret;
} }
@@ -215,22 +232,22 @@ int encode_and_write_frame(
av_packet_unref(enc_pkt); av_packet_unref(enc_pkt);
break; break;
} else if (ret < 0) { } else if (ret < 0) {
fprintf(stderr, "Error encoding frame\n"); spdlog::error("Error encoding frame");
av_packet_free(&enc_pkt); av_packet_free(&enc_pkt);
return ret; return ret;
} }
// Rescale packet timestamps // Rescale packet timestamps
av_packet_rescale_ts( av_packet_rescale_ts(
enc_pkt, enc_ctx->time_base, ofmt_ctx->streams[video_stream_index]->time_base enc_pkt, enc_ctx->time_base, ofmt_ctx->streams[vstream_idx]->time_base
); );
enc_pkt->stream_index = video_stream_index; enc_pkt->stream_index = vstream_idx;
// Write the packet // Write the packet
ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt);
av_packet_unref(enc_pkt); av_packet_unref(enc_pkt);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error muxing packet\n"); spdlog::error("Error muxing packet");
av_packet_free(&enc_pkt); av_packet_free(&enc_pkt);
return ret; return ret;
} }
@@ -244,7 +261,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) {
int ret; int ret;
AVPacket *enc_pkt = av_packet_alloc(); AVPacket *enc_pkt = av_packet_alloc();
if (!enc_pkt) { if (!enc_pkt) {
fprintf(stderr, "Could not allocate AVPacket\n"); spdlog::error("Could not allocate AVPacket");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
@@ -255,7 +272,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) {
av_packet_unref(enc_pkt); av_packet_unref(enc_pkt);
break; break;
} else if (ret < 0) { } else if (ret < 0) {
fprintf(stderr, "Error encoding frame\n"); spdlog::error("Error encoding frame");
av_packet_free(&enc_pkt); av_packet_free(&enc_pkt);
return ret; return ret;
} }
@@ -268,7 +285,7 @@ int flush_encoder(AVCodecContext *enc_ctx, AVFormatContext *ofmt_ctx) {
ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt);
av_packet_unref(enc_pkt); av_packet_unref(enc_pkt);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error muxing packet\n"); spdlog::error("Error muxing packet");
av_packet_free(&enc_pkt); av_packet_free(&enc_pkt);
return ret; return ret;
} }

View File

@@ -8,14 +8,16 @@
#include <cstring> #include <cstring>
#endif #endif
#include <spdlog/spdlog.h>
#if _WIN32 #if _WIN32
std::filesystem::path get_executable_directory() { static std::filesystem::path get_executable_directory() {
std::vector<wchar_t> filepath(MAX_PATH); std::vector<wchar_t> filepath(MAX_PATH);
// Get the executable path, expanding the buffer if necessary // Get the executable path, expanding the buffer if necessary
DWORD size = GetModuleFileNameW(NULL, filepath.data(), static_cast<DWORD>(filepath.size())); DWORD size = GetModuleFileNameW(NULL, filepath.data(), static_cast<DWORD>(filepath.size()));
if (size == 0) { if (size == 0) {
fprintf(stderr, "Error getting executable path: %lu\n", GetLastError()); spdlog::error("Error getting executable path: {}", GetLastError());
return std::filesystem::path(); return std::filesystem::path();
} }
@@ -24,7 +26,7 @@ std::filesystem::path get_executable_directory() {
filepath.resize(filepath.size() * 2); filepath.resize(filepath.size() * 2);
size = GetModuleFileNameW(NULL, filepath.data(), static_cast<DWORD>(filepath.size())); size = GetModuleFileNameW(NULL, filepath.data(), static_cast<DWORD>(filepath.size()));
if (size == 0) { if (size == 0) {
fprintf(stderr, "Error getting executable path: %lu\n", GetLastError()); spdlog::error("Error getting executable path: {}", GetLastError());
return std::filesystem::path(); return std::filesystem::path();
} }
} }
@@ -34,12 +36,12 @@ std::filesystem::path get_executable_directory() {
return execpath.parent_path(); return execpath.parent_path();
} }
#else // _WIN32 #else // _WIN32
std::filesystem::path get_executable_directory() { static std::filesystem::path get_executable_directory() {
std::error_code ec; std::error_code ec;
std::filesystem::path filepath = std::filesystem::read_symlink("/proc/self/exe", ec); std::filesystem::path filepath = std::filesystem::read_symlink("/proc/self/exe", ec);
if (ec) { if (ec) {
fprintf(stderr, "Error reading /proc/self/exe: %s\n", ec.message().c_str()); spdlog::error("Error reading /proc/self/exe: {}", ec.message());
return std::filesystem::path(); return std::filesystem::path();
} }

View File

@@ -185,7 +185,7 @@ int getopt_long(
return (-1); return (-1);
} }
if ((has_equal = strchr(current_argv, '=')) != NULL) { if ((has_equal = strchr(current_argv, '=')) != NULL) {
current_argv_len = has_equal - current_argv; current_argv_len = (size_t)(has_equal - current_argv);
has_equal++; has_equal++;
} else { } else {
current_argv_len = strlen(current_argv); current_argv_len = strlen(current_argv);

View File

@@ -3,6 +3,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <spdlog/spdlog.h>
#include "fsutils.h" #include "fsutils.h"
int init_libplacebo( int init_libplacebo(
@@ -11,8 +13,8 @@ int init_libplacebo(
AVFilterContext **buffersrc_ctx, AVFilterContext **buffersrc_ctx,
AVFilterContext **buffersink_ctx, AVFilterContext **buffersink_ctx,
AVCodecContext *dec_ctx, AVCodecContext *dec_ctx,
int output_width, int out_width,
int output_height, int out_height,
const std::filesystem::path &shader_path const std::filesystem::path &shader_path
) { ) {
char args[512]; char args[512];
@@ -20,7 +22,7 @@ int init_libplacebo(
AVFilterGraph *graph = avfilter_graph_alloc(); AVFilterGraph *graph = avfilter_graph_alloc();
if (!graph) { if (!graph) {
fprintf(stderr, "Unable to create filter graph.\n"); spdlog::error("Unable to create filter graph.");
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
} }
@@ -30,7 +32,7 @@ int init_libplacebo(
args, args,
sizeof(args), sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:" "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:frame_rate=%d/%d:"
"pixel_aspect=%d/%d:colorspace=%d", "pixel_aspect=%d/%d:colorspace=%d:range=%d",
dec_ctx->width, dec_ctx->width,
dec_ctx->height, dec_ctx->height,
dec_ctx->pix_fmt, dec_ctx->pix_fmt,
@@ -40,12 +42,13 @@ int init_libplacebo(
dec_ctx->framerate.den, dec_ctx->framerate.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den, dec_ctx->sample_aspect_ratio.den,
dec_ctx->colorspace dec_ctx->colorspace,
dec_ctx->color_range
); );
ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args, NULL, graph); ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in", args, NULL, graph);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Cannot create buffer source\n"); spdlog::error("Cannot create buffer source.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }
@@ -55,7 +58,7 @@ int init_libplacebo(
// Create the libplacebo filter // Create the libplacebo filter
const AVFilter *libplacebo_filter = avfilter_get_by_name("libplacebo"); const AVFilter *libplacebo_filter = avfilter_get_by_name("libplacebo");
if (!libplacebo_filter) { if (!libplacebo_filter) {
fprintf(stderr, "Filter 'libplacebo' not found\n"); spdlog::error("Filter 'libplacebo' not found.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return AVERROR_FILTER_NOT_FOUND; return AVERROR_FILTER_NOT_FOUND;
} }
@@ -69,22 +72,29 @@ int init_libplacebo(
#endif #endif
// Prepare the filter arguments // Prepare the filter arguments
char filter_args[512]; char filter_args[4096];
snprintf( int filter_args_size = snprintf(
filter_args, filter_args,
sizeof(filter_args), sizeof(filter_args),
"w=%d:h=%d:upscaler=ewa_lanczos:custom_shader_path=%s", "w=%d:h=%d:custom_shader_path='%s'",
output_width, out_width,
output_height, out_height,
shader_path_string.c_str() shader_path_string.c_str()
); );
// Check if the filter arguments are too long
if (filter_args_size < 0 || filter_args_size >= static_cast<int>(sizeof(filter_args))) {
spdlog::error("libplacebo filter arguments too long.");
avfilter_graph_free(&graph);
return AVERROR(EINVAL);
}
AVFilterContext *libplacebo_ctx; AVFilterContext *libplacebo_ctx;
ret = avfilter_graph_create_filter( ret = avfilter_graph_create_filter(
&libplacebo_ctx, libplacebo_filter, "libplacebo", filter_args, NULL, graph &libplacebo_ctx, libplacebo_filter, "libplacebo", filter_args, NULL, graph
); );
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Cannot create libplacebo filter\n"); spdlog::error("Cannot create libplacebo filter.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }
@@ -97,7 +107,7 @@ int init_libplacebo(
// Link buffersrc to libplacebo // Link buffersrc to libplacebo
ret = avfilter_link(last_filter, 0, libplacebo_ctx, 0); ret = avfilter_link(last_filter, 0, libplacebo_ctx, 0);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error connecting buffersrc to libplacebo filter\n"); spdlog::error("Error connecting buffersrc to libplacebo filter.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }
@@ -108,7 +118,7 @@ int init_libplacebo(
const AVFilter *buffersink = avfilter_get_by_name("buffersink"); const AVFilter *buffersink = avfilter_get_by_name("buffersink");
ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out", NULL, NULL, graph); ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out", NULL, NULL, graph);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Cannot create buffer sink\n"); spdlog::error("Cannot create buffer sink.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }
@@ -116,7 +126,7 @@ int init_libplacebo(
// Link libplacebo to buffersink // Link libplacebo to buffersink
ret = avfilter_link(last_filter, 0, *buffersink_ctx, 0); ret = avfilter_link(last_filter, 0, *buffersink_ctx, 0);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error connecting libplacebo filter to buffersink\n"); spdlog::error("Error connecting libplacebo filter to buffersink.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }
@@ -124,7 +134,7 @@ int init_libplacebo(
// Configure the filter graph // Configure the filter graph
ret = avfilter_graph_config(graph, NULL); ret = avfilter_graph_config(graph, NULL);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error configuring the filter graph\n"); spdlog::error("Error configuring the filter graph.");
avfilter_graph_free(&graph); avfilter_graph_free(&graph);
return ret; return ret;
} }

View File

@@ -2,15 +2,21 @@
#include <cstdio> #include <cstdio>
#include <spdlog/spdlog.h>
#include "fsutils.h" #include "fsutils.h"
#include "libplacebo.h" #include "libplacebo.h"
LibplaceboFilter::LibplaceboFilter(int width, int height, const std::filesystem::path &shader_path) LibplaceboFilter::LibplaceboFilter(
int out_width,
int out_height,
const std::filesystem::path &shader_path
)
: filter_graph(nullptr), : filter_graph(nullptr),
buffersrc_ctx(nullptr), buffersrc_ctx(nullptr),
buffersink_ctx(nullptr), buffersink_ctx(nullptr),
output_width(width), out_width(out_width),
output_height(height), out_height(out_height),
shader_path(std::move(shader_path)) {} shader_path(std::move(shader_path)) {}
LibplaceboFilter::~LibplaceboFilter() { LibplaceboFilter::~LibplaceboFilter() {
@@ -36,66 +42,77 @@ int LibplaceboFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
shader_full_path = shader_path; shader_full_path = shader_path;
} else { } else {
// Construct the fallback path using std::filesystem // Construct the fallback path using std::filesystem
shader_full_path = shader_full_path = find_resource_file(
find_resource_file(std::filesystem::path("models") / (shader_path.string() + ".glsl")); std::filesystem::path("models") / "libplacebo" / (shader_path.string() + ".glsl")
);
} }
// Check if the shader file exists // Check if the shader file exists
if (!std::filesystem::exists(shader_full_path)) { if (!std::filesystem::exists(shader_full_path)) {
fprintf(stderr, "libplacebo shader file not found: %s\n", shader_full_path.c_str()); spdlog::error("libplacebo shader file not found: {}", shader_full_path.string());
return -1; return -1;
} }
// Save the output time base // Save the output time base
output_time_base = enc_ctx->time_base; in_time_base = dec_ctx->time_base;
out_time_base = enc_ctx->time_base;
return init_libplacebo( // Initialize the libplacebo filter
int ret = init_libplacebo(
hw_ctx, hw_ctx,
&filter_graph, &filter_graph,
&buffersrc_ctx, &buffersrc_ctx,
&buffersink_ctx, &buffersink_ctx,
dec_ctx, dec_ctx,
output_width, out_width,
output_height, out_height,
shader_full_path shader_full_path
); );
// Set these resources to nullptr since they are already freed by `avfilter_graph_free`
if (ret < 0) {
buffersrc_ctx = nullptr;
buffersink_ctx = nullptr;
filter_graph = nullptr;
}
return ret;
} }
int LibplaceboFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame) { int LibplaceboFilter::process_frame(AVFrame *in_frame, AVFrame **out_frame) {
int ret; int ret;
// Get the filtered frame // Get the filtered frame
*output_frame = av_frame_alloc(); *out_frame = av_frame_alloc();
if (*output_frame == nullptr) { if (*out_frame == nullptr) {
fprintf(stderr, "Failed to allocate output frame\n"); spdlog::error("Failed to allocate output frame");
return -1; return -1;
} }
// Feed the frame to the filter graph // Feed the frame to the filter graph
ret = av_buffersrc_add_frame(buffersrc_ctx, input_frame); ret = av_buffersrc_add_frame(buffersrc_ctx, in_frame);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error while feeding the filter graph\n"); spdlog::error("Error while feeding the filter graph");
av_frame_free(out_frame);
return ret; return ret;
} }
ret = av_buffersink_get_frame(buffersink_ctx, *output_frame); ret = av_buffersink_get_frame(buffersink_ctx, *out_frame);
if (ret < 0) { if (ret < 0) {
av_frame_free(output_frame); av_frame_free(out_frame);
return ret; return ret;
} }
// Rescale PTS to encoder's time base // Rescale PTS to encoder's time base
(*output_frame)->pts = (*out_frame)->pts = av_rescale_q((*out_frame)->pts, in_time_base, out_time_base);
av_rescale_q((*output_frame)->pts, buffersink_ctx->inputs[0]->time_base, output_time_base);
// Return the processed frame to the caller // Return the processed frame to the caller
return 0; return 0;
} }
int LibplaceboFilter::flush(std::vector<AVFrame *> &processed_frames) { int LibplaceboFilter::flush(std::vector<AVFrame *> &flushed_frames) {
int ret = av_buffersrc_add_frame(buffersrc_ctx, nullptr); int ret = av_buffersrc_add_frame(buffersrc_ctx, nullptr);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error while flushing filter graph\n"); spdlog::error("Error while flushing filter graph");
return ret; return ret;
} }
@@ -117,11 +134,10 @@ int LibplaceboFilter::flush(std::vector<AVFrame *> &processed_frames) {
} }
// Rescale PTS to encoder's time base // Rescale PTS to encoder's time base
filt_frame->pts = filt_frame->pts = av_rescale_q(filt_frame->pts, in_time_base, out_time_base);
av_rescale_q(filt_frame->pts, buffersink_ctx->inputs[0]->time_base, output_time_base);
// Add to processed frames // Add to processed frames
processed_frames.push_back(filt_frame); flushed_frames.push_back(filt_frame);
} }
return 0; return 0;

View File

@@ -1,12 +1,13 @@
#include "libvideo2x.h" #include "libvideo2x.h"
#include <libavutil/mathematics.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <cstdint>
#include <thread> #include <thread>
#include <spdlog/spdlog.h>
#include <opencv2/videoio.hpp>
#include "decoder.h" #include "decoder.h"
#include "encoder.h" #include "encoder.h"
#include "filter.h" #include "filter.h"
@@ -16,16 +17,19 @@
/** /**
* @brief Process frames using the selected filter. * @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,out] proc_ctx Struct containing the processing context
* @param[in] fmt_ctx Input format context * @param[in] ifmt_ctx Input format context
* @param[in] ofmt_ctx Output format context * @param[in] ofmt_ctx Output format context
* @param[in] dec_ctx Decoder context * @param[in] dec_ctx Decoder context
* @param[in] enc_ctx Encoder context * @param[in] enc_ctx Encoder context
* @param[in] filter Filter instance * @param[in] filter Filter instance
* @param[in] video_stream_index Index of the video stream in the input format context * @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 * @return int 0 on success, negative value on error
*/ */
int process_frames( static int process_frames(
EncoderConfig *encoder_config, EncoderConfig *encoder_config,
VideoProcessingContext *proc_ctx, VideoProcessingContext *proc_ctx,
AVFormatContext *ifmt_ctx, AVFormatContext *ifmt_ctx,
@@ -33,26 +37,29 @@ int process_frames(
AVCodecContext *dec_ctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, AVCodecContext *enc_ctx,
Filter *filter, Filter *filter,
int video_stream_index, int vstream_idx,
int *stream_mapping, int *stream_map,
bool benchmark = false bool benchmark = false
) { ) {
int ret; int ret;
AVPacket packet;
std::vector<AVFrame *> flushed_frames;
char errbuf[AV_ERROR_MAX_STRING_SIZE]; char errbuf[AV_ERROR_MAX_STRING_SIZE];
std::vector<AVFrame *> flushed_frames;
// Get the total number of frames in the video // Get the total number of frames in the video with OpenCV
AVStream *video_stream = ifmt_ctx->streams[video_stream_index]; spdlog::debug("Reading total number of frames with OpenCV");
proc_ctx->total_frames = video_stream->nb_frames; cv::VideoCapture cap(ifmt_ctx->url);
if (!cap.isOpened()) {
spdlog::error("Failed to open video file with OpenCV");
return -1;
}
proc_ctx->total_frames = static_cast<int64_t>(cap.get(cv::CAP_PROP_FRAME_COUNT));
cap.release();
// If nb_frames is not set, calculate total frames using duration and frame rate // Check if the total number of frames is still 0
if (proc_ctx->total_frames == 0) { if (proc_ctx->total_frames == 0) {
int64_t duration = video_stream->duration; spdlog::warn("Unable to determine total number of frames");
AVRational frame_rate = video_stream->avg_frame_rate; } else {
if (duration != AV_NOPTS_VALUE && frame_rate.num != 0 && frame_rate.den != 0) { spdlog::debug("{} frames to process", proc_ctx->total_frames);
proc_ctx->total_frames = duration * frame_rate.num / frame_rate.den;
}
} }
// Get start time // Get start time
@@ -64,29 +71,53 @@ int process_frames(
AVFrame *frame = av_frame_alloc(); AVFrame *frame = av_frame_alloc();
if (frame == nullptr) { if (frame == nullptr) {
ret = AVERROR(ENOMEM); ret = AVERROR(ENOMEM);
goto end; return ret;
} }
AVPacket *packet = av_packet_alloc();
if (packet == nullptr) {
spdlog::error("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 // Read frames from the input file
while (!proc_ctx->abort) { while (!proc_ctx->abort) {
ret = av_read_frame(ifmt_ctx, &packet); ret = av_read_frame(ifmt_ctx, packet);
if (ret < 0) { if (ret < 0) {
break; // End of file or error if (ret == AVERROR_EOF) {
spdlog::debug("Reached end of file");
break;
}
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error reading packet: {}", errbuf);
cleanup();
return ret;
} }
if (packet.stream_index == video_stream_index) { if (packet->stream_index == vstream_idx) {
// Send the packet to the decoder ret = avcodec_send_packet(dec_ctx, packet);
ret = avcodec_send_packet(dec_ctx, &packet);
if (ret < 0) { if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error sending packet to decoder: %s\n", errbuf); spdlog::error("Error sending packet to decoder: {}", errbuf);
av_packet_unref(&packet); av_packet_unref(packet);
goto end; cleanup();
return ret;
} }
// Receive and process frames from the decoder
while (!proc_ctx->abort) { while (!proc_ctx->abort) {
// Check if the processing is paused
if (proc_ctx->pause) { if (proc_ctx->pause) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue; continue;
@@ -94,142 +125,110 @@ int process_frames(
ret = avcodec_receive_frame(dec_ctx, frame); ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
spdlog::debug("Frame not ready");
break; break;
} else if (ret < 0) { } else if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error decoding video frame: %s\n", errbuf); spdlog::error("Error decoding video frame: {}", errbuf);
goto end; av_packet_unref(packet);
cleanup();
return ret;
} }
// Process the frame using the selected filter
AVFrame *processed_frame = nullptr; AVFrame *processed_frame = nullptr;
ret = filter->process_frame(frame, &processed_frame); ret = filter->process_frame(frame, &processed_frame);
if (ret == 0 && processed_frame != nullptr) { if (ret < 0 && ret != AVERROR(EAGAIN)) {
// Encode and write the processed frame 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) { if (!benchmark) {
ret = encode_and_write_frame( ret = write_frame(processed_frame, enc_ctx, ofmt_ctx, vstream_idx);
processed_frame, enc_ctx, ofmt_ctx, video_stream_index
);
if (ret < 0) { if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error encoding/writing frame: %s\n", errbuf); spdlog::error("Error encoding/writing frame: {}", errbuf);
av_frame_free(&processed_frame); av_frame_free(&processed_frame);
goto end; av_packet_unref(packet);
cleanup();
return ret;
} }
} }
av_frame_free(&processed_frame); av_frame_free(&processed_frame);
proc_ctx->processed_frames++; proc_ctx->processed_frames++;
} else if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
fprintf(stderr, "Filter returned an error\n");
goto end;
} }
av_frame_unref(frame); av_frame_unref(frame);
// TODO: Print the debug processing status spdlog::debug(
"Processed frame {}/{}", proc_ctx->processed_frames, proc_ctx->total_frames
);
} }
} else if (encoder_config->copy_streams && stream_mapping[packet.stream_index] >= 0) { } else if (encoder_config->copy_streams && stream_map[packet->stream_index] >= 0) {
AVStream *in_stream = ifmt_ctx->streams[packet.stream_index]; AVStream *in_stream = ifmt_ctx->streams[packet->stream_index];
int out_stream_index = stream_mapping[packet.stream_index]; int out_stream_index = stream_map[packet->stream_index];
AVStream *out_stream = ofmt_ctx->streams[out_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);
av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base); packet->stream_index = out_stream_index;
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) { if (ret < 0) {
fprintf(stderr, "Error muxing packet\n"); av_strerror(ret, errbuf, sizeof(errbuf));
av_packet_unref(&packet); spdlog::error("Error muxing packet: {}", errbuf);
av_packet_unref(packet);
cleanup();
return ret; return ret;
} }
} }
av_packet_unref(&packet); av_packet_unref(packet);
} }
// Flush the filter // Flush the filter
ret = filter->flush(flushed_frames); ret = filter->flush(flushed_frames);
if (ret < 0) { if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error flushing filter: %s\n", errbuf); spdlog::error("Error flushing filter: {}", errbuf);
goto end; cleanup();
return ret;
} }
// Encode and write all flushed frames // Encode and write all flushed frames
for (AVFrame *&flushed_frame : flushed_frames) { for (AVFrame *&flushed_frame : flushed_frames) {
ret = encode_and_write_frame(flushed_frame, enc_ctx, ofmt_ctx, video_stream_index); ret = write_frame(flushed_frame, enc_ctx, ofmt_ctx, vstream_idx);
if (ret < 0) { if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error encoding/writing flushed frame: %s\n", errbuf); spdlog::error("Error encoding/writing flushed frame: {}", errbuf);
av_frame_free(&flushed_frame); av_frame_free(&flushed_frame);
flushed_frame = nullptr; flushed_frame = nullptr;
goto end; cleanup();
return ret;
} }
av_frame_free(&flushed_frame); av_frame_free(&flushed_frame);
flushed_frame = nullptr; flushed_frame = nullptr;
proc_ctx->processed_frames++;
} }
// Flush the encoder // Flush the encoder
ret = flush_encoder(enc_ctx, ofmt_ctx); ret = flush_encoder(enc_ctx, ofmt_ctx);
if (ret < 0) { if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error flushing encoder: %s\n", errbuf); spdlog::error("Error flushing encoder: {}", errbuf);
goto end; cleanup();
return ret;
} }
end: cleanup();
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);
}
}
return ret; return ret;
} }
// Cleanup resources after processing the video
void cleanup(
AVFormatContext *ifmt_ctx,
AVFormatContext *ofmt_ctx,
AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx,
AVBufferRef *hw_ctx,
int *stream_mapping,
Filter *filter
) {
if (ifmt_ctx) {
avformat_close_input(&ifmt_ctx);
}
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&ofmt_ctx->pb);
}
if (ofmt_ctx) {
avformat_free_context(ofmt_ctx);
}
if (dec_ctx) {
avcodec_free_context(&dec_ctx);
}
if (enc_ctx) {
avcodec_free_context(&enc_ctx);
}
if (hw_ctx) {
av_buffer_unref(&hw_ctx);
}
if (stream_mapping) {
av_free(stream_mapping);
}
if (filter) {
delete filter;
}
}
/** /**
* @brief Process a video file using the selected filter and encoder settings. * @brief Process a video file using the selected filter and encoder settings.
* *
* @param[in] input_filename Path to the input video file * @param[in] in_fname Path to the input video file
* @param[in] output_filename Path to the output 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] hw_type Hardware device type
* @param[in] filter_config Filter configurations * @param[in] filter_config Filter configurations
* @param[in] encoder_config Encoder configurations * @param[in] encoder_config Encoder configurations
@@ -237,8 +236,9 @@ void cleanup(
* @return int 0 on success, non-zero value on error * @return int 0 on success, non-zero value on error
*/ */
extern "C" int process_video( extern "C" int process_video(
const char *input_filename, const char *in_fname,
const char *output_filename, const char *out_fname,
Libvideo2xLogLevel log_level,
bool benchmark, bool benchmark,
AVHWDeviceType hw_type, AVHWDeviceType hw_type,
const FilterConfig *filter_config, const FilterConfig *filter_config,
@@ -250,126 +250,191 @@ extern "C" int process_video(
AVCodecContext *dec_ctx = nullptr; AVCodecContext *dec_ctx = nullptr;
AVCodecContext *enc_ctx = nullptr; AVCodecContext *enc_ctx = nullptr;
AVBufferRef *hw_ctx = nullptr; AVBufferRef *hw_ctx = nullptr;
int *stream_mapping = nullptr; int *stream_map = nullptr;
Filter *filter = nullptr; Filter *filter = nullptr;
int video_stream_index = -1; int vstream_idx = -1;
char errbuf[AV_ERROR_MAX_STRING_SIZE];
int ret = 0; 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:
av_log_set_level(AV_LOG_TRACE);
spdlog::set_level(spdlog::level::trace);
break;
case LIBVIDEO2X_LOG_LEVEL_DEBUG:
av_log_set_level(AV_LOG_DEBUG);
spdlog::set_level(spdlog::level::debug);
break;
case LIBVIDEO2X_LOG_LEVEL_INFO:
av_log_set_level(AV_LOG_INFO);
spdlog::set_level(spdlog::level::info);
break;
case LIBVIDEO2X_LOG_LEVEL_WARNING:
av_log_set_level(AV_LOG_WARNING);
spdlog::set_level(spdlog::level::warn);
break;
case LIBVIDEO2X_LOG_LEVEL_ERROR:
av_log_set_level(AV_LOG_ERROR);
spdlog::set_level(spdlog::level::err);
break;
case LIBVIDEO2X_LOG_LEVEL_CRITICAL:
av_log_set_level(AV_LOG_FATAL);
spdlog::set_level(spdlog::level::critical);
break;
case LIBVIDEO2X_LOG_LEVEL_OFF:
av_log_set_level(AV_LOG_QUIET);
spdlog::set_level(spdlog::level::off);
break;
default:
av_log_set_level(AV_LOG_INFO);
spdlog::set_level(spdlog::level::info);
break;
}
// Initialize hardware device context // Initialize hardware device context
if (hw_type != AV_HWDEVICE_TYPE_NONE) { if (hw_type != AV_HWDEVICE_TYPE_NONE) {
ret = av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0); ret = av_hwdevice_ctx_create(&hw_ctx, hw_type, NULL, NULL, 0);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Unable to initialize hardware device context\n"); av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::error("Error initializing hardware device context: {}", errbuf);
cleanup();
return ret; return ret;
} }
} }
// Initialize input // Initialize input
ret = init_decoder(hw_type, hw_ctx, input_filename, &ifmt_ctx, &dec_ctx, &video_stream_index); ret = init_decoder(hw_type, hw_ctx, in_fname, &ifmt_ctx, &dec_ctx, &vstream_idx);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to initialize decoder\n"); av_strerror(ret, errbuf, sizeof(errbuf));
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); spdlog::error("Failed to initialize decoder: {}", errbuf);
cleanup();
return ret; return ret;
} }
// Initialize output based on Libplacebo or RealESRGAN configuration // Initialize output dimensions based on filter configuration
int output_width = 0, output_height = 0; int output_width = 0, output_height = 0;
switch (filter_config->filter_type) { switch (filter_config->filter_type) {
case FILTER_LIBPLACEBO: case FILTER_LIBPLACEBO:
output_width = filter_config->config.libplacebo.output_width; output_width = filter_config->config.libplacebo.out_width;
output_height = filter_config->config.libplacebo.output_height; output_height = filter_config->config.libplacebo.out_height;
break; break;
case FILTER_REALESRGAN: case FILTER_REALESRGAN:
// Calculate the output dimensions based on the scaling factor
output_width = dec_ctx->width * filter_config->config.realesrgan.scaling_factor; output_width = dec_ctx->width * filter_config->config.realesrgan.scaling_factor;
output_height = dec_ctx->height * 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();
return -1;
} }
spdlog::info("Output video dimensions: {}x{}", output_width, output_height);
// Initialize output encoder // Initialize output encoder
encoder_config->output_width = output_width; encoder_config->out_width = output_width;
encoder_config->output_height = output_height; encoder_config->out_height = output_height;
ret = init_encoder( ret = init_encoder(
hw_ctx, hw_ctx,
output_filename, out_fname,
ifmt_ctx, ifmt_ctx,
&ofmt_ctx, &ofmt_ctx,
&enc_ctx, &enc_ctx,
dec_ctx, dec_ctx,
encoder_config, encoder_config,
video_stream_index, vstream_idx,
&stream_mapping &stream_map
); );
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to initialize encoder\n"); av_strerror(ret, errbuf, sizeof(errbuf));
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); spdlog::error("Failed to initialize encoder: {}", errbuf);
cleanup();
return ret; return ret;
} }
// Write the output file header // Write the output file header
ret = avformat_write_header(ofmt_ctx, NULL); ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n"); av_strerror(ret, errbuf, sizeof(errbuf));
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); spdlog::error("Error occurred when opening output file: {}", errbuf);
cleanup();
return ret; return ret;
} }
// Create and initialize the appropriate filter // Create and initialize the appropriate filter
switch (filter_config->filter_type) { if (filter_config->filter_type == FILTER_LIBPLACEBO) {
case FILTER_LIBPLACEBO: { const auto &config = filter_config->config.libplacebo;
const auto &config = filter_config->config.libplacebo; if (!config.shader_path) {
spdlog::error("Shader path must be provided for the libplacebo filter");
// Validate shader path cleanup();
if (!config.shader_path) {
fprintf(stderr, "Shader path must be provided for the libplacebo filter\n");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter);
return -1;
}
// Validate output dimensions
if (config.output_width <= 0 || config.output_height <= 0) {
fprintf(stderr, "Output dimensions must be provided for the libplacebo filter\n");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter);
return -1;
}
filter = new LibplaceboFilter{
config.output_width, config.output_height, std::filesystem::path(config.shader_path)
};
break;
}
case FILTER_REALESRGAN: {
const auto &config = filter_config->config.realesrgan;
// Validate model name
if (!config.model) {
fprintf(stderr, "Model name must be provided for the RealESRGAN filter\n");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter);
return -1;
}
// Validate scaling factor
if (config.scaling_factor <= 0) {
fprintf(stderr, "Scaling factor must be provided for the RealESRGAN filter\n");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter);
return -1;
}
filter = new RealesrganFilter{
config.gpuid, config.tta_mode, config.scaling_factor, config.model
};
break;
}
default:
fprintf(stderr, "Unknown filter type\n");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter);
return -1; return -1;
}
filter = new LibplaceboFilter{
config.out_width, config.out_height, std::filesystem::path(config.shader_path)
};
} else if (filter_config->filter_type == FILTER_REALESRGAN) {
const auto &config = filter_config->config.realesrgan;
if (!config.model) {
spdlog::error("Model name must be provided for the RealESRGAN filter");
cleanup();
return -1;
}
filter = new RealesrganFilter{
config.gpuid, config.tta_mode, config.scaling_factor, config.model
};
} else {
spdlog::error("Unknown filter type");
cleanup();
return -1;
}
// Check if the filter instance was created successfully
if (filter == nullptr) {
spdlog::error("Failed to create filter instance");
cleanup();
return -1;
} }
// Initialize the filter // Initialize the filter
ret = filter->init(dec_ctx, enc_ctx, hw_ctx); ret = filter->init(dec_ctx, enc_ctx, hw_ctx);
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Failed to initialize filter\n"); spdlog::error("Failed to initialize filter");
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); cleanup();
return ret; return ret;
} }
@@ -382,13 +447,14 @@ extern "C" int process_video(
dec_ctx, dec_ctx,
enc_ctx, enc_ctx,
filter, filter,
video_stream_index, vstream_idx,
stream_mapping, stream_map,
benchmark benchmark
); );
if (ret < 0) { if (ret < 0) {
fprintf(stderr, "Error processing frames\n"); av_strerror(ret, errbuf, sizeof(errbuf));
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); spdlog::error("Error processing frames: {}", errbuf);
cleanup();
return ret; return ret;
} }
@@ -396,12 +462,11 @@ extern "C" int process_video(
av_write_trailer(ofmt_ctx); av_write_trailer(ofmt_ctx);
// Cleanup before returning // Cleanup before returning
cleanup(ifmt_ctx, ofmt_ctx, dec_ctx, enc_ctx, hw_ctx, stream_mapping, filter); cleanup();
if (ret < 0 && ret != AVERROR_EOF) { if (ret < 0 && ret != AVERROR_EOF) {
char errbuf[AV_ERROR_MAX_STRING_SIZE];
av_strerror(ret, errbuf, sizeof(errbuf)); av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "Error occurred: %s\n", errbuf); spdlog::error("Error occurred: {}", errbuf);
return ret; return ret;
} }
return 0; return 0;

View File

@@ -4,6 +4,8 @@
#include <cstdio> #include <cstdio>
#include <string> #include <string>
#include <spdlog/spdlog.h>
#include "conversions.h" #include "conversions.h"
#include "fsutils.h" #include "fsutils.h"
@@ -30,16 +32,16 @@ RealesrganFilter::~RealesrganFilter() {
} }
} }
int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *hw_ctx) { int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVBufferRef *_) {
// Construct the model paths using std::filesystem // Construct the model paths using std::filesystem
std::filesystem::path model_param_path; std::filesystem::path model_param_path;
std::filesystem::path model_bin_path; std::filesystem::path model_bin_path;
if (model) { if (model) {
// Find the model paths by model name if provided // Find the model paths by model name if provided
model_param_path = std::filesystem::path("models") / model_param_path = std::filesystem::path("models") / "realesrgan" /
(std::string(model) + "-x" + std::to_string(scaling_factor) + ".param"); (std::string(model) + "-x" + std::to_string(scaling_factor) + ".param");
model_bin_path = std::filesystem::path("models") / model_bin_path = std::filesystem::path("models") / "realesrgan" /
(std::string(model) + "-x" + std::to_string(scaling_factor) + ".bin"); (std::string(model) + "-x" + std::to_string(scaling_factor) + ".bin");
} else if (!custom_model_param_path.empty() && !custom_model_bin_path.empty()) { } else if (!custom_model_param_path.empty() && !custom_model_bin_path.empty()) {
// Use the custom model paths if provided // Use the custom model paths if provided
@@ -47,7 +49,7 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
model_bin_path = custom_model_bin_path; model_bin_path = custom_model_bin_path;
} else { } else {
// Neither model name nor custom model paths provided // Neither model name nor custom model paths provided
fprintf(stderr, "Model or model paths must be provided for RealESRGAN filter\n"); spdlog::error("Model or model paths must be provided for RealESRGAN filter");
return -1; return -1;
} }
@@ -57,13 +59,11 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
// Check if the model files exist // Check if the model files exist
if (!std::filesystem::exists(model_param_full_path)) { if (!std::filesystem::exists(model_param_full_path)) {
fprintf( spdlog::error("RealESRGAN model param file not found: {}", model_param_full_path.string());
stderr, "RealESRGAN model param file not found: %s\n", model_param_full_path.c_str()
);
return -1; return -1;
} }
if (!std::filesystem::exists(model_bin_full_path)) { if (!std::filesystem::exists(model_bin_full_path)) {
fprintf(stderr, "RealESRGAN model bin file not found: %s\n", model_bin_full_path.c_str()); spdlog::error("RealESRGAN model bin file not found: {}", model_bin_full_path.string());
return -1; return -1;
} }
@@ -71,13 +71,13 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
realesrgan = new RealESRGAN(gpuid, tta_mode); realesrgan = new RealESRGAN(gpuid, tta_mode);
// Store the time bases // Store the time bases
input_time_base = dec_ctx->time_base; in_time_base = dec_ctx->time_base;
output_time_base = enc_ctx->time_base; out_time_base = enc_ctx->time_base;
output_pix_fmt = enc_ctx->pix_fmt; out_pix_fmt = enc_ctx->pix_fmt;
// Load the model // Load the model
if (realesrgan->load(model_param_full_path, model_bin_full_path) != 0) { if (realesrgan->load(model_param_full_path, model_bin_full_path) != 0) {
fprintf(stderr, "Failed to load RealESRGAN model\n"); spdlog::error("Failed to load RealESRGAN model");
return -1; return -1;
} }
@@ -100,38 +100,33 @@ int RealesrganFilter::init(AVCodecContext *dec_ctx, AVCodecContext *enc_ctx, AVB
return 0; return 0;
} }
int RealesrganFilter::process_frame(AVFrame *input_frame, AVFrame **output_frame) { int RealesrganFilter::process_frame(AVFrame *in_frame, AVFrame **out_frame) {
int ret; int ret;
// Convert the input frame to RGB24 // Convert the input frame to RGB24
ncnn::Mat input_mat = avframe_to_ncnn_mat(input_frame); ncnn::Mat in_mat = avframe_to_ncnn_mat(in_frame);
if (input_mat.empty()) { if (in_mat.empty()) {
fprintf(stderr, "Failed to convert AVFrame to ncnn::Mat\n"); spdlog::error("Failed to convert AVFrame to ncnn::Mat");
return -1; return -1;
} }
// Allocate space for ouptut ncnn::Mat // Allocate space for ouptut ncnn::Mat
int output_width = input_mat.w * realesrgan->scale; int output_width = in_mat.w * realesrgan->scale;
int output_height = input_mat.h * realesrgan->scale; int output_height = in_mat.h * realesrgan->scale;
ncnn::Mat output_mat = ncnn::Mat(output_width, output_height, (size_t)3, 3); ncnn::Mat out_mat = ncnn::Mat(output_width, output_height, static_cast<size_t>(3), 3);
ret = realesrgan->process(input_mat, output_mat); ret = realesrgan->process(in_mat, out_mat);
if (ret != 0) { if (ret != 0) {
fprintf(stderr, "RealESRGAN processing failed\n"); spdlog::error("RealESRGAN processing failed");
return ret; return ret;
} }
// Convert ncnn::Mat to AVFrame // Convert ncnn::Mat to AVFrame
*output_frame = ncnn_mat_to_avframe(output_mat, output_pix_fmt); *out_frame = ncnn_mat_to_avframe(out_mat, out_pix_fmt);
// Rescale PTS to encoder's time base // Rescale PTS to encoder's time base
(*output_frame)->pts = av_rescale_q(input_frame->pts, input_time_base, output_time_base); (*out_frame)->pts = av_rescale_q(in_frame->pts, in_time_base, out_time_base);
// Return the processed frame to the caller // Return the processed frame to the caller
return ret; return ret;
} }
int RealesrganFilter::flush(std::vector<AVFrame *> &processed_frames) {
// No special flushing needed for RealESRGAN
return 0;
}

View File

@@ -17,31 +17,15 @@
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
#include <libavutil/pixfmt.h> #include <libavutil/pixfmt.h>
#include <libvideo2x.h> #include <libvideo2x/libvideo2x.h>
#include <libvideo2x/version.h>
#include "getopt.h" #include "getopt.h"
const char *VIDEO2X_VERSION = "6.0.0";
// Set UNIX terminal input to non-blocking mode
#ifndef _WIN32
void set_nonblocking_input(bool enable) {
static struct termios oldt, newt;
if (enable) {
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(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
// Define command line options // Define command line options
static struct option long_options[] = { static struct option long_options[] = {
{"loglevel", required_argument, NULL, 0},
{"noprogress", no_argument, NULL, 0},
{"version", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 0}, {"help", no_argument, NULL, 0},
@@ -72,11 +56,23 @@ static struct option long_options[] = {
{0, 0, 0, 0} {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 // Structure to hold parsed arguments
struct arguments { struct arguments {
// General options // General options
const char *input_filename; const char *loglevel;
const char *output_filename; bool noprogress;
const char *in_fname;
const char *out_fname;
const char *filter_type; const char *filter_type;
const char *hwaccel; const char *hwaccel;
bool nocopystreams; bool nocopystreams;
@@ -91,8 +87,8 @@ struct arguments {
// libplacebo options // libplacebo options
const char *shader_path; const char *shader_path;
int output_width; int out_width;
int output_height; int out_height;
// RealESRGAN options // RealESRGAN options
int gpuid; int gpuid;
@@ -108,27 +104,52 @@ struct ProcessVideoThreadArguments {
struct VideoProcessingContext *proc_ctx; struct VideoProcessingContext *proc_ctx;
}; };
const char *valid_models[] = { // Set UNIX terminal input to non-blocking mode
"realesrgan-plus", #ifndef _WIN32
"realesrgan-plus-anime", void set_nonblocking_input(bool enable) {
"realesr-animevideov3", 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) { int is_valid_realesrgan_model(const char *model) {
if (!model) { if (!model) {
return 0; return 0;
} }
for (int i = 0; i < sizeof(valid_models) / sizeof(valid_models[0]); i++) { for (unsigned long i = 0;
if (strcmp(model, valid_models[i]) == 0) { i < sizeof(valid_realesrgan_models) / sizeof(valid_realesrgan_models[0]);
i++) {
if (strcmp(model, valid_realesrgan_models[i]) == 0) {
return 1; return 1;
} }
} }
return 0; return 0;
} }
void print_help() { void print_help(void) {
printf("Usage: video2x [OPTIONS]\n"); printf("Usage: video2x [OPTIONS]\n");
printf("\nOptions:\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(" -v, --version Print program version\n");
printf(" -?, --help Display this help page\n"); printf(" -?, --help Display this help page\n");
printf("\nGeneral Processing Options:\n"); printf("\nGeneral Processing Options:\n");
@@ -147,7 +168,9 @@ void print_help() {
printf(" -q, --crf Constant Rate Factor (default: 20.0)\n"); printf(" -q, --crf Constant Rate Factor (default: 20.0)\n");
printf("\nlibplacebo Options:\n"); printf("\nlibplacebo Options:\n");
printf(" -s, --shader Name or path to custom GLSL shader file\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(" -w, --width Output width\n");
printf(" -h, --height Output height\n"); printf(" -h, --height Output height\n");
@@ -157,7 +180,7 @@ void print_help() {
printf(" -r, --scale Scaling factor (2, 3, or 4)\n"); printf(" -r, --scale Scaling factor (2, 3, or 4)\n");
printf("\nExamples Usage:\n"); printf("\nExamples Usage:\n");
printf(" video2x -i in.mp4 -o out.mp4 -f libplacebo -s anime4k-mode-a -w 3840 -h 2160\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"); printf(" video2x -i in.mp4 -o out.mp4 -f realesrgan -m realesr-animevideov3 -r 4\n");
} }
@@ -166,8 +189,10 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
int c; int c;
// Default argument values // Default argument values
arguments->input_filename = NULL; arguments->loglevel = "info";
arguments->output_filename = NULL; arguments->noprogress = false;
arguments->in_fname = NULL;
arguments->out_fname = NULL;
arguments->filter_type = NULL; arguments->filter_type = NULL;
arguments->hwaccel = "none"; arguments->hwaccel = "none";
arguments->nocopystreams = false; arguments->nocopystreams = false;
@@ -182,8 +207,8 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
// libplacebo options // libplacebo options
arguments->shader_path = NULL; arguments->shader_path = NULL;
arguments->output_width = 0; arguments->out_width = 0;
arguments->output_height = 0; arguments->out_height = 0;
// RealESRGAN options // RealESRGAN options
arguments->gpuid = 0; arguments->gpuid = 0;
@@ -191,14 +216,14 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
arguments->scaling_factor = 0; arguments->scaling_factor = 0;
while ((c = getopt_long( 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 argc, argv, "i:o:f:a:c:x:p:b:q:s:w:h:g:m:r:v", long_options, &option_index
)) != -1) { )) != -1) {
switch (c) { switch (c) {
case 'i': case 'i':
arguments->input_filename = optarg; arguments->in_fname = optarg;
break; break;
case 'o': case 'o':
arguments->output_filename = optarg; arguments->out_fname = optarg;
break; break;
case 'f': case 'f':
arguments->filter_type = optarg; arguments->filter_type = optarg;
@@ -223,7 +248,7 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
} }
break; break;
case 'q': case 'q':
arguments->crf = atof(optarg); arguments->crf = (float)atof(optarg);
if (arguments->crf < 0.0 || arguments->crf > 51.0) { if (arguments->crf < 0.0 || arguments->crf > 51.0) {
fprintf(stderr, "Error: CRF must be between 0 and 51.\n"); fprintf(stderr, "Error: CRF must be between 0 and 51.\n");
exit(1); exit(1);
@@ -233,15 +258,15 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
arguments->shader_path = optarg; arguments->shader_path = optarg;
break; break;
case 'w': case 'w':
arguments->output_width = atoi(optarg); arguments->out_width = atoi(optarg);
if (arguments->output_width <= 0) { if (arguments->out_width <= 0) {
fprintf(stderr, "Error: Output width must be greater than 0.\n"); fprintf(stderr, "Error: Output width must be greater than 0.\n");
exit(1); exit(1);
} }
break; break;
case 'h': case 'h':
arguments->output_height = atoi(optarg); arguments->out_height = atoi(optarg);
if (arguments->output_height <= 0) { if (arguments->out_height <= 0) {
fprintf(stderr, "Error: Output height must be greater than 0.\n"); fprintf(stderr, "Error: Output height must be greater than 0.\n");
exit(1); exit(1);
} }
@@ -269,10 +294,14 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
} }
break; break;
case 'v': case 'v':
printf("Video2X v%s\n", VIDEO2X_VERSION); printf("Video2X version %s\n", LIBVIDEO2X_VERSION_STRING);
exit(0); exit(0);
case 0: // Long-only options without short equivalents case 0: // Long-only options without short equivalents
if (strcmp(long_options[option_index].name, "help") == 0) { if (strcmp(long_options[option_index].name, "loglevel") == 0) {
arguments->loglevel = optarg;
} else if (strcmp(long_options[option_index].name, "noprogress") == 0) {
arguments->noprogress = true;
} else if (strcmp(long_options[option_index].name, "help") == 0) {
print_help(); print_help();
exit(0); exit(0);
} else if (strcmp(long_options[option_index].name, "nocopystreams") == 0) { } else if (strcmp(long_options[option_index].name, "nocopystreams") == 0) {
@@ -288,12 +317,12 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
} }
// Check for required arguments // Check for required arguments
if (!arguments->input_filename) { if (!arguments->in_fname) {
fprintf(stderr, "Error: Input file path is required.\n"); fprintf(stderr, "Error: Input file path is required.\n");
exit(1); exit(1);
} }
if (!arguments->output_filename && !arguments->benchmark) { if (!arguments->out_fname && !arguments->benchmark) {
fprintf(stderr, "Error: Output file path is required.\n"); fprintf(stderr, "Error: Output file path is required.\n");
exit(1); exit(1);
} }
@@ -304,12 +333,11 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
} }
if (strcmp(arguments->filter_type, "libplacebo") == 0) { if (strcmp(arguments->filter_type, "libplacebo") == 0) {
if (!arguments->shader_path || arguments->output_width == 0 || if (!arguments->shader_path || arguments->out_width == 0 || arguments->out_height == 0) {
arguments->output_height == 0) {
fprintf( fprintf(
stderr, stderr,
"Error: For libplacebo, shader name/path (-s), width (-w), " "Error: For libplacebo, shader name/path (-s), width (-w), "
"and height (-e) are required.\n" "and height (-h) are required.\n"
); );
exit(1); exit(1);
} }
@@ -323,11 +351,33 @@ void parse_arguments(int argc, char **argv, struct arguments *arguments) {
} }
} }
enum Libvideo2xLogLevel parse_log_level(const char *level_name) {
if (strcmp(level_name, "trace") == 0) {
return LIBVIDEO2X_LOG_LEVEL_TRACE;
} else if (strcmp(level_name, "debug") == 0) {
return LIBVIDEO2X_LOG_LEVEL_DEBUG;
} else if (strcmp(level_name, "info") == 0) {
return LIBVIDEO2X_LOG_LEVEL_INFO;
} else if (strcmp(level_name, "warning") == 0) {
return LIBVIDEO2X_LOG_LEVEL_WARNING;
} else if (strcmp(level_name, "error") == 0) {
return LIBVIDEO2X_LOG_LEVEL_ERROR;
} else if (strcmp(level_name, "critical") == 0) {
return LIBVIDEO2X_LOG_LEVEL_CRITICAL;
} else if (strcmp(level_name, "off") == 0) {
return LIBVIDEO2X_LOG_LEVEL_OFF;
} else {
fprintf(stderr, "Warning: Invalid log level specified. Defaulting to 'info'.\n");
return LIBVIDEO2X_LOG_LEVEL_INFO;
}
}
// Wrapper function for video processing thread // Wrapper function for video processing thread
int process_video_thread(void *arg) { int process_video_thread(void *arg) {
struct ProcessVideoThreadArguments *thread_args = (struct ProcessVideoThreadArguments *)arg; struct ProcessVideoThreadArguments *thread_args = (struct ProcessVideoThreadArguments *)arg;
// Extract individual arguments // Extract individual arguments
enum Libvideo2xLogLevel log_level = parse_log_level(thread_args->arguments->loglevel);
struct arguments *arguments = thread_args->arguments; struct arguments *arguments = thread_args->arguments;
enum AVHWDeviceType hw_device_type = thread_args->hw_device_type; enum AVHWDeviceType hw_device_type = thread_args->hw_device_type;
struct FilterConfig *filter_config = thread_args->filter_config; struct FilterConfig *filter_config = thread_args->filter_config;
@@ -336,8 +386,9 @@ int process_video_thread(void *arg) {
// Call the process_video function // Call the process_video function
int result = process_video( int result = process_video(
arguments->input_filename, arguments->in_fname,
arguments->output_filename, arguments->out_fname,
log_level,
arguments->benchmark, arguments->benchmark,
hw_device_type, hw_device_type,
filter_config, filter_config,
@@ -364,8 +415,8 @@ int main(int argc, char **argv) {
struct FilterConfig filter_config; struct FilterConfig filter_config;
if (strcmp(arguments.filter_type, "libplacebo") == 0) { if (strcmp(arguments.filter_type, "libplacebo") == 0) {
filter_config.filter_type = FILTER_LIBPLACEBO; filter_config.filter_type = FILTER_LIBPLACEBO;
filter_config.config.libplacebo.output_width = arguments.output_width; filter_config.config.libplacebo.out_width = arguments.out_width;
filter_config.config.libplacebo.output_height = arguments.output_height; filter_config.config.libplacebo.out_height = arguments.out_height;
filter_config.config.libplacebo.shader_path = arguments.shader_path; filter_config.config.libplacebo.shader_path = arguments.shader_path;
} else if (strcmp(arguments.filter_type, "realesrgan") == 0) { } else if (strcmp(arguments.filter_type, "realesrgan") == 0) {
filter_config.filter_type = FILTER_REALESRGAN; filter_config.filter_type = FILTER_REALESRGAN;
@@ -397,8 +448,8 @@ int main(int argc, char **argv) {
// Setup encoder configuration // Setup encoder configuration
struct EncoderConfig encoder_config = { struct EncoderConfig encoder_config = {
.output_width = 0, // To be filled by libvideo2x .out_width = 0, // To be filled by libvideo2x
.output_height = 0, // To be filled by libvideo2x .out_height = 0, // To be filled by libvideo2x
.copy_streams = !arguments.nocopystreams, .copy_streams = !arguments.nocopystreams,
.codec = codec->id, .codec = codec->id,
.pix_fmt = pix_fmt, .pix_fmt = pix_fmt,
@@ -436,10 +487,9 @@ int main(int argc, char **argv) {
.proc_ctx = &proc_ctx .proc_ctx = &proc_ctx
}; };
// Enable non-blocking input // Register a newline-safe log callback for FFmpeg
#ifndef _WIN32 // This will ensure that log messages are printed on a new line after the progress bar
set_nonblocking_input(true); av_log_set_callback(newline_safe_ffmpeg_log_callback);
#endif
// Create a thread for video processing // Create a thread for video processing
thrd_t processing_thread; thrd_t processing_thread;
@@ -447,8 +497,12 @@ int main(int argc, char **argv) {
fprintf(stderr, "Failed to create processing thread\n"); fprintf(stderr, "Failed to create processing thread\n");
return 1; return 1;
} }
printf("[Video2X] Video processing started.\n"); printf("Video processing started; press SPACE to pause/resume, 'q' to abort.\n");
printf("[Video2X] 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 // Main thread loop to display progress and handle input
while (!proc_ctx.completed) { while (!proc_ctx.completed) {
@@ -468,37 +522,36 @@ int main(int argc, char **argv) {
// Toggle pause state // Toggle pause state
proc_ctx.pause = !proc_ctx.pause; proc_ctx.pause = !proc_ctx.pause;
if (proc_ctx.pause) { if (proc_ctx.pause) {
printf("\n[Video2X] Processing paused. Press SPACE to resume, 'q' to abort."); printf("\nProcessing paused. Press SPACE to resume, 'q' to abort.\n");
} else { } else {
printf("\n[Video2X] Resuming processing..."); printf("Resuming processing...\n");
} }
fflush(stdout);
} else if (ch == 'q' || ch == 'Q') { } else if (ch == 'q' || ch == 'Q') {
// Abort processing // Abort processing
printf("\n[Video2X] Aborting processing..."); printf("\nAborting processing...\n");
fflush(stdout);
proc_ctx.abort = true; proc_ctx.abort = true;
newline_required = false;
break; break;
} }
// Display progress // Display progress
if (!proc_ctx.pause && proc_ctx.total_frames > 0) { if (!arguments.noprogress && !proc_ctx.pause && proc_ctx.total_frames > 0) {
printf( printf(
"\r[Video2X] Processing frame %ld/%ld (%.2f%%); time elapsed: %lds", "\rProcessing frame %ld/%ld (%.2f%%); time elapsed: %lds",
proc_ctx.processed_frames, proc_ctx.processed_frames,
proc_ctx.total_frames, proc_ctx.total_frames,
proc_ctx.total_frames > 0 proc_ctx.total_frames > 0
? proc_ctx.processed_frames * 100.0 / proc_ctx.total_frames ? (double)proc_ctx.processed_frames * 100.0 / (double)proc_ctx.total_frames
: 0.0, : 0.0,
time(NULL) - proc_ctx.start_time time(NULL) - proc_ctx.start_time
); );
fflush(stdout); fflush(stdout);
newline_required = true;
} }
// Sleep for a short duration // Sleep for 50ms
thrd_sleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 100000000}, NULL); // Sleep for 100ms thrd_sleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 100000000}, NULL);
} }
puts(""); // Print newline after progress bar is complete
// Restore terminal to blocking mode // Restore terminal to blocking mode
#ifndef _WIN32 #ifndef _WIN32
@@ -509,31 +562,37 @@ int main(int argc, char **argv) {
int process_result; int process_result;
thrd_join(processing_thread, &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) { if (proc_ctx.abort) {
fprintf(stderr, "Video processing aborted\n"); fprintf(stderr, "Video processing aborted\n");
return 2; return 2;
} } else if (process_result != 0) {
if (process_result != 0) {
fprintf(stderr, "Video processing failed\n"); fprintf(stderr, "Video processing failed\n");
return process_result; return process_result;
} else {
printf("Video processing completed successfully\n");
} }
// Calculate statistics // Calculate statistics
time_t time_elapsed = time(NULL) - proc_ctx.start_time; time_t time_elapsed = time(NULL) - proc_ctx.start_time;
float average_speed_fps = float average_speed_fps =
(float)proc_ctx.processed_frames / (time_elapsed > 0 ? time_elapsed : 1); (float)proc_ctx.processed_frames / (time_elapsed > 0 ? (float)time_elapsed : 1);
// Print processing summary // Print processing summary
printf("====== Video2X %s summary ======\n", arguments.benchmark ? "Benchmark" : "Processing"); printf("====== Video2X %s summary ======\n", arguments.benchmark ? "Benchmark" : "Processing");
printf("Video file processed: %s\n", arguments.input_filename); printf("Video file processed: %s\n", arguments.in_fname);
printf("Total frames processed: %ld\n", proc_ctx.processed_frames); printf("Total frames processed: %ld\n", proc_ctx.processed_frames);
printf("Total time taken: %lds\n", time_elapsed); printf("Total time taken: %lds\n", time_elapsed);
printf("Average processing speed: %.2f FPS\n", average_speed_fps); printf("Average processing speed: %.2f FPS\n", average_speed_fps);
// Print additional information if not in benchmark mode // Print additional information if not in benchmark mode
if (!arguments.benchmark) { if (!arguments.benchmark) {
printf("Output written to: %s\n", arguments.output_filename); printf("Output written to: %s\n", arguments.out_fname);
} }
return 0; return 0;

1
third_party/opencv vendored Submodule

Submodule third_party/opencv added at 71d3237a09

1
third_party/spdlog vendored Submodule

Submodule third_party/spdlog added at e593f6695c