diff --git a/cargos/tvai/docs/FILTER_COMBINATIONS_GUIDE.md b/cargos/tvai/docs/FILTER_COMBINATIONS_GUIDE.md new file mode 100644 index 0000000..bc79874 --- /dev/null +++ b/cargos/tvai/docs/FILTER_COMBINATIONS_GUIDE.md @@ -0,0 +1,318 @@ +# Topaz Video AI 滤镜组合使用指南 + +## 概述 + +Topaz Video AI 滤镜组合库提供了一套易用的高级 API,封装了复杂的 TVAI 滤镜参数,让您能够轻松地进行各种视频和图片处理任务。 + +## 快速开始 + +### 基本用法 + +```rust +use tvai::*; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // 创建处理器 + let topaz_path = detect_topaz_installation().unwrap(); + let config = TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + let processor = TvaiProcessor::new(config)?; + let mut filter_processor = FilterProcessor::new(processor); + + // 视频超分辨率放大 + let combination = FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::High, + }; + + let result = filter_processor.apply_to_video( + Path::new("input.mp4"), + Path::new("output.mp4"), + combination, + ProcessOptions::default(), + None, + ).await?; + + println!("处理完成!时间: {:?}", result.processing_time); + Ok(()) +} +``` + +### 快速函数 + +对于简单的处理任务,可以使用快速函数: + +```rust +use tvai::filters::quick; + +// 快速视频放大 +let result = quick::upscale_video( + Path::new("input.mp4"), + Path::new("output.mp4"), + 2.0, +).await?; + +// 快速慢动作处理 +let result = quick::slow_motion( + Path::new("input.mp4"), + Path::new("output.mp4"), + 2.0, +).await?; + +// 快速视频稳定化 +let result = quick::stabilize_video( + Path::new("input.mp4"), + Path::new("output.mp4"), + 1.0, +).await?; +``` + +## 滤镜组合类型 + +### 1. 超分辨率放大 (Upscale) + +```rust +let combination = FilterCombination::Upscale { + model: UpscaleModel::Iris3, // AI 模型 + scale: 2.0, // 放大倍数 + quality: QualityLevel::High, // 质量等级 +}; +``` + +**可用模型**: +- `Iris3`: 通用最佳模型 +- `Ahq12`: 高质量模型 +- `Nyx3`: 人像优化模型 +- `Ghq5`: 游戏/CG 内容模型 +- `Prob4`: 问题视频修复模型 + +### 2. 慢动作处理 (SlowMotion) + +```rust +let combination = FilterCombination::SlowMotion { + factor: 2.0, // 慢动作倍数 + quality: QualityLevel::High, +}; +``` + +### 3. 视频稳定化 (Stabilize) + +```rust +let combination = FilterCombination::Stabilize { + strength: 1.0, // 稳定化强度 (0.0-2.0) + quality: QualityLevel::High, +}; +``` + +### 4. 完整增强 (FullEnhance) + +```rust +let combination = FilterCombination::FullEnhance { + upscale_factor: 2.0, // 放大倍数 + stabilize: true, // 是否启用稳定化 + quality: QualityLevel::High, +}; +``` + +### 5. 老视频修复 (RestoreOldVideo) + +```rust +let combination = FilterCombination::RestoreOldVideo { + upscale_factor: 2.0, // 放大倍数 + denoise_level: 0.3, // 降噪等级 (0.0-1.0) + quality: QualityLevel::High, +}; +``` + +### 6. 游戏录像优化 (GameFootage) + +```rust +let combination = FilterCombination::GameFootage { + upscale_factor: 1.5, // 放大倍数 + sharpen: true, // 是否启用锐化 + quality: QualityLevel::High, +}; +``` + +## 构建器模式 + +使用构建器模式可以更灵活地配置处理参数: + +```rust +use tvai::{FilterCombinationBuilder, quick_builders}; + +// 详细配置 +let (combination, options) = FilterCombinationBuilder::new() + .upscale(UpscaleModel::Ahq12, 2.0) + .quality(QualityLevel::Maximum) + .output_format("mp4".to_string()) + .output_resolution(3840, 2160) + .vram_limit(0.8) + .keep_audio(true) + .device(0) // 指定 GPU 0 + .build()?; + +// 快速构建器 +let (combination, options) = quick_builders::upscale_hq(2.0) + .output_format("mov".to_string()) + .build()?; +``` + +### 可用的快速构建器 + +- `quick_builders::upscale(scale)` - 标准放大 +- `quick_builders::upscale_hq(scale)` - 高质量放大 +- `quick_builders::slow_motion(factor)` - 慢动作 +- `quick_builders::stabilize()` - 视频稳定化 +- `quick_builders::full_enhance()` - 完整增强 +- `quick_builders::restore_old_video()` - 老视频修复 +- `quick_builders::game_footage()` - 游戏录像优化 +- `quick_builders::social_media()` - 社交媒体优化 +- `quick_builders::portrait()` - 人像优化 +- `quick_builders::animation()` - 动画优化 + +## 预设系统 + +### 内置预设 + +```rust +use tvai::PresetType; + +// 使用内置预设 +let preset = PresetType::Gaming; +let combination = preset.get_filter_combination(); +let options = preset.get_process_options(); + +println!("预设描述: {}", preset.description()); +``` + +**可用预设**: +- `SocialMedia` - 社交媒体优化 +- `FilmRestoration` - 电影修复 +- `Gaming` - 游戏录像 +- `Surveillance` - 监控视频 +- `Animation` - 动画内容 +- `Portrait` - 人像视频 +- `Landscape` - 风景视频 +- `QuickPreview` - 快速预览 + +### 自定义预设 + +```rust +use tvai::CustomPreset; + +// 创建自定义预设 +let preset = CustomPreset::builder("我的预设".to_string()) + .with_combination(FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::High, + }) + .with_options(ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + ..Default::default() + }) + .with_description("自定义高质量放大预设".to_string()) + .build()?; + +// 保存预设 +preset.save_to_file(Path::new("my_preset.json"))?; + +// 加载预设 +let loaded_preset = CustomPreset::load_from_file(Path::new("my_preset.json"))?; +``` + +## 处理选项 + +```rust +let options = ProcessOptions { + output_format: Some("mp4".to_string()), // 输出格式 + keep_audio: true, // 保留音频 + output_resolution: Some((1920, 1080)), // 输出分辨率 + device: -2, // 设备 (-2=自动, -1=CPU, 0+=GPU) + vram_limit: 1.0, // VRAM 使用限制 (0.1-1.0) + enable_progress: true, // 启用进度回调 +}; +``` + +## 质量等级 + +- `QualityLevel::Fast` - 快速处理 +- `QualityLevel::Balanced` - 平衡质量和速度 +- `QualityLevel::High` - 高质量 +- `QualityLevel::Maximum` - 最高质量 + +## 进度回调 + +```rust +let progress_callback = Box::new(|progress: f32| { + let percentage = (progress * 100.0) as u32; + println!("处理进度: {}%", percentage); +}); + +let result = filter_processor.apply_to_video( + input_path, + output_path, + combination, + options, + Some(&progress_callback), +).await?; +``` + +## 智能推荐 + +系统可以根据视频特征自动推荐最适合的滤镜组合: + +```rust +let recommended = filter_processor.get_recommended_combination( + Path::new("input.mp4") +).await?; + +println!("推荐的组合: {:?}", recommended); +``` + +## 错误处理 + +```rust +match result { + Ok(process_result) => { + println!("处理成功!"); + println!("输出文件: {}", process_result.output_path.display()); + println!("处理时间: {:?}", process_result.processing_time); + } + Err(TvaiError::TopazNotFound(path)) => { + eprintln!("未找到 Topaz Video AI: {}", path); + } + Err(TvaiError::FileNotFound(path)) => { + eprintln!("文件不存在: {}", path); + } + Err(TvaiError::FfmpegError(msg)) => { + eprintln!("FFmpeg 错误: {}", msg); + } + Err(e) => { + eprintln!("其他错误: {}", e); + } +} +``` + +## 最佳实践 + +1. **选择合适的模型**: 根据内容类型选择最适合的 AI 模型 +2. **合理设置质量等级**: 平衡处理时间和输出质量 +3. **监控 VRAM 使用**: 避免显存不足导致的处理失败 +4. **使用进度回调**: 为长时间处理提供用户反馈 +5. **批量处理**: 对多个文件使用相同配置时,重用处理器实例 +6. **错误处理**: 妥善处理各种可能的错误情况 + +## 性能优化 + +- 使用 GPU 加速可显著提升处理速度 +- 调整 `vram_limit` 以优化内存使用 +- 对于快速预览,使用 `QualityLevel::Fast` +- 批量处理时重用 `FilterProcessor` 实例 diff --git a/cargos/tvai/examples/filter_combinations_demo.rs b/cargos/tvai/examples/filter_combinations_demo.rs new file mode 100644 index 0000000..a6470a1 --- /dev/null +++ b/cargos/tvai/examples/filter_combinations_demo.rs @@ -0,0 +1,325 @@ +//! Topaz Video AI 滤镜组合演示 +//! +//! 展示如何使用新的滤镜组合库进行各种视频和图片处理 + +use std::path::Path; +use tvai::*; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("🎬 Topaz Video AI 滤镜组合演示"); + println!("================================\n"); + + // 检查 Topaz 安装 + if let Some(topaz_path) = detect_topaz_installation() { + println!("✅ 找到 Topaz Video AI: {}", topaz_path.display()); + } else { + println!("❌ 未找到 Topaz Video AI 安装"); + return Ok(()); + } + + // 检查测试文件 + let demo_video = Path::new("target/demo.mp4"); + let demo_image = Path::new("target/demo.jpg"); + + if !demo_video.exists() { + println!("❌ 测试视频文件不存在: {}", demo_video.display()); + return Ok(()); + } + + if !demo_image.exists() { + println!("❌ 测试图片文件不存在: {}", demo_image.display()); + return Ok(()); + } + + println!("✅ 测试文件检查完成\n"); + + // 创建输出目录 + let output_dir = Path::new("target/filter_demo_output"); + std::fs::create_dir_all(output_dir)?; + + // 演示各种滤镜组合 + demo_basic_usage(demo_video, demo_image, output_dir).await?; + demo_builder_pattern(demo_video, output_dir).await?; + demo_presets(demo_video, output_dir).await?; + demo_quick_functions(demo_video, output_dir).await?; + demo_advanced_combinations(demo_video, output_dir).await?; + + println!("\n🎉 所有演示完成!"); + println!("输出文件位于: {}", output_dir.display()); + + Ok(()) +} + +/// 演示基本用法 +async fn demo_basic_usage( + demo_video: &Path, + demo_image: &Path, + output_dir: &Path, +) -> Result<(), Box> { + println!("📋 1. 基本用法演示"); + println!("------------------"); + + // 创建处理器 + let topaz_path = detect_topaz_installation().unwrap(); + let config = TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + let processor = TvaiProcessor::new(config)?; + let mut filter_processor = FilterProcessor::new(processor); + + // 1. 视频超分辨率放大 + println!("🔍 视频超分辨率放大 (2x)..."); + let upscale_combination = FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::Balanced, + }; + + let progress_callback = create_progress_callback("视频放大"); + let result = filter_processor.apply_to_video( + demo_video, + &output_dir.join("basic_upscale_2x.mp4"), + upscale_combination, + ProcessOptions::default(), + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + // 2. 图片超分辨率放大 + println!("\n🖼️ 图片超分辨率放大 (2x)..."); + let image_combination = FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::High, + }; + + let image_options = ProcessOptions { + output_format: Some("png".to_string()), + ..Default::default() + }; + + let result = filter_processor.apply_to_image( + demo_image, + &output_dir.join("basic_upscale_image_2x.png"), + image_combination, + image_options, + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + Ok(()) +} + +/// 演示构建器模式 +async fn demo_builder_pattern( + demo_video: &Path, + output_dir: &Path, +) -> Result<(), Box> { + println!("\n📋 2. 构建器模式演示"); + println!("--------------------"); + + let topaz_path = detect_topaz_installation().unwrap(); + let config = TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + let processor = TvaiProcessor::new(config)?; + let mut filter_processor = FilterProcessor::new(processor); + + // 使用构建器创建复杂配置 + println!("🔧 使用构建器创建高质量放大配置..."); + let (combination, options) = FilterCombinationBuilder::new() + .upscale(UpscaleModel::Ahq12, 2.0) + .quality(QualityLevel::High) + .output_format("mp4".to_string()) + .output_resolution(1920, 1080) + .vram_limit(0.8) + .build()?; + + let progress_callback = create_progress_callback("构建器模式"); + let result = filter_processor.apply_to_video( + demo_video, + &output_dir.join("builder_hq_upscale.mp4"), + combination, + options, + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + // 使用快速构建器 + println!("\n⚡ 使用快速构建器..."); + let (combination, options) = quick_builders::social_media() + .quality(QualityLevel::Balanced) + .build()?; + + let result = filter_processor.apply_to_video( + demo_video, + &output_dir.join("builder_social_media.mp4"), + combination, + options, + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + Ok(()) +} + +/// 演示预设功能 +async fn demo_presets( + demo_video: &Path, + output_dir: &Path, +) -> Result<(), Box> { + println!("\n📋 3. 预设功能演示"); + println!("------------------"); + + let topaz_path = detect_topaz_installation().unwrap(); + let config = TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + let processor = TvaiProcessor::new(config)?; + let mut filter_processor = FilterProcessor::new(processor); + + // 使用内置预设 + println!("🎮 游戏录像预设..."); + let preset = PresetType::Gaming; + let combination = preset.get_filter_combination(); + let options = preset.get_process_options(); + + let progress_callback = create_progress_callback("游戏预设"); + let result = filter_processor.apply_to_video( + demo_video, + &output_dir.join("preset_gaming.mp4"), + combination, + options, + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + // 创建自定义预设 + println!("\n🛠️ 创建自定义预设..."); + let custom_preset = CustomPreset::builder("我的自定义预设".to_string()) + .with_combination(FilterCombination::RestoreOldVideo { + upscale_factor: 2.5, + denoise_level: 0.4, + quality: QualityLevel::Maximum, + }) + .with_options(ProcessOptions { + output_format: Some("mov".to_string()), + keep_audio: true, + vram_limit: 1.0, + ..Default::default() + }) + .with_description("用于修复老电影的自定义预设".to_string()) + .build()?; + + // 保存预设 + let preset_file = output_dir.join("custom_preset.json"); + custom_preset.save_to_file(&preset_file)?; + println!("💾 预设已保存到: {}", preset_file.display()); + + Ok(()) +} + +/// 演示快速函数 +async fn demo_quick_functions( + demo_video: &Path, + output_dir: &Path, +) -> Result<(), Box> { + println!("\n📋 4. 快速函数演示"); + println!("------------------"); + + // 快速视频放大 + println!("⚡ 快速视频放大..."); + let result = filters::quick::upscale_video( + demo_video, + &output_dir.join("quick_upscale.mp4"), + 2.0, + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + // 快速视频稳定化 + println!("\n🎯 快速视频稳定化..."); + let result = filters::quick::stabilize_video( + demo_video, + &output_dir.join("quick_stabilize.mp4"), + 1.0, + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + Ok(()) +} + +/// 演示高级组合 +async fn demo_advanced_combinations( + demo_video: &Path, + output_dir: &Path, +) -> Result<(), Box> { + println!("\n📋 5. 高级组合演示"); + println!("------------------"); + + let topaz_path = detect_topaz_installation().unwrap(); + let config = TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + let processor = TvaiProcessor::new(config)?; + let mut filter_processor = FilterProcessor::new(processor); + + // 完整增强 (放大 + 稳定化) + println!("🚀 完整增强 (放大 + 稳定化)..."); + let combination = FilterCombination::FullEnhance { + upscale_factor: 1.5, + stabilize: true, + quality: QualityLevel::High, + }; + + let options = ProcessOptions { + output_format: Some("mp4".to_string()), + vram_limit: 1.0, + ..Default::default() + }; + + let progress_callback = create_progress_callback("完整增强"); + let result = filter_processor.apply_to_video( + demo_video, + &output_dir.join("advanced_full_enhance.mp4"), + combination, + options, + Some(&progress_callback), + ).await?; + + println!("✅ 完成!处理时间: {:?}", result.processing_time); + + // 智能推荐 + println!("\n🧠 智能推荐组合..."); + let recommended = filter_processor.get_recommended_combination(demo_video).await?; + println!("推荐的组合: {:?}", recommended); + + Ok(()) +} + +/// 创建进度回调 +fn create_progress_callback(operation_name: &str) -> ProgressCallback { + let name = operation_name.to_string(); + Box::new(move |progress| { + let percentage = (progress * 100.0) as u32; + print!("\r{}: {}%", name, percentage); + if progress >= 1.0 { + println!(); + } + }) +} diff --git a/cargos/tvai/examples/model_management_demo.rs b/cargos/tvai/examples/model_management_demo.rs new file mode 100644 index 0000000..72f5f92 --- /dev/null +++ b/cargos/tvai/examples/model_management_demo.rs @@ -0,0 +1,218 @@ +//! Topaz Video AI 模型管理演示 +//! +//! 展示如何使用模型管理器检查、下载和管理 AI 模型 + +use std::path::Path; +use tvai::*; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("🧠 Topaz Video AI 模型管理演示"); + println!("==============================\n"); + + // 检查 Topaz 安装 + let topaz_path = match detect_topaz_installation() { + Some(path) => { + println!("✅ 找到 Topaz Video AI: {}", path.display()); + path + } + None => { + println!("❌ 未找到 Topaz Video AI 安装"); + return Ok(()); + } + }; + + // 创建模型管理器 + let model_manager = match ModelManager::new(&topaz_path) { + Ok(manager) => { + println!("✅ 模型管理器初始化成功"); + manager + } + Err(e) => { + println!("❌ 模型管理器初始化失败: {}", e); + return Ok(()); + } + }; + + // 演示各种功能 + demo_model_status(&model_manager).await?; + demo_model_recommendations(&model_manager).await?; + demo_download_guide(&model_manager).await?; + demo_model_testing(&model_manager).await?; + + println!("\n🎉 模型管理演示完成!"); + + Ok(()) +} + +/// 演示模型状态检查 +async fn demo_model_status(model_manager: &ModelManager) -> Result<(), Box> { + println!("📊 1. 模型状态检查"); + println!("------------------"); + + // 获取所有模型 + let all_models = model_manager.get_all_models()?; + println!("总模型数量: {}", all_models.len()); + + // 统计各类型模型 + let upscale_count = all_models.iter().filter(|m| m.model_type == ModelType::Upscale).count(); + let interpolation_count = all_models.iter().filter(|m| m.model_type == ModelType::Interpolation).count(); + let other_count = all_models.iter().filter(|m| m.model_type == ModelType::Other).count(); + + println!(" 🔍 超分辨率模型: {} 个", upscale_count); + println!(" 🎬 帧插值模型: {} 个", interpolation_count); + println!(" 🔧 其他模型: {} 个", other_count); + + // 检查下载状态 + let downloaded_models = model_manager.get_downloaded_models()?; + let missing_models = model_manager.get_missing_models()?; + + println!("\n📥 下载状态:"); + println!(" ✅ 已下载: {} 个模型", downloaded_models.len()); + println!(" ❌ 缺失: {} 个模型", missing_models.len()); + + if !downloaded_models.is_empty() { + println!("\n✅ 已下载的模型:"); + for model_name in &downloaded_models { + if let Some(model) = all_models.iter().find(|m| m.short_name == *model_name) { + println!(" {} - {}", + model.short_name, + model.display_name.as_deref().unwrap_or("N/A")); + } + } + } + + if !missing_models.is_empty() { + println!("\n❌ 缺失的模型 (前10个):"); + for model in missing_models.iter().take(10) { + println!(" {} - {}", + model.short_name, + model.display_name.as_deref().unwrap_or("N/A")); + } + if missing_models.len() > 10 { + println!(" ... 还有 {} 个模型", missing_models.len() - 10); + } + } + + Ok(()) +} + +/// 演示模型推荐 +async fn demo_model_recommendations(model_manager: &ModelManager) -> Result<(), Box> { + println!("\n💡 2. 模型推荐"); + println!("-------------"); + + let use_cases = vec![ + ("general", "通用处理"), + ("high_quality", "高质量处理"), + ("fast", "快速处理"), + ("gaming", "游戏内容"), + ("old_video", "老视频修复"), + ("portrait", "人像视频"), + ]; + + for (use_case, description) in use_cases { + let recommended = model_manager.get_recommended_models(use_case)?; + println!("🎯 {} ({}):", description, use_case); + + if recommended.is_empty() { + println!(" (无推荐模型)"); + } else { + for model_name in recommended { + let is_downloaded = model_manager.is_model_downloaded(&model_name)?; + let status = if is_downloaded { "✅" } else { "❌" }; + println!(" {} {}", status, model_name); + } + } + println!(); + } + + Ok(()) +} + +/// 演示下载指南生成 +async fn demo_download_guide(model_manager: &ModelManager) -> Result<(), Box> { + println!("📋 3. 生成下载指南"); + println!("------------------"); + + let guide_path = Path::new("model_download_guide_demo.md"); + + match model_manager.generate_download_guide(guide_path) { + Ok(()) => { + println!("✅ 下载指南已生成: {}", guide_path.display()); + + // 显示指南的前几行 + if let Ok(content) = std::fs::read_to_string(guide_path) { + let lines: Vec<&str> = content.lines().take(15).collect(); + println!("\n📄 指南预览:"); + for line in lines { + println!(" {}", line); + } + if content.lines().count() > 15 { + println!(" ... (更多内容请查看文件)"); + } + } + } + Err(e) => { + println!("❌ 生成指南失败: {}", e); + } + } + + Ok(()) +} + +/// 演示模型测试 +async fn demo_model_testing(model_manager: &ModelManager) -> Result<(), Box> { + println!("\n🧪 4. 模型测试"); + println!("-------------"); + + let test_video = Path::new("target/demo.mp4"); + + if !test_video.exists() { + println!("❌ 测试视频不存在: {}", test_video.display()); + println!("💡 请确保测试视频文件存在以进行模型测试"); + return Ok(()); + } + + println!("✅ 找到测试视频: {}", test_video.display()); + + // 获取一些常用模型进行测试 + let test_models = vec!["iris", "amq", "prob", "chr"]; + + println!("\n🔍 测试常用模型可用性:"); + + for model_name in test_models { + print!(" 测试 {} ... ", model_name); + + match model_manager.attempt_model_download(model_name, test_video) { + Ok(true) => { + println!("✅ 可用或已触发下载"); + } + Ok(false) => { + println!("❌ 模型不可用"); + } + Err(e) => { + println!("❌ 测试失败: {}", e); + } + } + } + + println!("\n💡 提示:"); + println!(" - 如果模型显示'不可用',需要先在 Topaz 应用中下载"); + println!(" - 某些模型可能需要特定的输入格式才能正确加载"); + println!(" - 建议使用生成的下载指南进行手动下载"); + + Ok(()) +} + +/// 创建进度回调 +fn create_progress_callback(operation_name: &str) -> ProgressCallback { + let name = operation_name.to_string(); + Box::new(move |progress| { + let percentage = (progress * 100.0) as u32; + print!("\r{}: {}%", name, percentage); + if progress >= 1.0 { + println!(); + } + }) +} diff --git a/cargos/tvai/src/filters/builder.rs b/cargos/tvai/src/filters/builder.rs new file mode 100644 index 0000000..c4d0ab5 --- /dev/null +++ b/cargos/tvai/src/filters/builder.rs @@ -0,0 +1,305 @@ +//! 滤镜组合构建器 + +use super::{FilterCombination, QualityLevel, ProcessOptions}; +use crate::config::UpscaleModel; + +/// 滤镜组合构建器 +pub struct FilterCombinationBuilder { + combination_type: Option, + quality: QualityLevel, + options: ProcessOptions, +} + +#[derive(Debug)] +enum CombinationType { + Upscale { + model: UpscaleModel, + scale: f32, + }, + SlowMotion { + factor: f32, + }, + Stabilize { + strength: f32, + }, + FullEnhance { + upscale_factor: f32, + stabilize: bool, + }, + RestoreOldVideo { + upscale_factor: f32, + denoise_level: f32, + }, + GameFootage { + upscale_factor: f32, + sharpen: bool, + }, +} + +impl FilterCombinationBuilder { + /// 创建新的构建器 + pub fn new() -> Self { + Self { + combination_type: None, + quality: QualityLevel::Balanced, + options: ProcessOptions::default(), + } + } + + /// 设置超分辨率放大 + pub fn upscale(mut self, model: UpscaleModel, scale: f32) -> Self { + self.combination_type = Some(CombinationType::Upscale { model, scale }); + self + } + + /// 设置慢动作处理 + pub fn slow_motion(mut self, factor: f32) -> Self { + self.combination_type = Some(CombinationType::SlowMotion { factor }); + self + } + + /// 设置视频稳定化 + pub fn stabilize(mut self, strength: f32) -> Self { + self.combination_type = Some(CombinationType::Stabilize { strength }); + self + } + + /// 设置完整增强 + pub fn full_enhance(mut self, upscale_factor: f32, stabilize: bool) -> Self { + self.combination_type = Some(CombinationType::FullEnhance { upscale_factor, stabilize }); + self + } + + /// 设置老视频修复 + pub fn restore_old_video(mut self, upscale_factor: f32, denoise_level: f32) -> Self { + self.combination_type = Some(CombinationType::RestoreOldVideo { upscale_factor, denoise_level }); + self + } + + /// 设置游戏录像优化 + pub fn game_footage(mut self, upscale_factor: f32, sharpen: bool) -> Self { + self.combination_type = Some(CombinationType::GameFootage { upscale_factor, sharpen }); + self + } + + /// 设置质量等级 + pub fn quality(mut self, quality: QualityLevel) -> Self { + self.quality = quality; + self + } + + /// 设置输出格式 + pub fn output_format(mut self, format: String) -> Self { + self.options.output_format = Some(format); + self + } + + /// 设置是否保留音频 + pub fn keep_audio(mut self, keep: bool) -> Self { + self.options.keep_audio = keep; + self + } + + /// 设置输出分辨率 + pub fn output_resolution(mut self, width: u32, height: u32) -> Self { + self.options.output_resolution = Some((width, height)); + self + } + + /// 设置设备 + pub fn device(mut self, device: i32) -> Self { + self.options.device = device; + self + } + + /// 设置 VRAM 限制 + pub fn vram_limit(mut self, limit: f32) -> Self { + self.options.vram_limit = limit; + self + } + + /// 启用进度回调 + pub fn enable_progress(mut self, enable: bool) -> Self { + self.options.enable_progress = enable; + self + } + + /// 构建滤镜组合和选项 + pub fn build(self) -> Result<(FilterCombination, ProcessOptions), String> { + let combination_type = self.combination_type + .ok_or_else(|| "No filter combination type specified".to_string())?; + + let combination = match combination_type { + CombinationType::Upscale { model, scale } => { + FilterCombination::Upscale { + model, + scale, + quality: self.quality, + } + } + CombinationType::SlowMotion { factor } => { + FilterCombination::SlowMotion { + factor, + quality: self.quality, + } + } + CombinationType::Stabilize { strength } => { + FilterCombination::Stabilize { + strength, + quality: self.quality, + } + } + CombinationType::FullEnhance { upscale_factor, stabilize } => { + FilterCombination::FullEnhance { + upscale_factor, + stabilize, + quality: self.quality, + } + } + CombinationType::RestoreOldVideo { upscale_factor, denoise_level } => { + FilterCombination::RestoreOldVideo { + upscale_factor, + denoise_level, + quality: self.quality, + } + } + CombinationType::GameFootage { upscale_factor, sharpen } => { + FilterCombination::GameFootage { + upscale_factor, + sharpen, + quality: self.quality, + } + } + }; + + Ok((combination, self.options)) + } +} + +impl Default for FilterCombinationBuilder { + fn default() -> Self { + Self::new() + } +} + +/// 快速构建器函数 +pub mod quick_builders { + use super::*; + + /// 快速创建超分辨率放大组合 + pub fn upscale(scale: f32) -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .upscale(UpscaleModel::Iris3, scale) + .quality(QualityLevel::Balanced) + } + + /// 快速创建高质量超分辨率放大组合 + pub fn upscale_hq(scale: f32) -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .upscale(UpscaleModel::Ahq12, scale) + .quality(QualityLevel::High) + } + + /// 快速创建慢动作组合 + pub fn slow_motion(factor: f32) -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .slow_motion(factor) + .quality(QualityLevel::Balanced) + } + + /// 快速创建视频稳定化组合 + pub fn stabilize() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .stabilize(1.0) + .quality(QualityLevel::Balanced) + } + + /// 快速创建完整增强组合 + pub fn full_enhance() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .full_enhance(2.0, true) + .quality(QualityLevel::High) + } + + /// 快速创建老视频修复组合 + pub fn restore_old_video() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .restore_old_video(2.0, 0.3) + .quality(QualityLevel::High) + } + + /// 快速创建游戏录像优化组合 + pub fn game_footage() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .game_footage(1.5, true) + .quality(QualityLevel::High) + } + + /// 快速创建社交媒体优化组合 + pub fn social_media() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .upscale(UpscaleModel::Iris3, 2.0) + .quality(QualityLevel::Balanced) + .output_resolution(1920, 1080) + .output_format("mp4".to_string()) + } + + /// 快速创建人像优化组合 + pub fn portrait() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .upscale(UpscaleModel::Nyx3, 2.0) + .quality(QualityLevel::High) + } + + /// 快速创建动画优化组合 + pub fn animation() -> FilterCombinationBuilder { + FilterCombinationBuilder::new() + .upscale(UpscaleModel::Iris3, 2.0) + .quality(QualityLevel::High) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_builder_upscale() { + let (combination, options) = FilterCombinationBuilder::new() + .upscale(UpscaleModel::Iris3, 2.0) + .quality(QualityLevel::High) + .output_format("mp4".to_string()) + .build() + .unwrap(); + + match combination { + FilterCombination::Upscale { model, scale, quality } => { + assert_eq!(model, UpscaleModel::Iris3); + assert_eq!(scale, 2.0); + assert!(matches!(quality, QualityLevel::High)); + } + _ => panic!("Expected Upscale combination"), + } + + assert_eq!(options.output_format, Some("mp4".to_string())); + } + + #[test] + fn test_quick_builders() { + let (combination, _) = quick_builders::upscale(2.0).build().unwrap(); + + match combination { + FilterCombination::Upscale { model, scale, .. } => { + assert_eq!(model, UpscaleModel::Iris3); + assert_eq!(scale, 2.0); + } + _ => panic!("Expected Upscale combination"), + } + } + + #[test] + fn test_builder_validation() { + let result = FilterCombinationBuilder::new().build(); + assert!(result.is_err()); + } +} diff --git a/cargos/tvai/src/filters/combinations.rs b/cargos/tvai/src/filters/combinations.rs new file mode 100644 index 0000000..c024bf3 --- /dev/null +++ b/cargos/tvai/src/filters/combinations.rs @@ -0,0 +1,668 @@ +//! 滤镜组合实现 + +use std::path::Path; +use crate::core::{TvaiError, ProcessResult, ProgressCallback}; +use crate::config::UpscaleModel; +use super::{FilterProcessor, QualityLevel, ProcessOptions}; + +impl FilterProcessor { + /// 应用超分辨率放大 + pub async fn apply_upscale( + &mut self, + input_path: &Path, + output_path: &Path, + model: UpscaleModel, + scale: f32, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 构建 tvai_up 滤镜参数 + let mut filter_args = vec![ + format!("model={}", model.as_str()), + format!("scale={}", scale as u32), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + ]; + + // 根据质量等级调整参数 + match quality { + QualityLevel::Fast => { + filter_args.extend_from_slice(&[ + "estimate=4".to_string(), + "instances=2".to_string(), + ]); + } + QualityLevel::Balanced => { + filter_args.extend_from_slice(&[ + "estimate=8".to_string(), + "instances=1".to_string(), + ]); + } + QualityLevel::High => { + filter_args.extend_from_slice(&[ + "estimate=12".to_string(), + "instances=1".to_string(), + "preblur=0".to_string(), + "noise=0".to_string(), + "details=0".to_string(), + ]); + } + QualityLevel::Maximum => { + filter_args.extend_from_slice(&[ + "estimate=20".to_string(), + "instances=1".to_string(), + "preblur=0".to_string(), + "noise=0".to_string(), + "details=0".to_string(), + "halo=0".to_string(), + "blur=0".to_string(), + "compression=0".to_string(), + ]); + } + } + + // 如果指定了输出分辨率 + if let Some((width, height)) = options.output_resolution { + filter_args.extend_from_slice(&[ + format!("w={}", width), + format!("h={}", height), + ]); + } + + let filter_complex = format!("tvai_up={}", filter_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.2); + } + + // 构建完整的 FFmpeg 命令 + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &filter_complex, + ]; + + // 添加编码参数 + self.add_encoding_args(&mut args, &quality, &options); + + // 添加音频处理 + if options.keep_audio { + args.extend_from_slice(&["-c:a", "copy"]); + } else { + args.push("-an"); + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.3); + } + + // 执行命令 + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("upscale: model={}, scale={}, quality={:?}", + model.as_str(), scale, quality), + ), + }) + } + + /// 应用慢动作处理 + pub async fn apply_slow_motion( + &mut self, + input_path: &Path, + output_path: &Path, + factor: f32, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 选择插值模型 + let model = match quality { + QualityLevel::Fast => "apf-1", + QualityLevel::Balanced => "chr-2", + QualityLevel::High | QualityLevel::Maximum => "apo-8", + }; + + // 构建 tvai_fi 滤镜参数 + let filter_args = vec![ + format!("model={}", model), + format!("slowmo={}", factor), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + "rdt=0.01".to_string(), + ]; + + let filter_complex = format!("tvai_fi={}", filter_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.2); + } + + // 构建 FFmpeg 命令 + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &filter_complex, + ]; + + self.add_encoding_args(&mut args, &quality, &options); + + if options.keep_audio { + args.extend_from_slice(&["-c:a", "copy"]); + } else { + args.push("-an"); + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.3); + } + + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("slow_motion: factor={}, model={}, quality={:?}", + factor, model, quality), + ), + }) + } + + /// 应用视频稳定化 + pub async fn apply_stabilize( + &mut self, + input_path: &Path, + output_path: &Path, + strength: f32, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 第一步:相机姿态估算 + let cpe_file = self.processor.create_temp_path(&operation_id, "cpe.json"); + + let cpe_args = vec![ + format!("model=cpe-1"), + format!("filename={}", cpe_file.to_str().unwrap()), + format!("device={}", options.device), + ]; + + let cpe_filter = format!("tvai_cpe={}", cpe_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.1); + } + + // 执行 CPE + let mut cpe_cmd = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &cpe_filter, + "-f", "null", "-", + ]; + + self.processor.execute_ffmpeg_command(&cpe_cmd, true, None).await?; + + if let Some(callback) = progress_callback { + callback(0.4); + } + + // 第二步:视频稳定化 + let smoothness = match quality { + QualityLevel::Fast => 4.0, + QualityLevel::Balanced => 6.0, + QualityLevel::High => 8.0, + QualityLevel::Maximum => 12.0, + }; + + let stb_args = vec![ + format!("model=ref-2"), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + format!("filename={}", cpe_file.to_str().unwrap()), + format!("smoothness={}", smoothness * strength), + "full=1".to_string(), + "dof=1111".to_string(), + ]; + + let stb_filter = format!("tvai_stb={}", stb_args.join(":")); + + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &stb_filter, + ]; + + self.add_encoding_args(&mut args, &quality, &options); + + if options.keep_audio { + args.extend_from_slice(&["-c:a", "copy"]); + } else { + args.push("-an"); + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.5); + } + + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + // 清理临时文件 + let _ = std::fs::remove_file(&cpe_file); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("stabilize: strength={}, quality={:?}", strength, quality), + ), + }) + } + + /// 添加编码参数 + fn add_encoding_args(&self, args: &mut Vec<&str>, quality: &QualityLevel, options: &ProcessOptions) { + // 根据质量等级选择编码参数 + match quality { + QualityLevel::Fast => { + args.extend_from_slice(&["-c:v", "h264_nvenc", "-preset", "p1", "-crf", "25"]); + } + QualityLevel::Balanced => { + args.extend_from_slice(&["-c:v", "h264_nvenc", "-preset", "p4", "-crf", "20"]); + } + QualityLevel::High => { + args.extend_from_slice(&["-c:v", "h264_nvenc", "-preset", "p6", "-crf", "17"]); + } + QualityLevel::Maximum => { + args.extend_from_slice(&["-c:v", "h264_nvenc", "-preset", "p7", "-crf", "15"]); + } + } + + args.extend_from_slice(&["-pix_fmt", "yuv420p"]); + } + + /// 应用完整增强 (放大 + 稳定化) + pub async fn apply_full_enhance( + &mut self, + input_path: &Path, + output_path: &Path, + upscale_factor: f32, + stabilize: bool, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + if stabilize { + // 两步处理:先稳定化,再放大 + let temp_stabilized = self.processor.create_temp_path(&operation_id, "stabilized.mp4"); + + // 第一步:稳定化 + self.apply_stabilize(input_path, &temp_stabilized, 1.0, quality, options.clone(), None).await?; + + if let Some(callback) = progress_callback { + callback(0.5); + } + + // 第二步:放大 + let result = self.apply_upscale(&temp_stabilized, output_path, UpscaleModel::Iris3, upscale_factor, quality, options, None).await?; + + // 清理临时文件 + let _ = std::fs::remove_file(&temp_stabilized); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(result) + } else { + // 只进行放大 + self.apply_upscale(input_path, output_path, UpscaleModel::Iris3, upscale_factor, quality, options, progress_callback).await + } + } + + /// 应用老视频修复 + pub async fn apply_restore_old_video( + &mut self, + input_path: &Path, + output_path: &Path, + upscale_factor: f32, + denoise_level: f32, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 使用 Proteus 模型进行老视频修复 + let mut filter_args = vec![ + "model=prob-4".to_string(), + format!("scale={}", upscale_factor as u32), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + format!("noise={}", denoise_level), + format!("details={}", 0.3), + format!("compression={}", 0.2), + "estimate=12".to_string(), + ]; + + // 根据质量等级调整参数 + match quality { + QualityLevel::Fast => { + filter_args.push("instances=2".to_string()); + } + QualityLevel::Balanced => { + filter_args.extend_from_slice(&[ + "halo=0.1".to_string(), + "blur=0.1".to_string(), + ]); + } + QualityLevel::High | QualityLevel::Maximum => { + filter_args.extend_from_slice(&[ + "halo=0.2".to_string(), + "blur=0.2".to_string(), + "preblur=0.1".to_string(), + ]); + } + } + + let filter_complex = format!("tvai_up={}", filter_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.2); + } + + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &filter_complex, + ]; + + self.add_encoding_args(&mut args, &quality, &options); + + if options.keep_audio { + args.extend_from_slice(&["-c:a", "copy"]); + } else { + args.push("-an"); + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.3); + } + + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("restore_old_video: upscale={}, denoise={}, quality={:?}", + upscale_factor, denoise_level, quality), + ), + }) + } + + /// 应用游戏录像优化 + pub async fn apply_game_footage( + &mut self, + input_path: &Path, + output_path: &Path, + upscale_factor: f32, + sharpen: bool, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 使用 Gaia 模型进行游戏内容优化 + let mut filter_args = vec![ + "model=ghq-5".to_string(), + format!("scale={}", upscale_factor as u32), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + "compression=0".to_string(), + "noise=0".to_string(), + "estimate=8".to_string(), + ]; + + if sharpen { + filter_args.extend_from_slice(&[ + "details=0.5".to_string(), + "blur=-0.2".to_string(), + ]); + } + + // 根据质量等级调整参数 + match quality { + QualityLevel::Fast => { + filter_args.push("instances=2".to_string()); + } + QualityLevel::Maximum => { + filter_args.extend_from_slice(&[ + "estimate=16".to_string(), + "details=0.7".to_string(), + ]); + } + _ => {} + } + + let filter_complex = format!("tvai_up={}", filter_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.2); + } + + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &filter_complex, + ]; + + self.add_encoding_args(&mut args, &quality, &options); + + if options.keep_audio { + args.extend_from_slice(&["-c:a", "copy"]); + } else { + args.push("-an"); + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.3); + } + + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("game_footage: upscale={}, sharpen={}, quality={:?}", + upscale_factor, sharpen, quality), + ), + }) + } + + /// 应用图片放大 + pub async fn apply_image_upscale( + &mut self, + input_path: &Path, + output_path: &Path, + model: UpscaleModel, + scale: f32, + quality: QualityLevel, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + let operation_id = self.processor.generate_operation_id(); + + if let Some(callback) = progress_callback { + callback(0.0); + } + + // 构建图片放大滤镜参数 + let mut filter_args = vec![ + format!("model={}", model.as_str()), + format!("scale={}", scale as u32), + format!("device={}", options.device), + format!("vram={}", options.vram_limit), + ]; + + // 根据质量等级调整参数 + match quality { + QualityLevel::Fast => { + filter_args.push("estimate=4".to_string()); + } + QualityLevel::Balanced => { + filter_args.push("estimate=8".to_string()); + } + QualityLevel::High => { + filter_args.extend_from_slice(&[ + "estimate=12".to_string(), + "details=0.2".to_string(), + ]); + } + QualityLevel::Maximum => { + filter_args.extend_from_slice(&[ + "estimate=20".to_string(), + "details=0.3".to_string(), + "noise=0.1".to_string(), + ]); + } + } + + let filter_complex = format!("tvai_up={}", filter_args.join(":")); + + if let Some(callback) = progress_callback { + callback(0.2); + } + + let mut args = vec![ + "-y", "-hide_banner", "-nostdin", + "-i", input_path.to_str().unwrap(), + "-filter_complex", &filter_complex, + ]; + + // 图片输出格式 + let output_format = options.output_format.as_deref().unwrap_or("png"); + args.extend_from_slice(&["-f", output_format]); + + // 图片质量设置 + match quality { + QualityLevel::Fast => args.extend_from_slice(&["-q:v", "5"]), + QualityLevel::Balanced => args.extend_from_slice(&["-q:v", "3"]), + QualityLevel::High => args.extend_from_slice(&["-q:v", "2"]), + QualityLevel::Maximum => args.extend_from_slice(&["-q:v", "1"]), + } + + args.push(output_path.to_str().unwrap()); + + if let Some(callback) = progress_callback { + callback(0.3); + } + + let start_time = std::time::Instant::now(); + self.processor.execute_ffmpeg_command(&args, true, progress_callback).await?; + let processing_time = start_time.elapsed(); + + if let Some(callback) = progress_callback { + callback(1.0); + } + + Ok(ProcessResult { + output_path: output_path.to_path_buf(), + processing_time, + metadata: self.processor.create_metadata( + operation_id, + input_path, + format!("image_upscale: model={}, scale={}, quality={:?}", + model.as_str(), scale, quality), + ), + }) + } +} diff --git a/cargos/tvai/src/filters/mod.rs b/cargos/tvai/src/filters/mod.rs new file mode 100644 index 0000000..241d92d --- /dev/null +++ b/cargos/tvai/src/filters/mod.rs @@ -0,0 +1,282 @@ +//! Topaz Video AI 滤镜组合库 +//! +//! 提供易用的高级滤镜组合函数,封装复杂的 TVAI 滤镜参数 + +pub mod combinations; +pub mod presets; +pub mod builder; + +use std::path::Path; +use crate::core::{TvaiError, TvaiProcessor, ProcessResult, ProgressCallback}; +use crate::config::UpscaleModel; + +/// 滤镜组合类型 +#[derive(Debug, Clone)] +pub enum FilterCombination { + /// 超分辨率放大 + Upscale { + model: UpscaleModel, + scale: f32, + quality: QualityLevel, + }, + /// 帧插值慢动作 + SlowMotion { + factor: f32, + quality: QualityLevel, + }, + /// 视频稳定化 + Stabilize { + strength: f32, + quality: QualityLevel, + }, + /// 完整增强 (放大 + 稳定化) + FullEnhance { + upscale_factor: f32, + stabilize: bool, + quality: QualityLevel, + }, + /// 老视频修复 + RestoreOldVideo { + upscale_factor: f32, + denoise_level: f32, + quality: QualityLevel, + }, + /// 游戏录像优化 + GameFootage { + upscale_factor: f32, + sharpen: bool, + quality: QualityLevel, + }, +} + +/// 质量等级 +#[derive(Debug, Clone, Copy)] +pub enum QualityLevel { + /// 快速处理 + Fast, + /// 平衡质量和速度 + Balanced, + /// 高质量 + High, + /// 最高质量 + Maximum, +} + +/// 处理选项 +#[derive(Debug, Clone)] +pub struct ProcessOptions { + /// 输出格式 (mp4, mov, avi 等) + pub output_format: Option, + /// 是否保留音频 + pub keep_audio: bool, + /// 自定义输出分辨率 + pub output_resolution: Option<(u32, u32)>, + /// GPU 设备索引 (-2=自动, -1=CPU, 0+=GPU) + pub device: i32, + /// VRAM 使用限制 (0.1-1.0) + pub vram_limit: f32, + /// 是否启用进度回调 + pub enable_progress: bool, +} + +impl Default for ProcessOptions { + fn default() -> Self { + Self { + output_format: None, + keep_audio: true, + output_resolution: None, + device: -2, // 自动选择 + vram_limit: 1.0, + enable_progress: true, + } + } +} + +/// 滤镜组合处理器 +pub struct FilterProcessor { + processor: TvaiProcessor, +} + +impl FilterProcessor { + /// 创建新的滤镜处理器 + pub fn new(processor: TvaiProcessor) -> Self { + Self { processor } + } + + /// 应用滤镜组合到视频 + pub async fn apply_to_video( + &mut self, + input_path: &Path, + output_path: &Path, + combination: FilterCombination, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + match combination { + FilterCombination::Upscale { model, scale, quality } => { + self.apply_upscale(input_path, output_path, model, scale, quality, options, progress_callback).await + } + FilterCombination::SlowMotion { factor, quality } => { + self.apply_slow_motion(input_path, output_path, factor, quality, options, progress_callback).await + } + FilterCombination::Stabilize { strength, quality } => { + self.apply_stabilize(input_path, output_path, strength, quality, options, progress_callback).await + } + FilterCombination::FullEnhance { upscale_factor, stabilize, quality } => { + self.apply_full_enhance(input_path, output_path, upscale_factor, stabilize, quality, options, progress_callback).await + } + FilterCombination::RestoreOldVideo { upscale_factor, denoise_level, quality } => { + self.apply_restore_old_video(input_path, output_path, upscale_factor, denoise_level, quality, options, progress_callback).await + } + FilterCombination::GameFootage { upscale_factor, sharpen, quality } => { + self.apply_game_footage(input_path, output_path, upscale_factor, sharpen, quality, options, progress_callback).await + } + } + } + + /// 应用滤镜组合到图片 + pub async fn apply_to_image( + &mut self, + input_path: &Path, + output_path: &Path, + combination: FilterCombination, + options: ProcessOptions, + progress_callback: Option<&ProgressCallback>, + ) -> Result { + match combination { + FilterCombination::Upscale { model, scale, quality } => { + self.apply_image_upscale(input_path, output_path, model, scale, quality, options, progress_callback).await + } + _ => Err(TvaiError::InvalidParameter( + "This filter combination is not supported for images".to_string() + )) + } + } + + /// 获取推荐的滤镜组合 + pub async fn get_recommended_combination( + &self, + input_path: &Path, + ) -> Result { + // 获取视频信息 + let video_info = crate::utils::get_video_info(input_path).await?; + + // 基于视频特征推荐组合 + if video_info.width < 720 || video_info.height < 480 { + // 低分辨率视频,推荐老视频修复 + Ok(FilterCombination::RestoreOldVideo { + upscale_factor: 2.0, + denoise_level: 0.3, + quality: QualityLevel::High, + }) + } else if video_info.width >= 1920 && video_info.fps > 30.0 { + // 高分辨率高帧率,可能是游戏录像 + Ok(FilterCombination::GameFootage { + upscale_factor: 1.5, + sharpen: true, + quality: QualityLevel::Balanced, + }) + } else if video_info.fps < 24.0 { + // 低帧率视频,推荐帧插值 + Ok(FilterCombination::SlowMotion { + factor: 2.0, + quality: QualityLevel::High, + }) + } else { + // 通用增强 + Ok(FilterCombination::FullEnhance { + upscale_factor: 1.5, + stabilize: true, + quality: QualityLevel::Balanced, + }) + } + } +} + +/// 快速处理函数 +pub mod quick { + use super::*; + + /// 快速视频放大 + pub async fn upscale_video( + input: &Path, + output: &Path, + scale: f32, + ) -> Result { + let combination = FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale, + quality: QualityLevel::Balanced, + }; + + let processor = create_default_processor().await?; + let mut filter_processor = FilterProcessor::new(processor); + + filter_processor.apply_to_video( + input, + output, + combination, + ProcessOptions::default(), + None, + ).await + } + + /// 快速慢动作处理 + pub async fn slow_motion( + input: &Path, + output: &Path, + factor: f32, + ) -> Result { + let combination = FilterCombination::SlowMotion { + factor, + quality: QualityLevel::Balanced, + }; + + let processor = create_default_processor().await?; + let mut filter_processor = FilterProcessor::new(processor); + + filter_processor.apply_to_video( + input, + output, + combination, + ProcessOptions::default(), + None, + ).await + } + + /// 快速视频稳定化 + pub async fn stabilize_video( + input: &Path, + output: &Path, + strength: f32, + ) -> Result { + let combination = FilterCombination::Stabilize { + strength, + quality: QualityLevel::Balanced, + }; + + let processor = create_default_processor().await?; + let mut filter_processor = FilterProcessor::new(processor); + + filter_processor.apply_to_video( + input, + output, + combination, + ProcessOptions::default(), + None, + ).await + } + + /// 创建默认处理器 + async fn create_default_processor() -> Result { + let topaz_path = crate::utils::detect_topaz_installation() + .ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?; + + let config = crate::core::TvaiConfig::builder() + .topaz_path(topaz_path) + .use_gpu(true) + .build()?; + + TvaiProcessor::new(config) + } +} diff --git a/cargos/tvai/src/filters/presets.rs b/cargos/tvai/src/filters/presets.rs new file mode 100644 index 0000000..ea973ac --- /dev/null +++ b/cargos/tvai/src/filters/presets.rs @@ -0,0 +1,323 @@ +//! 预设配置 + +use super::{FilterCombination, QualityLevel, ProcessOptions}; +use crate::config::UpscaleModel; + +/// 预设类型 +#[derive(Debug, Clone)] +pub enum PresetType { + /// 社交媒体优化 + SocialMedia, + /// 电影修复 + FilmRestoration, + /// 游戏录像 + Gaming, + /// 监控视频 + Surveillance, + /// 动画内容 + Animation, + /// 人像视频 + Portrait, + /// 风景视频 + Landscape, + /// 快速预览 + QuickPreview, +} + +impl PresetType { + /// 获取预设的滤镜组合 + pub fn get_filter_combination(&self) -> FilterCombination { + match self { + PresetType::SocialMedia => FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::Balanced, + }, + PresetType::FilmRestoration => FilterCombination::RestoreOldVideo { + upscale_factor: 2.0, + denoise_level: 0.4, + quality: QualityLevel::High, + }, + PresetType::Gaming => FilterCombination::GameFootage { + upscale_factor: 1.5, + sharpen: true, + quality: QualityLevel::High, + }, + PresetType::Surveillance => FilterCombination::RestoreOldVideo { + upscale_factor: 2.0, + denoise_level: 0.6, + quality: QualityLevel::Balanced, + }, + PresetType::Animation => FilterCombination::Upscale { + model: UpscaleModel::Iris3, + scale: 2.0, + quality: QualityLevel::High, + }, + PresetType::Portrait => FilterCombination::Upscale { + model: UpscaleModel::Nyx3, + scale: 2.0, + quality: QualityLevel::High, + }, + PresetType::Landscape => FilterCombination::FullEnhance { + upscale_factor: 1.5, + stabilize: true, + quality: QualityLevel::High, + }, + PresetType::QuickPreview => FilterCombination::Upscale { + model: UpscaleModel::Alqs2, + scale: 1.5, + quality: QualityLevel::Fast, + }, + } + } + + /// 获取预设的处理选项 + pub fn get_process_options(&self) -> ProcessOptions { + match self { + PresetType::SocialMedia => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: Some((1920, 1080)), + device: -2, + vram_limit: 0.8, + enable_progress: true, + }, + PresetType::FilmRestoration => ProcessOptions { + output_format: Some("mov".to_string()), + keep_audio: true, + output_resolution: None, + device: -2, + vram_limit: 1.0, + enable_progress: true, + }, + PresetType::Gaming => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: None, + device: 0, // 优先使用 GPU + vram_limit: 1.0, + enable_progress: true, + }, + PresetType::Surveillance => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: false, + output_resolution: None, + device: -2, + vram_limit: 0.6, + enable_progress: true, + }, + PresetType::Animation => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: None, + device: -2, + vram_limit: 0.9, + enable_progress: true, + }, + PresetType::Portrait => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: None, + device: -2, + vram_limit: 0.8, + enable_progress: true, + }, + PresetType::Landscape => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: None, + device: -2, + vram_limit: 1.0, + enable_progress: true, + }, + PresetType::QuickPreview => ProcessOptions { + output_format: Some("mp4".to_string()), + keep_audio: true, + output_resolution: Some((1280, 720)), + device: -2, + vram_limit: 0.5, + enable_progress: true, + }, + } + } + + /// 获取预设描述 + pub fn description(&self) -> &'static str { + match self { + PresetType::SocialMedia => "优化用于社交媒体分享的视频,平衡质量和文件大小", + PresetType::FilmRestoration => "修复老电影和胶片视频,去除噪点和伪影", + PresetType::Gaming => "优化游戏录像,增强细节和锐度", + PresetType::Surveillance => "增强监控视频质量,去除噪点", + PresetType::Animation => "优化动画内容,保持清晰的线条和色彩", + PresetType::Portrait => "优化人像视频,增强面部细节", + PresetType::Landscape => "优化风景视频,增强细节并稳定画面", + PresetType::QuickPreview => "快速预览模式,速度优先", + } + } + + /// 获取所有可用预设 + pub fn all_presets() -> Vec { + vec![ + PresetType::SocialMedia, + PresetType::FilmRestoration, + PresetType::Gaming, + PresetType::Surveillance, + PresetType::Animation, + PresetType::Portrait, + PresetType::Landscape, + PresetType::QuickPreview, + ] + } +} + +/// 自定义预设构建器 +pub struct PresetBuilder { + combination: Option, + options: ProcessOptions, + name: String, + description: String, +} + +impl PresetBuilder { + /// 创建新的预设构建器 + pub fn new(name: String) -> Self { + Self { + combination: None, + options: ProcessOptions::default(), + name, + description: String::new(), + } + } + + /// 设置滤镜组合 + pub fn with_combination(mut self, combination: FilterCombination) -> Self { + self.combination = Some(combination); + self + } + + /// 设置处理选项 + pub fn with_options(mut self, options: ProcessOptions) -> Self { + self.options = options; + self + } + + /// 设置描述 + pub fn with_description(mut self, description: String) -> Self { + self.description = description; + self + } + + /// 构建自定义预设 + pub fn build(self) -> Result { + let combination = self.combination + .ok_or_else(|| "Filter combination is required".to_string())?; + + Ok(CustomPreset { + name: self.name, + description: self.description, + combination, + options: self.options, + }) + } +} + +/// 自定义预设 +#[derive(Debug, Clone)] +pub struct CustomPreset { + pub name: String, + pub description: String, + pub combination: FilterCombination, + pub options: ProcessOptions, +} + +impl CustomPreset { + /// 创建预设构建器 + pub fn builder(name: String) -> PresetBuilder { + PresetBuilder::new(name) + } + + /// 保存预设到文件 + pub fn save_to_file(&self, path: &std::path::Path) -> Result<(), Box> { + let json = serde_json::to_string_pretty(self)?; + std::fs::write(path, json)?; + Ok(()) + } + + /// 从文件加载预设 + pub fn load_from_file(path: &std::path::Path) -> Result> { + let json = std::fs::read_to_string(path)?; + let preset = serde_json::from_str(&json)?; + Ok(preset) + } +} + +/// 预设管理器 +pub struct PresetManager { + custom_presets: Vec, +} + +impl PresetManager { + /// 创建新的预设管理器 + pub fn new() -> Self { + Self { + custom_presets: Vec::new(), + } + } + + /// 添加自定义预设 + pub fn add_custom_preset(&mut self, preset: CustomPreset) { + self.custom_presets.push(preset); + } + + /// 获取自定义预设 + pub fn get_custom_preset(&self, name: &str) -> Option<&CustomPreset> { + self.custom_presets.iter().find(|p| p.name == name) + } + + /// 列出所有自定义预设 + pub fn list_custom_presets(&self) -> &[CustomPreset] { + &self.custom_presets + } + + /// 删除自定义预设 + pub fn remove_custom_preset(&mut self, name: &str) -> bool { + if let Some(pos) = self.custom_presets.iter().position(|p| p.name == name) { + self.custom_presets.remove(pos); + true + } else { + false + } + } + + /// 加载预设目录 + pub fn load_presets_from_dir(&mut self, dir: &std::path::Path) -> Result> { + let mut loaded = 0; + + if dir.exists() && dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.extension().and_then(|s| s.to_str()) == Some("json") { + match CustomPreset::load_from_file(&path) { + Ok(preset) => { + self.add_custom_preset(preset); + loaded += 1; + } + Err(e) => { + eprintln!("Failed to load preset from {}: {}", path.display(), e); + } + } + } + } + } + + Ok(loaded) + } +} + +impl Default for PresetManager { + fn default() -> Self { + Self::new() + } +} diff --git a/cargos/tvai/src/lib.rs b/cargos/tvai/src/lib.rs index e73b0fa..83d2518 100644 --- a/cargos/tvai/src/lib.rs +++ b/cargos/tvai/src/lib.rs @@ -35,6 +35,7 @@ pub mod video; pub mod image; pub mod config; pub mod utils; +pub mod filters; // Re-export main types for convenience pub use core::{TvaiProcessor, TvaiConfig, ProcessResult, ProcessMetadata, ProgressCallback, ProcessingOptions}; @@ -49,8 +50,14 @@ pub use utils::{ GpuInfo, FfmpegInfo, VideoInfo, ImageInfo, TempFileManager, GpuManager, DetailedGpuInfo, GpuDevice, GpuSettings, GpuBenchmarkResult, PerformanceMonitor, PerformanceMetrics, PerformanceSettings, ProcessingMode, PerformanceSummary, + ModelManager, ModelInfo, ModelType, optimize_for_system }; +pub use filters::{ + FilterCombination, QualityLevel, ProcessOptions, FilterProcessor, + presets::{PresetType, CustomPreset, PresetManager as FilterPresetManager}, + builder::{FilterCombinationBuilder, quick_builders} +}; // Re-export error types pub use core::TvaiError; diff --git a/cargos/tvai/src/utils/mod.rs b/cargos/tvai/src/utils/mod.rs index f3c6c12..52c68d9 100644 --- a/cargos/tvai/src/utils/mod.rs +++ b/cargos/tvai/src/utils/mod.rs @@ -3,10 +3,12 @@ pub mod temp; pub mod gpu; pub mod performance; +pub mod model_manager; // Re-export main types pub use temp::TempFileManager; pub use gpu::{GpuManager, DetailedGpuInfo, GpuDevice, GpuSettings, GpuBenchmarkResult}; +pub use model_manager::{ModelManager, ModelInfo, ModelType}; pub use performance::{PerformanceMonitor, PerformanceMetrics, PerformanceSettings, ProcessingMode, PerformanceSummary, optimize_for_system}; use std::path::{Path, PathBuf}; diff --git a/cargos/tvai/src/utils/model_manager.rs b/cargos/tvai/src/utils/model_manager.rs new file mode 100644 index 0000000..c794d9d --- /dev/null +++ b/cargos/tvai/src/utils/model_manager.rs @@ -0,0 +1,362 @@ +//! Topaz Video AI 模型管理工具 +//! +//! 提供模型下载、检查和管理功能 + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use crate::core::TvaiError; + +/// 模型信息 +#[derive(Debug, Clone)] +pub struct ModelInfo { + pub short_name: String, + pub display_name: Option, + pub model_type: ModelType, + pub is_downloaded: bool, +} + +/// 模型类型 +#[derive(Debug, Clone, PartialEq)] +pub enum ModelType { + /// 超分辨率模型 + Upscale, + /// 帧插值模型 + Interpolation, + /// 其他模型 (稳定化、参数估算等) + Other, +} + +/// 模型管理器 +pub struct ModelManager { + models_dir: PathBuf, + topaz_exe: Option, + topaz_ffmpeg: PathBuf, +} + +impl ModelManager { + /// 创建新的模型管理器 + pub fn new(topaz_path: &Path) -> Result { + let models_dir = topaz_path.join("models"); + let topaz_exe = topaz_path.join("Topaz Video AI.exe"); + let topaz_ffmpeg = topaz_path.join("ffmpeg.exe"); + + if !models_dir.exists() { + return Err(TvaiError::TopazNotFound( + format!("Models directory not found: {}", models_dir.display()) + )); + } + + if !topaz_ffmpeg.exists() { + return Err(TvaiError::TopazNotFound( + format!("Topaz FFmpeg not found: {}", topaz_ffmpeg.display()) + )); + } + + Ok(Self { + models_dir, + topaz_exe: if topaz_exe.exists() { Some(topaz_exe) } else { None }, + topaz_ffmpeg, + }) + } + + /// 获取所有模型信息 + pub fn get_all_models(&self) -> Result, TvaiError> { + let mut models = Vec::new(); + let downloaded_models = self.get_downloaded_models()?; + + if let Ok(entries) = fs::read_dir(&self.models_dir) { + for entry in entries { + if let Ok(entry) = entry { + let path = entry.path(); + if let Some(extension) = path.extension() { + if extension == "json" { + let filename = path.file_name().unwrap().to_string_lossy(); + + // 跳过非模型文件 + if filename.contains("audio-codecs") || + filename.contains("benchmarks") || + filename.contains("proxy") || + filename.contains("video-encoders") { + continue; + } + + if let Ok(content) = fs::read_to_string(&path) { + if let Some(short_name) = extract_short_name(&content) { + let display_name = extract_display_name(&content); + let model_type = determine_model_type(&short_name); + let is_downloaded = downloaded_models.contains(&short_name); + + models.push(ModelInfo { + short_name, + display_name, + model_type, + is_downloaded, + }); + } + } + } + } + } + } + } + + // 去重并排序 + models.sort_by(|a, b| a.short_name.cmp(&b.short_name)); + models.dedup_by(|a, b| a.short_name == b.short_name); + + Ok(models) + } + + /// 获取已下载的模型列表 + pub fn get_downloaded_models(&self) -> Result, TvaiError> { + let mut downloaded = Vec::new(); + + if let Ok(entries) = fs::read_dir(&self.models_dir) { + for entry in entries { + if let Ok(entry) = entry { + let filename = entry.file_name().to_string_lossy().to_string(); + if filename.ends_with(".tz") { + // 提取模型名称 (格式: model_name_version_resolution.tz) + if let Some(underscore_pos) = filename.find('_') { + let model_name = filename[..underscore_pos].to_string(); + if !downloaded.contains(&model_name) { + downloaded.push(model_name); + } + } + } + } + } + } + + downloaded.sort(); + downloaded.dedup(); + Ok(downloaded) + } + + /// 获取缺失的模型列表 + pub fn get_missing_models(&self) -> Result, TvaiError> { + let all_models = self.get_all_models()?; + Ok(all_models.into_iter() + .filter(|m| !m.is_downloaded) + .collect()) + } + + /// 检查特定模型是否已下载 + pub fn is_model_downloaded(&self, model_name: &str) -> Result { + let downloaded = self.get_downloaded_models()?; + Ok(downloaded.contains(&model_name.to_string())) + } + + /// 生成模型下载指南 + pub fn generate_download_guide(&self, output_path: &Path) -> Result<(), TvaiError> { + let missing_models = self.get_missing_models()?; + + if missing_models.is_empty() { + return Ok(()); + } + + let mut guide = String::new(); + guide.push_str("# Topaz Video AI 模型下载指南\n\n"); + + // 统计信息 + let total_models = self.get_all_models()?.len(); + let downloaded_count = total_models - missing_models.len(); + + guide.push_str(&format!("## 📊 模型状态\n\n")); + guide.push_str(&format!("- 总计: {} 个模型\n", total_models)); + guide.push_str(&format!("- 已下载: {} 个模型\n", downloaded_count)); + guide.push_str(&format!("- 缺失: {} 个模型\n\n", missing_models.len())); + + // 按类型分组 + let upscale_models: Vec<_> = missing_models.iter() + .filter(|m| m.model_type == ModelType::Upscale) + .collect(); + + let interpolation_models: Vec<_> = missing_models.iter() + .filter(|m| m.model_type == ModelType::Interpolation) + .collect(); + + let other_models: Vec<_> = missing_models.iter() + .filter(|m| m.model_type == ModelType::Other) + .collect(); + + if !upscale_models.is_empty() { + guide.push_str("## 🔍 超分辨率模型 (Enhancement)\n\n"); + for model in upscale_models { + guide.push_str(&format!("- **{}** - {}\n", + model.short_name, + model.display_name.as_deref().unwrap_or("N/A"))); + } + guide.push_str("\n"); + } + + if !interpolation_models.is_empty() { + guide.push_str("## 🎬 帧插值模型 (Frame Interpolation)\n\n"); + for model in interpolation_models { + guide.push_str(&format!("- **{}** - {}\n", + model.short_name, + model.display_name.as_deref().unwrap_or("N/A"))); + } + guide.push_str("\n"); + } + + if !other_models.is_empty() { + guide.push_str("## 🔧 其他模型\n\n"); + for model in other_models { + guide.push_str(&format!("- **{}** - {}\n", + model.short_name, + model.display_name.as_deref().unwrap_or("N/A"))); + } + guide.push_str("\n"); + } + + guide.push_str("## 🚀 下载步骤\n\n"); + guide.push_str("1. 启动 Topaz Video AI 应用程序\n"); + guide.push_str("2. 导入任意视频文件\n"); + guide.push_str("3. 在右侧面板中选择相应的处理类型\n"); + guide.push_str("4. 依次选择上述列出的每个模型\n"); + guide.push_str("5. 点击 Preview 或 Export,应用程序会自动下载模型\n"); + guide.push_str("6. 等待下载完成后取消处理\n"); + guide.push_str("7. 重复步骤4-6直到所有模型下载完成\n\n"); + + guide.push_str("## 💡 提示\n\n"); + guide.push_str("- 模型文件较大,请确保网络连接稳定\n"); + guide.push_str("- 下载过程中不要关闭应用程序\n"); + guide.push_str("- 某些模型可能需要特定的输入分辨率才能触发下载\n"); + guide.push_str("- 下载完成后可以在模型目录中看到 .tz 文件\n"); + + if let Some(topaz_exe) = &self.topaz_exe { + guide.push_str(&format!("\n## 🚀 启动命令\n\n")); + guide.push_str(&format!("```\n\"{}\"\n```\n", topaz_exe.display())); + } + + fs::write(output_path, guide) + .map_err(|e| TvaiError::IoError(format!("Failed to write guide: {}", e)))?; + + Ok(()) + } + + /// 启动 Topaz Video AI 应用程序 + pub fn launch_topaz_app(&self) -> Result<(), TvaiError> { + if let Some(topaz_exe) = &self.topaz_exe { + Command::new(topaz_exe) + .spawn() + .map_err(|e| TvaiError::ProcessError(format!("Failed to launch Topaz: {}", e)))?; + Ok(()) + } else { + Err(TvaiError::TopazNotFound("Topaz Video AI executable not found".to_string())) + } + } + + /// 尝试触发模型下载 (实验性功能) + pub fn attempt_model_download(&self, model_name: &str, test_video: &Path) -> Result { + if !test_video.exists() { + return Err(TvaiError::FileNotFound(test_video.to_string_lossy().to_string())); + } + + let output_file = format!("temp_download_test_{}.mp4", model_name); + + // 构建测试命令 + let filter = format!("tvai_up=model={}:scale=2:device=-2:vram=0.1:estimate=1", model_name); + + let args = vec![ + "-y", "-hide_banner", + "-i", test_video.to_str().unwrap(), + "-t", "0.1", // 只处理 0.1 秒 + "-filter_complex", &filter, + "-c:v", "h264_nvenc", + "-preset", "p1", + "-an", + &output_file, + ]; + + let output = Command::new(&self.topaz_ffmpeg) + .args(&args) + .output() + .map_err(|e| TvaiError::ProcessError(format!("Failed to execute FFmpeg: {}", e)))?; + + // 清理临时文件 + let _ = fs::remove_file(&output_file); + + let stderr = String::from_utf8_lossy(&output.stderr); + + // 检查结果 + if stderr.contains("Model not found") { + Ok(false) + } else if stderr.contains("Downloading") || + stderr.contains("Loading") || + output.status.success() { + Ok(true) + } else { + Ok(false) + } + } + + /// 获取推荐的模型列表 (基于用途) + pub fn get_recommended_models(&self, use_case: &str) -> Result, TvaiError> { + let models = self.get_all_models()?; + + let recommended = match use_case.to_lowercase().as_str() { + "general" | "通用" => vec!["iris", "amq", "chr"], + "high_quality" | "高质量" => vec!["ahq", "nyx", "apo"], + "fast" | "快速" => vec!["alq", "apf", "chf"], + "gaming" | "游戏" => vec!["ghq", "gcg"], + "old_video" | "老视频" => vec!["prob", "dtv", "ddv"], + "portrait" | "人像" => vec!["nyx", "nxf"], + _ => vec!["iris", "amq", "chr"], // 默认推荐 + }; + + // 过滤出实际存在的模型 + let available_recommended: Vec = recommended.into_iter() + .filter(|name| models.iter().any(|m| m.short_name == *name)) + .map(|s| s.to_string()) + .collect(); + + Ok(available_recommended) + } +} + +// 辅助函数 +fn extract_short_name(content: &str) -> Option { + if let Some(start) = content.find("\"shortName\"") { + let after_key = &content[start..]; + if let Some(colon_pos) = after_key.find(':') { + let after_colon = &after_key[colon_pos + 1..]; + if let Some(quote_start) = after_colon.find('"') { + let after_quote = &after_colon[quote_start + 1..]; + if let Some(quote_end) = after_quote.find('"') { + return Some(after_quote[..quote_end].to_string()); + } + } + } + } + None +} + +fn extract_display_name(content: &str) -> Option { + if let Some(start) = content.find("\"displayName\"") { + let after_key = &content[start..]; + if let Some(colon_pos) = after_key.find(':') { + let after_colon = &after_key[colon_pos + 1..]; + if let Some(quote_start) = after_colon.find('"') { + let after_quote = &after_colon[quote_start + 1..]; + if let Some(quote_end) = after_quote.find('"') { + return Some(after_quote[..quote_end].to_string()); + } + } + } + } + None +} + +fn determine_model_type(short_name: &str) -> ModelType { + if short_name.starts_with("ap") || short_name.starts_with("ch") || short_name == "ifi" { + ModelType::Interpolation + } else if short_name.starts_with("cpe") || short_name.starts_with("ref") || + short_name.starts_with("prap") || short_name.starts_with("nap") { + ModelType::Other + } else { + ModelType::Upscale + } +}