feat: Add comprehensive Topaz Video AI filter combinations and model management
This commit is contained in:
318
cargos/tvai/docs/FILTER_COMBINATIONS_GUIDE.md
Normal file
318
cargos/tvai/docs/FILTER_COMBINATIONS_GUIDE.md
Normal 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` 实例
|
||||
325
cargos/tvai/examples/filter_combinations_demo.rs
Normal file
325
cargos/tvai/examples/filter_combinations_demo.rs
Normal 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!();
|
||||
}
|
||||
})
|
||||
}
|
||||
218
cargos/tvai/examples/model_management_demo.rs
Normal file
218
cargos/tvai/examples/model_management_demo.rs
Normal 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!();
|
||||
}
|
||||
})
|
||||
}
|
||||
305
cargos/tvai/src/filters/builder.rs
Normal file
305
cargos/tvai/src/filters/builder.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
668
cargos/tvai/src/filters/combinations.rs
Normal file
668
cargos/tvai/src/filters/combinations.rs
Normal 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),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
282
cargos/tvai/src/filters/mod.rs
Normal file
282
cargos/tvai/src/filters/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
323
cargos/tvai/src/filters/presets.rs
Normal file
323
cargos/tvai/src/filters/presets.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
362
cargos/tvai/src/utils/model_manager.rs
Normal file
362
cargos/tvai/src/utils/model_manager.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user