582 lines
18 KiB
Rust
582 lines
18 KiB
Rust
//! Video processing functionality
|
|
|
|
pub mod upscale;
|
|
pub mod interpolation;
|
|
pub mod converter;
|
|
|
|
use std::path::Path;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::core::{TvaiError, ProcessResult, TvaiProcessor, ProgressCallback};
|
|
use crate::config::{UpscaleModel, InterpolationModel, QualityPreset};
|
|
|
|
/// 音频处理模式
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub enum AudioMode {
|
|
/// 保留原音频
|
|
Keep,
|
|
/// 移除音频
|
|
Remove,
|
|
/// 重新编码音频
|
|
Reencode { codec: String, bitrate: u32 },
|
|
}
|
|
|
|
/// Parameters for video upscaling
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VideoUpscaleParams {
|
|
// 基础参数
|
|
pub scale_factor: f32,
|
|
pub model: UpscaleModel,
|
|
pub compression: f32,
|
|
pub blend: f32,
|
|
pub quality_preset: QualityPreset,
|
|
|
|
// 高级TVAI参数
|
|
pub preblur: f32, // 预模糊 (0-100)
|
|
pub noise: f32, // 噪点减少 (0-100)
|
|
pub details: f32, // 细节恢复 (0-100)
|
|
pub halo: f32, // 光晕减少 (0-100)
|
|
pub blur: f32, // 模糊减少 (0-100)
|
|
pub estimate: u32, // 估算质量 (1-20)
|
|
pub device: i32, // 设备ID (-2=auto, -1=CPU, 0+=GPU)
|
|
pub vram: u32, // VRAM使用 (0-1)
|
|
pub instances: u32, // 实例数量 (1-4)
|
|
|
|
// 输出尺寸控制
|
|
pub target_width: Option<u32>, // 目标宽度
|
|
pub target_height: Option<u32>, // 目标高度
|
|
pub maintain_aspect: bool, // 保持宽高比
|
|
pub pad_color: String, // 填充颜色
|
|
|
|
// 编码参数
|
|
pub codec: Option<String>, // 编码器 (h264_nvenc, hevc_nvenc, libx264等)
|
|
pub profile: Option<String>, // 编码配置文件
|
|
pub pixel_format: String, // 像素格式
|
|
pub gop_size: u32, // GOP大小
|
|
pub preset: Option<String>, // 编码预设
|
|
pub tune: Option<String>, // 调优选项
|
|
pub rate_control: String, // 码率控制模式
|
|
pub quality_param: u32, // 质量参数 (CRF/QP)
|
|
pub lookahead: u32, // 前瞻帧数
|
|
pub spatial_aq: bool, // 空间自适应量化
|
|
pub aq_strength: u32, // AQ强度
|
|
pub bitrate: u32, // 目标码率 (0=VBR)
|
|
pub buffer_frames: u32, // 缓冲帧数
|
|
|
|
// 音频处理
|
|
pub audio_mode: AudioMode, // 音频处理模式
|
|
|
|
// 元数据
|
|
pub preserve_metadata: bool, // 保留元数据
|
|
pub custom_metadata: Option<String>, // 自定义元数据
|
|
}
|
|
|
|
/// Parameters for frame interpolation
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct InterpolationParams {
|
|
pub input_fps: u32,
|
|
pub multiplier: f32,
|
|
pub model: InterpolationModel,
|
|
pub target_fps: Option<u32>,
|
|
}
|
|
|
|
/// Combined parameters for video enhancement
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct VideoEnhanceParams {
|
|
pub upscale: Option<VideoUpscaleParams>,
|
|
pub interpolation: Option<InterpolationParams>,
|
|
}
|
|
|
|
impl Default for VideoUpscaleParams {
|
|
fn default() -> Self {
|
|
Self {
|
|
// 基础参数
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Iris3,
|
|
compression: 0.0,
|
|
blend: 0.0,
|
|
quality_preset: QualityPreset::HighQuality,
|
|
|
|
// 高级TVAI参数
|
|
preblur: 0.0,
|
|
noise: 0.0,
|
|
details: 0.0,
|
|
halo: 0.0,
|
|
blur: 0.0,
|
|
estimate: 8,
|
|
device: -2, // 自动选择
|
|
vram: 1,
|
|
instances: 1,
|
|
|
|
// 输出尺寸控制
|
|
target_width: None,
|
|
target_height: None,
|
|
maintain_aspect: true,
|
|
pad_color: "black".to_string(),
|
|
|
|
// 编码参数
|
|
codec: None, // 自动选择
|
|
profile: Some("high".to_string()),
|
|
pixel_format: "yuv420p".to_string(),
|
|
gop_size: 30,
|
|
preset: Some("p7".to_string()),
|
|
tune: Some("hq".to_string()),
|
|
rate_control: "constqp".to_string(),
|
|
quality_param: 18,
|
|
lookahead: 20,
|
|
spatial_aq: true,
|
|
aq_strength: 15,
|
|
bitrate: 0, // VBR
|
|
buffer_frames: 0,
|
|
|
|
// 音频处理
|
|
audio_mode: AudioMode::Keep,
|
|
|
|
// 元数据
|
|
preserve_metadata: true,
|
|
custom_metadata: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VideoUpscaleParams {
|
|
/// Create parameters optimized for old video content
|
|
pub fn for_old_video() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Thf4,
|
|
compression: 0.3,
|
|
blend: 0.2,
|
|
preblur: 0.0,
|
|
noise: 20.0,
|
|
details: 30.0,
|
|
halo: 10.0,
|
|
blur: 15.0,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create parameters optimized for game content
|
|
pub fn for_game_content() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Ghq5,
|
|
compression: 0.0,
|
|
blend: 0.0,
|
|
preblur: 0.0,
|
|
noise: 0.0,
|
|
details: 50.0,
|
|
halo: 0.0,
|
|
blur: 0.0,
|
|
quality_param: 15, // 更高质量
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create parameters optimized for animation
|
|
pub fn for_animation() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Iris3,
|
|
compression: -0.1,
|
|
blend: 0.1,
|
|
preblur: 0.0,
|
|
noise: 5.0,
|
|
details: 25.0,
|
|
halo: 5.0,
|
|
blur: 10.0,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create parameters optimized for portrait video
|
|
pub fn for_portrait() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Nyx3,
|
|
compression: -0.2,
|
|
blend: 0.1,
|
|
preblur: 0.0,
|
|
noise: 15.0,
|
|
details: 40.0,
|
|
halo: 20.0,
|
|
blur: 25.0,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create parameters for maximum quality (slow processing)
|
|
pub fn for_maximum_quality() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Ahq12,
|
|
compression: 0.0,
|
|
blend: 0.0,
|
|
preblur: 0.0,
|
|
noise: 30.0,
|
|
details: 60.0,
|
|
halo: 30.0,
|
|
blur: 40.0,
|
|
estimate: 20, // 最高估算质量
|
|
quality_param: 12, // 最高质量
|
|
lookahead: 32,
|
|
instances: 1, // 单实例确保质量
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create parameters for fast processing
|
|
pub fn for_fast_processing() -> Self {
|
|
Self {
|
|
scale_factor: 2.0,
|
|
model: UpscaleModel::Alqs2,
|
|
compression: 0.2,
|
|
blend: 0.0,
|
|
preblur: 0.0,
|
|
noise: 10.0,
|
|
details: 20.0,
|
|
halo: 5.0,
|
|
blur: 10.0,
|
|
estimate: 4, // 较低估算质量
|
|
quality_param: 22, // 较低质量但更快
|
|
preset: Some("p1".to_string()), // 最快预设
|
|
instances: 2, // 多实例加速
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InterpolationParams {
|
|
/// Create parameters for smooth slow motion
|
|
pub fn for_slow_motion(input_fps: u32, multiplier: f32) -> Self {
|
|
Self {
|
|
input_fps,
|
|
multiplier,
|
|
model: InterpolationModel::Apo8,
|
|
target_fps: None,
|
|
}
|
|
}
|
|
|
|
/// Create parameters for animation interpolation
|
|
pub fn for_animation(input_fps: u32, multiplier: f32) -> Self {
|
|
Self {
|
|
input_fps,
|
|
multiplier,
|
|
model: InterpolationModel::Chr2,
|
|
target_fps: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Enhanced video processing with both upscaling and interpolation
|
|
impl TvaiProcessor {
|
|
/// Enhance video with combined upscaling and interpolation
|
|
pub async fn enhance_video(
|
|
&mut self,
|
|
input_path: &Path,
|
|
output_path: &Path,
|
|
params: VideoEnhanceParams,
|
|
progress_callback: Option<&ProgressCallback>,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
let start_time = std::time::Instant::now();
|
|
|
|
// Validate inputs
|
|
self.validate_input_file(input_path)?;
|
|
self.validate_output_path(output_path)?;
|
|
|
|
let operation_id = self.generate_operation_id();
|
|
let mut current_input = input_path.to_path_buf();
|
|
let mut intermediate_files = Vec::new();
|
|
|
|
if let Some(callback) = progress_callback {
|
|
callback(0.0);
|
|
}
|
|
|
|
// Step 1: Apply upscaling if requested
|
|
if let Some(upscale_params) = ¶ms.upscale {
|
|
let intermediate_output = self.create_temp_path(&operation_id, "upscaled.mp4");
|
|
intermediate_files.push(intermediate_output.clone());
|
|
|
|
// For now, pass None for progress callback to avoid lifetime issues
|
|
// TODO: Implement proper progress callback forwarding
|
|
self.upscale_video(
|
|
¤t_input,
|
|
&intermediate_output,
|
|
upscale_params.clone(),
|
|
None,
|
|
).await?;
|
|
|
|
if let Some(callback) = progress_callback {
|
|
callback(0.5);
|
|
}
|
|
|
|
current_input = intermediate_output;
|
|
}
|
|
|
|
if let Some(callback) = progress_callback {
|
|
callback(0.5);
|
|
}
|
|
|
|
// Step 2: Apply interpolation if requested
|
|
if let Some(interpolation_params) = ¶ms.interpolation {
|
|
// For now, pass None for progress callback to avoid lifetime issues
|
|
// TODO: Implement proper progress callback forwarding
|
|
let result = self.interpolate_video(
|
|
¤t_input,
|
|
output_path,
|
|
interpolation_params.clone(),
|
|
None,
|
|
).await?;
|
|
|
|
// Clean up intermediate files
|
|
self.cleanup_temp_files(&operation_id)?;
|
|
|
|
return Ok(result);
|
|
}
|
|
|
|
// If only upscaling was requested, move the result to final output
|
|
if params.upscale.is_some() && params.interpolation.is_none() {
|
|
std::fs::rename(¤t_input, output_path)?;
|
|
} else {
|
|
return Err(TvaiError::InvalidParameter(
|
|
"At least one enhancement (upscale or interpolation) must be specified".to_string()
|
|
));
|
|
}
|
|
|
|
let processing_time = start_time.elapsed();
|
|
|
|
// Create combined metadata
|
|
let mut metadata = self.create_metadata(
|
|
operation_id,
|
|
input_path,
|
|
format!("enhance: upscale={}, interpolation={}",
|
|
params.upscale.is_some(),
|
|
params.interpolation.is_some()
|
|
),
|
|
);
|
|
metadata.ffmpeg_version = self.get_ffmpeg_version(true).await.ok();
|
|
|
|
if let Some(callback) = progress_callback {
|
|
callback(1.0);
|
|
}
|
|
|
|
Ok(ProcessResult {
|
|
output_path: output_path.to_path_buf(),
|
|
processing_time,
|
|
metadata,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Quick video upscaling function
|
|
pub async fn quick_upscale_video(
|
|
input: &Path,
|
|
output: &Path,
|
|
scale: f32,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
quick_upscale_video_with_model(input, output, scale, None).await
|
|
}
|
|
|
|
/// Quick video upscaling function with model selection
|
|
pub async fn quick_upscale_video_with_model(
|
|
input: &Path,
|
|
output: &Path,
|
|
scale: f32,
|
|
model: Option<UpscaleModel>,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
// Detect Topaz installation
|
|
let topaz_path = crate::utils::detect_topaz_installation()
|
|
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
|
|
|
// Create default configuration
|
|
let config = crate::core::TvaiConfig::builder()
|
|
.topaz_path(topaz_path)
|
|
.use_gpu(true)
|
|
.build()?;
|
|
|
|
// Create processor
|
|
let mut processor = TvaiProcessor::new(config)?;
|
|
|
|
// Create upscaling parameters with specified or default model
|
|
let params = VideoUpscaleParams {
|
|
scale_factor: scale,
|
|
model: model.unwrap_or(crate::config::UpscaleModel::Iris3), // Use specified model or default to Iris3
|
|
compression: 0.0,
|
|
blend: 0.0,
|
|
quality_preset: crate::config::QualityPreset::HighQuality,
|
|
..Default::default()
|
|
};
|
|
|
|
// Perform upscaling
|
|
processor.upscale_video(input, output, params, None).await
|
|
}
|
|
|
|
/// Automatic video enhancement
|
|
pub async fn auto_enhance_video(
|
|
input: &Path,
|
|
output: &Path,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
// Detect Topaz installation
|
|
let topaz_path = crate::utils::detect_topaz_installation()
|
|
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
|
|
|
// Create default configuration
|
|
let config = crate::core::TvaiConfig::builder()
|
|
.topaz_path(topaz_path)
|
|
.use_gpu(true)
|
|
.build()?;
|
|
|
|
// Create processor
|
|
let mut processor = TvaiProcessor::new(config)?;
|
|
|
|
// Get video info to determine best enhancement strategy
|
|
let video_info = crate::utils::get_video_info(input).await?;
|
|
|
|
// Auto-determine enhancement parameters based on video characteristics
|
|
let mut enhance_params = VideoEnhanceParams {
|
|
upscale: None,
|
|
interpolation: None,
|
|
};
|
|
|
|
// Apply upscaling if resolution is low
|
|
if video_info.width < 1920 || video_info.height < 1080 {
|
|
let scale_factor = if video_info.width <= 720 { 2.0 } else { 1.5 };
|
|
enhance_params.upscale = Some(VideoUpscaleParams {
|
|
scale_factor,
|
|
model: crate::config::UpscaleModel::Iris3,
|
|
compression: 0.0,
|
|
blend: 0.1,
|
|
quality_preset: crate::config::QualityPreset::HighQuality,
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
// Apply interpolation if frame rate is low
|
|
if video_info.fps < 30.0 {
|
|
let multiplier = if video_info.fps <= 15.0 { 2.0 } else { 1.5 };
|
|
enhance_params.interpolation = Some(InterpolationParams {
|
|
input_fps: video_info.fps as u32,
|
|
multiplier,
|
|
model: crate::config::InterpolationModel::Apo8,
|
|
target_fps: None,
|
|
});
|
|
}
|
|
|
|
// If no enhancement is needed, just copy the file
|
|
if enhance_params.upscale.is_none() && enhance_params.interpolation.is_none() {
|
|
std::fs::copy(input, output)?;
|
|
return Ok(ProcessResult {
|
|
output_path: output.to_path_buf(),
|
|
processing_time: std::time::Duration::from_millis(0),
|
|
metadata: processor.create_metadata(
|
|
processor.generate_operation_id(),
|
|
input,
|
|
"auto_enhance: no enhancement needed".to_string(),
|
|
),
|
|
});
|
|
}
|
|
|
|
// Perform enhancement
|
|
processor.enhance_video(input, output, enhance_params, None).await
|
|
}
|
|
|
|
/// Quick video interpolation function
|
|
pub async fn quick_interpolate_video(
|
|
input: &Path,
|
|
output: &Path,
|
|
multiplier: f32,
|
|
input_fps: u32,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
// Detect Topaz installation
|
|
let topaz_path = crate::utils::detect_topaz_installation()
|
|
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
|
|
|
// Create default configuration
|
|
let config = crate::core::TvaiConfig::builder()
|
|
.topaz_path(topaz_path)
|
|
.use_gpu(true)
|
|
.build()?;
|
|
|
|
// Create processor
|
|
let mut processor = TvaiProcessor::new(config)?;
|
|
|
|
// Create default interpolation parameters
|
|
let params = InterpolationParams {
|
|
input_fps,
|
|
multiplier,
|
|
model: crate::config::InterpolationModel::Apo8, // Best general purpose interpolation model
|
|
target_fps: None, // Auto-calculate based on multiplier
|
|
};
|
|
|
|
// Perform interpolation
|
|
processor.interpolate_video(input, output, params, None).await
|
|
}
|
|
|
|
/// Process video using a Topaz template
|
|
pub async fn process_with_template(
|
|
input: &Path,
|
|
output: &Path,
|
|
template_name: &str,
|
|
) -> Result<ProcessResult, TvaiError> {
|
|
// Get template manager
|
|
let manager = crate::config::global_topaz_templates().lock()
|
|
.map_err(|_| TvaiError::ConfigurationError("Failed to access template manager".to_string()))?;
|
|
|
|
// Apply template
|
|
let applied = manager.apply_template(template_name)?;
|
|
drop(manager); // Release lock early
|
|
|
|
// Detect Topaz installation
|
|
let topaz_path = crate::utils::detect_topaz_installation()
|
|
.ok_or_else(|| TvaiError::TopazNotFound("Topaz Video AI not found".to_string()))?;
|
|
|
|
// Create default configuration
|
|
let config = crate::core::TvaiConfig::builder()
|
|
.topaz_path(topaz_path)
|
|
.use_gpu(true)
|
|
.build()?;
|
|
|
|
// Create processor
|
|
let mut processor = TvaiProcessor::new(config)?;
|
|
|
|
// Process video with template parameters
|
|
processor.enhance_video(input, output, applied.enhance_params, None).await
|
|
}
|
|
|
|
/// List all available templates
|
|
pub fn list_available_templates() -> Vec<String> {
|
|
let manager = crate::config::global_topaz_templates().lock()
|
|
.unwrap_or_else(|_| panic!("Failed to access template manager"));
|
|
manager.list_templates()
|
|
}
|
|
|
|
/// Get template information
|
|
pub fn get_template_info(template_name: &str) -> Option<(String, String)> {
|
|
let manager = crate::config::global_topaz_templates().lock()
|
|
.unwrap_or_else(|_| panic!("Failed to access template manager"));
|
|
|
|
manager.get_template(template_name)
|
|
.map(|template| (template.name.clone(), template.description.clone()))
|
|
}
|
|
|
|
/// Quick functions for common templates
|
|
pub async fn upscale_to_4k(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
|
process_with_template(input, output, "upscale_to_4k").await
|
|
}
|
|
|
|
pub async fn convert_to_60fps(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
|
process_with_template(input, output, "convert_to_60fps").await
|
|
}
|
|
|
|
pub async fn remove_noise(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
|
process_with_template(input, output, "remove_noise").await
|
|
}
|
|
|
|
pub async fn slow_motion_4x(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
|
process_with_template(input, output, "4x_slow_motion").await
|
|
}
|
|
|
|
pub async fn auto_crop_stabilization(input: &Path, output: &Path) -> Result<ProcessResult, TvaiError> {
|
|
process_with_template(input, output, "auto_crop_stabilization").await
|
|
}
|