feat: ffmpeg
This commit is contained in:
@@ -70,3 +70,11 @@ path = "examples/quick_verify.rs"
|
||||
[[example]]
|
||||
name = "comprehensive_demo"
|
||||
path = "examples/comprehensive_demo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "test_4x_slowmo"
|
||||
path = "examples/test_4x_slowmo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "detailed_ffmpeg_analysis"
|
||||
path = "examples/detailed_ffmpeg_analysis.rs"
|
||||
|
||||
274
cargos/tvai-v2/examples/detailed_ffmpeg_analysis.rs
Normal file
274
cargos/tvai-v2/examples/detailed_ffmpeg_analysis.rs
Normal file
@@ -0,0 +1,274 @@
|
||||
use tvai_sdk::TvaiSdk;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🔍 详细 FFmpeg 命令分析");
|
||||
println!("========================\n");
|
||||
|
||||
let sdk = TvaiSdk::new();
|
||||
|
||||
// 读取并分析 4x 慢动作模板
|
||||
let template_content = fs::read_to_string("template/4x slow motion.json")?;
|
||||
let template: tvai_sdk::Template = serde_json::from_str(&template_content)?;
|
||||
|
||||
println!("📋 模板信息:");
|
||||
println!(" 名称: {}", template.name);
|
||||
println!(" 描述: {}", template.description);
|
||||
println!();
|
||||
|
||||
// 生成基本命令
|
||||
let basic_command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4")?;
|
||||
println!("🔧 基本 FFmpeg 命令:");
|
||||
println!("{}\n", basic_command);
|
||||
|
||||
// 详细分析命令结构
|
||||
analyze_command_structure(&basic_command);
|
||||
|
||||
// 测试不同的输入输出组合
|
||||
println!("\n📊 不同场景测试:");
|
||||
|
||||
let test_cases = [
|
||||
("30fps_video.mp4", "30fps_to_120fps.mp4", "30fps → 120fps (4x慢动作)"),
|
||||
("60fps_video.mp4", "60fps_to_240fps.mp4", "60fps → 240fps (4x慢动作)"),
|
||||
("24fps_film.mov", "24fps_to_96fps.mov", "24fps → 96fps (电影素材)"),
|
||||
("sports_1080p.mp4", "sports_slowmo_4k.mp4", "体育视频慢动作"),
|
||||
];
|
||||
|
||||
for (input, output, description) in &test_cases {
|
||||
println!("\n 📹 {}", description);
|
||||
match sdk.generate_ffmpeg_command(&template, input, output) {
|
||||
Ok(cmd) => {
|
||||
println!(" ✓ 命令生成成功");
|
||||
analyze_specific_aspects(&cmd, &template);
|
||||
},
|
||||
Err(e) => println!(" ✗ 命令生成失败: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// 测试硬件加速版本
|
||||
println!("\n🚀 硬件加速测试:");
|
||||
test_hardware_acceleration(&sdk, &template);
|
||||
|
||||
// 分析潜在问题
|
||||
println!("\n⚠️ 潜在问题分析:");
|
||||
analyze_potential_issues(&basic_command, &template);
|
||||
|
||||
// 提供优化建议
|
||||
println!("\n💡 优化建议:");
|
||||
provide_optimization_suggestions(&template);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_command_structure(command: &str) {
|
||||
println!("🏗️ 命令结构分析:");
|
||||
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
println!(" 总参数数: {}", parts.len());
|
||||
|
||||
// 分析各个部分
|
||||
let mut i = 0;
|
||||
while i < parts.len() {
|
||||
match parts[i] {
|
||||
"ffmpeg" => println!(" [{}] 可执行文件: {}", i, parts[i]),
|
||||
"-i" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 输入文件: {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-vf" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 视频滤镜: {}", i, parts[i + 1]);
|
||||
analyze_video_filter(parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-c:v" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 视频编码器: {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-c:a" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 音频编码器: {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-crf" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 视频质量 (CRF): {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-preset" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 编码预设: {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
"-b:a" => {
|
||||
if i + 1 < parts.len() {
|
||||
println!(" [{}] 音频比特率: {}", i, parts[i + 1]);
|
||||
i += 1;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if parts[i].starts_with('"') && parts[i].ends_with('"') {
|
||||
println!(" [{}] 输出文件: {}", i, parts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_video_filter(filter: &str) {
|
||||
println!(" 🎛️ 滤镜详细分析:");
|
||||
|
||||
// 移除引号
|
||||
let filter = filter.trim_matches('"');
|
||||
|
||||
if filter.contains("tvai_fi") {
|
||||
println!(" 类型: TVAI 帧插值滤镜");
|
||||
|
||||
// 解析参数
|
||||
if let Some(params_part) = filter.split("tvai_fi=").nth(1) {
|
||||
let params: Vec<&str> = params_part.split(':').collect();
|
||||
for param in params {
|
||||
if param.starts_with("model=") {
|
||||
let model = ¶m[6..];
|
||||
println!(" 模型: {} ({})", model, get_model_description(model));
|
||||
} else if param.starts_with("slowmo=") {
|
||||
let factor = ¶m[7..];
|
||||
println!(" 慢动作倍数: {}x", factor);
|
||||
} else if param.starts_with("rdt=") {
|
||||
let threshold = ¶m[4..];
|
||||
println!(" 重复帧检测阈值: {}", threshold);
|
||||
} else if param.starts_with("fps=") {
|
||||
let fps = ¶m[4..];
|
||||
println!(" 目标帧率: {} fps", fps);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_model_description(model: &str) -> &'static str {
|
||||
match model {
|
||||
"chr-2" => "Chronos v2 - 通用帧插值模型",
|
||||
"apo-8" => "Apollo v8 - 高质量帧插值模型",
|
||||
"chf-3" => "Chronos Fast v3 - 快速帧插值模型",
|
||||
_ => "未知模型",
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_specific_aspects(command: &str, template: &tvai_sdk::Template) {
|
||||
// 检查帧率处理
|
||||
if !command.contains("fps=") && template.settings.output.out_fps == 0.0 {
|
||||
println!(" ⚠️ 未指定输出帧率,将依赖输入帧率 × 慢动作倍数");
|
||||
}
|
||||
|
||||
// 检查质量设置
|
||||
if command.contains("-crf 23") {
|
||||
println!(" ✓ 使用中等质量设置 (CRF 23)");
|
||||
}
|
||||
|
||||
// 检查编码器选择
|
||||
if command.contains("libx264") {
|
||||
println!(" ℹ️ 使用软件 H.264 编码器 (兼容性好,速度较慢)");
|
||||
}
|
||||
|
||||
// 检查音频处理
|
||||
if command.contains("-c:a aac") {
|
||||
println!(" ✓ 使用 AAC 音频编码器 (广泛兼容)");
|
||||
}
|
||||
}
|
||||
|
||||
fn test_hardware_acceleration(sdk: &TvaiSdk, template: &tvai_sdk::Template) {
|
||||
let hw_tests = [
|
||||
("windows", Some("nvidia"), "NVIDIA GPU"),
|
||||
("osx", Some("appleIntel"), "Apple VideoToolbox"),
|
||||
("windows", Some("amd"), "AMD GPU"),
|
||||
];
|
||||
|
||||
for (os, gpu, desc) in &hw_tests {
|
||||
match sdk.ffmpeg_generator.generate_with_auto_encoder(template, "input.mp4", "output.mp4", os, *gpu) {
|
||||
Ok(cmd) => {
|
||||
println!(" ✓ {} 硬件加速:", desc);
|
||||
if cmd.contains("nvenc") {
|
||||
println!(" 使用 NVIDIA NVENC 编码器");
|
||||
} else if cmd.contains("videotoolbox") {
|
||||
println!(" 使用 Apple VideoToolbox 编码器");
|
||||
} else if cmd.contains("amf") {
|
||||
println!(" 使用 AMD AMF 编码器");
|
||||
}
|
||||
},
|
||||
Err(e) => println!(" ✗ {} 硬件加速失败: {}", desc, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_potential_issues(command: &str, template: &tvai_sdk::Template) {
|
||||
let mut issues: Vec<&str> = Vec::new();
|
||||
let mut warnings: Vec<&str> = Vec::new();
|
||||
|
||||
// 检查帧率问题
|
||||
if template.settings.slow_motion.factor == 4.0 && !command.contains("fps=") {
|
||||
warnings.push("4倍慢动作但未明确指定输出帧率,可能导致帧率不符合预期");
|
||||
}
|
||||
|
||||
// 检查存储空间问题
|
||||
if template.settings.slow_motion.factor >= 4.0 {
|
||||
warnings.push("4倍慢动作会显著增加文件大小,请确保有足够存储空间");
|
||||
}
|
||||
|
||||
// 检查性能问题
|
||||
if command.contains("libx264") && !command.contains("preset fast") {
|
||||
warnings.push("使用软件编码器且未设置快速预设,编码时间可能较长");
|
||||
}
|
||||
|
||||
// 检查兼容性问题
|
||||
if !command.contains("-pix_fmt") {
|
||||
warnings.push("未明确指定像素格式,可能在某些播放器上出现兼容性问题");
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
if issues.is_empty() && warnings.is_empty() {
|
||||
println!(" ✅ 未发现明显问题");
|
||||
} else {
|
||||
for issue in issues {
|
||||
println!(" ❌ 问题: {}", issue);
|
||||
}
|
||||
for warning in warnings {
|
||||
println!(" ⚠️ 警告: {}", warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn provide_optimization_suggestions(template: &tvai_sdk::Template) {
|
||||
println!(" 🎯 针对 4倍慢动作的优化建议:");
|
||||
|
||||
println!(" 1. 帧率设置:");
|
||||
println!(" - 建议明确设置输出帧率,如 30fps → 120fps");
|
||||
println!(" - 避免过高的输出帧率以减少文件大小");
|
||||
|
||||
println!(" 2. 编码器选择:");
|
||||
println!(" - 优先使用硬件编码器 (NVENC/VideoToolbox/AMF) 提升速度");
|
||||
println!(" - 考虑使用 H.265 编码器减少文件大小");
|
||||
|
||||
println!(" 3. 质量设置:");
|
||||
println!(" - 慢动作视频建议使用较高质量 (CRF 18-20)");
|
||||
println!(" - 可以适当降低音频比特率节省空间");
|
||||
|
||||
println!(" 4. 性能优化:");
|
||||
println!(" - 使用 SSD 存储提升 I/O 性能");
|
||||
println!(" - 确保有足够内存处理高帧率视频");
|
||||
|
||||
if template.settings.slow_motion.duplicate {
|
||||
println!(" 5. 重复帧处理:");
|
||||
println!(" - 当前启用重复帧检测,有助于提升质量");
|
||||
println!(" - 阈值设置为 {} (推荐范围: 5-15)", template.settings.slow_motion.duplicate_threshold);
|
||||
}
|
||||
}
|
||||
250
cargos/tvai-v2/examples/test_4x_slowmo.rs
Normal file
250
cargos/tvai-v2/examples/test_4x_slowmo.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use tvai_sdk::TvaiSdk;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🎬 测试 4倍慢动作模板转换为 FFmpeg 命令");
|
||||
println!("==========================================\n");
|
||||
|
||||
// 创建 SDK 实例
|
||||
let sdk = TvaiSdk::new();
|
||||
|
||||
// 读取模板文件
|
||||
let template_path = "template/4x slow motion.json";
|
||||
println!("📁 读取模板文件: {}", template_path);
|
||||
|
||||
let template_content = fs::read_to_string(template_path)
|
||||
.map_err(|e| format!("无法读取模板文件: {}", e))?;
|
||||
|
||||
// 解析模板
|
||||
let template: tvai_sdk::Template = serde_json::from_str(&template_content)
|
||||
.map_err(|e| format!("解析模板失败: {}", e))?;
|
||||
|
||||
println!("✓ 模板解析成功");
|
||||
println!(" 名称: {}", template.name);
|
||||
println!(" 描述: {}", template.description);
|
||||
println!(" 作者: {}", template.author);
|
||||
println!();
|
||||
|
||||
// 分析模板设置
|
||||
println!("🔍 模板设置分析:");
|
||||
let settings = &template.settings;
|
||||
|
||||
println!(" 增强 (enhance): {}", if settings.enhance.active { "启用" } else { "禁用" });
|
||||
if settings.enhance.active {
|
||||
println!(" 模型: {}", settings.enhance.model);
|
||||
}
|
||||
|
||||
println!(" 慢动作 (slowmo): {}", if settings.slow_motion.active { "启用" } else { "禁用" });
|
||||
if settings.slow_motion.active {
|
||||
println!(" 模型: {}", settings.slow_motion.model);
|
||||
println!(" 倍数: {}x", settings.slow_motion.factor);
|
||||
println!(" 重复帧检测: {}", settings.slow_motion.duplicate);
|
||||
println!(" 重复帧阈值: {}", settings.slow_motion.duplicate_threshold);
|
||||
}
|
||||
|
||||
println!(" 稳定化 (stabilize): {}", if settings.stabilize.active { "启用" } else { "禁用" });
|
||||
println!(" 运动模糊 (motionblur): {}", if settings.motion_blur.active { "启用" } else { "禁用" });
|
||||
println!(" 颗粒 (grain): {}", if settings.grain.active { "启用" } else { "禁用" });
|
||||
|
||||
println!(" 输出设置 (output): {}", if settings.output.active { "启用" } else { "禁用" });
|
||||
if settings.output.active {
|
||||
println!(" 尺寸方法: {}", settings.output.out_size_method);
|
||||
println!(" 输出FPS: {}", settings.output.out_fps);
|
||||
println!(" 裁剪适应: {}", settings.output.crop_to_fit);
|
||||
}
|
||||
println!();
|
||||
|
||||
// 验证模板
|
||||
println!("✅ 验证模板:");
|
||||
match sdk.ffmpeg_generator.validate_template(&template) {
|
||||
Ok(_) => println!(" ✓ 模板验证通过"),
|
||||
Err(e) => {
|
||||
println!(" ✗ 模板验证失败: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 生成 FFmpeg 命令
|
||||
println!("⚙️ 生成 FFmpeg 命令:");
|
||||
let input_file = "input_30fps.mp4";
|
||||
let output_file = "output_4x_slowmo.mp4";
|
||||
|
||||
let ffmpeg_command = sdk.generate_ffmpeg_command(&template, input_file, output_file)?;
|
||||
|
||||
println!(" 输入文件: {}", input_file);
|
||||
println!(" 输出文件: {}", output_file);
|
||||
println!();
|
||||
println!("📋 生成的 FFmpeg 命令:");
|
||||
println!("{}", ffmpeg_command);
|
||||
println!();
|
||||
|
||||
// 分析生成的命令
|
||||
println!("🔍 命令分析:");
|
||||
analyze_ffmpeg_command(&ffmpeg_command);
|
||||
println!();
|
||||
|
||||
// 检查命令是否有问题
|
||||
println!("🔧 命令检查:");
|
||||
check_ffmpeg_command(&ffmpeg_command, &template);
|
||||
println!();
|
||||
|
||||
// 测试不同的输出格式
|
||||
println!("🎯 测试不同输出格式:");
|
||||
let test_outputs = [
|
||||
("output_4x_slowmo.mp4", "MP4"),
|
||||
("output_4x_slowmo.mov", "MOV"),
|
||||
("output_4x_slowmo.mkv", "MKV"),
|
||||
];
|
||||
|
||||
for (output, format) in &test_outputs {
|
||||
match sdk.generate_ffmpeg_command(&template, input_file, output) {
|
||||
Ok(cmd) => {
|
||||
println!(" ✓ {} 格式: 命令生成成功", format);
|
||||
// 检查是否包含适当的编码器
|
||||
if cmd.contains("-c:v") {
|
||||
println!(" 包含视频编码器设置");
|
||||
}
|
||||
if cmd.contains("-c:a") {
|
||||
println!(" 包含音频编码器设置");
|
||||
}
|
||||
},
|
||||
Err(e) => println!(" ✗ {} 格式: {}", format, e),
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
||||
// 测试自动编码器选择
|
||||
println!("🚀 测试自动编码器选择:");
|
||||
let platforms = [
|
||||
("windows", Some("nvidia"), "Windows + NVIDIA"),
|
||||
("osx", Some("appleIntel"), "macOS + Intel"),
|
||||
("linux", None, "Linux 软件编码"),
|
||||
];
|
||||
|
||||
for (os, gpu, desc) in &platforms {
|
||||
match sdk.ffmpeg_generator.generate_with_auto_encoder(&template, input_file, output_file, os, *gpu) {
|
||||
Ok(_) => println!(" ✓ {}: 自动编码器选择成功", desc),
|
||||
Err(e) => println!(" ✗ {}: {}", desc, e),
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n🎉 测试完成!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn analyze_ffmpeg_command(command: &str) {
|
||||
let parts: Vec<&str> = command.split_whitespace().collect();
|
||||
|
||||
println!(" 命令长度: {} 字符", command.len());
|
||||
println!(" 参数数量: {} 个", parts.len());
|
||||
|
||||
// 检查关键组件
|
||||
if command.contains("ffmpeg") {
|
||||
println!(" ✓ 包含 ffmpeg 可执行文件");
|
||||
}
|
||||
|
||||
if command.contains("-i") {
|
||||
println!(" ✓ 包含输入文件参数");
|
||||
}
|
||||
|
||||
if command.contains("tvai_fi") {
|
||||
println!(" ✓ 包含 TVAI 帧插值滤镜");
|
||||
|
||||
// 分析帧插值参数
|
||||
if let Some(fi_part) = command.split("tvai_fi=").nth(1) {
|
||||
if let Some(params) = fi_part.split_whitespace().next() {
|
||||
println!(" 帧插值参数: {}", params);
|
||||
|
||||
if params.contains("model=") {
|
||||
println!(" ✓ 包含模型参数");
|
||||
}
|
||||
if params.contains("slowmo=") {
|
||||
println!(" ✓ 包含慢动作倍数参数");
|
||||
}
|
||||
if params.contains("rdt=") {
|
||||
println!(" ✓ 包含重复帧检测参数");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if command.contains("-c:v") {
|
||||
println!(" ✓ 包含视频编码器设置");
|
||||
}
|
||||
|
||||
if command.contains("-c:a") {
|
||||
println!(" ✓ 包含音频编码器设置");
|
||||
}
|
||||
}
|
||||
|
||||
fn check_ffmpeg_command(command: &str, template: &tvai_sdk::Template) {
|
||||
let mut issues = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
|
||||
// 检查基本结构
|
||||
if !command.starts_with("ffmpeg") {
|
||||
issues.push("命令不以 'ffmpeg' 开头");
|
||||
}
|
||||
|
||||
if !command.contains("-i") {
|
||||
issues.push("缺少输入文件参数 (-i)");
|
||||
}
|
||||
|
||||
// 检查慢动作设置
|
||||
if template.settings.slow_motion.active {
|
||||
if !command.contains("tvai_fi") {
|
||||
issues.push("启用了慢动作但缺少 tvai_fi 滤镜");
|
||||
} else {
|
||||
// 检查慢动作参数
|
||||
if !command.contains("slowmo=4") {
|
||||
warnings.push("慢动作倍数可能不正确,期望 4x");
|
||||
}
|
||||
|
||||
if !command.contains("model=chr-2") {
|
||||
warnings.push("模型映射可能不正确,apo-8 应该映射到 chr-2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查输出设置
|
||||
if template.settings.output.active {
|
||||
if template.settings.output.out_fps == 0.0 {
|
||||
warnings.push("输出FPS为0,可能需要设置目标帧率");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查编码器设置
|
||||
if !command.contains("-c:v") {
|
||||
warnings.push("没有明确指定视频编码器");
|
||||
}
|
||||
|
||||
if !command.contains("-c:a") {
|
||||
warnings.push("没有明确指定音频编码器");
|
||||
}
|
||||
|
||||
// 检查滤镜链
|
||||
let filter_count = command.matches("-vf").count();
|
||||
if filter_count > 1 {
|
||||
issues.push("存在多个 -vf 参数,可能导致滤镜冲突");
|
||||
}
|
||||
|
||||
// 输出结果
|
||||
if issues.is_empty() && warnings.is_empty() {
|
||||
println!(" ✅ 命令检查通过,未发现问题");
|
||||
} else {
|
||||
if !issues.is_empty() {
|
||||
println!(" ❌ 发现严重问题:");
|
||||
for issue in &issues {
|
||||
println!(" - {}", issue);
|
||||
}
|
||||
}
|
||||
|
||||
if !warnings.is_empty() {
|
||||
println!(" ⚠️ 发现警告:");
|
||||
for warning in &warnings {
|
||||
println!(" - {}", warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,115 @@ pub struct OutputSettings {
|
||||
/// Custom FFmpeg parameters
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_params: Option<Vec<String>>,
|
||||
|
||||
// NVENC-specific parameters
|
||||
/// NVENC tune setting (e.g., "hq", "ll", "ull", "lossless")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nvenc_tune: Option<String>,
|
||||
/// Rate control mode (e.g., "constqp", "vbr", "cbr", "vbr_hq")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rate_control: Option<String>,
|
||||
/// Quantization parameter for constant QP mode (0-51)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub qp: Option<i32>,
|
||||
/// Rate control lookahead frames
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub rc_lookahead: Option<i32>,
|
||||
/// Spatial adaptive quantization (0=disabled, 1=enabled)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub spatial_aq: Option<i32>,
|
||||
/// AQ strength (1-15, higher = more aggressive)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aq_strength: Option<i32>,
|
||||
|
||||
// Scaling and filtering parameters
|
||||
/// Software scaling flags for libswscale
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sws_flags: Option<String>,
|
||||
|
||||
// Frame rate and timing parameters
|
||||
/// Frame rate mode (e.g., "passthrough", "cfr", "vfr")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fps_mode: Option<String>,
|
||||
|
||||
// MP4 container specific parameters
|
||||
/// MP4 movflags for fragmentation and metadata
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub movflags: Option<String>,
|
||||
|
||||
// Input/Output timing parameters
|
||||
/// Start time offset in seconds
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub start_time: Option<f64>,
|
||||
/// Duration limit in seconds
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<f64>,
|
||||
|
||||
// Stream mapping and metadata
|
||||
/// Whether to copy metadata from input
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub copy_metadata: Option<bool>,
|
||||
/// Whether to disable audio output
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub no_audio: Option<bool>,
|
||||
/// Whether to hide FFmpeg banner
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hide_banner: Option<bool>,
|
||||
|
||||
// Filter complex and enhancement parameters
|
||||
/// Complex filter graph for video processing
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filter_complex: Option<String>,
|
||||
/// Custom metadata to add to output
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub metadata: Option<Vec<(String, String)>>,
|
||||
|
||||
// TVAI-specific enhancement parameters
|
||||
/// TVAI enhancement model (e.g., "prob-4", "gaia-1", "artemis-1")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_model: Option<String>,
|
||||
/// TVAI scale factor (0 = auto)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_scale: Option<i32>,
|
||||
/// TVAI target width
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_width: Option<i32>,
|
||||
/// TVAI target height
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_height: Option<i32>,
|
||||
/// TVAI preblur setting
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_preblur: Option<i32>,
|
||||
/// TVAI noise reduction
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_noise: Option<i32>,
|
||||
/// TVAI detail enhancement
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_details: Option<i32>,
|
||||
/// TVAI halo reduction
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_halo: Option<i32>,
|
||||
/// TVAI blur reduction
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_blur: Option<i32>,
|
||||
/// TVAI compression artifact reduction
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_compression: Option<i32>,
|
||||
/// TVAI estimation setting
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_estimate: Option<i32>,
|
||||
/// TVAI blend factor
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_blend: Option<f64>,
|
||||
/// TVAI device selection (-2 = auto, -1 = CPU, 0+ = GPU index)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_device: Option<i32>,
|
||||
/// TVAI VRAM usage (0 = low, 1 = high)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_vram: Option<i32>,
|
||||
/// TVAI instances count
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tvai_instances: Option<i32>,
|
||||
}
|
||||
|
||||
/// Filter manager settings (for newer template versions)
|
||||
@@ -682,6 +791,77 @@ impl OutputSettings {
|
||||
self.audio_codec = Some("aac".to_string());
|
||||
}
|
||||
|
||||
// Validate NVENC parameters
|
||||
if let Some(qp) = self.qp {
|
||||
if qp < 0 || qp > 51 {
|
||||
self.qp = Some(qp.clamp(0, 51));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(rc_lookahead) = self.rc_lookahead {
|
||||
if rc_lookahead < 0 || rc_lookahead > 32 {
|
||||
self.rc_lookahead = Some(rc_lookahead.clamp(0, 32));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(spatial_aq) = self.spatial_aq {
|
||||
if spatial_aq < 0 || spatial_aq > 1 {
|
||||
self.spatial_aq = Some(spatial_aq.clamp(0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(aq_strength) = self.aq_strength {
|
||||
if aq_strength < 1 || aq_strength > 15 {
|
||||
self.aq_strength = Some(aq_strength.clamp(1, 15));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate timing parameters
|
||||
if let Some(start_time) = self.start_time {
|
||||
if start_time < 0.0 {
|
||||
self.start_time = Some(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(duration) = self.duration {
|
||||
if duration <= 0.0 {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid duration: {}", duration)));
|
||||
}
|
||||
}
|
||||
|
||||
// Set default values for new optional fields
|
||||
if self.nvenc_tune.is_none() {
|
||||
self.nvenc_tune = Some("hq".to_string());
|
||||
}
|
||||
|
||||
if self.rate_control.is_none() {
|
||||
self.rate_control = Some("constqp".to_string());
|
||||
}
|
||||
|
||||
if self.sws_flags.is_none() {
|
||||
self.sws_flags = Some("spline+accurate_rnd+full_chroma_int".to_string());
|
||||
}
|
||||
|
||||
if self.fps_mode.is_none() {
|
||||
self.fps_mode = Some("passthrough".to_string());
|
||||
}
|
||||
|
||||
if self.movflags.is_none() {
|
||||
self.movflags = Some("frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr".to_string());
|
||||
}
|
||||
|
||||
if self.copy_metadata.is_none() {
|
||||
self.copy_metadata = Some(true);
|
||||
}
|
||||
|
||||
if self.no_audio.is_none() {
|
||||
self.no_audio = Some(false);
|
||||
}
|
||||
|
||||
if self.hide_banner.is_none() {
|
||||
self.hide_banner = Some(true);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -752,6 +932,32 @@ impl Default for OutputSettings {
|
||||
gpu_device: None,
|
||||
two_pass: Some(false),
|
||||
custom_params: None,
|
||||
|
||||
// NVENC defaults
|
||||
nvenc_tune: Some("hq".to_string()),
|
||||
rate_control: Some("constqp".to_string()),
|
||||
qp: Some(18),
|
||||
rc_lookahead: Some(20),
|
||||
spatial_aq: Some(1),
|
||||
aq_strength: Some(15),
|
||||
|
||||
// Scaling defaults
|
||||
sws_flags: Some("spline+accurate_rnd+full_chroma_int".to_string()),
|
||||
|
||||
// Frame rate defaults
|
||||
fps_mode: Some("passthrough".to_string()),
|
||||
|
||||
// MP4 defaults
|
||||
movflags: Some("frag_keyframe+empty_moov+delay_moov+use_metadata_tags+write_colr".to_string()),
|
||||
|
||||
// Timing defaults
|
||||
start_time: None,
|
||||
duration: None,
|
||||
|
||||
// Metadata and audio defaults
|
||||
copy_metadata: Some(true),
|
||||
no_audio: Some(false),
|
||||
hide_banner: Some(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user