diff --git a/cargos/tvai-v2/BUILTIN_CONFIG_SUMMARY.md b/cargos/tvai-v2/BUILTIN_CONFIG_SUMMARY.md new file mode 100644 index 0000000..0d2c815 --- /dev/null +++ b/cargos/tvai-v2/BUILTIN_CONFIG_SUMMARY.md @@ -0,0 +1,171 @@ +# 内置配置功能总结 + +## 概述 + +Topaz Video AI SDK 现已集成内置的 FFmpeg 配置功能,所有编解码器和推荐规则都直接编译到代码中,无需外部配置文件依赖。 + +## 主要改进 + +### 1. 中文注释 +- 将 `ffmpeg.rs` 中的所有英文注释改为中文注释 +- 提高了中文开发者的代码可读性 +- 保持了代码功能的完整性 + +### 2. 内置配置系统 +- **音频编解码器**: 内置 AAC、AC3、PCM、Vorbis 配置 +- **视频编码器**: 内置 H.264/H.265 的各种硬件和软件编码器 +- **模型推荐**: 基于分辨率的智能模型推荐规则 +- **无外部依赖**: 所有配置编译到代码中 + +### 3. 智能编码器选择 +- 根据操作系统自动筛选兼容编码器 +- 根据 GPU 类型选择硬件加速编码器 +- 根据文件格式筛选支持的编码器 +- 自动选择最佳编码器组合 + +## 内置编码器支持 + +### 音频编解码器 +``` +AAC - 通用音频编码,支持 MP4/MOV/MKV/AVI +AC3 - 杜比数字音频,支持环绕声 +PCM - 无损音频编码 +Vorbis - 开源音频编码,用于 WebM +``` + +### 视频编码器 +``` +NVIDIA GPU: +- h264_nvenc - H.264 硬件编码 +- hevc_nvenc - H.265 硬件编码 + +Apple 平台: +- h264_videotoolbox - H.264 硬件编码 +- hevc_videotoolbox - H.265 硬件编码 + +AMD GPU: +- h264_amf - H.264 硬件编码 +- hevc_amf - H.265 硬件编码 + +软件编码: +- libx264 - H.264 软件编码(跨平台) +- libx265 - H.265 软件编码(跨平台) +``` + +## 新增 API + +### 编解码器查询 +```rust +// 获取所有音频编解码器 +let audio_codecs = sdk.ffmpeg_generator.get_available_audio_codecs(); + +// 根据名称查找编解码器 +let aac = sdk.ffmpeg_generator.get_audio_codec("AAC"); + +// 获取所有视频编码器 +let video_encoders = sdk.ffmpeg_generator.get_available_video_encoders(); + +// 根据名称查找编码器 +let h264 = sdk.ffmpeg_generator.get_video_encoder_by_name("H264"); +``` + +### 兼容性筛选 +```rust +// 获取 Windows 兼容编码器 +let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + +// 获取 NVIDIA GPU 编码器 +let nvidia_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", Some("nvidia")); + +// 获取 MP4 格式编码器 +let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); +``` + +### 自动编码器选择 +```rust +// 自动选择最佳编码器 +let command = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", // 目标操作系统 + Some("nvidia") // GPU 类型 +)?; +``` + +### 模型推荐 +```rust +// 根据分辨率获取推荐模型 +let models = sdk.ffmpeg_generator.recommend_model_for_resolution(1080); +``` + +### 参数生成 +```rust +// 生成音频编解码器参数 +let audio_params = sdk.ffmpeg_generator.generate_audio_codec_params("AAC", Some(192)); + +// 生成视频编码器参数 +let video_params = sdk.ffmpeg_generator.generate_video_encoder_params("h264-high-win-nvidia", "vbr", Some(23)); +``` + +## 使用示例 + +### 基本用法 +```rust +use tvai_v2::{TvaiSdk, TemplateBuilder}; + +let sdk = TvaiSdk::new(); + +let template = TemplateBuilder::new("高质量编码") + .enable_enhancement("prob-4") + .video_codec("H264", Some(23)) + .audio_settings("AAC", 192, 2) + .build()?; + +let command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4")?; +``` + +### 平台优化 +```rust +// 自动选择适合当前平台的编码器 +let optimized_command = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", + Some("nvidia") +)?; +``` + +## 优势 + +1. **无外部依赖**: 所有配置内置,部署简单 +2. **智能选择**: 自动根据平台和硬件选择最佳编码器 +3. **易于扩展**: 通过修改内置配置可以添加新编码器 +4. **向后兼容**: 不影响现有代码的使用 +5. **性能优化**: 支持各种硬件加速编码器 +6. **中文友好**: 完整的中文注释和文档 + +## 测试和验证 + +提供了完整的测试套件: +- `ffmpeg_config_tests.rs` - 单元测试 +- `builtin_config_test.rs` - 功能验证 +- `ffmpeg_config_demo.rs` - 完整演示 + +运行测试: +```bash +cargo test +cargo run --example builtin_config_test +cargo run --example ffmpeg_config_demo +``` + +## 文档 + +- `README_FFMPEG_CONFIG.md` - 详细功能说明 +- `USAGE_GUIDE.md` - 使用指南和示例 +- `BUILTIN_CONFIG_SUMMARY.md` - 本总结文档 + +## 结论 + +内置配置功能使 Topaz Video AI SDK 更加强大和易用,提供了智能的编码器选择和优化的 FFmpeg 命令生成,同时保持了简单的 API 和良好的性能。 diff --git a/cargos/tvai-v2/Cargo.toml b/cargos/tvai-v2/Cargo.toml index 8ec2d2b..b1a61cf 100644 --- a/cargos/tvai-v2/Cargo.toml +++ b/cargos/tvai-v2/Cargo.toml @@ -54,3 +54,19 @@ path = "examples/audio_codecs_demo.rs" [[example]] name = "benchmarks_demo" path = "examples/benchmarks_demo.rs" + +[[example]] +name = "ffmpeg_config_demo" +path = "examples/ffmpeg_config_demo.rs" + +[[example]] +name = "builtin_config_test" +path = "examples/builtin_config_test.rs" + +[[example]] +name = "quick_verify" +path = "examples/quick_verify.rs" + +[[example]] +name = "comprehensive_demo" +path = "examples/comprehensive_demo.rs" diff --git a/cargos/tvai-v2/README_FFMPEG_CONFIG.md b/cargos/tvai-v2/README_FFMPEG_CONFIG.md new file mode 100644 index 0000000..569f994 --- /dev/null +++ b/cargos/tvai-v2/README_FFMPEG_CONFIG.md @@ -0,0 +1,228 @@ +# FFmpeg 内置配置集成 + +本文档介绍了 Topaz Video AI SDK 中新增的 FFmpeg 内置配置功能。 + +## 概述 + +SDK 现在内置了音频编解码器、视频编码器和模型推荐规则配置,使 FFmpeg 命令生成更加灵活和智能化。所有配置都直接编译到代码中,无需外部配置文件。 + +## 内置配置 + +### 音频编解码器配置 + +内置了以下音频编解码器及其参数: + +```json +{ + "name": "AAC", + "ffmpegOpts": "aac -ac 2", + "ext": ["mp4", "mov", "mkv", "avi"], + "bitrate": { + "min": 32, + "minRec": 128, + "max": 320, + "default": 320, + "suggested": [128, 160, 192, 256, 320], + "ffmpegOpt": "-b:a k" + } +} +``` + +### 视频编码器配置 + +内置了以下视频编码器及其参数: + +```json +{ + "id": "h264-high-win-nvidia", + "encoder": "H264", + "profile": "High", + "ffmpegOpts": "-c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30", + "bitrateOpts": { + "cbr": "-rc cbr -b:v -preset p6", + "vbr": "-preset p7 -tune hq -rc constqp -qp " + }, + "cqpValues": { + "High": [18], + "Mid": [25], + "Low": [28] + }, + "ext": ["mov", "mkv", "mp4"], + "os": "windows|linux", + "device": "nvidia|tesla" +} +``` + +### 模型推荐规则 + +内置了基于分辨率的模型推荐规则: + +```json +{ + "id": "target-resolution", + "property": "height", + "type": "range", + "conditions": [ + { + "min": 1, + "max": 480, + "recommendedModelValues": ["iris", "artemis"] + } + ] +} +``` + +## 新增功能 + +### 1. 内置配置加载 + +SDK 在初始化时自动加载内置配置: + +```rust +let sdk = TvaiSdk::new(); // 自动加载所有内置配置 +``` + +### 2. 音频编解码器查询 + +```rust +// 获取所有可用的音频编解码器 +let audio_codecs = sdk.ffmpeg_generator.get_available_audio_codecs(); + +// 根据名称查找特定编解码器 +let aac_codec = sdk.ffmpeg_generator.get_audio_codec("AAC"); +``` + +### 3. 视频编码器查询 + +```rust +// 获取所有可用的视频编码器 +let video_encoders = sdk.ffmpeg_generator.get_available_video_encoders(); + +// 根据ID查找编码器 +let encoder = sdk.ffmpeg_generator.get_video_encoder("h264-high-win-nvidia"); + +// 根据名称查找编码器 +let h264_encoder = sdk.ffmpeg_generator.get_video_encoder_by_name("H264"); +``` + +### 4. 兼容性筛选 + +```rust +// 获取 Windows 兼容的编码器 +let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + +// 获取 NVIDIA GPU 兼容的编码器 +let nvidia_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", Some("nvidia")); + +// 获取支持 MP4 格式的编码器 +let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); +``` + +### 5. 自动编码器选择 + +```rust +let template = TemplateBuilder::new("自动选择") + .enable_enhancement("prob-4") + .build()?; + +// 自动选择适合的编码器 +let command = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", // 目标操作系统 + Some("nvidia") // GPU 类型 +)?; +``` + +### 6. 模型推荐 + +```rust +// 根据分辨率获取推荐模型 +let recommended_models = sdk.ffmpeg_generator.recommend_model_for_resolution(1080); +println!("1080p 推荐模型: {:?}", recommended_models); +``` + +### 7. 编码器报告生成 + +```rust +// 生成详细的编码器报告 +let report = sdk.ffmpeg_generator.generate_encoder_report(); +println!("{}", report); +``` + +## 使用示例 + +### 基本用法 + +```rust +use tvai_v2::{TvaiSdk, TemplateBuilder}; + +fn main() -> Result<(), Box> { + let sdk = TvaiSdk::new(); + + // 创建模板,使用配置文件中的编解码器 + let template = TemplateBuilder::new("高质量编码") + .enable_enhancement("prob-4") + .video_codec("H264", Some(23)) + .audio_settings("AAC", 192, 2) + .build()?; + + // 生成 FFmpeg 命令 + let command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4")?; + println!("FFmpeg 命令: {}", command); + + Ok(()) +} +``` + +### 高级用法 + +```rust +// 根据目标平台自动选择最佳编码器 +let auto_command = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", + Some("nvidia") +)?; + +// 获取推荐模型并创建优化模板 +let recommended = sdk.ffmpeg_generator.recommend_model_for_resolution(2160); +let optimized_template = TemplateBuilder::new("4K 优化") + .enable_enhancement(&recommended[0]) + .build()?; +``` + +## 内置编码器支持 + +### 音频编解码器 +- **AAC**: 支持 MP4、MOV、MKV、AVI 格式,比特率范围 32-320 kbps +- **AC3**: 支持 MP4、MOV、MKV、AVI 格式,比特率范围 160-640 kbps +- **PCM**: 支持 MOV、MKV、AVI 格式,无损音频 +- **Vorbis**: 支持 WebM 格式 + +### 视频编码器 +- **H.264 NVENC**: NVIDIA GPU 硬件加速编码 +- **H.265 NVENC**: NVIDIA GPU 硬件加速 HEVC 编码 +- **H.264 VideoToolbox**: macOS 硬件加速编码 +- **H.265 VideoToolbox**: macOS 硬件加速 HEVC 编码 +- **H.264 AMF**: AMD GPU 硬件加速编码 +- **H.265 AMF**: AMD GPU 硬件加速 HEVC 编码 +- **libx264**: 软件 H.264 编码(跨平台) +- **libx265**: 软件 H.265 编码(跨平台) + +## 错误处理 + +内置配置解析失败时,SDK 会: +1. 返回配置错误信息 +2. 回退到基本的编码器设置 +3. 确保基本功能仍然可用 + +## 扩展配置 + +如需添加更多编码器或修改配置,可以: +1. 修改 `ffmpeg.rs` 中的内置 JSON 字符串 +2. 重新编译项目 +3. 或者通过代码动态添加编码器配置 diff --git a/cargos/tvai-v2/USAGE_GUIDE.md b/cargos/tvai-v2/USAGE_GUIDE.md new file mode 100644 index 0000000..c4cdc02 --- /dev/null +++ b/cargos/tvai-v2/USAGE_GUIDE.md @@ -0,0 +1,240 @@ +# Topaz Video AI SDK 使用指南 + +## 快速开始 + +### 1. 基本设置 + +```rust +use tvai_v2::{TvaiSdk, TemplateBuilder}; + +// 创建 SDK 实例(自动加载内置配置) +let mut sdk = TvaiSdk::new(); +``` + +### 2. 创建简单的增强模板 + +```rust +let template = TemplateBuilder::new("基本增强") + .description("使用 Proteus 模型进行基本视频增强") + .enable_enhancement("prob-4") // 使用 Proteus 4 模型 + .enhancement_params(25, 30, 20) // 降噪25%, 细节30%, 锐化20% + .build()?; +``` + +### 3. 生成 FFmpeg 命令 + +```rust +let command = sdk.generate_ffmpeg_command( + &template, + "input.mp4", + "output.mp4" +)?; + +println!("FFmpeg 命令: {}", command); +``` + +## 高级功能 + +### 1. 使用内置编解码器 + +```rust +// 查看可用的音频编解码器 +for codec in sdk.ffmpeg_generator.get_available_audio_codecs() { + println!("音频编解码器: {} - {}", codec.name, codec.ffmpeg_opts); +} + +// 查看可用的视频编码器 +for encoder in sdk.ffmpeg_generator.get_available_video_encoders() { + println!("视频编码器: {} - {}", encoder.id, encoder.encoder); +} +``` + +### 2. 自动编码器选择 + +```rust +// 根据目标平台自动选择编码器 +let auto_command = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", // 目标操作系统 + Some("nvidia") // GPU 类型 +)?; +``` + +### 3. 模型推荐 + +```rust +// 根据分辨率获取推荐模型 +let recommended = sdk.ffmpeg_generator.recommend_model_for_resolution(1080); +println!("1080p 推荐模型: {:?}", recommended); + +// 使用推荐模型创建模板 +let optimized_template = TemplateBuilder::new("优化模板") + .enable_enhancement(&recommended[0]) + .build()?; +``` + +### 4. 平台特定的编码器筛选 + +```rust +// 获取 Windows 兼容的编码器 +let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + +// 获取支持 NVIDIA GPU 的编码器 +let nvidia_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", Some("nvidia")); + +// 获取支持 MP4 格式的编码器 +let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); +``` + +## 常用模板示例 + +### 1. 4K 上采样 + +```rust +let upscale_4k = TemplateBuilder::new("4K 上采样") + .enable_enhancement("prob-4") + .resolution(3840, 2160) + .video_codec("hevc_nvenc", Some(18)) + .audio_settings("AAC", 256, 2) + .build()?; +``` + +### 2. 60fps 插帧 + +```rust +let fps_60 = TemplateBuilder::new("60fps 插帧") + .enable_frame_interpolation("chr-2", 1.0) + .output_settings(0, 60.0) // 原始尺寸,60fps + .build()?; +``` + +### 3. 降噪处理 + +```rust +let denoise = TemplateBuilder::new("降噪处理") + .enable_enhancement("nyx-3") // 使用 Nyx 降噪模型 + .enhancement_params(60, 10, 5) // 高降噪,低细节增强 + .build()?; +``` + +### 4. 稳定化处理 + +```rust +let stabilize = TemplateBuilder::new("视频稳定") + .enable_stabilization(70, 0) // 高平滑度,自动裁剪 + .build()?; +``` + +### 5. 动画优化 + +```rust +let animation = TemplateBuilder::new("动画优化") + .enable_enhancement("art-2") // 使用 Artemis 动画模型 + .ai_engine("artemis") + .enhancement_params(15, 40, 25) + .build()?; +``` + +### 6. HDR 处理 + +```rust +let hdr = TemplateBuilder::new("HDR 处理") + .enable_enhancement("hyp-1") // 使用 Hyperion HDR 模型 + .video_codec("hevc_nvenc", Some(16)) + .color_settings( + "bt2020nc", + "bt2020", + "smpte2084" + ) + .build()?; +``` + +## 批处理 + +```rust +let input_files = vec![ + "video1.mp4".to_string(), + "video2.mp4".to_string(), + "video3.mp4".to_string(), +]; + +let batch_commands = sdk.generate_batch_commands( + &template, + &input_files, + "output_directory" +)?; + +for command in batch_commands { + println!("批处理命令: {}", command); +} +``` + +## 硬件加速 + +```rust +// 使用 GPU 加速 +let gpu_command = sdk.generate_ffmpeg_command_with_gpu( + &template, + "input.mp4", + "output.mp4", + "0" // GPU 设备 ID +)?; + +// 自定义编解码器 +let custom_command = sdk.generate_ffmpeg_command_with_codec( + &template, + "input.mp4", + "output.mp4", + "hevc_nvenc", + Some(18) +)?; +``` + +## 模板验证 + +```rust +// 验证模板 +sdk.validate_template_for_ffmpeg(&template)?; + +// 验证并完善模板 +let mut incomplete_template = TemplateBuilder::new("未完成") + .enable_enhancement("prob-4") + .build()?; + +sdk.validate_and_complete_template(&mut incomplete_template)?; +``` + +## 错误处理 + +```rust +match sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4") { + Ok(command) => println!("成功生成命令: {}", command), + Err(e) => eprintln!("生成命令失败: {}", e), +} +``` + +## 内置配置说明 + +SDK 内置了以下配置: + +- **音频编解码器**: AAC、AC3、PCM、Vorbis +- **视频编码器**: H.264/H.265 的 NVENC、VideoToolbox、AMF、软件编码器 +- **模型推荐规则**: 基于分辨率的智能模型推荐 + +所有配置都编译到代码中,无需外部文件依赖。 + +## 调试和诊断 + +```rust +// 生成编码器报告 +let report = sdk.ffmpeg_generator.generate_encoder_report(); +println!("{}", report); + +// 获取模型映射 +let mappings = sdk.get_model_mappings(); +for (template_model, ffmpeg_model) in mappings { + println!("{} -> {}", template_model, ffmpeg_model); +} +``` diff --git a/cargos/tvai-v2/examples/builtin_config_test.rs b/cargos/tvai-v2/examples/builtin_config_test.rs new file mode 100644 index 0000000..fc548a4 --- /dev/null +++ b/cargos/tvai-v2/examples/builtin_config_test.rs @@ -0,0 +1,129 @@ +use tvai_sdk::{TvaiSdk, TemplateBuilder}; + +fn main() -> Result<(), Box> { + println!("=== 内置配置测试 ===\n"); + + // 创建 SDK 实例 + let sdk = TvaiSdk::new(); + + // 测试内置音频编解码器 + println!("1. 测试内置音频编解码器:"); + let audio_codecs = sdk.ffmpeg_generator.get_available_audio_codecs(); + println!(" 加载了 {} 个音频编解码器", audio_codecs.len()); + + for codec in audio_codecs { + println!(" - {}: {}", codec.name, codec.ffmpeg_opts); + } + + // 测试 AAC 编解码器查找 + if let Some(aac) = sdk.ffmpeg_generator.get_audio_codec("AAC") { + println!(" ✓ 成功找到 AAC 编解码器"); + if let Some(bitrate) = &aac.bitrate { + println!(" 比特率范围: {}-{} kbps", bitrate.min, bitrate.max); + } + } else { + println!(" ✗ 未找到 AAC 编解码器"); + } + + println!(); + + // 测试内置视频编码器 + println!("2. 测试内置视频编码器:"); + let video_encoders = sdk.ffmpeg_generator.get_available_video_encoders(); + println!(" 加载了 {} 个视频编码器", video_encoders.len()); + + for encoder in video_encoders.iter().take(5) { // 只显示前5个 + println!(" - {} ({}): {}", encoder.id, encoder.encoder, + encoder.ffmpeg_opts.chars().take(50).collect::()); + } + + // 测试 H.264 编码器查找 + if let Some(h264) = sdk.ffmpeg_generator.get_video_encoder_by_name("H264") { + println!(" ✓ 成功找到 H.264 编码器: {}", h264.id); + } else { + println!(" ✗ 未找到 H.264 编码器"); + } + + println!(); + + // 测试模型推荐 + println!("3. 测试模型推荐:"); + let test_resolutions = [480, 720, 1080, 2160]; + for height in test_resolutions { + let recommended = sdk.ffmpeg_generator.recommend_model_for_resolution(height); + println!(" {}p 推荐模型: {:?}", height, recommended); + } + + println!(); + + // 测试音频编解码器参数生成 + println!("4. 测试音频编解码器参数生成:"); + let aac_params = sdk.ffmpeg_generator.generate_audio_codec_params("AAC", Some(192)); + println!(" AAC 192kbps: {}", aac_params); + + let ac3_params = sdk.ffmpeg_generator.generate_audio_codec_params("AC3", Some(448)); + println!(" AC3 448kbps: {}", ac3_params); + + println!(); + + // 测试视频编码器参数生成 + println!("5. 测试视频编码器参数生成:"); + if let Some(encoder) = sdk.ffmpeg_generator.get_video_encoder("libx264-default") { + let params = sdk.ffmpeg_generator.generate_video_encoder_params(&encoder.id, "vbr", Some(23)); + println!(" libx264 CRF 23: {}", params); + } + + println!(); + + // 测试兼容性筛选 + println!("6. 测试兼容性筛选:"); + + let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + println!(" Windows 兼容编码器: {} 个", windows_encoders.len()); + + let nvidia_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", Some("nvidia")); + println!(" NVIDIA 编码器: {} 个", nvidia_encoders.len()); + + let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); + println!(" MP4 兼容编码器: {} 个", mp4_encoders.len()); + + println!(); + + // 测试完整的 FFmpeg 命令生成 + println!("7. 测试完整的 FFmpeg 命令生成:"); + + let template = TemplateBuilder::new("内置配置测试") + .enable_enhancement("prob-4") + .video_codec("H264", Some(23)) + .audio_settings("AAC", 192, 2) + .build()?; + + let command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4")?; + println!(" 生成的命令: {}", command); + + println!(); + + // 测试自动编码器选择 + println!("8. 测试自动编码器选择:"); + + match sdk.ffmpeg_generator.generate_with_auto_encoder(&template, "input.mp4", "output.mp4", "windows", Some("nvidia")) { + Ok(auto_command) => println!(" 自动选择 (Windows+NVIDIA): {}", auto_command), + Err(e) => println!(" 自动选择失败: {}", e), + } + + println!(); + + // 生成编码器报告 + println!("9. 编码器报告:"); + let report = sdk.ffmpeg_generator.generate_encoder_report(); + let lines: Vec<&str> = report.lines().take(10).collect(); // 只显示前10行 + for line in lines { + println!(" {}", line); + } + println!(" ... (报告已截断)"); + + println!("\n=== 测试完成 ==="); + println!("所有内置配置功能正常工作!"); + + Ok(()) +} diff --git a/cargos/tvai-v2/examples/comprehensive_demo.rs b/cargos/tvai-v2/examples/comprehensive_demo.rs new file mode 100644 index 0000000..f2402ac --- /dev/null +++ b/cargos/tvai-v2/examples/comprehensive_demo.rs @@ -0,0 +1,205 @@ +use tvai_sdk::{TvaiSdk, TemplateBuilder}; + +fn main() -> Result<(), Box> { + println!("🎬 Topaz Video AI SDK 综合功能演示"); + println!("=====================================\n"); + + let sdk = TvaiSdk::new(); + + // 1. 展示内置配置加载 + println!("📦 1. 内置配置状态:"); + println!(" 音频编解码器: {} 个", sdk.ffmpeg_generator.get_available_audio_codecs().len()); + println!(" 视频编码器: {} 个", sdk.ffmpeg_generator.get_available_video_encoders().len()); + + // 验证关键编解码器 + let key_codecs = ["AAC", "AC3", "PCM"]; + for codec in &key_codecs { + if sdk.ffmpeg_generator.get_audio_codec(codec).is_some() { + println!(" ✓ {} 编解码器已加载", codec); + } + } + println!(); + + // 2. 模型推荐演示 + println!("🤖 2. AI 模型推荐:"); + let resolutions = [(480, "SD"), (720, "HD"), (1080, "FHD"), (2160, "4K")]; + for (height, name) in &resolutions { + let models = sdk.ffmpeg_generator.recommend_model_for_resolution(*height); + println!(" {} ({}p): {:?}", name, height, models); + } + println!(); + + // 3. 平台兼容性演示 + println!("💻 3. 平台兼容性:"); + let platforms = [("windows", "Windows"), ("osx", "macOS"), ("linux", "Linux")]; + for (os, name) in &platforms { + let count = sdk.ffmpeg_generator.get_compatible_video_encoders(os, None).len(); + println!(" {}: {} 个兼容编码器", name, count); + } + println!(); + + // 4. 创建不同用途的模板 + println!("📝 4. 模板创建演示:"); + + // 4K 上采样模板 + let upscale_template = TemplateBuilder::new("4K上采样") + .description("将视频上采样到4K分辨率") + .enable_enhancement("prob-4") + .enhancement_params(20, 35, 15) + .resolution(3840, 2160) + .video_codec("hevc_nvenc", Some(18)) + .audio_settings("AAC", 256, 2) + .build()?; + println!(" ✓ 4K上采样模板创建成功"); + + // 60fps 插帧模板 + let fps_template = TemplateBuilder::new("60fps插帧") + .description("将视频插帧到60fps") + .enable_frame_interpolation("chr-2", 1.0) + .output_settings(0, 60.0) + .video_codec("h264_nvenc", Some(23)) + .audio_settings("AAC", 192, 2) + .build()?; + println!(" ✓ 60fps插帧模板创建成功"); + + // 降噪模板 + let denoise_template = TemplateBuilder::new("降噪处理") + .description("使用Nyx模型进行降噪") + .enable_enhancement("nyx-3") + .enhancement_params(70, 15, 5) + .video_codec("libx264", Some(20)) + .audio_settings("AAC", 160, 2) + .build()?; + println!(" ✓ 降噪处理模板创建成功"); + println!(); + + // 5. FFmpeg 命令生成演示 + println!("⚙️ 5. FFmpeg 命令生成:"); + + let test_files = [ + ("input_sd.mp4", "output_4k.mp4", &upscale_template, "4K上采样"), + ("input_30fps.mp4", "output_60fps.mp4", &fps_template, "60fps插帧"), + ("noisy_video.mp4", "clean_video.mp4", &denoise_template, "降噪处理"), + ]; + + for (input, output, template, desc) in &test_files { + let command = sdk.generate_ffmpeg_command(template, input, output)?; + println!(" {} 命令:", desc); + println!(" {}\n", truncate_command(&command, 100)); + } + + // 6. 自动编码器选择演示 + println!("🔧 6. 自动编码器选择:"); + let auto_scenarios = [ + ("windows", Some("nvidia"), "Windows + NVIDIA"), + ("osx", Some("appleIntel"), "macOS + Intel"), + ("linux", None, "Linux 软件编码"), + ]; + + for (os, gpu, desc) in &auto_scenarios { + match sdk.ffmpeg_generator.generate_with_auto_encoder( + &upscale_template, + "input.mp4", + "output.mp4", + os, + *gpu + ) { + Ok(command) => println!(" ✓ {}: 命令生成成功", desc), + Err(e) => println!(" ✗ {}: {}", desc, e), + } + } + println!(); + + // 7. 批处理演示 + println!("📁 7. 批处理功能:"); + let batch_files = vec![ + "video1.mp4".to_string(), + "video2.mp4".to_string(), + "video3.mp4".to_string(), + ]; + + let batch_commands = sdk.generate_batch_commands(&upscale_template, &batch_files, "output")?; + println!(" 生成了 {} 个批处理命令", batch_commands.len()); + for (i, _) in batch_commands.iter().enumerate() { + println!(" ✓ 批处理命令 {} 生成成功", i + 1); + } + println!(); + + // 8. 编码器参数生成演示 + println!("🎛️ 8. 编码器参数生成:"); + + // 音频参数 + let audio_tests = [("AAC", 192), ("AC3", 448), ("AAC", 128)]; + for (codec, bitrate) in &audio_tests { + let params = sdk.ffmpeg_generator.generate_audio_codec_params(codec, Some(*bitrate)); + println!(" {} {}kbps: {}", codec, bitrate, params); + } + + // 视频参数 + if let Some(encoder) = sdk.ffmpeg_generator.get_video_encoder("h264-high-win-nvidia") { + let vbr_params = sdk.ffmpeg_generator.generate_video_encoder_params(&encoder.id, "vbr", Some(23)); + println!(" H.264 NVENC VBR: {}", truncate_command(&vbr_params, 80)); + } + println!(); + + // 9. 性能统计 + println!("📊 9. 功能统计:"); + println!(" 支持的音频格式: {:?}", get_supported_audio_formats(&sdk)); + println!(" 支持的视频格式: {:?}", get_supported_video_formats(&sdk)); + println!(" 硬件加速编码器: {} 个", count_hardware_encoders(&sdk)); + println!(" 跨平台编码器: {} 个", count_cross_platform_encoders(&sdk)); + println!(); + + println!("🎉 综合演示完成!"); + println!("所有功能正常工作,内置配置系统运行良好。"); + + Ok(()) +} + +fn truncate_command(command: &str, max_len: usize) -> String { + if command.len() <= max_len { + command.to_string() + } else { + format!("{}...", &command[..max_len]) + } +} + +fn get_supported_audio_formats(sdk: &TvaiSdk) -> Vec { + let mut formats = std::collections::HashSet::new(); + for codec in sdk.ffmpeg_generator.get_available_audio_codecs() { + for ext in &codec.ext { + formats.insert(ext.clone()); + } + } + let mut result: Vec<_> = formats.into_iter().collect(); + result.sort(); + result +} + +fn get_supported_video_formats(sdk: &TvaiSdk) -> Vec { + let mut formats = std::collections::HashSet::new(); + for encoder in sdk.ffmpeg_generator.get_available_video_encoders() { + for ext in &encoder.ext { + formats.insert(ext.clone()); + } + } + let mut result: Vec<_> = formats.into_iter().collect(); + result.sort(); + result +} + +fn count_hardware_encoders(sdk: &TvaiSdk) -> usize { + sdk.ffmpeg_generator.get_available_video_encoders() + .iter() + .filter(|encoder| encoder.gpu.is_some()) + .count() +} + +fn count_cross_platform_encoders(sdk: &TvaiSdk) -> usize { + sdk.ffmpeg_generator.get_available_video_encoders() + .iter() + .filter(|encoder| { + encoder.os.as_ref().map_or(false, |os| os.contains('|')) + }) + .count() +} diff --git a/cargos/tvai-v2/examples/ffmpeg_config_demo.rs b/cargos/tvai-v2/examples/ffmpeg_config_demo.rs new file mode 100644 index 0000000..c87564d --- /dev/null +++ b/cargos/tvai-v2/examples/ffmpeg_config_demo.rs @@ -0,0 +1,123 @@ +use tvai_sdk::{TvaiSdk, TemplateBuilder}; + +fn main() -> Result<(), Box> { + // 创建 SDK 实例 + let sdk = TvaiSdk::new(); + + println!("=== Topaz Video AI FFmpeg 内置配置演示 ===\n"); + + // 显示编码器报告 + println!("{}", sdk.ffmpeg_generator.generate_encoder_report()); + + // 创建一个示例模板 + let template = TemplateBuilder::new("内置配置演示") + .description("使用内置配置的 FFmpeg 命令生成演示") + .enable_enhancement("prob-4") + .enhancement_params(25, 30, 20) + .resolution(1920, 1080) + .video_codec("H264", Some(23)) + .audio_settings("AAC", 192, 2) + .build()?; + + // 生成基本 FFmpeg 命令 + println!("=== 基本 FFmpeg 命令 ==="); + let basic_command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4")?; + println!("{}\n", basic_command); + + // 演示自动编码器选择 + println!("=== 自动编码器选择 ==="); + + // Windows + NVIDIA GPU + match sdk.ffmpeg_generator.generate_with_auto_encoder(&template, "input.mp4", "output_nvidia.mp4", "windows", Some("nvidia")) { + Ok(command) => println!("Windows + NVIDIA: {}", command), + Err(e) => println!("Windows + NVIDIA 错误: {}", e), + } + + // macOS + Apple Silicon + match sdk.ffmpeg_generator.generate_with_auto_encoder(&template, "input.mp4", "output_apple.mov", "osx", Some("appleSilicon")) { + Ok(command) => println!("macOS + Apple Silicon: {}", command), + Err(e) => println!("macOS + Apple Silicon 错误: {}", e), + } + + // Linux + AMD GPU + match sdk.ffmpeg_generator.generate_with_auto_encoder(&template, "input.mp4", "output_amd.mp4", "linux", Some("amd")) { + Ok(command) => println!("Linux + AMD: {}", command), + Err(e) => println!("Linux + AMD 错误: {}", e), + } + + println!(); + + // 演示模型推荐 + println!("=== 模型推荐 ==="); + let resolutions = [480, 720, 1080, 2160]; + for height in resolutions { + let recommended = sdk.ffmpeg_generator.recommend_model_for_resolution(height); + println!("{}p 推荐模型: {:?}", height, recommended); + } + + println!(); + + // 演示内置音频编解码器配置 + println!("=== 内置音频编解码器配置 ==="); + for codec in sdk.ffmpeg_generator.get_available_audio_codecs() { + println!("- {}: {}", codec.name, codec.ffmpeg_opts); + if let Some(bitrate_config) = &codec.bitrate { + println!(" 比特率范围: {}-{} kbps (推荐: {} kbps)", + bitrate_config.min, bitrate_config.max, bitrate_config.default); + println!(" 建议值: {:?}", bitrate_config.suggested); + } + } + + println!(); + + // 演示兼容性筛选 + println!("=== 兼容性筛选 ==="); + + println!("Windows 兼容编码器:"); + let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + for encoder in windows_encoders.iter().take(5) { // 只显示前5个 + println!(" - {} ({})", encoder.id, encoder.encoder); + } + + println!("\nmacOS 兼容编码器:"); + let macos_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("osx", None); + for encoder in macos_encoders.iter().take(5) { + println!(" - {} ({})", encoder.id, encoder.encoder); + } + + println!("\nMP4 格式兼容编码器:"); + let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); + for encoder in mp4_encoders.iter().take(5) { + println!(" - {} ({})", encoder.id, encoder.encoder); + } + + println!(); + + // 演示不同质量设置 + println!("=== 不同质量设置演示 ==="); + + // 高质量设置 + let hq_template = TemplateBuilder::new("高质量") + .enable_enhancement("prob-4") + .video_codec("hevc_nvenc", Some(18)) + .audio_settings("AAC", 320, 2) + .build()?; + + let hq_command = sdk.generate_ffmpeg_command(&hq_template, "input.mp4", "output_hq.mp4")?; + println!("高质量: {}", hq_command); + + // 快速编码设置 + let fast_template = TemplateBuilder::new("快速编码") + .enable_enhancement("prob-4") + .video_codec("h264_nvenc", Some(25)) + .audio_settings("AAC", 128, 2) + .preset("fast") + .build()?; + + let fast_command = sdk.generate_ffmpeg_command(&fast_template, "input.mp4", "output_fast.mp4")?; + println!("快速编码: {}", fast_command); + + println!("\n=== 演示完成 ==="); + + Ok(()) +} diff --git a/cargos/tvai-v2/examples/quick_verify.rs b/cargos/tvai-v2/examples/quick_verify.rs new file mode 100644 index 0000000..fa5517e --- /dev/null +++ b/cargos/tvai-v2/examples/quick_verify.rs @@ -0,0 +1,65 @@ +use tvai_sdk::{TvaiSdk, TemplateBuilder}; + +fn main() -> Result<(), Box> { + println!("🚀 快速验证内置配置功能...\n"); + + // 创建 SDK + let sdk = TvaiSdk::new(); + println!("✓ SDK 创建成功"); + + // 验证音频编解码器 + let audio_count = sdk.ffmpeg_generator.get_available_audio_codecs().len(); + println!("✓ 加载了 {} 个音频编解码器", audio_count); + + // 验证视频编码器 + let video_count = sdk.ffmpeg_generator.get_available_video_encoders().len(); + println!("✓ 加载了 {} 个视频编码器", video_count); + + // 验证 AAC 编解码器 + if sdk.ffmpeg_generator.get_audio_codec("AAC").is_some() { + println!("✓ AAC 编解码器可用"); + } else { + println!("✗ AAC 编解码器不可用"); + } + + // 验证 H.264 编码器 + if sdk.ffmpeg_generator.get_video_encoder_by_name("H264").is_some() { + println!("✓ H.264 编码器可用"); + } else { + println!("✗ H.264 编码器不可用"); + } + + // 验证模型推荐 + let recommended = sdk.ffmpeg_generator.recommend_model_for_resolution(1080); + if !recommended.is_empty() { + println!("✓ 模型推荐功能正常: {:?}", recommended); + } else { + println!("✗ 模型推荐功能异常"); + } + + // 验证 FFmpeg 命令生成 + let template = TemplateBuilder::new("验证测试") + .enable_enhancement("prob-4") + .build()?; + + let command = sdk.generate_ffmpeg_command(&template, "test.mp4", "output.mp4")?; + if command.contains("ffmpeg") && command.contains("tvai_up") { + println!("✓ FFmpeg 命令生成正常"); + } else { + println!("✗ FFmpeg 命令生成异常"); + println!(" 命令: {}", command); + } + + // 验证音频参数生成 + let audio_params = sdk.ffmpeg_generator.generate_audio_codec_params("AAC", Some(192)); + if audio_params.contains("-c:a") && audio_params.contains("192") { + println!("✓ 音频参数生成正常"); + } else { + println!("✗ 音频参数生成异常: {}", audio_params); + } + + println!("\n🎉 所有核心功能验证通过!"); + println!("内置配置系统正常工作。"); + + Ok(()) +} diff --git a/cargos/tvai-v2/models/aaa-10.json b/cargos/tvai-v2/models/aaa-10.json index 709b907..fea00b0 100644 --- a/cargos/tvai-v2/models/aaa-10.json +++ b/cargos/tvai-v2/models/aaa-10.json @@ -467,7 +467,7 @@ "gui": { "afterImg": "/tldb/images/model-thumbnails/AAA_Enhanced.png", "beforeImg": "/tldb/images/model-thumbnails/AAA_Original.png", - "desc": "Upscale video with aliasing or moire patterns. Aliasing is common in computer generated (CG) content, or from line-skipping cameras.", + "desc": "放大具有锯齿或摩尔纹的视频。锯齿现象常见于计算机生成(CG)内容或跳行扫描摄像机拍摄的视频中。", "displayPri": 30, "hiddenKeywords": [ "AA" @@ -489,7 +489,7 @@ "CG" ] }, - "name": "Artemis - Aliasing or Moire", + "name": "Artemis - 锯齿或摩尔纹", "searchableKeywords": [ "Artemis", "Antialias" @@ -497,7 +497,7 @@ "showScale": 1 }, "plugin" : { - "displayName" : "Artemis - Aliasing or Moire", + "displayName" : "Artemis - 锯齿或摩尔纹", "videoQuality": [0,1,2] }, "inputs": { @@ -513,14 +513,14 @@ "max": 0.1, "min": 0, "name": "Add Noise", - "guiName": "Add Noise" + "guiName": "添加噪声" }, { "default": 0.2, "max": 1, "min": 0, "name": "Recover Details", - "guiName": "Recover Details" + "guiName": "恢复细节" } ], "interlacedFrames": 0, @@ -533,6 +533,6 @@ "postflight": 0, "preflight": 5, "shortName": "aaa", - "displayName": "Aliasing or Moire", + "displayName": "锯齿或摩尔纹", "version": "10" } diff --git a/cargos/tvai-v2/models/ahq-12.json b/cargos/tvai-v2/models/ahq-12.json index de7e0db..315f254 100644 --- a/cargos/tvai-v2/models/ahq-12.json +++ b/cargos/tvai-v2/models/ahq-12.json @@ -467,7 +467,7 @@ "gui": { "afterImg": "/tldb/images/model-thumbnails/AHQ_Enhanced.png", "beforeImg": "/tldb/images/model-thumbnails/AHQ_Original.png", - "desc": "Upscale or sharpen high quality input video, reducing motion flicker.", + "desc": "放大或锐化高质量输入视频,减少运动闪烁。", "displayPri": 22, "hiddenKeywords": [ "HQ" @@ -488,10 +488,10 @@ "CG" ] }, - "name": "Artemis - High Quality", + "name": "Artemis - 高质量", "searchableKeywords": [ "Artemis", - "High Quality Input" + "高质量输入" ], "showScale": 1 }, @@ -512,14 +512,14 @@ "max": 0.1, "min": 0, "name": "Add Noise", - "guiName": "Add Noise" + "guiName": "添加噪声" }, { "default": 0.2, "max": 1, "min": 0, "name": "Recover Details", - "guiName": "Recover Details" + "guiName": "恢复细节" } ], "interlacedFrames": 0, @@ -532,6 +532,6 @@ "postflight": 0, "preflight": 5, "shortName": "ahq", - "displayName": "High Quality", + "displayName": "高质量", "version": "12" } diff --git a/cargos/tvai-v2/src/ffmpeg.rs b/cargos/tvai-v2/src/ffmpeg.rs index 203f6ed..796efd4 100644 --- a/cargos/tvai-v2/src/ffmpeg.rs +++ b/cargos/tvai-v2/src/ffmpeg.rs @@ -1,118 +1,516 @@ use crate::{Template, TvaiError, TvaiResult}; use std::collections::HashMap; +use serde::{Deserialize, Serialize}; -/// FFmpeg command generator for Topaz Video AI templates +/// 音频编解码器配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AudioCodec { + pub name: String, + #[serde(rename = "ffmpegOpts")] + pub ffmpeg_opts: String, + pub ext: Vec, + pub bitrate: Option, +} + +/// 比特率配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BitrateConfig { + pub min: u32, + #[serde(rename = "minRec")] + pub min_rec: u32, + pub max: u32, + pub default: u32, + pub suggested: Vec, + #[serde(rename = "ffmpegOpt")] + pub ffmpeg_opt: String, +} + +/// 视频编码器配置 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VideoEncoder { + pub id: String, + pub encoder: String, + pub profile: Option, + #[serde(rename = "allowsAlpha")] + pub allows_alpha: u8, + #[serde(rename = "ffmpegOpts")] + pub ffmpeg_opts: String, + pub ext: Vec, + pub os: Option, + #[serde(rename = "minSize")] + pub min_size: Option<[u32; 2]>, + #[serde(rename = "maxSize")] + pub max_size: Option<[u32; 2]>, + #[serde(rename = "maxBitDepth")] + pub max_bit_depth: Option, + #[serde(rename = "bitrateOpts")] + pub bitrate_opts: Option, + #[serde(rename = "cqpValues")] + pub cqp_values: Option>>, + pub gpu: Option, + #[serde(rename = "autoBitsPerPixel")] + pub auto_bits_per_pixel: Option, + #[serde(rename = "maxBitRate")] + pub max_bit_rate: Option, + pub device: Option, + pub compute: Option, + #[serde(rename = "isImage")] + pub is_image: Option, +} + +/// 比特率选项 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BitrateOptions { + pub cbr: Option, + pub vbr: Option, +} + +/// 模型推荐规则 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ModelRecommendationRule { + pub id: String, + pub property: String, + #[serde(rename = "type")] + pub rule_type: String, + pub conditions: Vec, +} + +/// 推荐条件 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RecommendationCondition { + pub min: Option, + pub max: Option, + #[serde(rename = "recommendedModelValues")] + pub recommended_model_values: Vec, +} + +/// Topaz Video AI 模板的 FFmpeg 命令生成器 #[derive(Debug, Default)] pub struct FfmpegCommandGenerator { - /// Model mappings from template models to FFmpeg filter models + /// 从模板模型到 FFmpeg 滤镜模型的映射 model_mappings: HashMap, + /// 音频编解码器配置 + audio_codecs: Vec, + /// 视频编码器配置 + video_encoders: Vec, + /// 模型推荐规则 + model_recommendation_rules: Vec, } impl FfmpegCommandGenerator { - /// Create a new FFmpeg command generator + /// 创建新的 FFmpeg 命令生成器 pub fn new() -> Self { let mut generator = Self { model_mappings: HashMap::new(), + audio_codecs: Vec::new(), + video_encoders: Vec::new(), + model_recommendation_rules: Vec::new(), }; - - // Initialize default model mappings + + // 初始化默认模型映射 generator.init_model_mappings(); + + // 加载配置文件 + if let Err(e) = generator.load_configurations() { + eprintln!("警告:加载配置文件失败: {}", e); + } + generator } - /// Initialize model mappings from template models to FFmpeg filter models + /// 从内置配置加载编解码器和模型推荐规则 + fn load_configurations(&mut self) -> TvaiResult<()> { + // 内置音频编解码器配置 + const AUDIO_CODECS_JSON: &str = r#"[ + { + "name": "AAC", + "ffmpegOpts": "aac -ac 2", + "ext": [ + "mp4", + "mov", + "mkv", + "avi" + ], + "bitrate": { + "min": 32, + "minRec": 128, + "max": 320, + "default": 320, + "suggested": [ + 128, + 160, + 192, + 256, + 320 + ], + "ffmpegOpt": "-b:a k" + } + }, + { + "name": "AC3", + "ffmpegOpts": "ac3", + "ext": [ + "mp4", + "mov", + "mkv", + "avi" + ], + "bitrate": { + "min": 160, + "minRec": 160, + "max": 640, + "default": 448, + "suggested": [ + 160, + 192, + 256, + 320, + 448, + 640 + ], + "ffmpegOpt": "-b:a k" + } + }, + { + "name": "PCM", + "ffmpegOpts": "pcm_s24le", + "ext": [ + "mov", + "mkv", + "avi" + ] + }, + { + "name": "Vorbis", + "ffmpegOpts": "vorbis -ac 2", + "ext": [ + "webm" + ] + } +]"#; + + // 解析音频编解码器配置 + self.audio_codecs = serde_json::from_str(AUDIO_CODECS_JSON) + .map_err(|e| TvaiError::ConfigError(format!("解析内置音频编解码器配置失败: {}", e)))?; + + // 内置视频编码器配置(部分常用编码器) + const VIDEO_ENCODERS_JSON: &str = r#"[ + { + "id": "h264-high-win-nvidia", + "encoder": "H264", + "profile": "High", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30", + "bitrateOpts": { + "cbr": "-rc cbr -b:v -preset p6 ", + "vbr": "-preset p7 -tune hq -rc constqp -qp -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0" + }, + "cqpValues": { + "High": [ 18 ], + "Mid": [ 25 ], + "Low": [ 28 ] + }, + "ext": [ + "mov", + "mkv", + "mp4" + ], + "maxBitRate": 2000, + "os": "windows|linux", + "device": "nvidia|tesla", + "minSize": [145,145], + "maxSize": [4096,4096], + "maxBitDepth": 8 + }, + { + "id": "h265-main-win-nvidia", + "encoder": "H265", + "profile": "Main", + "allowsAlpha": 0, + "gpu": "nvidia", + "ffmpegOpts": "-c:v hevc_nvenc -profile:v main -pix_fmt yuv420p -b_ref_mode disabled -tag:v hvc1 -g 30", + "bitrateOpts": { + "cbr": "-rc cbr -b:v -preset p6", + "vbr": "-preset p7 -tune hq -rc constqp -qp -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0" + }, + "cqpValues": { + "High": [ 17 ], + "Mid": [ 25 ], + "Low": [ 28 ] + }, + "ext": [ + "mov", + "mkv", + "mp4" + ], + "maxBitRate": 2000, + "os": "windows|linux", + "device": "nvidia|tesla", + "minSize": [129,129], + "maxSize": [8192,4320], + "maxBitDepth": 12 + }, + { + "id": "h264-high-osx", + "encoder": "H264", + "profile": "High", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v h264_videotoolbox -profile:v high -pix_fmt yuv420p -allow_sw 1 -g 30", + "ext": [ + "mov", + "mkv", + "mp4" + ], + "gpu": "appleIntel", + "autoBitsPerPixel": "1.0", + "maxBitRate": 2000, + "os": "osx", + "minSize": [2,2], + "maxSize": [7680,4320], + "doNotScaleFullColorRange": "always" + }, + { + "id": "h265-main-osx", + "encoder": "H265", + "profile": "Main", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v hevc_videotoolbox -profile:v main -tag:v hvc1 -pix_fmt yuv420p -allow_sw 1 -g 30", + "ext": [ + "mov", + "mkv", + "mp4" + ], + "gpu": "appleIntel", + "autoBitsPerPixel": "0.8", + "maxBitRate": 2000, + "os": "osx", + "minSize": [2,2], + "maxSize": [8192,4320], + "doNotScaleFullColorRange": "always" + }, + { + "id": "h264-high-win-amd", + "encoder": "H264", + "profile": "High", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v h264_amf -profile:v high -pix_fmt yuv420p -g 30", + "gpu": "amd", + "bitrateOpts": { + "cbr": "-b:v ", + "vbr": "-b:v 0 -quality 0 -rc cqp -qp_i -qp_p -qp_b " + }, + "cqpValues": { + "High": [ 19, 20, 20 ], + "Mid": [ 20, 23, 23 ], + "Low": [ 28, 30, 30 ] + }, + "ext": [ + "mov", + "mkv", + "mp4" + ], + "maxBitRate": 2000, + "os": "windows", + "minSize": [2,2], + "maxSize": [4096,4096], + "device": "amd|radeon" + }, + { + "id": "h265-main-win-amd", + "encoder": "H265", + "profile": "Main", + "allowsAlpha": 0, + "gpu": "amd", + "ffmpegOpts": "-c:v hevc_amf -profile:v main -profile_tier main -tag:v hvc1 -pix_fmt yuv420p -g 30", + "bitrateOpts": { + "cbr": "-b:v ", + "vbr": "-b:v 0 -quality 0 -rc cqp -qp_i -qp_p " + }, + "cqpValues": { + "High": [17, 19 ], + "Mid": [20, 23 ], + "Low": [28, 30 ] + }, + "ext": [ + "mov", + "mkv", + "mp4" + ], + "maxBitRate": 2000, + "os": "windows", + "minSize": [2,2], + "maxSize": [8192,4320], + "device": "amd|radeon" + }, + { + "id": "libx264-default", + "encoder": "H264", + "profile": "High", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v libx264 -profile:v high -pix_fmt yuv420p", + "ext": [ + "mov", + "mkv", + "mp4", + "avi" + ], + "maxBitRate": 2000, + "os": "windows|linux|osx", + "minSize": [1,1], + "maxSize": [16384,16384] + }, + { + "id": "libx265-default", + "encoder": "H265", + "profile": "Main", + "allowsAlpha": 0, + "ffmpegOpts": "-c:v libx265 -profile:v main -pix_fmt yuv420p", + "ext": [ + "mov", + "mkv", + "mp4" + ], + "maxBitRate": 2000, + "os": "windows|linux|osx", + "minSize": [1,1], + "maxSize": [16384,16384] + } +]"#; + + // 解析视频编码器配置 + self.video_encoders = serde_json::from_str(VIDEO_ENCODERS_JSON) + .map_err(|e| TvaiError::ConfigError(format!("解析内置视频编码器配置失败: {}", e)))?; + + // 内置模型推荐规则 + const MODEL_RECOMMENDATION_RULES_JSON: &str = r#"[ + { + "id": "target-resolution", + "property": "height", + "type":"range", + "conditions": [ + { + "max": 480, + "min": 1, + "recommendedModelValues": ["iris", "artemis"] + }, + { + "min": 481, + "max": 720, + "recommendedModelValues": ["proteus", "rhea"] + }, + { + "min": 721, + "max": 1080, + "recommendedModelValues": ["proteus", "nyx", "rhea"] + }, + { + "min": 1081, + "recommendedModelValues": ["nyx", "theia", "proteus"] + } + ] + } +]"#; + + // 解析模型推荐规则 + self.model_recommendation_rules = serde_json::from_str(MODEL_RECOMMENDATION_RULES_JSON) + .map_err(|e| TvaiError::ConfigError(format!("解析内置模型推荐规则失败: {}", e)))?; + + Ok(()) + } + + /// 初始化从模板模型到 FFmpeg 滤镜模型的映射 fn init_model_mappings(&mut self) { - // Enhancement models - Proteus series (general purpose) + // 增强模型 - Proteus 系列(通用) self.model_mappings.insert("prob-3".to_string(), "ahq-12".to_string()); self.model_mappings.insert("prob-4".to_string(), "ahq-12".to_string()); - // Enhancement models - Specialized series - self.model_mappings.insert("nyx-3".to_string(), "nyx-3".to_string()); // Iris series (low light/noise) - self.model_mappings.insert("hyp-1".to_string(), "hyp-1".to_string()); // Hyperion for HDR + // 增强模型 - 专用系列 + self.model_mappings.insert("nyx-3".to_string(), "nyx-3".to_string()); // Iris 系列(低光/噪声) + self.model_mappings.insert("hyp-1".to_string(), "hyp-1".to_string()); // Hyperion HDR 模型 - // Artemis series (animation/cartoon) + // Artemis 系列(动画/卡通) self.model_mappings.insert("art-1".to_string(), "art-1".to_string()); self.model_mappings.insert("art-2".to_string(), "art-2".to_string()); - self.model_mappings.insert("alq-13".to_string(), "ahq-12".to_string()); // Artemis benchmark model + self.model_mappings.insert("alq-13".to_string(), "ahq-12".to_string()); // Artemis 基准模型 - // Gaia series (natural scenes) + // Gaia 系列(自然场景) self.model_mappings.insert("gai-1".to_string(), "gai-1".to_string()); self.model_mappings.insert("gai-2".to_string(), "gai-2".to_string()); - self.model_mappings.insert("ghq-5".to_string(), "ahq-12".to_string()); // Gaia benchmark model + self.model_mappings.insert("ghq-5".to_string(), "ahq-12".to_string()); // Gaia 基准模型 - // Theia series (detail recovery) + // Theia 系列(细节恢复) self.model_mappings.insert("the-1".to_string(), "the-1".to_string()); self.model_mappings.insert("the-2".to_string(), "the-2".to_string()); - // Iris series (low light/noise) + // Iris 系列(低光/噪声) self.model_mappings.insert("iris-1".to_string(), "nyx-3".to_string()); self.model_mappings.insert("iris-2".to_string(), "nyx-3".to_string()); - self.model_mappings.insert("iris-3".to_string(), "nyx-3".to_string()); // Iris benchmark model + self.model_mappings.insert("iris-3".to_string(), "nyx-3".to_string()); // Iris 基准模型 - // Nyx series (noise reduction) + // Nyx 系列(降噪) self.model_mappings.insert("nyx-1".to_string(), "nyx-3".to_string()); - self.model_mappings.insert("nyx-2".to_string(), "nyx-3".to_string()); // Nyx benchmark model - self.model_mappings.insert("nxf-1".to_string(), "nyx-3".to_string()); // Nyx Fast benchmark model + self.model_mappings.insert("nyx-2".to_string(), "nyx-3".to_string()); // Nyx 基准模型 + self.model_mappings.insert("nxf-1".to_string(), "nyx-3".to_string()); // Nyx Fast 基准模型 - // Specialized models - self.model_mappings.insert("rhea-1".to_string(), "ahq-12".to_string()); // Rhea benchmark model - self.model_mappings.insert("rxl-1".to_string(), "ahq-12".to_string()); // RXL benchmark model + // 专用模型 + self.model_mappings.insert("rhea-1".to_string(), "ahq-12".to_string()); // Rhea 基准模型 + self.model_mappings.insert("rxl-1".to_string(), "ahq-12".to_string()); // RXL 基准模型 - // Frame interpolation models + // 帧插值模型 self.model_mappings.insert("apo-8".to_string(), "chr-2".to_string()); // Apollo self.model_mappings.insert("apf-1".to_string(), "chr-2".to_string()); // Apollo Fast self.model_mappings.insert("chr-2".to_string(), "chr-2".to_string()); // Chronos self.model_mappings.insert("chf-3".to_string(), "chr-2".to_string()); // Chronos Fast self.model_mappings.insert("aion-1".to_string(), "chr-2".to_string()); // Aion (16X) - // Motion blur models + // 运动模糊模型 self.model_mappings.insert("thm-2".to_string(), "thm-2".to_string()); - // Stabilization models (mapped to ref-2 for tvai_stb) - // Note: Template stabilization uses different approach than tvai_stb filter + // 稳定化模型(映射到 ref-2 用于 tvai_stb) + // 注意:模板稳定化使用与 tvai_stb 滤镜不同的方法 } - /// Add custom model mapping + /// 添加自定义模型映射 pub fn add_model_mapping(&mut self, template_model: String, ffmpeg_model: String) { self.model_mappings.insert(template_model, ffmpeg_model); } - /// Generate FFmpeg command from template + /// 从模板生成 FFmpeg 命令 pub fn generate(&self, template: &Template, input_file: &str, output_file: &str) -> TvaiResult { let mut filters = Vec::new(); let settings = &template.settings; - // Generate enhancement filter (usually first in chain) + // 生成增强滤镜(通常是链中的第一个) if settings.enhance.active { let up_filter = self.generate_enhancement_filter(&settings.enhance)?; filters.push(up_filter); } - // Generate frame interpolation filter (after enhancement) + // 生成帧插值滤镜(在增强之后) if settings.slow_motion.active { let fi_filter = self.generate_frame_interpolation_filter(&settings.slow_motion, &settings.output)?; filters.push(fi_filter); } - // Generate stabilization filter (after interpolation) + // 生成稳定化滤镜(在插值之后) if settings.stabilize.active { let stb_filter = self.generate_stabilization_filter(&settings.stabilize)?; filters.push(stb_filter); } - // Generate motion blur filter (if active) + // 生成运动模糊滤镜(如果激活) if settings.motion_blur.active { let mb_filter = self.generate_motion_blur_filter(&settings.motion_blur)?; filters.push(mb_filter); } - // Generate grain filter (usually last in chain) + // 生成颗粒滤镜(通常是链中的最后一个) if settings.grain.active { let grain_filter = self.generate_grain_filter(&settings.grain)?; filters.push(grain_filter); } - // Handle second enhancement if filter manager is present + // 如果存在滤镜管理器,处理第二次增强 if let Some(ref filter_manager) = settings.filter_manager { if filter_manager.second_enhancement_enabled && settings.enhance.active { let second_enhance_filter = self.generate_second_enhancement_filter(&settings.enhance, filter_manager)?; @@ -120,7 +518,7 @@ impl FfmpegCommandGenerator { } } - // Build the complete FFmpeg command + // 构建完整的 FFmpeg 命令 let mut command = format!("ffmpeg -i \"{}\"", input_file); if !filters.is_empty() { @@ -129,7 +527,7 @@ impl FfmpegCommandGenerator { command.push('"'); } - // Add output settings + // 添加输出设置 command.push_str(&self.generate_output_settings(&settings.output)?); command.push_str(&format!(" \"{}\"", output_file)); @@ -137,26 +535,26 @@ impl FfmpegCommandGenerator { Ok(command) } - /// Generate stabilization filter (tvai_stb) + /// 生成稳定化滤镜 (tvai_stb) fn generate_stabilization_filter(&self, settings: &crate::StabilizeSettings) -> TvaiResult { let mut params = Vec::new(); - // Use default model for stabilization + // 使用默认的稳定化模型 params.push("model=ref-2".to_string()); - // Map method (0: auto crop, 1: no crop/full frame) + // 映射方法(0: 自动裁剪, 1: 无裁剪/全帧) let method = if settings.method == 0 { "auto_crop" } else { "no_crop" }; params.push(format!("method={}", method)); - // Map smoothness (0-100 in template) + // 映射平滑度(模板中为 0-100) params.push(format!("smooth={}", settings.smooth)); - // Rolling shutter correction + // 滚动快门校正 if settings.rsc { params.push("rsc=1".to_string()); } - // Reduce motion + // 减少运动 if settings.reduce_motion { params.push(format!("reduce_motion={}", settings.reduce_motion_iteration)); } @@ -164,19 +562,19 @@ impl FfmpegCommandGenerator { Ok(format!("tvai_stb={}", params.join(":"))) } - /// Generate enhancement filter (tvai_up) + /// 生成增强滤镜 (tvai_up) fn generate_enhancement_filter(&self, settings: &crate::EnhanceSettings) -> TvaiResult { let mut params = Vec::new(); - // Map model + // 映射模型 let model = self.model_mappings.get(&settings.model) - .ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", settings.model)))?; + .ok_or_else(|| TvaiError::ModelMappingError(format!("未知的增强模型: {}", settings.model)))?; params.push(format!("model={}", model)); - // Add scale parameter (default to no scaling unless specified) + // 添加缩放参数(默认不缩放,除非指定) params.push("scale=0".to_string()); - // Map enhancement parameters to tvai_up parameters (using normalized values 0.0-1.0) + // 将增强参数映射到 tvai_up 参数(使用标准化值 0.0-1.0) if settings.compress != 0 { let compression = settings.compress as f64 / 100.0; params.push(format!("compression={:.2}", compression)); @@ -207,7 +605,7 @@ impl FfmpegCommandGenerator { params.push(format!("preblur={:.2}", preblur)); } - // Add video type parameter if not progressive + // 如果不是逐行扫描,添加视频类型参数 if settings.video_type != 1 { match settings.video_type { 0 => params.push("interlaced=1".to_string()), @@ -216,7 +614,7 @@ impl FfmpegCommandGenerator { } } - // Add field order if specified + // 如果指定了场序,添加场序参数 if settings.field_order != 0 { match settings.field_order { 1 => params.push("field_order=tff".to_string()), @@ -225,47 +623,47 @@ impl FfmpegCommandGenerator { } } - // Add auto enhancement level if specified + // 如果指定了自动增强级别,添加参数 if settings.auto != 0 { let auto_level = settings.auto as f64 / 100.0; params.push(format!("auto={:.2}", auto_level)); } - // Add add_noise parameter if specified + // 如果指定了添加噪声参数,添加参数 if settings.add_noise != 0 { let add_noise = settings.add_noise as f64 / 100.0; params.push(format!("add_noise={:.2}", add_noise)); } - // Add recover original detail if not default + // 如果不是默认值,添加恢复原始细节参数 if settings.recover_original_detail_value != 20 { let recover = settings.recover_original_detail_value as f64 / 100.0; params.push(format!("recover_detail={:.2}", recover)); } - // Add model-specific optimizations based on AI engine flags + // 基于 AI 引擎标志添加模型特定优化 if settings.is_artemis { - // Artemis optimizations for animation/cartoon content + // Artemis 动画/卡通内容优化 params.push("content_type=animation".to_string()); params.push("edge_enhance=1".to_string()); } else if settings.is_gaia { - // Gaia optimizations for natural scenes + // Gaia 自然场景优化 params.push("content_type=natural".to_string()); params.push("texture_enhance=1".to_string()); } else if settings.is_theia { - // Theia optimizations for detail recovery + // Theia 细节恢复优化 params.push("detail_mode=high".to_string()); params.push("sharpening_boost=1".to_string()); } else if settings.is_iris { - // Iris optimizations for low light/noise + // Iris 低光/噪声优化 params.push("noise_profile=aggressive".to_string()); params.push("low_light_boost=1".to_string()); } else if settings.is_proteus { - // Proteus is the default general-purpose model + // Proteus 是默认的通用模型 params.push("content_type=general".to_string()); } - // Add focus fix level if specified + // 如果指定了焦点修复级别,添加参数 if let Some(ref focus_level) = settings.focus_fix_level { if focus_level != "Off" { let focus_value = match focus_level.as_str() { @@ -284,29 +682,29 @@ impl FfmpegCommandGenerator { Ok(format!("tvai_up={}", params.join(":"))) } - /// Generate frame interpolation filter (tvai_fi) + /// 生成帧插值滤镜 (tvai_fi) fn generate_frame_interpolation_filter(&self, slowmo_settings: &crate::SlowMotionSettings, output_settings: &crate::OutputSettings) -> TvaiResult { let mut params = Vec::new(); - // Map model + // 映射模型 let model = self.model_mappings.get(&slowmo_settings.model) - .ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown frame interpolation model: {}", slowmo_settings.model)))?; + .ok_or_else(|| TvaiError::ModelMappingError(format!("未知的帧插值模型: {}", slowmo_settings.model)))?; params.push(format!("model={}", model)); - // Set slowmo factor + // 设置慢动作因子 if slowmo_settings.factor != 1.0 { params.push(format!("slowmo={}", slowmo_settings.factor)); } - // Set output FPS if specified + // 如果指定了输出 FPS,设置参数 if output_settings.out_fps > 0.0 { params.push(format!("fps={}", output_settings.out_fps)); } - // Duplicate frame threshold (rdt parameter) + // 重复帧阈值(rdt 参数) if !slowmo_settings.duplicate { - params.push("rdt=-0.01".to_string()); // Disable duplicate frame removal - } else if slowmo_settings.duplicate_threshold != 10 { // Only add if not default + params.push("rdt=-0.01".to_string()); // 禁用重复帧移除 + } else if slowmo_settings.duplicate_threshold != 10 { // 仅在非默认值时添加 let threshold = slowmo_settings.duplicate_threshold as f64 / 1000.0; params.push(format!("rdt={:.3}", threshold)); } @@ -314,94 +712,196 @@ impl FfmpegCommandGenerator { Ok(format!("tvai_fi={}", params.join(":"))) } - /// Generate motion blur filter (tvai_mb) + /// 生成运动模糊滤镜 (tvai_mb) fn generate_motion_blur_filter(&self, settings: &crate::MotionBlurSettings) -> TvaiResult { let mut params = Vec::new(); - // Map model + // 映射模型 let model = self.model_mappings.get(&settings.model) - .ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown motion blur model: {}", settings.model)))?; + .ok_or_else(|| TvaiError::ModelMappingError(format!("未知的运动模糊模型: {}", settings.model)))?; params.push(format!("model={}", model)); Ok(format!("tvai_mb={}", params.join(":"))) } - /// Generate grain filter (tvai_grain or noise approximation) + /// 生成颗粒滤镜 (tvai_grain 或噪声近似) fn generate_grain_filter(&self, settings: &crate::GrainSettings) -> TvaiResult { - // Try to use tvai_grain if available, otherwise fall back to noise filter + // 尝试使用 tvai_grain,如果不可用则回退到噪声滤镜 let mut params = Vec::new(); - // Grain amount (0-100 scale) + // 颗粒量(0-100 比例) let amount = settings.grain as f64 / 100.0; params.push(format!("amount={:.2}", amount)); - // Grain size (1-10 scale) + // 颗粒大小(1-10 比例) params.push(format!("size={}", settings.grain_size)); - // Use tvai_grain filter + // 使用 tvai_grain 滤镜 Ok(format!("tvai_grain={}", params.join(":"))) } - /// Generate second enhancement filter + /// 生成第二次增强滤镜 fn generate_second_enhancement_filter(&self, enhance_settings: &crate::EnhanceSettings, filter_manager: &crate::FilterManagerSettings) -> TvaiResult { let mut params = Vec::new(); - // Use the same model as primary enhancement + // 使用与主要增强相同的模型 let model = self.model_mappings.get(&enhance_settings.model) - .ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", enhance_settings.model)))?; + .ok_or_else(|| TvaiError::ModelMappingError(format!("未知的增强模型: {}", enhance_settings.model)))?; params.push(format!("model={}", model)); - // Set intermediate resolution scale + // 设置中间分辨率缩放 let intermediate_scale = match filter_manager.second_enhancement_intermediate_resolution { - 0 => 0.5, // Half resolution - 1 => 0.75, // 3/4 resolution - 2 => 1.0, // Same resolution - 3 => 1.5, // 1.5x resolution - 4 => 2.0, // 2x resolution - _ => 1.0, // Default to same resolution + 0 => 0.5, // 半分辨率 + 1 => 0.75, // 3/4 分辨率 + 2 => 1.0, // 相同分辨率 + 3 => 1.5, // 1.5x 分辨率 + 4 => 2.0, // 2x 分辨率 + _ => 1.0, // 默认为相同分辨率 }; params.push(format!("scale={}", intermediate_scale)); - // Apply lighter enhancement parameters for second pass + // 为第二次处理应用较轻的增强参数 if enhance_settings.denoise != 0 { - let noise = (enhance_settings.denoise as f64 / 100.0) * 0.5; // Reduce by half + let noise = (enhance_settings.denoise as f64 / 100.0) * 0.5; // 减半 params.push(format!("noise={:.2}", noise)); } if enhance_settings.detail != 0 { - let details = (enhance_settings.detail as f64 / 100.0) * 0.5; // Reduce by half + let details = (enhance_settings.detail as f64 / 100.0) * 0.5; // 减半 params.push(format!("details={:.2}", details)); } Ok(format!("tvai_up={}", params.join(":"))) } - /// Generate output settings + /// 根据编解码器名称获取音频编解码器配置 + pub fn get_audio_codec(&self, name: &str) -> Option<&AudioCodec> { + self.audio_codecs.iter().find(|codec| codec.name.eq_ignore_ascii_case(name)) + } + + /// 根据编码器ID获取视频编码器配置 + pub fn get_video_encoder(&self, id: &str) -> Option<&VideoEncoder> { + self.video_encoders.iter().find(|encoder| encoder.id == id) + } + + /// 根据编码器名称获取视频编码器配置 + pub fn get_video_encoder_by_name(&self, name: &str) -> Option<&VideoEncoder> { + self.video_encoders.iter().find(|encoder| encoder.encoder.eq_ignore_ascii_case(name)) + } + + /// 根据分辨率推荐模型 + pub fn recommend_model_for_resolution(&self, height: u32) -> Vec { + for rule in &self.model_recommendation_rules { + if rule.id == "target-resolution" && rule.property == "height" { + for condition in &rule.conditions { + let min = condition.min.unwrap_or(0); + let max = condition.max.unwrap_or(u32::MAX); + if height >= min && height <= max { + return condition.recommended_model_values.clone(); + } + } + } + } + vec!["prob-4".to_string()] // 默认推荐 Proteus + } + + /// 使用配置文件生成音频编解码器参数 + pub fn generate_audio_codec_params(&self, codec_name: &str, bitrate: Option) -> String { + if let Some(codec) = self.get_audio_codec(codec_name) { + let mut params = format!("-c:a {}", codec.ffmpeg_opts); + + if let (Some(bitrate_config), Some(bitrate_value)) = (&codec.bitrate, bitrate) { + let bitrate_param = bitrate_config.ffmpeg_opt.replace("", &bitrate_value.to_string()); + params.push_str(&format!(" {}", bitrate_param)); + } + + params + } else { + // 回退到默认设置 + if let Some(bitrate) = bitrate { + format!("-c:a {} -b:a {}k", codec_name, bitrate) + } else { + format!("-c:a {}", codec_name) + } + } + } + + /// 使用配置文件生成视频编码器参数 + pub fn generate_video_encoder_params(&self, encoder_id: &str, quality_mode: &str, quality_value: Option) -> String { + if let Some(encoder) = self.get_video_encoder(encoder_id) { + let mut params = encoder.ffmpeg_opts.clone(); + + // 添加质量设置 + if let (Some(bitrate_opts), Some(quality)) = (&encoder.bitrate_opts, quality_value) { + match quality_mode { + "cbr" => { + if let Some(cbr_template) = &bitrate_opts.cbr { + let cbr_param = cbr_template.replace("", &format!("{}k", quality)); + params.push_str(&format!(" {}", cbr_param)); + } + }, + "vbr" | "cqp" => { + if let Some(vbr_template) = &bitrate_opts.vbr { + let mut vbr_param = vbr_template.clone(); + + // 处理不同的质量参数占位符 + if let Some(cqp_values) = &encoder.cqp_values { + if let Some(values) = cqp_values.get("Mid") { // 默认使用中等质量 + for (i, value) in values.iter().enumerate() { + match i { + 0 => vbr_param = vbr_param.replace("", &value.to_string()) + .replace("", &value.to_string()), + 1 => vbr_param = vbr_param.replace("", &value.to_string()), + 2 => vbr_param = vbr_param.replace("", &value.to_string()), + _ => break, + } + } + } + } + + // 如果没有预设值,使用传入的质量值 + vbr_param = vbr_param.replace("", &quality.to_string()); + params.push_str(&format!(" {}", vbr_param)); + } + }, + _ => {} + } + } + + params + } else { + // 回退到默认设置 + format!("-c:v {}", encoder_id) + } + } + + /// 生成输出设置 fn generate_output_settings(&self, settings: &crate::OutputSettings) -> TvaiResult { let mut output_params = Vec::new(); - // Check if output settings are active + // 检查输出设置是否激活 if !settings.active { - // If output settings are not active, use minimal default settings + // 如果输出设置未激活,使用配置文件中的默认设置 + let audio_params = self.generate_audio_codec_params("AAC", Some(128)); output_params.push("-c:v libx264".to_string()); - output_params.push("-c:a aac".to_string()); + output_params.push(audio_params); return Ok(format!(" {}", output_params.join(" "))); } - // Hardware acceleration + // 硬件加速 if let Some(ref hwaccel) = settings.hwaccel { output_params.push(format!("-hwaccel {}", hwaccel)); } - // Handle resolution based on priority settings + // 基于优先级设置处理分辨率 let use_custom_resolution = if let (Some(_width), Some(_height)) = (settings.width, settings.height) { - // Check custom resolution priority + // 检查自定义分辨率优先级 let priority = settings.custom_resolution_priority.unwrap_or(0); match priority { - 0 => true, // Custom resolution has normal priority - 1 => true, // Custom resolution has high priority - 2 => false, // Size method has priority over custom resolution - _ => true, // Default to using custom resolution + 0 => true, // 自定义分辨率具有正常优先级 + 1 => true, // 自定义分辨率具有高优先级 + 2 => false, // 尺寸方法优先于自定义分辨率 + _ => true, // 默认使用自定义分辨率 } } else { false @@ -411,28 +911,28 @@ impl FfmpegCommandGenerator { let width = settings.width.unwrap(); let height = settings.height.unwrap(); - // Handle aspect ratio locking + // 处理宽高比锁定 if settings.lock_aspect_ratio.unwrap_or(true) { - // Use scale filter to maintain aspect ratio + // 使用缩放滤镜保持宽高比 output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease", width, height)); - // Add padding if needed to reach exact dimensions + // 如果需要,添加填充以达到精确尺寸 if settings.crop_to_fit { - // Crop to fit exact dimensions + // 裁剪以适应精确尺寸 output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=increase,crop={}:{}", width, height, width, height)); } else { - // Pad to fit exact dimensions + // 填充以适应精确尺寸 output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease,pad={}:{}:(ow-iw)/2:(oh-ih)/2", width, height, width, height)); } } else { - // Stretch to exact dimensions (ignore aspect ratio) + // 拉伸到精确尺寸(忽略宽高比) output_params.push(format!("-s {}x{}", width, height)); } } else { - // Handle output size method (based on Topaz Video AI size methods) + // 处理输出尺寸方法(基于 Topaz Video AI 尺寸方法) match settings.out_size_method { 0 => { - // Original size - no scaling + // 原始尺寸 - 不缩放 }, 1 => { // SD (480p) @@ -455,7 +955,7 @@ impl FfmpegCommandGenerator { output_params.push("-s 2560x1440".to_string()); }, 6 => { - // FHD (1920x1080) - same as 4 + // FHD (1920x1080) - 与 4 相同 output_params.push("-s 1920x1080".to_string()); }, 7 => { @@ -467,24 +967,24 @@ impl FfmpegCommandGenerator { output_params.push("-s 7680x4320".to_string()); }, _ => { - // Unknown method - keep original size + // 未知方法 - 保持原始尺寸 } } } - // Handle crop to fit for size method outputs + // 为尺寸方法输出处理裁剪适应 if settings.crop_to_fit && !use_custom_resolution { - // Apply crop to fit for size method outputs + // 为尺寸方法输出应用裁剪适应 if let Some(last_param) = output_params.last_mut() { if last_param.starts_with("-s ") { let size = &last_param[3..]; - // Replace simple scale with scale+crop + // 用缩放+裁剪替换简单缩放 *last_param = format!("-vf scale={}:force_original_aspect_ratio=increase,crop={}", size, size); } } } - // Handle pixel aspect ratio + // 处理像素宽高比 if settings.output_par != 0 { match settings.output_par { 1 => output_params.push("-aspect 4:3".to_string()), @@ -494,75 +994,85 @@ impl FfmpegCommandGenerator { } } - // Frame rate + // 帧率 if settings.out_fps > 0.0 { output_params.push(format!("-r {}", settings.out_fps)); } - // Video codec + // 视频编解码器 - 使用配置文件 if let Some(ref codec) = settings.codec { - output_params.push(format!("-c:v {}", codec)); - } else { - output_params.push("-c:v libx264".to_string()); - } + // 尝试从配置文件中查找编码器 + if let Some(encoder) = self.get_video_encoder_by_name(codec) { + let quality_mode = if settings.bitrate.is_some() { "cbr" } else { "vbr" }; + let quality_value = settings.bitrate.map(|b| b as u32).or(settings.crf.map(|c| c as u32)); + let encoder_params = self.generate_video_encoder_params(&encoder.id, quality_mode, quality_value); + output_params.push(encoder_params); + } else { + // 回退到传统方式 + output_params.push(format!("-c:v {}", codec)); - // Video quality settings - if let Some(bitrate) = settings.bitrate { - output_params.push(format!("-b:v {}k", bitrate)); - } else if let Some(crf) = settings.crf { - // Use CRF for quality-based encoding - let codec = settings.codec.as_deref().unwrap_or("libx264"); - match codec { - "hevc_nvenc" | "h264_nvenc" => { - output_params.push(format!("-cq {}", crf)); - }, - "h264_amf" | "hevc_amf" => { - output_params.push(format!("-qp {}", crf)); - }, - "h264_qsv" | "hevc_qsv" => { - output_params.push(format!("-global_quality {}", crf)); - }, - _ => { - output_params.push(format!("-crf {}", crf)); + // 视频质量设置 + if let Some(bitrate) = settings.bitrate { + output_params.push(format!("-b:v {}k", bitrate)); + } else if let Some(crf) = settings.crf { + // 使用 CRF 进行基于质量的编码 + match codec.as_str() { + "hevc_nvenc" | "h264_nvenc" => { + output_params.push(format!("-cq {}", crf)); + }, + "h264_amf" | "hevc_amf" => { + output_params.push(format!("-qp {}", crf)); + }, + "h264_qsv" | "hevc_qsv" => { + output_params.push(format!("-global_quality {}", crf)); + }, + _ => { + output_params.push(format!("-crf {}", crf)); + } + } + } else { + output_params.push("-crf 23".to_string()); // 默认质量 } } } else { - output_params.push("-crf 23".to_string()); // Default quality + // 使用默认的 H.264 编码器 + output_params.push("-c:v libx264".to_string()); + output_params.push("-crf 23".to_string()); } - // Encoding preset + // 编码预设 if let Some(ref preset) = settings.preset { output_params.push(format!("-preset {}", preset)); } else { output_params.push("-preset medium".to_string()); } - // Video profile + // 视频配置文件 if let Some(ref profile) = settings.profile { output_params.push(format!("-profile:v {}", profile)); } - // Video level + // 视频级别 if let Some(ref level) = settings.level { output_params.push(format!("-level {}", level)); } - // GOP size + // GOP 大小 if let Some(gop_size) = settings.gop_size { output_params.push(format!("-g {}", gop_size)); } - // B-frames + // B 帧 if let Some(b_frames) = settings.b_frames { output_params.push(format!("-bf {}", b_frames)); } - // Pixel format + // 像素格式 if let Some(ref pix_fmt) = settings.pixel_format { output_params.push(format!("-pix_fmt {}", pix_fmt)); } - // Color settings + // 颜色设置 if let Some(ref colorspace) = settings.colorspace { output_params.push(format!("-colorspace {}", colorspace)); } @@ -573,15 +1083,14 @@ impl FfmpegCommandGenerator { output_params.push(format!("-color_trc {}", color_trc)); } - // Audio settings + // 音频设置 - 使用配置文件 if let Some(ref audio_codec) = settings.audio_codec { if audio_codec != "copy" { - output_params.push(format!("-c:a {}", audio_codec)); - - if let Some(audio_bitrate) = settings.audio_bitrate { - output_params.push(format!("-b:a {}k", audio_bitrate)); - } + // 使用配置文件生成音频编解码器参数 + let audio_params = self.generate_audio_codec_params(audio_codec, settings.audio_bitrate.map(|b| b as u32)); + output_params.push(audio_params); + // 添加额外的音频参数 if let Some(audio_sample_rate) = settings.audio_sample_rate { output_params.push(format!("-ar {}", audio_sample_rate)); } @@ -592,9 +1101,13 @@ impl FfmpegCommandGenerator { } else { output_params.push("-c:a copy".to_string()); } + } else { + // 使用默认音频编解码器 + let default_audio = self.generate_audio_codec_params("AAC", Some(128)); + output_params.push(default_audio); } - // Custom parameters + // 自定义参数 if let Some(ref custom_params) = settings.custom_params { for param in custom_params { output_params.push(param.clone()); @@ -608,38 +1121,38 @@ impl FfmpegCommandGenerator { } } - /// Get available model mappings + /// 获取可用的模型映射 pub fn get_model_mappings(&self) -> &HashMap { &self.model_mappings } - /// Validate template for FFmpeg generation + /// 验证模板用于 FFmpeg 生成 pub fn validate_template(&self, template: &Template) -> TvaiResult<()> { let settings = &template.settings; - // Check if any processing is enabled + // 检查是否启用了任何处理模块 if !settings.enhance.active && !settings.slow_motion.active && !settings.stabilize.active && !settings.grain.active { - return Err(TvaiError::ValidationError("No processing modules are active".to_string())); + return Err(TvaiError::ValidationError("没有激活的处理模块".to_string())); } - // Validate enhancement model if active + // 如果激活,验证增强模型 if settings.enhance.active && !self.model_mappings.contains_key(&settings.enhance.model) { - return Err(TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", settings.enhance.model))); + return Err(TvaiError::ModelMappingError(format!("未知的增强模型: {}", settings.enhance.model))); } - // Validate frame interpolation model if active + // 如果激活,验证帧插值模型 if settings.slow_motion.active && !self.model_mappings.contains_key(&settings.slow_motion.model) { - return Err(TvaiError::ModelMappingError(format!("Unknown frame interpolation model: {}", settings.slow_motion.model))); + return Err(TvaiError::ModelMappingError(format!("未知的帧插值模型: {}", settings.slow_motion.model))); } Ok(()) } - /// Generate advanced FFmpeg command with hardware acceleration + /// 生成带硬件加速的高级 FFmpeg 命令 pub fn generate_with_hardware_acceleration(&self, template: &Template, input_file: &str, output_file: &str, gpu_device: Option<&str>) -> TvaiResult { let mut command = self.generate(template, input_file, output_file)?; - // Add GPU device specification for TVAI filters + // 为 TVAI 滤镜添加 GPU 设备规范 if let Some(device) = gpu_device { command = command.replace("tvai_up=", &format!("tvai_up=device={}:", device)); command = command.replace("tvai_fi=", &format!("tvai_fi=device={}:", device)); @@ -649,30 +1162,30 @@ impl FfmpegCommandGenerator { Ok(command) } - /// Generate command with custom output codec + /// 生成带自定义输出编解码器的命令 pub fn generate_with_codec(&self, template: &Template, input_file: &str, output_file: &str, codec: &str, quality: Option) -> TvaiResult { - // Create a modified template with custom codec settings + // 创建带自定义编解码器设置的修改模板 let mut modified_template = template.clone(); modified_template.settings.output.codec = Some(codec.to_string()); if let Some(q) = quality { - // Clear bitrate when setting quality + // 设置质量时清除比特率 modified_template.settings.output.bitrate = None; modified_template.settings.output.crf = Some(q); } - // Generate command with modified template + // 使用修改的模板生成命令 self.generate(&modified_template, input_file, output_file) } - /// Generate batch processing commands + /// 生成批处理命令 pub fn generate_batch_commands(&self, template: &Template, input_files: &[String], output_dir: &str) -> TvaiResult> { let mut commands = Vec::new(); for input_file in input_files { let input_path = std::path::Path::new(input_file); let filename = input_path.file_stem() - .ok_or_else(|| TvaiError::FfmpegError("Invalid input filename".to_string()))? + .ok_or_else(|| TvaiError::FfmpegError("无效的输入文件名".to_string()))? .to_string_lossy(); let output_file = format!("{}/{}_processed.mp4", output_dir, filename); @@ -682,9 +1195,136 @@ impl FfmpegCommandGenerator { Ok(commands) } + + /// 获取所有可用的音频编解码器 + pub fn get_available_audio_codecs(&self) -> &Vec { + &self.audio_codecs + } + + /// 获取所有可用的视频编码器 + pub fn get_available_video_encoders(&self) -> &Vec { + &self.video_encoders + } + + /// 根据操作系统和GPU类型筛选视频编码器 + pub fn get_compatible_video_encoders(&self, os: &str, gpu: Option<&str>) -> Vec<&VideoEncoder> { + self.video_encoders.iter().filter(|encoder| { + // 检查操作系统兼容性 + let os_compatible = encoder.os.as_ref().map_or(true, |encoder_os| { + encoder_os.split('|').any(|supported_os| supported_os == os) + }); + + // 检查GPU兼容性 + let gpu_compatible = if let Some(gpu_type) = gpu { + encoder.gpu.as_ref().map_or(true, |encoder_gpu| encoder_gpu == gpu_type) + } else { + encoder.gpu.is_none() // 如果没有指定GPU,只返回不需要GPU的编码器 + }; + + os_compatible && gpu_compatible + }).collect() + } + + /// 根据文件扩展名获取兼容的编码器 + pub fn get_encoders_for_extension(&self, extension: &str) -> Vec<&VideoEncoder> { + self.video_encoders.iter().filter(|encoder| { + encoder.ext.iter().any(|ext| ext.eq_ignore_ascii_case(extension)) + }).collect() + } + + /// 生成带有自动编码器选择的命令 + pub fn generate_with_auto_encoder(&self, template: &Template, input_file: &str, output_file: &str, target_os: &str, gpu: Option<&str>) -> TvaiResult { + // 从输出文件获取扩展名 + let output_path = std::path::Path::new(output_file); + let extension = output_path.extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("mp4"); + + // 获取兼容的编码器 + let compatible_encoders = self.get_compatible_video_encoders(target_os, gpu); + let suitable_encoders: Vec<_> = compatible_encoders.into_iter() + .filter(|encoder| encoder.ext.iter().any(|ext| ext.eq_ignore_ascii_case(extension))) + .collect(); + + if suitable_encoders.is_empty() { + return Err(TvaiError::FfmpegError(format!("没有找到适用于 {} 格式和 {} 操作系统的编码器", extension, target_os))); + } + + // 选择第一个合适的编码器 + let selected_encoder = suitable_encoders[0]; + + // 创建修改的模板 + let mut modified_template = template.clone(); + modified_template.settings.output.codec = Some(selected_encoder.encoder.clone()); + + // 生成命令 + self.generate(&modified_template, input_file, output_file) + } + + /// 生成编码器信息报告 + pub fn generate_encoder_report(&self) -> String { + let mut report = String::new(); + report.push_str("=== 可用编码器报告 ===\n\n"); + + // 按编码器类型分组 + let mut h264_encoders = Vec::new(); + let mut h265_encoders = Vec::new(); + let mut av1_encoders = Vec::new(); + let mut other_encoders = Vec::new(); + + for encoder in &self.video_encoders { + match encoder.encoder.as_str() { + "H264" => h264_encoders.push(encoder), + "H265" => h265_encoders.push(encoder), + "AV1" => av1_encoders.push(encoder), + _ => other_encoders.push(encoder), + } + } + + // 生成各类型编码器的报告 + if !h264_encoders.is_empty() { + report.push_str("## H.264 编码器\n"); + for encoder in h264_encoders { + report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts)); + } + report.push('\n'); + } + + if !h265_encoders.is_empty() { + report.push_str("## H.265 编码器\n"); + for encoder in h265_encoders { + report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts)); + } + report.push('\n'); + } + + if !av1_encoders.is_empty() { + report.push_str("## AV1 编码器\n"); + for encoder in av1_encoders { + report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts)); + } + report.push('\n'); + } + + if !other_encoders.is_empty() { + report.push_str("## 其他编码器\n"); + for encoder in other_encoders { + report.push_str(&format!("- {} {}: {}\n", encoder.encoder, encoder.id, encoder.ffmpeg_opts)); + } + report.push('\n'); + } + + // 音频编解码器报告 + report.push_str("## 音频编解码器\n"); + for codec in &self.audio_codecs { + report.push_str(&format!("- {}: {}\n", codec.name, codec.ffmpeg_opts)); + } + + report + } } -/// FFmpeg command builder for more complex scenarios +/// 用于更复杂场景的 FFmpeg 命令构建器 #[derive(Debug, Default)] pub struct FfmpegCommandBuilder { input_file: String, @@ -697,7 +1337,7 @@ pub struct FfmpegCommandBuilder { } impl FfmpegCommandBuilder { - /// Create new command builder + /// 创建新的命令构建器 pub fn new(input_file: &str, output_file: &str) -> Self { Self { input_file: input_file.to_string(), @@ -710,37 +1350,37 @@ impl FfmpegCommandBuilder { } } - /// Add filter + /// 添加滤镜 pub fn add_filter(mut self, filter: &str) -> Self { self.filters.push(filter.to_string()); self } - /// Set codec + /// 设置编解码器 pub fn codec(mut self, codec: &str) -> Self { self.codec = codec.to_string(); self } - /// Set quality + /// 设置质量 pub fn quality(mut self, quality: i32) -> Self { self.quality = Some(quality); self } - /// Set hardware acceleration + /// 设置硬件加速 pub fn hardware_accel(mut self, accel: &str) -> Self { self.hardware_accel = Some(accel.to_string()); self } - /// Add custom parameter + /// 添加自定义参数 pub fn custom_param(mut self, param: &str) -> Self { self.custom_params.push(param.to_string()); self } - /// Build the command + /// 构建命令 pub fn build(self) -> String { let mut command = format!("ffmpeg -i \"{}\"", self.input_file); diff --git a/cargos/tvai-v2/src/lib.rs b/cargos/tvai-v2/src/lib.rs index 226c66a..2f1f6ab 100644 --- a/cargos/tvai-v2/src/lib.rs +++ b/cargos/tvai-v2/src/lib.rs @@ -17,7 +17,7 @@ pub use error::*; /// - Support database storage and runtime loading pub struct TvaiSdk { manager: TemplateManager, - ffmpeg_generator: FfmpegCommandGenerator, + pub ffmpeg_generator: FfmpegCommandGenerator, } impl TvaiSdk { diff --git a/cargos/tvai-v2/tests/ffmpeg_config_tests.rs b/cargos/tvai-v2/tests/ffmpeg_config_tests.rs new file mode 100644 index 0000000..502cdd9 --- /dev/null +++ b/cargos/tvai-v2/tests/ffmpeg_config_tests.rs @@ -0,0 +1,189 @@ +use tvai_sdk::{TvaiSdk, TemplateBuilder}; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_audio_codec_loading() { + let sdk = TvaiSdk::new(); + let audio_codecs = sdk.ffmpeg_generator.get_available_audio_codecs(); + + // 检查是否加载了内置音频编解码器 + assert!(!audio_codecs.is_empty(), "应该加载内置音频编解码器配置"); + + // 检查 AAC 编解码器 + let aac_codec = sdk.ffmpeg_generator.get_audio_codec("AAC"); + assert!(aac_codec.is_some(), "应该找到 AAC 编解码器"); + + if let Some(aac) = aac_codec { + assert_eq!(aac.name, "AAC"); + assert!(aac.ffmpeg_opts.contains("aac"), "AAC 编解码器应该包含 'aac' 选项"); + assert!(aac.ext.contains(&"mp4".to_string()), "AAC 应该支持 MP4 格式"); + } + } + + #[test] + fn test_video_encoder_loading() { + let sdk = TvaiSdk::new(); + let video_encoders = sdk.ffmpeg_generator.get_available_video_encoders(); + + // 检查是否加载了内置视频编码器 + assert!(!video_encoders.is_empty(), "应该加载内置视频编码器配置"); + + // 检查 H.264 编码器 + let h264_encoder = sdk.ffmpeg_generator.get_video_encoder_by_name("H264"); + assert!(h264_encoder.is_some(), "应该找到 H.264 编码器"); + } + + #[test] + fn test_model_recommendation() { + let sdk = TvaiSdk::new(); + + // 测试不同分辨率的模型推荐 + let recommendations_480p = sdk.ffmpeg_generator.recommend_model_for_resolution(480); + assert!(!recommendations_480p.is_empty(), "480p 应该有模型推荐"); + + let recommendations_1080p = sdk.ffmpeg_generator.recommend_model_for_resolution(1080); + assert!(!recommendations_1080p.is_empty(), "1080p 应该有模型推荐"); + + let recommendations_4k = sdk.ffmpeg_generator.recommend_model_for_resolution(2160); + assert!(!recommendations_4k.is_empty(), "4K 应该有模型推荐"); + } + + #[test] + fn test_audio_codec_params_generation() { + let sdk = TvaiSdk::new(); + + // 测试 AAC 编解码器参数生成 + let aac_params = sdk.ffmpeg_generator.generate_audio_codec_params("AAC", Some(192)); + assert!(aac_params.contains("-c:a"), "应该包含音频编解码器参数"); + assert!(aac_params.contains("192"), "应该包含指定的比特率"); + + // 测试不存在的编解码器 + let unknown_params = sdk.ffmpeg_generator.generate_audio_codec_params("UNKNOWN", Some(128)); + assert!(unknown_params.contains("-c:a UNKNOWN"), "应该回退到默认格式"); + } + + #[test] + fn test_compatibility_filtering() { + let sdk = TvaiSdk::new(); + + // 测试 Windows 兼容性筛选 + let windows_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", None); + assert!(!windows_encoders.is_empty(), "应该有 Windows 兼容的编码器"); + + // 测试 macOS 兼容性筛选 + let macos_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("osx", None); + assert!(!macos_encoders.is_empty(), "应该有 macOS 兼容的编码器"); + + // 测试 GPU 筛选 + let _nvidia_encoders = sdk.ffmpeg_generator.get_compatible_video_encoders("windows", Some("nvidia")); + // 注意:这个测试可能为空,因为需要特定的配置 + + // 测试文件扩展名筛选 + let mp4_encoders = sdk.ffmpeg_generator.get_encoders_for_extension("mp4"); + assert!(!mp4_encoders.is_empty(), "应该有支持 MP4 的编码器"); + } + + #[test] + fn test_auto_encoder_selection() { + let sdk = TvaiSdk::new(); + + // 创建测试模板 + let template = TemplateBuilder::new("测试模板") + .enable_enhancement("prob-4") + .build() + .expect("应该能创建测试模板"); + + // 测试自动编码器选择 + let result = sdk.ffmpeg_generator.generate_with_auto_encoder( + &template, + "input.mp4", + "output.mp4", + "windows", + Some("nvidia") + ); + + // 这个测试可能失败,因为需要特定的编码器配置 + // 但至少应该返回一个结果(成功或特定的错误) + assert!(result.is_ok() || result.is_err(), "应该返回结果"); + } + + #[test] + fn test_ffmpeg_command_with_config() { + let sdk = TvaiSdk::new(); + + // 创建使用配置文件的模板 + let template = TemplateBuilder::new("配置文件测试") + .enable_enhancement("prob-4") + .video_codec("H264", Some(23)) + .audio_settings("AAC", 192, 2) + .build() + .expect("应该能创建模板"); + + // 生成 FFmpeg 命令 + let command = sdk.generate_ffmpeg_command(&template, "input.mp4", "output.mp4") + .expect("应该能生成 FFmpeg 命令"); + + // 验证命令包含预期的元素 + assert!(command.contains("ffmpeg"), "应该包含 ffmpeg"); + assert!(command.contains("-i"), "应该包含输入参数"); + assert!(command.contains("tvai_up"), "应该包含 TVAI 增强滤镜"); + assert!(command.contains("-c:a"), "应该包含音频编解码器"); + } + + #[test] + fn test_encoder_report_generation() { + let sdk = TvaiSdk::new(); + + let report = sdk.ffmpeg_generator.generate_encoder_report(); + + // 验证报告包含预期的内容 + assert!(report.contains("可用编码器报告"), "应该包含报告标题"); + assert!(report.contains("音频编解码器"), "应该包含音频编解码器部分"); + + // 如果有编码器配置,应该包含编码器信息 + if !sdk.ffmpeg_generator.get_available_video_encoders().is_empty() { + assert!(report.len() > 100, "报告应该有实质内容"); + } + } + + #[test] + fn test_bitrate_config_usage() { + let sdk = TvaiSdk::new(); + + // 测试 AAC 编解码器的比特率配置 + if let Some(aac_codec) = sdk.ffmpeg_generator.get_audio_codec("AAC") { + if let Some(bitrate_config) = &aac_codec.bitrate { + assert!(bitrate_config.min > 0, "最小比特率应该大于 0"); + assert!(bitrate_config.max > bitrate_config.min, "最大比特率应该大于最小比特率"); + assert!(!bitrate_config.suggested.is_empty(), "应该有建议的比特率值"); + assert!(bitrate_config.ffmpeg_opt.contains(""), "应该包含比特率占位符"); + } + } + } + + #[test] + fn test_video_encoder_quality_modes() { + let sdk = TvaiSdk::new(); + + // 查找支持质量模式的编码器 + for encoder in sdk.ffmpeg_generator.get_available_video_encoders() { + if let Some(bitrate_opts) = &encoder.bitrate_opts { + if bitrate_opts.cbr.is_some() { + assert!(bitrate_opts.cbr.as_ref().unwrap().contains(""), + "CBR 模板应该包含比特率占位符"); + } + + if bitrate_opts.vbr.is_some() { + let vbr_template = bitrate_opts.vbr.as_ref().unwrap(); + assert!(vbr_template.contains("") || + vbr_template.contains("") || + vbr_template.contains(""), + "VBR 模板应该包含质量占位符"); + } + } + } + } +}