diff --git a/cargos/tvai-v2/Cargo.toml b/cargos/tvai-v2/Cargo.toml index b1a61cf..4bd4ebb 100644 --- a/cargos/tvai-v2/Cargo.toml +++ b/cargos/tvai-v2/Cargo.toml @@ -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" diff --git a/cargos/tvai-v2/examples/detailed_ffmpeg_analysis.rs b/cargos/tvai-v2/examples/detailed_ffmpeg_analysis.rs new file mode 100644 index 0000000..150db1f --- /dev/null +++ b/cargos/tvai-v2/examples/detailed_ffmpeg_analysis.rs @@ -0,0 +1,274 @@ +use tvai_sdk::TvaiSdk; +use std::fs; + +fn main() -> Result<(), Box> { + 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); + } +} diff --git a/cargos/tvai-v2/examples/test_4x_slowmo.rs b/cargos/tvai-v2/examples/test_4x_slowmo.rs new file mode 100644 index 0000000..3b43f35 --- /dev/null +++ b/cargos/tvai-v2/examples/test_4x_slowmo.rs @@ -0,0 +1,250 @@ +use tvai_sdk::TvaiSdk; +use std::fs; + +fn main() -> Result<(), Box> { + 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); + } + } + } +} diff --git a/cargos/tvai-v2/src/template.rs b/cargos/tvai-v2/src/template.rs index ec9bfe0..0c3db4a 100644 --- a/cargos/tvai-v2/src/template.rs +++ b/cargos/tvai-v2/src/template.rs @@ -252,6 +252,115 @@ pub struct OutputSettings { /// Custom FFmpeg parameters #[serde(skip_serializing_if = "Option::is_none")] pub custom_params: Option>, + + // NVENC-specific parameters + /// NVENC tune setting (e.g., "hq", "ll", "ull", "lossless") + #[serde(skip_serializing_if = "Option::is_none")] + pub nvenc_tune: Option, + /// Rate control mode (e.g., "constqp", "vbr", "cbr", "vbr_hq") + #[serde(skip_serializing_if = "Option::is_none")] + pub rate_control: Option, + /// Quantization parameter for constant QP mode (0-51) + #[serde(skip_serializing_if = "Option::is_none")] + pub qp: Option, + /// Rate control lookahead frames + #[serde(skip_serializing_if = "Option::is_none")] + pub rc_lookahead: Option, + /// Spatial adaptive quantization (0=disabled, 1=enabled) + #[serde(skip_serializing_if = "Option::is_none")] + pub spatial_aq: Option, + /// AQ strength (1-15, higher = more aggressive) + #[serde(skip_serializing_if = "Option::is_none")] + pub aq_strength: Option, + + // Scaling and filtering parameters + /// Software scaling flags for libswscale + #[serde(skip_serializing_if = "Option::is_none")] + pub sws_flags: Option, + + // Frame rate and timing parameters + /// Frame rate mode (e.g., "passthrough", "cfr", "vfr") + #[serde(skip_serializing_if = "Option::is_none")] + pub fps_mode: Option, + + // MP4 container specific parameters + /// MP4 movflags for fragmentation and metadata + #[serde(skip_serializing_if = "Option::is_none")] + pub movflags: Option, + + // Input/Output timing parameters + /// Start time offset in seconds + #[serde(skip_serializing_if = "Option::is_none")] + pub start_time: Option, + /// Duration limit in seconds + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + + // Stream mapping and metadata + /// Whether to copy metadata from input + #[serde(skip_serializing_if = "Option::is_none")] + pub copy_metadata: Option, + /// Whether to disable audio output + #[serde(skip_serializing_if = "Option::is_none")] + pub no_audio: Option, + /// Whether to hide FFmpeg banner + #[serde(skip_serializing_if = "Option::is_none")] + pub hide_banner: Option, + + // Filter complex and enhancement parameters + /// Complex filter graph for video processing + #[serde(skip_serializing_if = "Option::is_none")] + pub filter_complex: Option, + /// Custom metadata to add to output + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + + // 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, + /// TVAI scale factor (0 = auto) + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_scale: Option, + /// TVAI target width + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_width: Option, + /// TVAI target height + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_height: Option, + /// TVAI preblur setting + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_preblur: Option, + /// TVAI noise reduction + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_noise: Option, + /// TVAI detail enhancement + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_details: Option, + /// TVAI halo reduction + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_halo: Option, + /// TVAI blur reduction + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_blur: Option, + /// TVAI compression artifact reduction + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_compression: Option, + /// TVAI estimation setting + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_estimate: Option, + /// TVAI blend factor + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_blend: Option, + /// TVAI device selection (-2 = auto, -1 = CPU, 0+ = GPU index) + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_device: Option, + /// TVAI VRAM usage (0 = low, 1 = high) + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_vram: Option, + /// TVAI instances count + #[serde(skip_serializing_if = "Option::is_none")] + pub tvai_instances: Option, } /// 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), } } }