Files
chicken-counting-sukawarna-det/cpp/include/chicken_counter/compress.hpp
T
2026-07-22 00:03:02 +07:00

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