forked from zakaria/chicken-counting-sukawarna-det
107 lines
3.7 KiB
C++
107 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
#include <opencv2/videoio.hpp>
|
|
|
|
namespace cc {
|
|
|
|
inline double video_duration_seconds(const std::string& path) {
|
|
cv::VideoCapture cap(path);
|
|
if (!cap.isOpened())
|
|
throw std::runtime_error("Unable to open video for duration probe: " + path);
|
|
double fc = cap.get(cv::CAP_PROP_FRAME_COUNT);
|
|
double fps = cap.get(cv::CAP_PROP_FPS);
|
|
cap.release();
|
|
if (fps > 0 && fc > 0) return fc / fps;
|
|
throw std::runtime_error("Unable to determine duration for video: " + path);
|
|
}
|
|
|
|
inline double file_size_mb(const std::string& path) {
|
|
return static_cast<double>(std::filesystem::file_size(path)) / (1024.0 * 1024.0);
|
|
}
|
|
|
|
inline void run_ffmpeg(const std::vector<std::string>& command) {
|
|
std::string cmd;
|
|
for (const auto& arg : command) cmd += arg + " ";
|
|
cmd = cmd.substr(0, cmd.size() - 1) + " 2>&1";
|
|
int ret = std::system(cmd.c_str());
|
|
if (ret != 0)
|
|
throw std::runtime_error("ffmpeg failed with code " + std::to_string(ret));
|
|
}
|
|
|
|
inline double compress_video_to_target(
|
|
const std::string& input_path,
|
|
const std::string& output_path,
|
|
int max_mb = 200,
|
|
int max_attempts = 3)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
if (!fs::is_regular_file(input_path))
|
|
throw std::runtime_error("Input video not found: " + input_path);
|
|
|
|
fs::create_directories(fs::path(output_path).parent_path());
|
|
|
|
double duration = video_duration_seconds(input_path);
|
|
if (duration <= 0)
|
|
throw std::runtime_error("Invalid video duration for " + input_path);
|
|
|
|
int target_kbps = std::max(300, static_cast<int>((max_mb * 8192) / duration * 0.92));
|
|
|
|
for (int attempt = 0; attempt < max_attempts; ++attempt) {
|
|
int attempt_kbps = std::max(300,
|
|
static_cast<int>(target_kbps * std::pow(0.85, attempt)));
|
|
|
|
if (fs::exists(output_path)) fs::remove(output_path);
|
|
|
|
std::vector<std::vector<std::string>> codec_attempts = {
|
|
{"-c:v", "h264_nvmpi", "-b:v", std::to_string(attempt_kbps) + "k",
|
|
"-maxrate", std::to_string(attempt_kbps) + "k",
|
|
"-bufsize", std::to_string(attempt_kbps * 2) + "k"},
|
|
{"-c:v", "libx264", "-preset", "fast",
|
|
"-b:v", std::to_string(attempt_kbps) + "k",
|
|
"-maxrate", std::to_string(attempt_kbps) + "k",
|
|
"-bufsize", std::to_string(attempt_kbps * 2) + "k"}
|
|
};
|
|
|
|
bool succeeded = false;
|
|
for (const auto& cargs : codec_attempts) {
|
|
std::vector<std::string> cmd = {"ffmpeg", "-y", "-i", input_path};
|
|
cmd.insert(cmd.end(), cargs.begin(), cargs.end());
|
|
cmd.push_back("-c:a");
|
|
cmd.push_back("copy");
|
|
cmd.push_back(output_path);
|
|
try {
|
|
run_ffmpeg(cmd);
|
|
succeeded = true;
|
|
break;
|
|
} catch (const std::runtime_error&) {
|
|
if (fs::exists(output_path)) fs::remove(output_path);
|
|
}
|
|
}
|
|
if (!succeeded)
|
|
throw std::runtime_error("Unable to compress video: " + input_path);
|
|
|
|
double size_mb = file_size_mb(output_path);
|
|
std::cerr << "[compress] " << fs::path(output_path).filename().string()
|
|
<< ": " << size_mb << " MB (attempt " << (attempt + 1)
|
|
<< ", target " << attempt_kbps << " kbps)\n";
|
|
|
|
if (size_mb <= max_mb) return size_mb;
|
|
}
|
|
|
|
double final_size = file_size_mb(output_path);
|
|
if (final_size > max_mb)
|
|
throw std::runtime_error("Compressed video exceeds " + std::to_string(max_mb)
|
|
+ " MB: " + output_path);
|
|
return final_size;
|
|
}
|
|
|
|
} // namespace cc
|