feat: Add comprehensive Topaz Video AI filter combinations and model management

This commit is contained in:
imeepos
2025-08-13 14:16:26 +08:00
parent d0845c3933
commit 244cbbeeab
10 changed files with 2810 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
# Topaz Video AI 滤镜组合使用指南
## 概述
Topaz Video AI 滤镜组合库提供了一套易用的高级 API封装了复杂的 TVAI 滤镜参数,让您能够轻松地进行各种视频和图片处理任务。
## 快速开始
### 基本用法
```rust
use tvai::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建处理器
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` 实例

View File

@@ -0,0 +1,325 @@
//! Topaz Video AI 滤镜组合演示
//!
//! 展示如何使用新的滤镜组合库进行各种视频和图片处理
use std::path::Path;
use tvai::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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!();
}
})
}

View File

@@ -0,0 +1,218 @@
//! Topaz Video AI 模型管理演示
//!
//! 展示如何使用模型管理器检查、下载和管理 AI 模型
use std::path::Path;
use tvai::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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!();
}
})
}

View File

@@ -0,0 +1,305 @@
//! 滤镜组合构建器
use super::{FilterCombination, QualityLevel, ProcessOptions};
use crate::config::UpscaleModel;
/// 滤镜组合构建器
pub struct FilterCombinationBuilder {
combination_type: Option<CombinationType>,
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());
}
}

View File

@@ -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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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),
),
})
}
}

View File

@@ -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<String>,
/// 是否保留音频
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<FilterCombination, TvaiError> {
// 获取视频信息
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<ProcessResult, TvaiError> {
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<TvaiProcessor, TvaiError> {
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)
}
}

View File

@@ -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<PresetType> {
vec![
PresetType::SocialMedia,
PresetType::FilmRestoration,
PresetType::Gaming,
PresetType::Surveillance,
PresetType::Animation,
PresetType::Portrait,
PresetType::Landscape,
PresetType::QuickPreview,
]
}
}
/// 自定义预设构建器
pub struct PresetBuilder {
combination: Option<FilterCombination>,
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<CustomPreset, String> {
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<dyn std::error::Error>> {
let json = serde_json::to_string_pretty(self)?;
std::fs::write(path, json)?;
Ok(())
}
/// 从文件加载预设
pub fn load_from_file(path: &std::path::Path) -> Result<Self, Box<dyn std::error::Error>> {
let json = std::fs::read_to_string(path)?;
let preset = serde_json::from_str(&json)?;
Ok(preset)
}
}
/// 预设管理器
pub struct PresetManager {
custom_presets: Vec<CustomPreset>,
}
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<usize, Box<dyn std::error::Error>> {
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()
}
}

View File

@@ -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;

View File

@@ -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};

View File

@@ -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<String>,
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<PathBuf>,
topaz_ffmpeg: PathBuf,
}
impl ModelManager {
/// 创建新的模型管理器
pub fn new(topaz_path: &Path) -> Result<Self, TvaiError> {
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<Vec<ModelInfo>, 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<Vec<String>, 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<Vec<ModelInfo>, 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<bool, TvaiError> {
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<bool, TvaiError> {
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<Vec<String>, 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<String> = 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<String> {
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<String> {
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
}
}