diff --git a/CHANGELOG.md b/CHANGELOG.md
index 749390a..2714886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve error handling and error messages.
- Improve the CLI help message structure and clarity.
+- Improve CLI argument validation.
### Removed
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d075c54..3307f8a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -332,7 +332,6 @@ if(BUILD_VIDEO2X_CLI)
${ALL_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
${PROJECT_SOURCE_DIR}/include
- ${PROJECT_SOURCE_DIR}/include/libvideo2x
${PROJECT_SOURCE_DIR}/tools/video2x/include
)
diff --git a/README.md b/README.md
index 4e52c2d..83e0664 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-
+
@@ -69,15 +70,7 @@ Join our Telegram discussion group to ask any questions you have about Video2X,
Comprehensive documentation for Video2X is available at [https://docs.video2x.org/](https://docs.video2x.org/). It offers detailed instructions on how to [build](https://docs.video2x.org/building/index.html), [install](https://docs.video2x.org/installing/index.html), [use](https://docs.video2x.org/running/index.html), and [develop](https://docs.video2x.org/developing/index.html) with this program.
-## 🔰 Introduction
-
-Video2X is a machine-learning-powered framework for video upscaling and frame interpolation, built around three main components:
-
-- [libvideo2x](https://github.com/k4yt3x/video2x/blob/master/src/libvideo2x.cpp): The core C++ library providing upscaling and frame interpolation capabilities.
-- [Video2X CLI](https://github.com/k4yt3x/video2x/blob/master/src/video2x.c): A command-line interface that utilizes `libvideo2x` for video processing.
-- [Video2X Qt6](https://github.com/k4yt3x/video2x-qt6): A Qt6-based graphical interface that utilizes `libvideo2x` for video processing.
-
-### Video Demos
+## 📽️ Video Demos (Outdated)
![Spirited Away Demo](https://user-images.githubusercontent.com/21986859/49412428-65083280-f73a-11e8-8237-bb34158a545e.png)\
_Upscale demo: Spirited Away's movie trailer_
diff --git a/include/libvideo2x/avutils.h b/include/libvideo2x/avutils.h
index c4a799c..4eecffe 100644
--- a/include/libvideo2x/avutils.h
+++ b/include/libvideo2x/avutils.h
@@ -1,5 +1,4 @@
-#ifndef AVUTILS_H
-#define AVUTILS_H
+#pragma once
extern "C" {
#include
@@ -18,5 +17,3 @@ void av_bufferref_deleter(AVBufferRef *bufferref);
void av_frame_deleter(AVFrame *frame);
void av_packet_deleter(AVPacket *packet);
-
-#endif // AVUTILS_H
diff --git a/include/libvideo2x/conversions.h b/include/libvideo2x/conversions.h
index 22e883f..d84688b 100644
--- a/include/libvideo2x/conversions.h
+++ b/include/libvideo2x/conversions.h
@@ -1,5 +1,4 @@
-#ifndef CONVERSIONS_H
-#define CONVERSIONS_H
+#pragma once
extern "C" {
#include
@@ -16,5 +15,3 @@ ncnn::Mat avframe_to_ncnn_mat(AVFrame *frame);
// Convert ncnn::Mat to AVFrame
AVFrame *ncnn_mat_to_avframe(const ncnn::Mat &mat, AVPixelFormat pix_fmt);
-
-#endif // CONVERSIONS_H
diff --git a/include/libvideo2x/decoder.h b/include/libvideo2x/decoder.h
index b316ba0..89ae7b2 100644
--- a/include/libvideo2x/decoder.h
+++ b/include/libvideo2x/decoder.h
@@ -1,5 +1,4 @@
-#ifndef DECODER_H
-#define DECODER_H
+#pragma once
#include
@@ -27,5 +26,3 @@ class Decoder {
AVCodecContext *dec_ctx_;
int in_vstream_idx_;
};
-
-#endif // DECODER_H
diff --git a/include/libvideo2x/encoder.h b/include/libvideo2x/encoder.h
index 7cf9105..d85a4a0 100644
--- a/include/libvideo2x/encoder.h
+++ b/include/libvideo2x/encoder.h
@@ -1,5 +1,4 @@
-#ifndef ENCODER_H
-#define ENCODER_H
+#pragma once
#include
#include
@@ -16,34 +15,32 @@ extern "C" {
// Encoder configurations
struct EncoderConfig {
// Non-AVCodecContext options
- AVCodecID codec;
- bool copy_streams;
+ AVCodecID codec = AV_CODEC_ID_NONE;
+ bool copy_streams = true;
// Basic video options
- int width;
- int height;
- int frm_rate_mul;
- AVPixelFormat pix_fmt;
+ int frm_rate_mul = 0;
+ AVPixelFormat pix_fmt = AV_PIX_FMT_NONE;
// Rate control and compression
- int64_t bit_rate;
- int rc_buffer_size;
- int rc_min_rate;
- int rc_max_rate;
- int qmin;
- int qmax;
+ int64_t bit_rate = 0;
+ int rc_buffer_size = 0;
+ int rc_min_rate = 0;
+ int rc_max_rate = 0;
+ int qmin = -1;
+ int qmax = -1;
// GOP and frame structure
- int gop_size;
- int max_b_frames;
- int keyint_min;
- int refs;
+ int gop_size = -1;
+ int max_b_frames = -1;
+ int keyint_min = -1;
+ int refs = -1;
// Performance and threading
- int thread_count;
+ int thread_count = 0;
// Latency and buffering
- int delay;
+ int delay = -1;
// Extra AVOptions
std::vector> extra_opts;
@@ -60,6 +57,8 @@ class Encoder {
AVFormatContext *ifmt_ctx,
AVCodecContext *dec_ctx,
EncoderConfig &enc_cfg,
+ int width,
+ int height,
int in_vstream_idx
);
@@ -77,5 +76,3 @@ class Encoder {
int out_vstream_idx_;
int *stream_map_;
};
-
-#endif // ENCODER_H
diff --git a/include/libvideo2x/filter_libplacebo.h b/include/libvideo2x/filter_libplacebo.h
index f494952..1b31ddf 100644
--- a/include/libvideo2x/filter_libplacebo.h
+++ b/include/libvideo2x/filter_libplacebo.h
@@ -1,5 +1,4 @@
-#ifndef FILTER_LIBPLACEBO_H
-#define FILTER_LIBPLACEBO_H
+#pragma once
#include
@@ -57,5 +56,3 @@ class FilterLibplacebo : public Filter {
AVRational in_time_base_;
AVRational out_time_base_;
};
-
-#endif // FILTER_LIBPLACEBO_H
diff --git a/include/libvideo2x/filter_realesrgan.h b/include/libvideo2x/filter_realesrgan.h
index 15534fa..a8c6106 100644
--- a/include/libvideo2x/filter_realesrgan.h
+++ b/include/libvideo2x/filter_realesrgan.h
@@ -1,5 +1,4 @@
-#ifndef FILTER_REALESRGAN_H
-#define FILTER_REALESRGAN_H
+#pragma once
extern "C" {
#include
@@ -50,5 +49,3 @@ class FilterRealesrgan : public Filter {
AVRational out_time_base_;
AVPixelFormat out_pix_fmt_;
};
-
-#endif // FILTER_REALESRGAN_H
diff --git a/include/libvideo2x/fsutils.h b/include/libvideo2x/fsutils.h
index 57f86c3..bd9e43e 100644
--- a/include/libvideo2x/fsutils.h
+++ b/include/libvideo2x/fsutils.h
@@ -1,5 +1,4 @@
-#ifndef FSUTILS_H
-#define FSUTILS_H
+#pragma once
#include
#include
@@ -29,5 +28,3 @@ std::string wstring_to_u8string(const StringType &wstr);
StringType path_to_string_type(const std::filesystem::path &path);
StringType to_string_type(int value);
-
-#endif // FSUTILS_H
diff --git a/include/libvideo2x/interpolator_rife.h b/include/libvideo2x/interpolator_rife.h
index d94cdfc..f70e1ba 100644
--- a/include/libvideo2x/interpolator_rife.h
+++ b/include/libvideo2x/interpolator_rife.h
@@ -1,5 +1,4 @@
-#ifndef INTERPOLATOR_RIFE_H
-#define INTERPOLATOR_RIFE_H
+#pragma once
extern "C" {
#include
@@ -55,5 +54,3 @@ class InterpolatorRIFE : public Interpolator {
AVRational out_time_base_;
AVPixelFormat out_pix_fmt_;
};
-
-#endif // INTERPOLATOR_RIFE_H
diff --git a/include/libvideo2x/libplacebo.h b/include/libvideo2x/libplacebo.h
index b8ab418..16928fc 100644
--- a/include/libvideo2x/libplacebo.h
+++ b/include/libvideo2x/libplacebo.h
@@ -1,5 +1,4 @@
-#ifndef PLACEBO_H
-#define PLACEBO_H
+#pragma once
#include
@@ -18,5 +17,3 @@ int init_libplacebo(
uint32_t vk_device_index,
const std::filesystem::path &shader_path
);
-
-#endif // PLACEBO_H
diff --git a/include/libvideo2x/libvideo2x.h b/include/libvideo2x/libvideo2x.h
index f1b6e71..24af4f3 100644
--- a/include/libvideo2x/libvideo2x.h
+++ b/include/libvideo2x/libvideo2x.h
@@ -1,5 +1,4 @@
-#ifndef LIBVIDEO2X_H
-#define LIBVIDEO2X_H
+#pragma once
#include
#include
@@ -26,19 +25,15 @@ extern "C" {
#define LIBVIDEO2X_API
#endif
-struct HardwareConfig {
- uint32_t vk_device_index;
- AVHWDeviceType hw_device_type;
-};
-
class LIBVIDEO2X_API VideoProcessor {
public:
VideoProcessor(
- const HardwareConfig hw_cfg,
const ProcessorConfig proc_cfg,
- EncoderConfig enc_cfg,
- Video2xLogLevel = Video2xLogLevel::Info,
- bool benchmark = false
+ const EncoderConfig enc_cfg,
+ const uint32_t vk_device_index = 0,
+ const AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE,
+ const Video2xLogLevel = Video2xLogLevel::Info,
+ const bool benchmark = false
);
virtual ~VideoProcessor() = default;
@@ -85,9 +80,10 @@ class LIBVIDEO2X_API VideoProcessor {
AVFrame *proc_frame
);
- HardwareConfig hw_cfg_;
ProcessorConfig proc_cfg_;
EncoderConfig enc_cfg_;
+ uint32_t vk_device_index_ = 0;
+ AVHWDeviceType hw_device_type_ = AV_HWDEVICE_TYPE_NONE;
bool benchmark_ = false;
std::atomic frame_index_ = 0;
@@ -96,5 +92,3 @@ class LIBVIDEO2X_API VideoProcessor {
std::atomic aborted_ = false;
std::atomic completed_ = false;
};
-
-#endif // LIBVIDEO2X_H
diff --git a/include/libvideo2x/logging.h b/include/libvideo2x/logging.h
index fb20034..a8941b1 100644
--- a/include/libvideo2x/logging.h
+++ b/include/libvideo2x/logging.h
@@ -1,5 +1,4 @@
-#ifndef LOGGING_H
-#define LOGGING_H
+#pragma once
#include
@@ -19,5 +18,3 @@ enum class Video2xLogLevel {
void set_log_level(Video2xLogLevel log_level);
std::optional find_log_level_by_name(const StringType &log_level_name);
-
-#endif // LOGGING_H
diff --git a/include/libvideo2x/processor.h b/include/libvideo2x/processor.h
index 8cf4251..3edda3e 100644
--- a/include/libvideo2x/processor.h
+++ b/include/libvideo2x/processor.h
@@ -1,5 +1,4 @@
-#ifndef PROCESSOR_H
-#define PROCESSOR_H
+#pragma once
#include
#include
@@ -18,6 +17,7 @@ enum class ProcessingMode {
};
enum class ProcessorType {
+ None,
Libplacebo,
RealESRGAN,
RIFE,
@@ -28,26 +28,26 @@ struct LibplaceboConfig {
};
struct RealESRGANConfig {
- bool tta_mode;
+ bool tta_mode = false;
StringType model_name;
};
struct RIFEConfig {
- bool tta_mode;
- bool tta_temporal_mode;
- bool uhd_mode;
- int num_threads;
+ bool tta_mode = false;
+ bool tta_temporal_mode = false;
+ bool uhd_mode = false;
+ int num_threads = 0;
StringType model_name;
};
// Unified filter configuration
struct ProcessorConfig {
- ProcessorType processor_type;
- int width;
- int height;
- int scaling_factor;
- int frm_rate_mul;
- float scn_det_thresh;
+ ProcessorType processor_type = ProcessorType::None;
+ int width = 0;
+ int height = 0;
+ int scaling_factor = 0;
+ int frm_rate_mul = 0;
+ float scn_det_thresh = 0.0f;
std::variant config;
};
@@ -81,5 +81,3 @@ class Interpolator : public Processor {
virtual int
interpolate(AVFrame *prev_frame, AVFrame *in_frame, AVFrame **out_frame, float time_step) = 0;
};
-
-#endif // PROCESSOR_H
diff --git a/include/libvideo2x/processor_factory.h b/include/libvideo2x/processor_factory.h
index de7a490..5bd33a7 100644
--- a/include/libvideo2x/processor_factory.h
+++ b/include/libvideo2x/processor_factory.h
@@ -1,5 +1,4 @@
-#ifndef PROCESSOR_FACTORY_H
-#define PROCESSOR_FACTORY_H
+#pragma once
#include
#include
@@ -32,5 +31,3 @@ class ProcessorFactory {
// Static initializer for default processors
static void init_default_processors(ProcessorFactory &factory);
};
-
-#endif // PROCESSOR_FACTORY_H
diff --git a/include/libvideo2x/version.h.in b/include/libvideo2x/version.h.in
index 5cd7f54..660c211 100644
--- a/include/libvideo2x/version.h.in
+++ b/include/libvideo2x/version.h.in
@@ -1,6 +1,3 @@
-#ifndef VERSION_H
-#define VERSION_H
+#pragma once
#define LIBVIDEO2X_VERSION_STRING "@PROJECT_VERSION@"
-
-#endif // VERSION_H
diff --git a/src/avutils.cpp b/src/avutils.cpp
index e621dae..dc57c4e 100644
--- a/src/avutils.cpp
+++ b/src/avutils.cpp
@@ -169,9 +169,8 @@ float get_frame_diff(AVFrame *frame1, AVFrame *frame2) {
uint8_t *ptr1 = rgb_frame1->data[0] + y * rgb_frame1->linesize[0];
uint8_t *ptr2 = rgb_frame2->data[0] + y * rgb_frame2->linesize[0];
for (int x = 0; x < width * 3; x++) {
- sum_diff += static_cast(
- std::abs(static_cast(ptr1[x]) - static_cast(ptr2[x]))
- );
+ sum_diff +=
+ static_cast(ptr1[x] > ptr2[x] ? ptr1[x] - ptr2[x] : ptr2[x] - ptr1[x]);
max_diff += 255;
}
}
diff --git a/src/encoder.cpp b/src/encoder.cpp
index 33f0c09..74b4520 100644
--- a/src/encoder.cpp
+++ b/src/encoder.cpp
@@ -33,6 +33,8 @@ int Encoder::init(
AVFormatContext *ifmt_ctx,
AVCodecContext *dec_ctx,
EncoderConfig &enc_cfg,
+ int width,
+ int height,
int in_vstream_idx
) {
int ret;
@@ -84,8 +86,8 @@ int Encoder::init(
enc_ctx_->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
// Set basic video options
- enc_ctx_->width = enc_cfg.width;
- enc_ctx_->height = enc_cfg.height;
+ enc_ctx_->width = width;
+ enc_ctx_->height = height;
// Set rate control and compression options
enc_ctx_->bit_rate = enc_cfg.bit_rate;
diff --git a/src/fsutils.cpp b/src/fsutils.cpp
index b2c4c20..1986960 100644
--- a/src/fsutils.cpp
+++ b/src/fsutils.cpp
@@ -1,7 +1,7 @@
#include "fsutils.h"
#if _WIN32
-#include
+#include
#include
#else
#include
diff --git a/src/libvideo2x.cpp b/src/libvideo2x.cpp
index 792e106..6754098 100644
--- a/src/libvideo2x.cpp
+++ b/src/libvideo2x.cpp
@@ -14,13 +14,18 @@ extern "C" {
#include "processor_factory.h"
VideoProcessor::VideoProcessor(
- const HardwareConfig hw_cfg,
const ProcessorConfig proc_cfg,
const EncoderConfig enc_cfg,
- Video2xLogLevel log_level,
- bool benchmark
+ const uint32_t vk_device_index,
+ const AVHWDeviceType hw_device_type,
+ const Video2xLogLevel log_level,
+ const bool benchmark
)
- : hw_cfg_(hw_cfg), proc_cfg_(proc_cfg), enc_cfg_(enc_cfg), benchmark_(benchmark) {
+ : proc_cfg_(proc_cfg),
+ enc_cfg_(enc_cfg),
+ vk_device_index_(vk_device_index),
+ hw_device_type_(hw_device_type),
+ benchmark_(benchmark) {
set_log_level(log_level);
}
@@ -37,9 +42,9 @@ int VideoProcessor::process(
);
// Initialize hardware device context
- if (hw_cfg_.hw_device_type != AV_HWDEVICE_TYPE_NONE) {
+ if (hw_device_type_ != AV_HWDEVICE_TYPE_NONE) {
AVBufferRef *tmp_hw_ctx = nullptr;
- ret = av_hwdevice_ctx_create(&tmp_hw_ctx, hw_cfg_.hw_device_type, NULL, NULL, 0);
+ ret = av_hwdevice_ctx_create(&tmp_hw_ctx, hw_device_type_, NULL, NULL, 0);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::critical("Error initializing hardware device context: {}", errbuf);
@@ -50,7 +55,7 @@ int VideoProcessor::process(
// Initialize input decoder
Decoder decoder;
- ret = decoder.init(hw_cfg_.hw_device_type, hw_ctx.get(), in_fname);
+ ret = decoder.init(hw_device_type_, hw_ctx.get(), in_fname);
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::critical("Failed to initialize decoder: {}", errbuf);
@@ -63,7 +68,7 @@ int VideoProcessor::process(
// Create and initialize the appropriate filter
std::unique_ptr processor(
- ProcessorFactory::instance().create_processor(proc_cfg_, hw_cfg_.vk_device_index)
+ ProcessorFactory::instance().create_processor(proc_cfg_, vk_device_index_)
);
if (processor == nullptr) {
spdlog::critical("Failed to create filter instance");
@@ -80,16 +85,21 @@ int VideoProcessor::process(
return -1;
}
- // Update encoder output dimensions
- enc_cfg_.width = output_width;
- enc_cfg_.height = output_height;
-
// Update encoder frame rate multiplier
enc_cfg_.frm_rate_mul = proc_cfg_.frm_rate_mul;
// Initialize the encoder
Encoder encoder;
- ret = encoder.init(hw_ctx.get(), out_fname, ifmt_ctx, dec_ctx, enc_cfg_, in_vstream_idx);
+ ret = encoder.init(
+ hw_ctx.get(),
+ out_fname,
+ ifmt_ctx,
+ dec_ctx,
+ enc_cfg_,
+ output_width,
+ output_height,
+ in_vstream_idx
+ );
if (ret < 0) {
av_strerror(ret, errbuf, sizeof(errbuf));
spdlog::critical("Failed to initialize encoder: {}", errbuf);
diff --git a/tools/video2x/include/argparse.h b/tools/video2x/include/argparse.h
new file mode 100644
index 0000000..ff2c1f2
--- /dev/null
+++ b/tools/video2x/include/argparse.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include
+
+// Structure to hold parsed arguments
+struct Arguments {
+ Video2xLogLevel log_level = Video2xLogLevel::Info;
+ bool no_progress = false;
+
+ // General options
+ std::filesystem::path in_fname;
+ std::filesystem::path out_fname;
+ uint32_t vk_device_index = 0;
+ AVHWDeviceType hw_device_type = AV_HWDEVICE_TYPE_NONE;
+ bool benchmark = false;
+};
+
+[[nodiscard]] int parse_args(
+ int argc,
+#ifdef _WIN32
+ wchar_t *argv[],
+#else
+ char *argv[],
+#endif
+ Arguments &arguments,
+ ProcessorConfig &proc_cfg,
+ EncoderConfig &enc_cfg
+);
diff --git a/tools/video2x/include/logging.h b/tools/video2x/include/logging.h
new file mode 100644
index 0000000..adf0135
--- /dev/null
+++ b/tools/video2x/include/logging.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+extern std::atomic newline_required;
+
+void set_spdlog_level(Video2xLogLevel log_level);
+
+std::optional find_log_level_by_name(const StringType &log_level_name);
+
+void newline_safe_ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl);
diff --git a/tools/video2x/include/timer.h b/tools/video2x/include/timer.h
index 2d4907e..7e880e0 100644
--- a/tools/video2x/include/timer.h
+++ b/tools/video2x/include/timer.h
@@ -1,5 +1,4 @@
-#ifndef TIMER_H
-#define TIMER_H
+#pragma once
#include
#include
@@ -30,5 +29,3 @@ class Timer {
void update_elapsed_time();
};
-
-#endif // TIMER_H
diff --git a/tools/video2x/include/validators.h b/tools/video2x/include/validators.h
new file mode 100644
index 0000000..7177df5
--- /dev/null
+++ b/tools/video2x/include/validators.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include
+#include
+
+namespace po = boost::program_options;
+
+template
+void validate_positive(const T &value, const std::string &option_name) {
+ if (value < 0) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ option_name,
+ option_name + " must be positive"
+ );
+ }
+}
+
+template
+void validate_min(const T &value, const std::string &option_name, const T &min) {
+ if (value < min) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ option_name,
+ option_name + " must be at least " + std::to_string(min)
+ );
+ }
+}
+
+template
+void validate_max(const T &value, const std::string &option_name, const T &max) {
+ if (value > max) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ option_name,
+ option_name + " must be at most " + std::to_string(max)
+ );
+ }
+}
+
+template
+void validate_range(const T &value, const std::string &option_name, const T &min, const T &max) {
+ if (value < min || value > max) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ option_name,
+ option_name + " must be in the range [" + std::to_string(min) + ", " +
+ std::to_string(max) + "]"
+ );
+ }
+}
+
+template
+void validate_greater_equal_one(const T &value, const std::string &option_name) {
+ if (value < 1) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ option_name,
+ option_name + " must be greater than or equal to 1"
+ );
+ }
+}
+
+void validate_anime4k_shader_name(const StringType &shader_name);
+
+void validate_realesrgan_model_name(const StringType &model_name);
+
+void validate_rife_model_name(const StringType &model_name);
diff --git a/tools/video2x/include/vulkan_utils.h b/tools/video2x/include/vulkan_utils.h
new file mode 100644
index 0000000..26ee298
--- /dev/null
+++ b/tools/video2x/include/vulkan_utils.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include
+
+int list_vulkan_devices();
+
+int get_vulkan_device_prop(uint32_t vk_device_index, VkPhysicalDeviceProperties *dev_props);
diff --git a/tools/video2x/src/argparse.cpp b/tools/video2x/src/argparse.cpp
new file mode 100644
index 0000000..1426a30
--- /dev/null
+++ b/tools/video2x/src/argparse.cpp
@@ -0,0 +1,423 @@
+#include "argparse.h"
+
+#include
+
+#if _WIN32
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "logging.h"
+#include "validators.h"
+
+#ifdef _WIN32
+#define BOOST_PROGRAM_OPTIONS_WCHAR_T
+#define PO_STR_VALUE po::wvalue
+#else
+#define PO_STR_VALUE po::value
+#endif
+
+namespace po = boost::program_options;
+
+#ifdef _WIN32
+std::string wstring_to_u8string(const std::wstring &wstr) {
+ if (wstr.empty()) {
+ return std::string();
+ }
+ int size_needed = WideCharToMultiByte(
+ CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr
+ );
+ std::string converted_str(size_needed, 0);
+ WideCharToMultiByte(
+ CP_UTF8,
+ 0,
+ wstr.data(),
+ static_cast(wstr.size()),
+ &converted_str[0],
+ size_needed,
+ nullptr,
+ nullptr
+ );
+ return converted_str;
+}
+#else
+std::string wstring_to_u8string(const std::string &str) {
+ return str;
+}
+#endif
+
+int parse_args(
+ int argc,
+#ifdef _WIN32
+ wchar_t *argv[],
+#else
+ char *argv[],
+#endif
+ Arguments &arguments,
+ ProcessorConfig &proc_cfg,
+ EncoderConfig &enc_cfg
+) {
+ try {
+ // clang-format off
+ po::options_description all_opts("General options");
+ all_opts.add_options()
+ ("help", "Display this help page")
+ ("version,V", "Print program version and exit")
+ ("log-level", PO_STR_VALUE()->default_value(STR("info"), "info"),
+ "Set verbosity level (trace, debug, info, warn, error, critical, none)")
+ ("no-progress", po::bool_switch(&arguments.no_progress),
+ "Do not display the progress bar")
+ ("list-devices,l", "List the available Vulkan devices (GPUs)")
+
+ // General Processing Options
+ ("input,i", PO_STR_VALUE(), "Input video file path")
+ ("output,o", PO_STR_VALUE(), "Output video file path")
+ ("processor,p", PO_STR_VALUE(),
+ "Processor to use (libplacebo, realesrgan, rife)")
+ ("hwaccel,a", PO_STR_VALUE()->default_value(STR("none"), "none"),
+ "Hardware acceleration method (decoding)")
+ ("device,d", po::value(&arguments.vk_device_index)->default_value(0),
+ "Vulkan device index (GPU ID)")
+ ("benchmark,b", po::bool_switch(&arguments.benchmark),
+ "Discard processed frames and calculate average FPS; "
+ "useful for detecting encoder bottlenecks")
+ ;
+
+ po::options_description encoder_opts("Encoder options");
+ encoder_opts.add_options()
+ ("codec,c", PO_STR_VALUE()->default_value(STR("libx264"), "libx264"),
+ "Output codec")
+ ("no-copy-streams", "Do not copy audio and subtitle streams")
+ ("pix-fmt", PO_STR_VALUE(), "Output pixel format")
+ ("bit-rate", po::value(&enc_cfg.bit_rate)->default_value(0),
+ "Bitrate in bits per second")
+ ("rc-buffer-size", po::value(&enc_cfg.rc_buffer_size)->default_value(0),
+ "Rate control buffer size in bits")
+ ("rc-min-rate", po::value(&enc_cfg.rc_min_rate)->default_value(0),
+ "Minimum rate control")
+ ("rc-max-rate", po::value(&enc_cfg.rc_max_rate)->default_value(0),
+ "Maximum rate control")
+ ("qmin", po::value(&enc_cfg.qmin)->default_value(-1), "Minimum quantizer")
+ ("qmax", po::value(&enc_cfg.qmax)->default_value(-1), "Maximum quantizer")
+ ("gop-size", po::value(&enc_cfg.gop_size)->default_value(-1),
+ "Group of pictures structure size")
+ ("max-b-frames", po::value(&enc_cfg.max_b_frames)->default_value(-1),
+ "Maximum number of B-frames")
+ ("keyint-min", po::value(&enc_cfg.keyint_min)->default_value(-1),
+ "Minimum interval between keyframes")
+ ("refs", po::value(&enc_cfg.refs)->default_value(-1),
+ "Number of reference frames")
+ ("thread-count", po::value(&enc_cfg.thread_count)->default_value(0),
+ "Number of threads for encoding")
+ ("delay", po::value(&enc_cfg.delay)->default_value(0),
+ "Delay in milliseconds for encoder")
+
+ // Extra encoder options (key-value pairs)
+ ("extra-encoder-option,e", PO_STR_VALUE>()->multitoken(),
+ "Additional AVOption(s) for the encoder (format: -e key=value)")
+ ;
+
+ po::options_description upscale_opts("Upscaling options");
+ upscale_opts.add_options()
+ ("width,w", po::value(&proc_cfg.width)
+ ->notifier([](int v) { validate_greater_equal_one(v, "width"); }), "Output width")
+ ("height,h", po::value(&proc_cfg.height)
+ ->notifier([](int v) { validate_greater_equal_one(v, "height"); }), "Output height")
+ ("scaling-factor,s", po::value(&proc_cfg.scaling_factor)
+ ->notifier([](int v) { validate_min(v, "scaling-factor", 2); }), "Scaling factor")
+ ;
+
+ po::options_description interp_opts("Frame interpolation options");
+ interp_opts.add_options()
+ ("frame-rate-mul,m", po::value(&proc_cfg.frm_rate_mul)
+ ->notifier([](int v) { validate_min(v, "frame-rate-mul", 2); }),
+ "Frame rate multiplier")
+ ("scene-thresh,t", po::value(&proc_cfg.scn_det_thresh)->default_value(20.0f)
+ ->notifier([](float v) { validate_range(v, "scene-thresh", 0.0, 100.0); }),
+ "Scene detection threshold")
+ ;
+
+ po::options_description libplacebo_opts("libplacebo options");
+ libplacebo_opts.add_options()
+ ("libplacebo-shader", PO_STR_VALUE()
+ ->default_value(STR("anime4k-v4-a"), "anime4k-v4-a")
+ ->notifier(validate_anime4k_shader_name),
+ "Name/path of the GLSL shader file to use (built-in: anime4k-v4-a, anime4k-v4-a+a, "
+ "anime4k-v4-b, anime4k-v4-b+b, anime4k-v4-c, anime4k-v4-c+a, anime4k-v4.1-gan)")
+ ;
+
+ po::options_description realesrgan_opts("RealESRGAN options");
+ realesrgan_opts.add_options()
+ ("realesrgan-model", PO_STR_VALUE()
+ ->default_value(STR("realesr-animevideov3"), "realesr-animevideov3")
+ ->notifier(validate_realesrgan_model_name),
+ "Name of the RealESRGAN model to use (realesr-animevideov3, realesrgan-plus-anime, "
+ "realesrgan-plus)")
+ ;
+
+ po::options_description rife_opts("RIFE options");
+ rife_opts.add_options()
+ ("rife-model", PO_STR_VALUE()->default_value(STR("rife-v4.6"), "rife-v4.6")
+ ->notifier(validate_rife_model_name),
+ "Name of the RIFE model to use (rife, rife-HD, rife-UHD, rife-anime, rife-v2, "
+ "rife-v2.3, rife-v2.4, rife-v3.0, rife-v3.1, rife-v4, rife-v4.6)")
+ ("rife-uhd", "Enable Ultra HD mode")
+ ;
+ // clang-format on
+
+ // Combine all options
+ all_opts.add(encoder_opts)
+ .add(upscale_opts)
+ .add(interp_opts)
+ .add(libplacebo_opts)
+ .add(realesrgan_opts)
+ .add(rife_opts);
+
+ po::variables_map vm;
+#ifdef _WIN32
+ po::store(po::wcommand_line_parser(argc, argv).options(all_opts).run(), vm);
+#else
+ po::store(po::command_line_parser(argc, argv).options(all_opts).run(), vm);
+#endif
+ po::notify(vm);
+
+ if (vm.count("help") || argc == 1) {
+ std::cout
+ << all_opts << std::endl
+ << "Examples:" << std::endl
+ << " Upscale an anime video to 4K using libplacebo:" << std::endl
+ << " video2x -i input.mp4 -o output.mp4 -w 3840 -h 2160 \\" << std::endl
+ << " -p libplacebo --libplacebo-shader anime4k-v4-a+a" << std::endl
+ << std::endl
+ << " Upscale a film by 4x using RealESRGAN with custom encoder options:"
+ << std::endl
+ << " video2x -i input.mkv -o output.mkv -s 4 \\" << std::endl
+ << " -p realesrgan --realesrgan-model realesrgan-plus \\" << std::endl
+ << " -c libx264rgb -e crf=17 -e preset=veryslow -e tune=film" << std::endl
+ << std::endl
+ << " Frame-interpolate a video using RIFE to 4x the original frame rate:"
+ << std::endl
+ << " video2x -i input.mp4 -o output.mp4 -m 4 -p rife --rife-model rife-v4.6"
+ << std::endl;
+ return 1;
+ }
+
+ if (vm.count("version")) {
+ std::cout << "Video2X version " << LIBVIDEO2X_VERSION_STRING << std::endl;
+ return 1;
+ }
+
+ if (vm.count("list-devices")) {
+ if (list_vulkan_devices()) {
+ return -1;
+ }
+ return 1;
+ }
+
+ if (vm.count("log-level")) {
+ std::optional log_level =
+ find_log_level_by_name(vm["log-level"].as());
+ if (!log_level.has_value()) {
+ spdlog::critical("Invalid log level specified.");
+ return -1;
+ }
+ arguments.log_level = log_level.value();
+ }
+ set_spdlog_level(arguments.log_level);
+
+ // Print program banner
+ spdlog::info("Video2X version {}", LIBVIDEO2X_VERSION_STRING);
+ // spdlog::info("Copyright (C) 2018-2024 K4YT3X and contributors.");
+ // spdlog::info("Licensed under GNU AGPL version 3.");
+
+ // Assign positional arguments
+ if (vm.count("input")) {
+ arguments.in_fname = std::filesystem::path(vm["input"].as());
+ spdlog::info("Processing file: {}", arguments.in_fname.u8string());
+ } else {
+ spdlog::critical("Input file path is required.");
+ return -1;
+ }
+
+ if (vm.count("output")) {
+ arguments.out_fname = std::filesystem::path(vm["output"].as());
+ } else if (!arguments.benchmark) {
+ spdlog::critical("Output file path is required.");
+ return -1;
+ }
+
+ // Parse processor type
+ if (vm.count("processor")) {
+ StringType processor_type_str = vm["processor"].as();
+ if (processor_type_str == STR("libplacebo")) {
+ proc_cfg.processor_type = ProcessorType::Libplacebo;
+ } else if (processor_type_str == STR("realesrgan")) {
+ proc_cfg.processor_type = ProcessorType::RealESRGAN;
+ } else if (processor_type_str == STR("rife")) {
+ proc_cfg.processor_type = ProcessorType::RIFE;
+ } else {
+ spdlog::critical(
+ "Invalid processor specified. Must be 'libplacebo', 'realesrgan', or 'rife'."
+ );
+ return -1;
+ }
+ } else {
+ spdlog::critical("Processor type is required.");
+ return -1;
+ }
+
+ // Parse hardware acceleration method
+ arguments.hw_device_type = AV_HWDEVICE_TYPE_NONE;
+ if (vm.count("hwaccel")) {
+ StringType hwaccel_str = vm["hwaccel"].as();
+ if (hwaccel_str != STR("none")) {
+ arguments.hw_device_type =
+ av_hwdevice_find_type_by_name(wstring_to_u8string(hwaccel_str).c_str());
+ if (arguments.hw_device_type == AV_HWDEVICE_TYPE_NONE) {
+ spdlog::critical(
+ "Invalid hardware device type '{}'.", wstring_to_u8string(hwaccel_str)
+ );
+ return -1;
+ }
+ }
+ }
+
+ // Parse codec to AVCodec
+ enc_cfg.codec = AV_CODEC_ID_H264;
+ if (vm.count("codec")) {
+ StringType codec_str = vm["codec"].as();
+ const AVCodec *codec =
+ avcodec_find_encoder_by_name(wstring_to_u8string(codec_str).c_str());
+ if (codec == nullptr) {
+ spdlog::critical("Codec '{}' not found.", wstring_to_u8string(codec_str));
+ return -1;
+ }
+ enc_cfg.codec = codec->id;
+ }
+
+ // Parse copy streams flag
+ enc_cfg.copy_streams = vm.count("no-copy-streams") == 0;
+
+ // Parse pixel format to AVPixelFormat
+ enc_cfg.pix_fmt = AV_PIX_FMT_NONE;
+ if (vm.count("pix-fmt")) {
+ StringType pix_fmt_str = vm["pix-fmt"].as();
+ if (!pix_fmt_str.empty()) {
+ enc_cfg.pix_fmt = av_get_pix_fmt(wstring_to_u8string(pix_fmt_str).c_str());
+ if (enc_cfg.pix_fmt == AV_PIX_FMT_NONE) {
+ spdlog::critical(
+ "Invalid pixel format '{}'.", wstring_to_u8string(pix_fmt_str)
+ );
+ return -1;
+ }
+ }
+ }
+
+ // Parse extra AVOptions
+ if (vm.count("extra-encoder-option")) {
+ for (const auto &opt : vm["extra-encoder-option"].as>()) {
+ size_t eq_pos = opt.find('=');
+ if (eq_pos != StringType::npos) {
+ StringType key = opt.substr(0, eq_pos);
+ StringType value = opt.substr(eq_pos + 1);
+ enc_cfg.extra_opts.push_back(std::make_pair(key, value));
+ } else {
+ spdlog::critical("Invalid extra AVOption format: {}", wstring_to_u8string(opt));
+ return -1;
+ }
+ }
+ }
+
+ // Parse processor-specific configurations
+ switch (proc_cfg.processor_type) {
+ case ProcessorType::Libplacebo: {
+ if (!vm.count("libplacebo-shader")) {
+ spdlog::critical("Shader name/path must be set for libplacebo.");
+ return -1;
+ }
+ if (proc_cfg.width <= 0 || proc_cfg.height <= 0) {
+ spdlog::critical("Output width and height must be set for libplacebo.");
+ return -1;
+ }
+
+ proc_cfg.processor_type = ProcessorType::Libplacebo;
+ LibplaceboConfig libplacebo_config;
+ libplacebo_config.shader_path = vm["libplacebo-shader"].as();
+ proc_cfg.config = libplacebo_config;
+ break;
+ }
+ case ProcessorType::RealESRGAN: {
+ if (!vm.count("realesrgan-model")) {
+ spdlog::critical("RealESRGAN model name must be set for RealESRGAN.");
+ return -1;
+ }
+ if (proc_cfg.scaling_factor != 2 && proc_cfg.scaling_factor != 3 &&
+ proc_cfg.scaling_factor != 4) {
+ spdlog::critical("Scaling factor must be set to 2, 3, or 4 for RealESRGAN.");
+ return -1;
+ }
+
+ proc_cfg.processor_type = ProcessorType::RealESRGAN;
+ RealESRGANConfig realesrgan_config;
+ realesrgan_config.tta_mode = false;
+ realesrgan_config.model_name = vm["realesrgan-model"].as();
+ proc_cfg.config = realesrgan_config;
+ break;
+ }
+ case ProcessorType::RIFE: {
+ if (!vm.count("rife-model")) {
+ spdlog::critical("RIFE model name must be set for RIFE.");
+ return -1;
+ }
+ if (proc_cfg.frm_rate_mul < 2) {
+ spdlog::critical("Frame rate multiplier must be set to at least 2 for RIFE.");
+ return -1;
+ }
+
+ proc_cfg.processor_type = ProcessorType::RIFE;
+ RIFEConfig rife_config;
+ rife_config.tta_mode = false;
+ rife_config.tta_temporal_mode = false;
+ rife_config.uhd_mode = vm.count("rife-uhd") > 0;
+ rife_config.num_threads = 0;
+ rife_config.model_name = vm["rife-model"].as();
+ proc_cfg.config = rife_config;
+ break;
+ }
+ default:
+ spdlog::critical("Invalid processor type.");
+ return -1;
+ }
+ } catch (const po::error &e) {
+ spdlog::critical("Error parsing arguments: {}", e.what());
+ return -1;
+ } catch (const std::exception &e) {
+ spdlog::critical("Unexpected exception caught while parsing options: {}", e.what());
+ return -1;
+ }
+
+ // Validate Vulkan device ID
+ VkPhysicalDeviceProperties dev_props;
+ int get_vulkan_dev_ret = get_vulkan_device_prop(arguments.vk_device_index, &dev_props);
+ if (get_vulkan_dev_ret != 0) {
+ if (get_vulkan_dev_ret == -2) {
+ spdlog::critical("Invalid Vulkan device ID specified.");
+ return -1;
+ } else {
+ spdlog::warn("Unable to validate Vulkan device ID.");
+ return -1;
+ }
+ } else {
+ // Warn if the selected device is a CPU
+ spdlog::info("Using Vulkan device: {} ({:#x})", dev_props.deviceName, dev_props.deviceID);
+ if (dev_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
+ spdlog::warn("The selected Vulkan device is a CPU device.");
+ }
+ }
+ return 0;
+}
diff --git a/tools/video2x/src/logging.cpp b/tools/video2x/src/logging.cpp
new file mode 100644
index 0000000..a04a185
--- /dev/null
+++ b/tools/video2x/src/logging.cpp
@@ -0,0 +1,77 @@
+#include "logging.h"
+
+#include
+#include
+
+extern "C" {
+#include
+}
+
+std::atomic newline_required = false;
+
+void set_spdlog_level(Video2xLogLevel log_level) {
+ switch (log_level) {
+ case Video2xLogLevel::Trace:
+ spdlog::set_level(spdlog::level::trace);
+ break;
+ case Video2xLogLevel::Debug:
+ spdlog::set_level(spdlog::level::debug);
+ break;
+ case Video2xLogLevel::Info:
+ spdlog::set_level(spdlog::level::info);
+ break;
+ case Video2xLogLevel::Warning:
+ spdlog::set_level(spdlog::level::warn);
+ break;
+ case Video2xLogLevel::Error:
+ spdlog::set_level(spdlog::level::err);
+ break;
+ case Video2xLogLevel::Critical:
+ spdlog::set_level(spdlog::level::critical);
+ break;
+ case Video2xLogLevel::Off:
+ spdlog::set_level(spdlog::level::off);
+ break;
+ default:
+ spdlog::set_level(spdlog::level::info);
+ break;
+ }
+}
+
+std::optional find_log_level_by_name(const StringType &log_level_name) {
+ // Static map to store the mapping
+ static const std::unordered_map log_level_map = {
+ {STR("trace"), Video2xLogLevel::Trace},
+ {STR("debug"), Video2xLogLevel::Debug},
+ {STR("info"), Video2xLogLevel::Info},
+ {STR("warning"), Video2xLogLevel::Warning},
+ {STR("warn"), Video2xLogLevel::Warning},
+ {STR("error"), Video2xLogLevel::Error},
+ {STR("critical"), Video2xLogLevel::Critical},
+ {STR("off"), Video2xLogLevel::Off},
+ {STR("none"), Video2xLogLevel::Off}
+ };
+
+ // Normalize the input to lowercase
+ StringType normalized_name = log_level_name;
+ std::transform(
+ normalized_name.begin(), normalized_name.end(), normalized_name.begin(), ::tolower
+ );
+
+ // Lookup the log level in the map
+ auto it = log_level_map.find(normalized_name);
+ if (it != log_level_map.end()) {
+ return it->second;
+ }
+
+ return std::nullopt;
+}
+
+// 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.load()) {
+ putchar('\n');
+ newline_required.store(false);
+ }
+ av_log_default_callback(ptr, level, fmt, vl);
+}
diff --git a/tools/video2x/src/validators.cpp b/tools/video2x/src/validators.cpp
new file mode 100644
index 0000000..b3f9594
--- /dev/null
+++ b/tools/video2x/src/validators.cpp
@@ -0,0 +1,61 @@
+#include "validators.h"
+
+#include
+
+void validate_anime4k_shader_name(const StringType &shader_name) {
+ static const std::unordered_set valid_anime4k_shaders = {
+ STR("anime4k-v4-a"),
+ STR("anime4k-v4-a+a"),
+ STR("anime4k-v4-b"),
+ STR("anime4k-v4-b+b"),
+ STR("anime4k-v4-c"),
+ STR("anime4k-v4-c+a"),
+ STR("anime4k-v4.1-gan")
+ };
+ if (valid_anime4k_shaders.count(shader_name) == 0 && !std::filesystem::exists(shader_name)) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ "libplacebo-shader",
+ "libplacebo-shader must be one of: anime4k-v4-a, anime4k-v4-a+a, anime4k-v4-b, "
+ "anime4k-v4-b+b, anime4k-v4-c, anime4k-v4-c+a, anime4k-v4.1-gan, or a valid file path"
+ );
+ }
+}
+
+void validate_realesrgan_model_name(const StringType &model_name) {
+ static const std::unordered_set valid_realesrgan_models = {
+ STR("realesrgan-plus"), STR("realesrgan-plus-anime"), STR("realesr-animevideov3")
+ };
+ if (valid_realesrgan_models.count(model_name) == 0) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ "realesrgan-model",
+ "realesrgan-model must be one of: realesr-animevideov3, realesrgan-plus-anime, "
+ "realesrgan-plus"
+ );
+ }
+}
+
+void validate_rife_model_name(const StringType &model_name) {
+ static const std::unordered_set valid_realesrgan_models = {
+ STR("rife"),
+ STR("rife-HD"),
+ STR("rife-UHD"),
+ STR("rife-anime"),
+ STR("rife-v2"),
+ STR("rife-v2.3"),
+ STR("rife-v2.4"),
+ STR("rife-v3.0"),
+ STR("rife-v3.1"),
+ STR("rife-v4"),
+ STR("rife-v4.6"),
+ };
+ if (valid_realesrgan_models.count(model_name) == 0) {
+ throw po::validation_error(
+ po::validation_error::invalid_option_value,
+ "rife-model",
+ "RIFE model must be one of: rife, rife-HD, rife-UHD, rife-anime, rife-v2, rife-v2.3, "
+ "rife-v2.4, rife-v3.0, rife-v3.1, rife-v4, rife-v4.6"
+ );
+ }
+}
diff --git a/tools/video2x/src/video2x.cpp b/tools/video2x/src/video2x.cpp
index a7fad8f..9ebf26c 100644
--- a/tools/video2x/src/video2x.cpp
+++ b/tools/video2x/src/video2x.cpp
@@ -1,17 +1,4 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
-#include
-#include
-#include
#ifdef _WIN32
#include
@@ -22,82 +9,12 @@
#include
#endif
-extern "C" {
-#include
-#include
-#include
-#include
-#include
-}
-
-#include
-#include
#include
-#include
-
-#ifdef _WIN32
-#define BOOST_PROGRAM_OPTIONS_WCHAR_T
-#define PO_STR_VALUE po::wvalue
-#else
-#define PO_STR_VALUE po::value
-#endif
-#include
-namespace po = boost::program_options;
+#include "argparse.h"
+#include "logging.h"
#include "timer.h"
-// Indicate if a newline needs to be printed before the next output
-std::atomic newline_required = false;
-
-// Structure to hold parsed arguments
-struct Arguments {
- Video2xLogLevel log_level = Video2xLogLevel::Info;
- bool no_progress = false;
-
- // General options
- std::filesystem::path in_fname;
- std::filesystem::path out_fname;
- StringType processor_type;
- StringType hwaccel = STR("none");
- uint32_t vk_device_index = 0;
- bool no_copy_streams = false;
- bool benchmark = false;
-
- // Encoder options
- StringType codec = STR("libx264");
- StringType pix_fmt;
- int64_t bit_rate = 0;
- int rc_buffer_size = 0;
- int rc_min_rate = 0;
- int rc_max_rate = 0;
- int qmin = -1;
- int qmax = -1;
- int gop_size = -1;
- int max_b_frames = -1;
- int keyint_min = -1;
- int refs = -1;
- int thread_count = 0;
- int delay = 0;
- std::vector> extra_encoder_opts;
-
- // General processing options
- int width = 0;
- int height = 0;
- int scaling_factor = 0;
- int frm_rate_mul = 2;
- float scn_det_thresh = 0.0f;
-
- // libplacebo options
- StringType libplacebo_shader_path;
-
- // RealESRGAN options
- StringType realesrgan_model_name = STR("realesr-animevideov3");
-
- // RIFE options
- StringType rife_model_name = STR("rife-v4.6");
- bool rife_uhd_mode = false;
-};
-
// Set UNIX terminal input to non-blocking mode
#ifndef _WIN32
void set_nonblocking_input(bool enable) {
@@ -115,241 +32,11 @@ void set_nonblocking_input(bool enable) {
}
#endif
-#ifdef _WIN32
-std::string wstring_to_u8string(const std::wstring &wstr) {
- if (wstr.empty()) {
- return std::string();
- }
- int size_needed = WideCharToMultiByte(
- CP_UTF8, 0, wstr.data(), static_cast(wstr.size()), nullptr, 0, nullptr, nullptr
- );
- std::string converted_str(size_needed, 0);
- WideCharToMultiByte(
- CP_UTF8,
- 0,
- wstr.data(),
- static_cast(wstr.size()),
- &converted_str[0],
- size_needed,
- nullptr,
- nullptr
- );
- return converted_str;
-}
-#else
-std::string wstring_to_u8string(const std::string &str) {
- return str;
-}
-#endif
-
-void set_spdlog_level(Video2xLogLevel log_level) {
- switch (log_level) {
- case Video2xLogLevel::Trace:
- spdlog::set_level(spdlog::level::trace);
- break;
- case Video2xLogLevel::Debug:
- spdlog::set_level(spdlog::level::debug);
- break;
- case Video2xLogLevel::Info:
- spdlog::set_level(spdlog::level::info);
- break;
- case Video2xLogLevel::Warning:
- spdlog::set_level(spdlog::level::warn);
- break;
- case Video2xLogLevel::Error:
- spdlog::set_level(spdlog::level::err);
- break;
- case Video2xLogLevel::Critical:
- spdlog::set_level(spdlog::level::critical);
- break;
- case Video2xLogLevel::Off:
- spdlog::set_level(spdlog::level::off);
- break;
- default:
- spdlog::set_level(spdlog::level::info);
- break;
- }
-}
-
-std::optional find_log_level_by_name(const StringType &log_level_name) {
- // Static map to store the mapping
- static const std::unordered_map log_level_map = {
- {STR("trace"), Video2xLogLevel::Trace},
- {STR("debug"), Video2xLogLevel::Debug},
- {STR("info"), Video2xLogLevel::Info},
- {STR("warning"), Video2xLogLevel::Warning},
- {STR("warn"), Video2xLogLevel::Warning},
- {STR("error"), Video2xLogLevel::Error},
- {STR("critical"), Video2xLogLevel::Critical},
- {STR("off"), Video2xLogLevel::Off},
- {STR("none"), Video2xLogLevel::Off}
- };
-
- // Normalize the input to lowercase
- StringType normalized_name = log_level_name;
- std::transform(
- normalized_name.begin(), normalized_name.end(), normalized_name.begin(), ::tolower
- );
-
- // Lookup the log level in the map
- auto it = log_level_map.find(normalized_name);
- if (it != log_level_map.end()) {
- return it->second;
- }
-
- return std::nullopt;
-}
-
-// Newline-safe log callback for FFmpeg
-void newline_safe_ffmpeg_log_callback(void *ptr, int level, const char *fmt, va_list vl) {
- if (level <= av_log_get_level() && newline_required) {
- putchar('\n');
- newline_required = false;
- }
- av_log_default_callback(ptr, level, fmt, vl);
-}
-
-bool is_valid_realesrgan_model(const StringType &model) {
- static const std::unordered_set valid_realesrgan_models = {
- STR("realesrgan-plus"), STR("realesrgan-plus-anime"), STR("realesr-animevideov3")
- };
- return valid_realesrgan_models.count(model) > 0;
-}
-
-bool is_valid_rife_model(const StringType &model) {
- static const std::unordered_set valid_realesrgan_models = {
- STR("rife"),
- STR("rife-HD"),
- STR("rife-UHD"),
- STR("rife-anime"),
- STR("rife-v2"),
- STR("rife-v2.3"),
- STR("rife-v2.4"),
- STR("rife-v3.0"),
- STR("rife-v3.1"),
- STR("rife-v4"),
- STR("rife-v4.6"),
- };
- return valid_realesrgan_models.count(model) > 0;
-}
-
-int enumerate_vulkan_devices(VkInstance *instance, std::vector &devices) {
- // Create a Vulkan instance
- VkInstanceCreateInfo create_info{};
- create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
-
- VkResult result = vkCreateInstance(&create_info, nullptr, instance);
- if (result != VK_SUCCESS) {
- spdlog::error("Failed to create Vulkan instance.");
- return -1;
- }
-
- // Enumerate physical devices
- uint32_t device_count = 0;
- result = vkEnumeratePhysicalDevices(*instance, &device_count, nullptr);
- if (result != VK_SUCCESS || device_count == 0) {
- spdlog::error("Failed to enumerate Vulkan physical devices or no devices available.");
- vkDestroyInstance(*instance, nullptr);
- return -1;
- }
-
- devices.resize(device_count);
- result = vkEnumeratePhysicalDevices(*instance, &device_count, devices.data());
- if (result != VK_SUCCESS) {
- spdlog::error("Failed to retrieve Vulkan physical devices.");
- vkDestroyInstance(*instance, nullptr);
- return -1;
- }
-
- return 0;
-}
-
-int list_vulkan_devices() {
- VkInstance instance;
- std::vector physical_devices;
- int result = enumerate_vulkan_devices(&instance, physical_devices);
- if (result != 0) {
- return result;
- }
-
- uint32_t device_count = static_cast(physical_devices.size());
-
- // List Vulkan device information
- for (uint32_t i = 0; i < device_count; i++) {
- VkPhysicalDevice device = physical_devices[i];
- VkPhysicalDeviceProperties device_properties;
- vkGetPhysicalDeviceProperties(device, &device_properties);
-
- // Print Vulkan device ID and name
- std::cout << i << ". " << device_properties.deviceName << std::endl;
- std::cout << "\tType: ";
- switch (device_properties.deviceType) {
- case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
- std::cout << "Integrated GPU";
- break;
- case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
- std::cout << "Discrete GPU";
- break;
- case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
- std::cout << "Virtual GPU";
- break;
- case VK_PHYSICAL_DEVICE_TYPE_CPU:
- std::cout << "CPU";
- break;
- default:
- std::cout << "Unknown";
- break;
- }
- std::cout << std::endl;
-
- // Print Vulkan API version
- std::cout << "\tVulkan API Version: " << VK_VERSION_MAJOR(device_properties.apiVersion)
- << "." << VK_VERSION_MINOR(device_properties.apiVersion) << "."
- << VK_VERSION_PATCH(device_properties.apiVersion) << std::endl;
-
- // Print driver version
- std::cout << "\tDriver Version: " << VK_VERSION_MAJOR(device_properties.driverVersion)
- << "." << VK_VERSION_MINOR(device_properties.driverVersion) << "."
- << VK_VERSION_PATCH(device_properties.driverVersion) << std::endl;
-
- // Print device ID
- std::cout << "\tDevice ID: " << std::hex << std::showbase << device_properties.deviceID
- << std::dec << std::endl;
- }
-
- // Clean up Vulkan instance
- vkDestroyInstance(instance, nullptr);
- return 0;
-}
-
-int get_vulkan_device_prop(uint32_t vk_device_index, VkPhysicalDeviceProperties *dev_props) {
- if (dev_props == nullptr) {
- spdlog::error("Invalid device properties pointer.");
- return -1;
- }
-
- VkInstance instance;
- std::vector devices;
- int result = enumerate_vulkan_devices(&instance, devices);
- if (result != 0) {
- return result;
- }
-
- uint32_t device_count = static_cast(devices.size());
-
- // Check if the Vulkan device ID is valid
- if (vk_device_index >= device_count) {
- vkDestroyInstance(instance, nullptr);
- return -2;
- }
-
- // Get device properties for the specified Vulkan device ID
- vkGetPhysicalDeviceProperties(devices[vk_device_index], dev_props);
-
- // Clean up Vulkan instance
- vkDestroyInstance(instance, nullptr);
-
- return 0;
+std::tuple calculate_time_components(int time_elapsed) {
+ int hours_elapsed = time_elapsed / 3600;
+ int minutes_elapsed = (time_elapsed % 3600) / 60;
+ int seconds_elapsed = time_elapsed % 60;
+ return {hours_elapsed, minutes_elapsed, seconds_elapsed};
}
#ifdef _WIN32
@@ -366,385 +53,33 @@ int wmain(int argc, wchar_t *argv[]) {
#else
int main(int argc, char **argv) {
#endif
- // Initialize arguments structure
+ // Initialize arguments structures
Arguments arguments;
-
- // Parse command line arguments using Boost.Program_options
- try {
- // clang-format off
- po::options_description all_opts("General options");
- all_opts.add_options()
- ("help", "Display this help page")
- ("version,V", "Print program version and exit")
- ("log-level", PO_STR_VALUE()->default_value(STR("info"), "info"),
- "Set verbosity level (trace, debug, info, warn, error, critical, none)")
- ("no-progress", po::bool_switch(&arguments.no_progress),
- "Do not display the progress bar")
- ("list-devices,l", "List the available Vulkan devices (GPUs)")
-
- // General Processing Options
- ("input,i", PO_STR_VALUE(), "Input video file path")
- ("output,o", PO_STR_VALUE(), "Output video file path")
- ("processor,p", PO_STR_VALUE(&arguments.processor_type),
- "Processor to use (libplacebo, realesrgan, rife)")
- ("hwaccel,a", PO_STR_VALUE(&arguments.hwaccel)->default_value(STR("none"),
- "none"), "Hardware acceleration method (decoding)")
- ("device,d", po::value(&arguments.vk_device_index)->default_value(0),
- "Vulkan device index (GPU ID)")
- ("benchmark,b", po::bool_switch(&arguments.benchmark),
- "Discard processed frames and calculate average FPS; "
- "useful for detecting encoder bottlenecks")
- ;
-
- po::options_description encoder_opts("Encoder options");
- encoder_opts.add_options()
- ("codec,c", PO_STR_VALUE(&arguments.codec)->default_value(STR("libx264"),
- "libx264"), "Output codec")
- ("no-copy-streams", po::bool_switch(&arguments.no_copy_streams),
- "Do not copy audio and subtitle streams")
- ("pix-fmt", PO_STR_VALUE(&arguments.pix_fmt), "Output pixel format")
- ("bit-rate", po::value(&arguments.bit_rate)->default_value(0),
- "Bitrate in bits per second")
- ("rc-buffer-size", po::value(&arguments.rc_buffer_size)->default_value(0),
- "Rate control buffer size in bits")
- ("rc-min-rate", po::value(&arguments.rc_min_rate)->default_value(0),
- "Minimum rate control")
- ("rc-max-rate", po::value(&arguments.rc_max_rate)->default_value(0),
- "Maximum rate control")
- ("qmin", po::value(&arguments.qmin)->default_value(-1), "Minimum quantizer")
- ("qmax", po::value(&arguments.qmax)->default_value(-1), "Maximum quantizer")
- ("gop-size", po::value(&arguments.gop_size)->default_value(-1),
- "Group of pictures structure size")
- ("max-b-frames", po::value(&arguments.max_b_frames)->default_value(-1),
- "Maximum number of B-frames")
- ("keyint-min", po::value(&arguments.keyint_min)->default_value(-1),
- "Minimum interval between keyframes")
- ("refs", po::value(&arguments.refs)->default_value(-1),
- "Number of reference frames")
- ("thread-count", po::value(&arguments.thread_count)->default_value(0),
- "Number of threads for encoding")
- ("delay", po::value(&arguments.delay)->default_value(0),
- "Delay in milliseconds for encoder")
-
- // Extra encoder options (key-value pairs)
- ("extra-encoder-option,e", PO_STR_VALUE>()->multitoken(),
- "Additional AVOption(s) for the encoder (format: -e key=value)")
- ;
-
- po::options_description upscale_opts("Upscaling options");
- upscale_opts.add_options()
- ("width,w", po::value(&arguments.width), "Output width")
- ("height,h", po::value(&arguments.height), "Output height")
- ("scaling-factor,s", po::value(&arguments.scaling_factor), "Scaling factor")
- ;
-
- po::options_description interp_opts("Frame interpolation options");
- interp_opts.add_options()
- ("frame-rate-mul,m",
- po::value(&arguments.frm_rate_mul)->default_value(2),
- "Frame rate multiplier")
- ("scene-thresh,t", po::value(&arguments.scn_det_thresh)->default_value(10.0f),
- "Scene detection threshold")
- ;
-
- po::options_description libplacebo_opts("libplacebo options");
- libplacebo_opts.add_options()
- ("libplacebo-shader", PO_STR_VALUE(&arguments.libplacebo_shader_path),
- "Name/path of the GLSL shader file to use (built-in: anime4k-v4-a, anime4k-v4-a+a, "
- "anime4k-v4-b, anime4k-v4-b+b, anime4k-v4-c, anime4k-v4-c+a, anime4k-v4.1-gan)")
- ;
-
- po::options_description realesrgan_opts("RealESRGAN options");
- realesrgan_opts.add_options()
- ("realesrgan-model", PO_STR_VALUE(&arguments.realesrgan_model_name),
- "Name of the RealESRGAN model to use (realesr-animevideov3, realesrgan-plus-anime, "
- "realesrgan-plus)")
- ;
-
- po::options_description rife_opts("RIFE options");
- rife_opts.add_options()
- ("rife-model", PO_STR_VALUE(&arguments.rife_model_name),
- "Name of the RIFE model to use (rife, rife-HD, rife-UHD, rife-anime, rife-v2, "
- "rife-v2.3, rife-v2.4, rife-v3.0, rife-v3.1, rife-v4, rife-v4.6)")
- ("rife-uhd", po::bool_switch(&arguments.rife_uhd_mode),
- "Enable Ultra HD mode")
- ;
- // clang-format on
-
- // Combine all options
- all_opts.add(encoder_opts)
- .add(upscale_opts)
- .add(interp_opts)
- .add(libplacebo_opts)
- .add(realesrgan_opts)
- .add(rife_opts);
-
- // Positional arguments
- po::positional_options_description p;
- p.add("input", 1).add("output", 1).add("processor", 1);
-
- po::variables_map vm;
-#ifdef _WIN32
- po::store(po::wcommand_line_parser(argc, argv).options(all_opts).positional(p).run(), vm);
-#else
- po::store(po::command_line_parser(argc, argv).options(all_opts).positional(p).run(), vm);
-#endif
- po::notify(vm);
-
- if (vm.count("help") || argc == 1) {
- std::cout
- << all_opts << std::endl
- << "Examples:" << std::endl
- << " Upscale an anime video to 4K using libplacebo:" << std::endl
- << " video2x -i input.mp4 -o output.mp4 -w 3840 -h 2160 \\" << std::endl
- << " -p libplacebo --libplacebo-shader anime4k-v4-a+a" << std::endl
- << std::endl
- << " Upscale a film by 4x using RealESRGAN with custom encoder options:"
- << std::endl
- << " video2x -i input.mkv -o output.mkv -s 4 \\" << std::endl
- << " -p realesrgan --realesrgan-model realesrgan-plus \\" << std::endl
- << " -c libx264rgb -e crf=17 -e preset=veryslow -e tune=film" << std::endl
- << std::endl
- << " Frame-interpolate a video using RIFE to 4x the original frame rate:"
- << std::endl
- << " video2x -i input.mp4 -o output.mp4 -m 4 -p rife --rife-model rife-v4.6"
- << std::endl;
- return 0;
- }
-
- if (vm.count("version")) {
- std::cout << "Video2X version " << LIBVIDEO2X_VERSION_STRING << std::endl;
- return 0;
- }
-
- if (vm.count("list-devices")) {
- return list_vulkan_devices();
- }
-
- if (vm.count("log-level")) {
- std::optional log_level =
- find_log_level_by_name(vm["log-level"].as());
- if (!log_level.has_value()) {
- spdlog::critical("Invalid log level specified.");
- return 1;
- }
- arguments.log_level = log_level.value();
- }
- set_spdlog_level(arguments.log_level);
-
- // Print program banner
- spdlog::info("Video2X version {}", LIBVIDEO2X_VERSION_STRING);
- // spdlog::info("Copyright (C) 2018-2024 K4YT3X and contributors.");
- // spdlog::info("Licensed under GNU AGPL version 3.");
-
- // Assign positional arguments
- if (vm.count("input")) {
- arguments.in_fname = std::filesystem::path(vm["input"].as());
- spdlog::info("Processing file: {}", arguments.in_fname.u8string());
- } else {
- spdlog::critical("Input file path is required.");
- return 1;
- }
-
- if (vm.count("output")) {
- arguments.out_fname = std::filesystem::path(vm["output"].as());
- } else if (!arguments.benchmark) {
- spdlog::critical("Output file path is required.");
- return 1;
- }
-
- if (!vm.count("processor")) {
- spdlog::critical("Processor type is required (libplacebo, realesrgan, or rife).");
- return 1;
- }
-
- // Parse extra AVOptions
- if (vm.count("extra-encoder-option")) {
- for (const auto &opt : vm["extra-encoder-option"].as>()) {
- size_t eq_pos = opt.find('=');
- if (eq_pos != StringType::npos) {
- StringType key = opt.substr(0, eq_pos);
- StringType value = opt.substr(eq_pos + 1);
- arguments.extra_encoder_opts.push_back(std::make_pair(key, value));
- } else {
- spdlog::critical("Invalid extra AVOption format: {}", wstring_to_u8string(opt));
- return 1;
- }
- }
- }
-
- if (vm.count("libplacebo-model")) {
- if (!is_valid_realesrgan_model(vm["realesrgan-model"].as())) {
- spdlog::critical("Invalid model specified.");
- return 1;
- }
- }
-
- if (vm.count("rife-model")) {
- if (!is_valid_rife_model(vm["rife-model"].as())) {
- spdlog::critical("Invalid RIFE model specified.");
- return 1;
- }
- }
- } catch (const po::error &e) {
- spdlog::critical("Error parsing options: {}", e.what());
- return 1;
- } catch (const std::exception &e) {
- spdlog::critical("Unexpected exception caught while parsing options: {}", e.what());
- return 1;
- }
-
- // Additional validations
- if (arguments.width < 0 || arguments.height < 0) {
- spdlog::critical("Invalid output resolution specified.");
- return 1;
- }
- if (arguments.scaling_factor < 0) {
- spdlog::critical("Invalid scaling factor specified.");
- return 1;
- }
- if (arguments.frm_rate_mul <= 1) {
- spdlog::critical("Invalid frame rate multiplier specified.");
- return 1;
- }
- if (arguments.scn_det_thresh < 0.0f || arguments.scn_det_thresh > 100.0f) {
- spdlog::critical("Invalid scene detection threshold specified.");
- return 1;
- }
-
- if (arguments.processor_type == STR("libplacebo")) {
- if (arguments.libplacebo_shader_path.empty() || arguments.width == 0 ||
- arguments.height == 0) {
- spdlog::critical("Shader name/path, width, and height are required for libplacebo.");
- return 1;
- }
- } else if (arguments.processor_type == STR("realesrgan")) {
- if (arguments.scaling_factor != 2 && arguments.scaling_factor != 3 &&
- arguments.scaling_factor != 4) {
- spdlog::critical("Scaling factor must be 2, 3, or 4 for RealESRGAN.");
- return 1;
- }
- } else if (arguments.processor_type != STR("rife")) {
- spdlog::critical(
- "Invalid processor specified. Must be 'libplacebo', 'realesrgan', or 'rife'."
- );
- return 1;
- }
-
- // Validate GPU ID
- VkPhysicalDeviceProperties dev_props;
- int get_vulkan_dev_ret = get_vulkan_device_prop(arguments.vk_device_index, &dev_props);
- if (get_vulkan_dev_ret != 0) {
- if (get_vulkan_dev_ret == -2) {
- spdlog::critical("Invalid Vulkan device ID specified.");
- return 1;
- } else {
- spdlog::warn("Unable to validate Vulkan device ID.");
- return 1;
- }
- } else {
- // Warn if the selected device is a CPU
- spdlog::info("Using Vulkan device: {} ({:#x})", dev_props.deviceName, dev_props.deviceID);
- if (dev_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
- spdlog::warn("The selected Vulkan device is a CPU device.");
- }
- }
-
- // Validate bitrate
- if (arguments.bit_rate < 0) {
- spdlog::critical("Invalid bitrate specified.");
- return 1;
- }
-
- // Parse codec to AVCodec
- const AVCodec *codec =
- avcodec_find_encoder_by_name(wstring_to_u8string(arguments.codec).c_str());
- if (!codec) {
- spdlog::critical("Codec '{}' not found.", wstring_to_u8string(arguments.codec));
- return 1;
- }
-
- // Parse pixel format to AVPixelFormat
- AVPixelFormat pix_fmt = AV_PIX_FMT_NONE;
- if (!arguments.pix_fmt.empty()) {
- pix_fmt = av_get_pix_fmt(wstring_to_u8string(arguments.pix_fmt).c_str());
- if (pix_fmt == AV_PIX_FMT_NONE) {
- spdlog::critical("Invalid pixel format '{}'.", wstring_to_u8string(arguments.pix_fmt));
- return 1;
- }
- }
-
- // Setup filter configurations based on the parsed arguments
ProcessorConfig proc_cfg;
- proc_cfg.width = arguments.width;
- proc_cfg.height = arguments.height;
- proc_cfg.scaling_factor = arguments.scaling_factor;
- proc_cfg.frm_rate_mul = arguments.frm_rate_mul;
- proc_cfg.scn_det_thresh = arguments.scn_det_thresh;
+ EncoderConfig enc_cfg;
- if (arguments.processor_type == STR("libplacebo")) {
- proc_cfg.processor_type = ProcessorType::Libplacebo;
- LibplaceboConfig libplacebo_config;
- libplacebo_config.shader_path = arguments.libplacebo_shader_path;
- proc_cfg.config = libplacebo_config;
- } else if (arguments.processor_type == STR("realesrgan")) {
- proc_cfg.processor_type = ProcessorType::RealESRGAN;
- RealESRGANConfig realesrgan_config;
- realesrgan_config.tta_mode = false;
- realesrgan_config.model_name = arguments.realesrgan_model_name;
- proc_cfg.config = realesrgan_config;
- } else if (arguments.processor_type == STR("rife")) {
- proc_cfg.processor_type = ProcessorType::RIFE;
- RIFEConfig rife_config;
- rife_config.tta_mode = false;
- rife_config.tta_temporal_mode = false;
- rife_config.uhd_mode = arguments.rife_uhd_mode;
- rife_config.num_threads = 0;
- rife_config.model_name = arguments.rife_model_name;
- proc_cfg.config = rife_config;
+ // Parse command line arguments
+ int parse_ret = parse_args(argc, argv, arguments, proc_cfg, enc_cfg);
+
+ // Return if parsing failed
+ if (parse_ret < 0) {
+ return parse_ret;
}
- // Setup encoder configuration
- EncoderConfig enc_cfg;
- enc_cfg.codec = codec->id;
- enc_cfg.copy_streams = !arguments.no_copy_streams;
- enc_cfg.width = 0;
- enc_cfg.height = 0;
- enc_cfg.pix_fmt = pix_fmt;
- enc_cfg.bit_rate = arguments.bit_rate;
- enc_cfg.rc_buffer_size = arguments.rc_buffer_size;
- enc_cfg.rc_max_rate = arguments.rc_max_rate;
- enc_cfg.rc_min_rate = arguments.rc_min_rate;
- enc_cfg.qmin = arguments.qmin;
- enc_cfg.qmax = arguments.qmax;
- enc_cfg.gop_size = arguments.gop_size;
- enc_cfg.max_b_frames = arguments.max_b_frames;
- enc_cfg.keyint_min = arguments.keyint_min;
- enc_cfg.refs = arguments.refs;
- enc_cfg.thread_count = arguments.thread_count;
- enc_cfg.delay = arguments.delay;
- enc_cfg.extra_opts = arguments.extra_encoder_opts;
-
- // Setup hardware configuration
- HardwareConfig hw_cfg;
- hw_cfg.hw_device_type = AV_HWDEVICE_TYPE_NONE;
- hw_cfg.vk_device_index = arguments.vk_device_index;
-
- // Parse hardware acceleration method
- if (arguments.hwaccel != STR("none")) {
- hw_cfg.hw_device_type =
- av_hwdevice_find_type_by_name(wstring_to_u8string(arguments.hwaccel).c_str());
- if (hw_cfg.hw_device_type == AV_HWDEVICE_TYPE_NONE) {
- spdlog::critical(
- "Invalid hardware device type '{}'.", wstring_to_u8string(arguments.hwaccel)
- );
- return 1;
- }
+ // Return if help message or version info was displayed
+ if (parse_ret > 0) {
+ return 0;
}
// Create video processor object
- VideoProcessor video_processor =
- VideoProcessor(hw_cfg, proc_cfg, enc_cfg, arguments.log_level, arguments.benchmark);
+ VideoProcessor video_processor = VideoProcessor(
+ proc_cfg,
+ enc_cfg,
+ arguments.vk_device_index,
+ arguments.hw_device_type,
+ arguments.log_level,
+ arguments.benchmark
+ );
// Register a newline-safe log callback for FFmpeg
av_log_set_callback(newline_safe_ffmpeg_log_callback);
@@ -803,17 +138,17 @@ int main(int argc, char **argv) {
std::cout.flush();
timer.resume();
}
- newline_required = true;
+ newline_required.store(true);
}
} else if (ch == 'q' || ch == 'Q') {
// Abort processing
- if (newline_required) {
+ if (newline_required.load()) {
putchar('\n');
}
spdlog::warn("Aborting gracefully; press Ctrl+C to terminate forcefully.");
{
video_processor.abort();
- newline_required = false;
+ newline_required.store(false);
}
break;
}
@@ -834,9 +169,8 @@ int main(int argc, char **argv) {
int time_elapsed = static_cast(timer.get_elapsed_time() / 1000);
// Calculate hours, minutes, and seconds elapsed
- int hours_elapsed = time_elapsed / 3600;
- int minutes_elapsed = (time_elapsed % 3600) / 60;
- int seconds_elapsed = time_elapsed % 60;
+ auto [hours_elapsed, minutes_elapsed, seconds_elapsed] =
+ calculate_time_components(time_elapsed);
// Calculate estimated time remaining
int64_t frames_remaining = total_frames - processed_frames;
@@ -846,9 +180,8 @@ int main(int argc, char **argv) {
time_remaining = std::max(time_remaining, 0);
// Calculate hours, minutes, and seconds remaining
- int hours_remaining = time_remaining / 3600;
- int minutes_remaining = (time_remaining % 3600) / 60;
- int seconds_remaining = time_remaining % 60;
+ auto [hours_remaining, minutes_remaining, seconds_remaining] =
+ calculate_time_components(time_remaining);
// Print the progress bar
std::cout << "\r\033[Kframe=" << processed_frames << "/" << total_frames << " ("
@@ -861,7 +194,7 @@ int main(int argc, char **argv) {
<< ":" << std::setw(2) << std::setfill('0') << minutes_remaining << ":"
<< std::setw(2) << std::setfill('0') << seconds_remaining;
std::cout.flush();
- newline_required = true;
+ newline_required.store(true);
}
}
@@ -878,7 +211,7 @@ int main(int argc, char **argv) {
processing_thread.join();
// Print a newline if progress bar was displayed
- if (newline_required) {
+ if (newline_required.load()) {
std::cout << '\n';
}
@@ -896,9 +229,8 @@ int main(int argc, char **argv) {
// Calculate statistics
int64_t processed_frames = video_processor.get_processed_frames();
int time_elapsed = static_cast(timer.get_elapsed_time() / 1000);
- int hours_elapsed = time_elapsed / 3600;
- int minutes_elapsed = (time_elapsed % 3600) / 60;
- int seconds_elapsed = time_elapsed % 60;
+ auto [hours_elapsed, minutes_elapsed, seconds_elapsed] =
+ calculate_time_components(time_elapsed);
float average_speed_fps = static_cast(processed_frames) /
(time_elapsed > 0 ? static_cast(time_elapsed) : 1);
diff --git a/tools/video2x/src/vulkan_utils.cpp b/tools/video2x/src/vulkan_utils.cpp
new file mode 100644
index 0000000..4b25b77
--- /dev/null
+++ b/tools/video2x/src/vulkan_utils.cpp
@@ -0,0 +1,125 @@
+#include "vulkan_utils.h"
+
+#include
+#include
+
+#include
+
+static int enumerate_vulkan_devices(VkInstance *instance, std::vector &devices) {
+ // Create a Vulkan instance
+ VkInstanceCreateInfo create_info{};
+ create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+
+ VkResult result = vkCreateInstance(&create_info, nullptr, instance);
+ if (result != VK_SUCCESS) {
+ spdlog::error("Failed to create Vulkan instance.");
+ return -1;
+ }
+
+ // Enumerate physical devices
+ uint32_t device_count = 0;
+ result = vkEnumeratePhysicalDevices(*instance, &device_count, nullptr);
+ if (result != VK_SUCCESS || device_count == 0) {
+ spdlog::error("Failed to enumerate Vulkan physical devices or no devices available.");
+ vkDestroyInstance(*instance, nullptr);
+ return -1;
+ }
+
+ devices.resize(device_count);
+ result = vkEnumeratePhysicalDevices(*instance, &device_count, devices.data());
+ if (result != VK_SUCCESS) {
+ spdlog::error("Failed to retrieve Vulkan physical devices.");
+ vkDestroyInstance(*instance, nullptr);
+ return -1;
+ }
+
+ return 0;
+}
+
+int list_vulkan_devices() {
+ VkInstance instance;
+ std::vector physical_devices;
+ int result = enumerate_vulkan_devices(&instance, physical_devices);
+ if (result != 0) {
+ return result;
+ }
+
+ uint32_t device_count = static_cast(physical_devices.size());
+
+ // List Vulkan device information
+ for (uint32_t i = 0; i < device_count; i++) {
+ VkPhysicalDevice device = physical_devices[i];
+ VkPhysicalDeviceProperties device_properties;
+ vkGetPhysicalDeviceProperties(device, &device_properties);
+
+ // Print Vulkan device ID and name
+ std::cout << i << ". " << device_properties.deviceName << std::endl;
+ std::cout << "\tType: ";
+ switch (device_properties.deviceType) {
+ case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
+ std::cout << "Integrated GPU";
+ break;
+ case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
+ std::cout << "Discrete GPU";
+ break;
+ case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
+ std::cout << "Virtual GPU";
+ break;
+ case VK_PHYSICAL_DEVICE_TYPE_CPU:
+ std::cout << "CPU";
+ break;
+ default:
+ std::cout << "Unknown";
+ break;
+ }
+ std::cout << std::endl;
+
+ // Print Vulkan API version
+ std::cout << "\tVulkan API Version: " << VK_VERSION_MAJOR(device_properties.apiVersion)
+ << "." << VK_VERSION_MINOR(device_properties.apiVersion) << "."
+ << VK_VERSION_PATCH(device_properties.apiVersion) << std::endl;
+
+ // Print driver version
+ std::cout << "\tDriver Version: " << VK_VERSION_MAJOR(device_properties.driverVersion)
+ << "." << VK_VERSION_MINOR(device_properties.driverVersion) << "."
+ << VK_VERSION_PATCH(device_properties.driverVersion) << std::endl;
+
+ // Print device ID
+ std::cout << "\tDevice ID: " << std::hex << std::showbase << device_properties.deviceID
+ << std::dec << std::endl;
+ }
+
+ // Clean up Vulkan instance
+ vkDestroyInstance(instance, nullptr);
+ return 0;
+}
+
+int get_vulkan_device_prop(uint32_t vk_device_index, VkPhysicalDeviceProperties *dev_props) {
+ if (dev_props == nullptr) {
+ spdlog::error("Invalid device properties pointer.");
+ return -1;
+ }
+
+ VkInstance instance;
+ std::vector devices;
+ int result = enumerate_vulkan_devices(&instance, devices);
+ if (result != 0) {
+ return result;
+ }
+
+ uint32_t device_count = static_cast(devices.size());
+
+ // Check if the Vulkan device ID is valid
+ if (vk_device_index >= device_count) {
+ vkDestroyInstance(instance, nullptr);
+ return -2;
+ }
+
+ // Get device properties for the specified Vulkan device ID
+ vkGetPhysicalDeviceProperties(devices[vk_device_index], dev_props);
+
+ // Clean up Vulkan instance
+ vkDestroyInstance(instance, nullptr);
+
+ return 0;
+}