Files
mixvideo-v2/cargos/tvai-v2/src/ffmpeg.rs
imeepos 41c54098e8 Fix Topaz Video AI FFmpeg filter parameter errors
- Fix tvai_stb filter parameter names:
  method -> full, smooth -> smoothness
- Fix tvai_fi filter fps parameter:
  Skip fps=0 to avoid parse errors
- Resolve FFmpeg execution errors for stabilization and frame interpolation
2025-08-18 13:50:45 +08:00

1444 lines
52 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::{Template, TvaiError, TvaiResult};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// 音频编解码器配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioCodec {
pub name: String,
#[serde(rename = "ffmpegOpts")]
pub ffmpeg_opts: String,
pub ext: Vec<String>,
pub bitrate: Option<BitrateConfig>,
}
/// 比特率配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitrateConfig {
pub min: u32,
#[serde(rename = "minRec")]
pub min_rec: u32,
pub max: u32,
pub default: u32,
pub suggested: Vec<u32>,
#[serde(rename = "ffmpegOpt")]
pub ffmpeg_opt: String,
}
/// 视频编码器配置
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoEncoder {
pub id: String,
pub encoder: String,
pub profile: Option<String>,
#[serde(rename = "allowsAlpha")]
pub allows_alpha: u8,
#[serde(rename = "ffmpegOpts")]
pub ffmpeg_opts: String,
pub ext: Vec<String>,
pub os: Option<String>,
#[serde(rename = "minSize")]
pub min_size: Option<[u32; 2]>,
#[serde(rename = "maxSize")]
pub max_size: Option<[u32; 2]>,
#[serde(rename = "maxBitDepth")]
pub max_bit_depth: Option<u8>,
#[serde(rename = "bitrateOpts")]
pub bitrate_opts: Option<BitrateOptions>,
#[serde(rename = "cqpValues")]
pub cqp_values: Option<HashMap<String, Vec<u32>>>,
pub gpu: Option<String>,
#[serde(rename = "autoBitsPerPixel")]
pub auto_bits_per_pixel: Option<String>,
#[serde(rename = "maxBitRate")]
pub max_bit_rate: Option<u32>,
pub device: Option<String>,
pub compute: Option<u32>,
#[serde(rename = "isImage")]
pub is_image: Option<bool>,
}
/// 比特率选项
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BitrateOptions {
pub cbr: Option<String>,
pub vbr: Option<String>,
}
/// 模型推荐规则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelRecommendationRule {
pub id: String,
pub property: String,
#[serde(rename = "type")]
pub rule_type: String,
pub conditions: Vec<RecommendationCondition>,
}
/// 推荐条件
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecommendationCondition {
pub min: Option<u32>,
pub max: Option<u32>,
#[serde(rename = "recommendedModelValues")]
pub recommended_model_values: Vec<String>,
}
/// Topaz Video AI 模板的 FFmpeg 命令生成器
#[derive(Debug, Default)]
pub struct FfmpegCommandGenerator {
/// 从模板模型到 FFmpeg 滤镜模型的映射
model_mappings: HashMap<String, String>,
/// 音频编解码器配置
audio_codecs: Vec<AudioCodec>,
/// 视频编码器配置
video_encoders: Vec<VideoEncoder>,
/// 模型推荐规则
model_recommendation_rules: Vec<ModelRecommendationRule>,
}
impl FfmpegCommandGenerator {
/// 创建新的 FFmpeg 命令生成器
pub fn new() -> Self {
let mut generator = Self {
model_mappings: HashMap::new(),
audio_codecs: Vec::new(),
video_encoders: Vec::new(),
model_recommendation_rules: Vec::new(),
};
// 初始化默认模型映射
generator.init_model_mappings();
// 加载配置文件
if let Err(e) = generator.load_configurations() {
eprintln!("警告:加载配置文件失败: {}", e);
}
generator
}
/// 从内置配置加载编解码器和模型推荐规则
fn load_configurations(&mut self) -> TvaiResult<()> {
// 内置音频编解码器配置
const AUDIO_CODECS_JSON: &str = r#"[
{
"name": "AAC",
"ffmpegOpts": "aac -ac 2",
"ext": [
"mp4",
"mov",
"mkv",
"avi"
],
"bitrate": {
"min": 32,
"minRec": 128,
"max": 320,
"default": 320,
"suggested": [
128,
160,
192,
256,
320
],
"ffmpegOpt": "-b:a <BITRATE>k"
}
},
{
"name": "AC3",
"ffmpegOpts": "ac3",
"ext": [
"mp4",
"mov",
"mkv",
"avi"
],
"bitrate": {
"min": 160,
"minRec": 160,
"max": 640,
"default": 448,
"suggested": [
160,
192,
256,
320,
448,
640
],
"ffmpegOpt": "-b:a <BITRATE>k"
}
},
{
"name": "PCM",
"ffmpegOpts": "pcm_s24le",
"ext": [
"mov",
"mkv",
"avi"
]
},
{
"name": "Vorbis",
"ffmpegOpts": "vorbis -ac 2",
"ext": [
"webm"
]
}
]"#;
// 解析音频编解码器配置
self.audio_codecs = serde_json::from_str(AUDIO_CODECS_JSON)
.map_err(|e| TvaiError::ConfigError(format!("解析内置音频编解码器配置失败: {}", e)))?;
// 内置视频编码器配置(部分常用编码器)
const VIDEO_ENCODERS_JSON: &str = r#"[
{
"id": "h264-high-win-nvidia",
"encoder": "H264",
"profile": "High",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v h264_nvenc -profile:v high -pix_fmt yuv420p -g 30",
"bitrateOpts": {
"cbr": "-rc cbr -b:v <CONST_BITRATE_VALUE> -preset p6 ",
"vbr": "-preset p7 -tune hq -rc constqp -qp <QP_VALUE> -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0"
},
"cqpValues": {
"High": [ 18 ],
"Mid": [ 25 ],
"Low": [ 28 ]
},
"ext": [
"mov",
"mkv",
"mp4"
],
"maxBitRate": 2000,
"os": "windows|linux",
"device": "nvidia|tesla",
"minSize": [145,145],
"maxSize": [4096,4096],
"maxBitDepth": 8
},
{
"id": "h265-main-win-nvidia",
"encoder": "H265",
"profile": "Main",
"allowsAlpha": 0,
"gpu": "nvidia",
"ffmpegOpts": "-c:v hevc_nvenc -profile:v main -pix_fmt yuv420p -b_ref_mode disabled -tag:v hvc1 -g 30",
"bitrateOpts": {
"cbr": "-rc cbr -b:v <CONST_BITRATE_VALUE> -preset p6",
"vbr": "-preset p7 -tune hq -rc constqp -qp <QP_VALUE> -rc-lookahead 20 -spatial_aq 1 -aq-strength 15 -b:v 0"
},
"cqpValues": {
"High": [ 17 ],
"Mid": [ 25 ],
"Low": [ 28 ]
},
"ext": [
"mov",
"mkv",
"mp4"
],
"maxBitRate": 2000,
"os": "windows|linux",
"device": "nvidia|tesla",
"minSize": [129,129],
"maxSize": [8192,4320],
"maxBitDepth": 12
},
{
"id": "h264-high-osx",
"encoder": "H264",
"profile": "High",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v h264_videotoolbox -profile:v high -pix_fmt yuv420p -allow_sw 1 -g 30",
"ext": [
"mov",
"mkv",
"mp4"
],
"gpu": "appleIntel",
"autoBitsPerPixel": "1.0",
"maxBitRate": 2000,
"os": "osx",
"minSize": [2,2],
"maxSize": [7680,4320],
"doNotScaleFullColorRange": "always"
},
{
"id": "h265-main-osx",
"encoder": "H265",
"profile": "Main",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v hevc_videotoolbox -profile:v main -tag:v hvc1 -pix_fmt yuv420p -allow_sw 1 -g 30",
"ext": [
"mov",
"mkv",
"mp4"
],
"gpu": "appleIntel",
"autoBitsPerPixel": "0.8",
"maxBitRate": 2000,
"os": "osx",
"minSize": [2,2],
"maxSize": [8192,4320],
"doNotScaleFullColorRange": "always"
},
{
"id": "h264-high-win-amd",
"encoder": "H264",
"profile": "High",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v h264_amf -profile:v high -pix_fmt yuv420p -g 30",
"gpu": "amd",
"bitrateOpts": {
"cbr": "-b:v <CONST_BITRATE_VALUE>",
"vbr": "-b:v 0 -quality 0 -rc cqp -qp_i <QPI_VALUE> -qp_p <QPP_VALUE> -qp_b <QPB_VALUE>"
},
"cqpValues": {
"High": [ 19, 20, 20 ],
"Mid": [ 20, 23, 23 ],
"Low": [ 28, 30, 30 ]
},
"ext": [
"mov",
"mkv",
"mp4"
],
"maxBitRate": 2000,
"os": "windows",
"minSize": [2,2],
"maxSize": [4096,4096],
"device": "amd|radeon"
},
{
"id": "h265-main-win-amd",
"encoder": "H265",
"profile": "Main",
"allowsAlpha": 0,
"gpu": "amd",
"ffmpegOpts": "-c:v hevc_amf -profile:v main -profile_tier main -tag:v hvc1 -pix_fmt yuv420p -g 30",
"bitrateOpts": {
"cbr": "-b:v <CONST_BITRATE_VALUE> ",
"vbr": "-b:v 0 -quality 0 -rc cqp -qp_i <QPI_VALUE> -qp_p <QPP_VALUE>"
},
"cqpValues": {
"High": [17, 19 ],
"Mid": [20, 23 ],
"Low": [28, 30 ]
},
"ext": [
"mov",
"mkv",
"mp4"
],
"maxBitRate": 2000,
"os": "windows",
"minSize": [2,2],
"maxSize": [8192,4320],
"device": "amd|radeon"
},
{
"id": "libx264-default",
"encoder": "H264",
"profile": "High",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v libx264 -profile:v high -pix_fmt yuv420p",
"ext": [
"mov",
"mkv",
"mp4",
"avi"
],
"maxBitRate": 2000,
"os": "windows|linux|osx",
"minSize": [1,1],
"maxSize": [16384,16384]
},
{
"id": "libx265-default",
"encoder": "H265",
"profile": "Main",
"allowsAlpha": 0,
"ffmpegOpts": "-c:v libx265 -profile:v main -pix_fmt yuv420p",
"ext": [
"mov",
"mkv",
"mp4"
],
"maxBitRate": 2000,
"os": "windows|linux|osx",
"minSize": [1,1],
"maxSize": [16384,16384]
}
]"#;
// 解析视频编码器配置
self.video_encoders = serde_json::from_str(VIDEO_ENCODERS_JSON)
.map_err(|e| TvaiError::ConfigError(format!("解析内置视频编码器配置失败: {}", e)))?;
// 内置模型推荐规则
const MODEL_RECOMMENDATION_RULES_JSON: &str = r#"[
{
"id": "target-resolution",
"property": "height",
"type":"range",
"conditions": [
{
"max": 480,
"min": 1,
"recommendedModelValues": ["iris", "artemis"]
},
{
"min": 481,
"max": 720,
"recommendedModelValues": ["proteus", "rhea"]
},
{
"min": 721,
"max": 1080,
"recommendedModelValues": ["proteus", "nyx", "rhea"]
},
{
"min": 1081,
"recommendedModelValues": ["nyx", "theia", "proteus"]
}
]
}
]"#;
// 解析模型推荐规则
self.model_recommendation_rules = serde_json::from_str(MODEL_RECOMMENDATION_RULES_JSON)
.map_err(|e| TvaiError::ConfigError(format!("解析内置模型推荐规则失败: {}", e)))?;
Ok(())
}
/// 初始化从模板模型到 FFmpeg 滤镜模型的映射
fn init_model_mappings(&mut self) {
// 增强模型 - Proteus 系列(通用)
self.model_mappings.insert("prob-3".to_string(), "ahq-12".to_string());
self.model_mappings.insert("prob-4".to_string(), "ahq-12".to_string());
// 增强模型 - 专用系列
self.model_mappings.insert("nyx-3".to_string(), "nyx-3".to_string()); // Iris 系列(低光/噪声)
self.model_mappings.insert("hyp-1".to_string(), "hyp-1".to_string()); // Hyperion HDR 模型
// Artemis 系列(动画/卡通)
self.model_mappings.insert("art-1".to_string(), "art-1".to_string());
self.model_mappings.insert("art-2".to_string(), "art-2".to_string());
self.model_mappings.insert("alq-13".to_string(), "ahq-12".to_string()); // Artemis 基准模型
// Gaia 系列(自然场景)
self.model_mappings.insert("gai-1".to_string(), "gai-1".to_string());
self.model_mappings.insert("gai-2".to_string(), "gai-2".to_string());
self.model_mappings.insert("ghq-5".to_string(), "ahq-12".to_string()); // Gaia 基准模型
// Theia 系列(细节恢复)
self.model_mappings.insert("the-1".to_string(), "the-1".to_string());
self.model_mappings.insert("the-2".to_string(), "the-2".to_string());
// Iris 系列(低光/噪声)
self.model_mappings.insert("iris-1".to_string(), "nyx-3".to_string());
self.model_mappings.insert("iris-2".to_string(), "nyx-3".to_string());
self.model_mappings.insert("iris-3".to_string(), "nyx-3".to_string()); // Iris 基准模型
// Nyx 系列(降噪)
self.model_mappings.insert("nyx-1".to_string(), "nyx-3".to_string());
self.model_mappings.insert("nyx-2".to_string(), "nyx-3".to_string()); // Nyx 基准模型
self.model_mappings.insert("nxf-1".to_string(), "nyx-3".to_string()); // Nyx Fast 基准模型
// 专用模型
self.model_mappings.insert("rhea-1".to_string(), "ahq-12".to_string()); // Rhea 基准模型
self.model_mappings.insert("rxl-1".to_string(), "ahq-12".to_string()); // RXL 基准模型
// 帧插值模型
self.model_mappings.insert("apo-8".to_string(), "chr-2".to_string()); // Apollo
self.model_mappings.insert("apf-1".to_string(), "chr-2".to_string()); // Apollo Fast
self.model_mappings.insert("chr-2".to_string(), "chr-2".to_string()); // Chronos
self.model_mappings.insert("chf-3".to_string(), "chr-2".to_string()); // Chronos Fast
self.model_mappings.insert("aion-1".to_string(), "chr-2".to_string()); // Aion (16X)
// 运动模糊模型
self.model_mappings.insert("thm-2".to_string(), "thm-2".to_string());
// 稳定化模型(映射到 ref-2 用于 tvai_stb
// 注意:模板稳定化使用与 tvai_stb 滤镜不同的方法
}
/// 添加自定义模型映射
pub fn add_model_mapping(&mut self, template_model: String, ffmpeg_model: String) {
self.model_mappings.insert(template_model, ffmpeg_model);
}
/// 从模板生成 FFmpeg 命令
pub fn generate(&self, template: &Template, input_file: &str, output_file: &str) -> TvaiResult<String> {
let mut filters = Vec::new();
let settings = &template.settings;
// 生成增强滤镜(通常是链中的第一个)
if settings.enhance.active {
let up_filter = self.generate_enhancement_filter(&settings.enhance)?;
filters.push(up_filter);
}
// 生成帧插值滤镜(在增强之后)
if settings.slow_motion.active {
let fi_filter = self.generate_frame_interpolation_filter(&settings.slow_motion, &settings.output)?;
filters.push(fi_filter);
}
// 生成稳定化滤镜(在插值之后)
if settings.stabilize.active {
let stb_filter = self.generate_stabilization_filter(&settings.stabilize)?;
filters.push(stb_filter);
}
// 生成运动模糊滤镜(如果激活)
if settings.motion_blur.active {
let mb_filter = self.generate_motion_blur_filter(&settings.motion_blur)?;
filters.push(mb_filter);
}
// 生成颗粒滤镜(通常是链中的最后一个)
if settings.grain.active {
let grain_filter = self.generate_grain_filter(&settings.grain)?;
filters.push(grain_filter);
}
// 如果存在滤镜管理器,处理第二次增强
if let Some(ref filter_manager) = settings.filter_manager {
if filter_manager.second_enhancement_enabled && settings.enhance.active {
let second_enhance_filter = self.generate_second_enhancement_filter(&settings.enhance, filter_manager)?;
filters.push(second_enhance_filter);
}
}
// 构建完整的 FFmpeg 命令
let mut command = format!("ffmpeg -i \"{}\"", input_file);
if !filters.is_empty() {
command.push_str(" -vf \"");
command.push_str(&filters.join(","));
command.push('"');
}
// 添加输出设置
command.push_str(&self.generate_output_settings(&settings.output)?);
command.push_str(&format!(" \"{}\"", output_file));
Ok(command)
}
/// 生成稳定化滤镜 (tvai_stb)
fn generate_stabilization_filter(&self, settings: &crate::StabilizeSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// 使用默认的稳定化模型
params.push("model=ref-2".to_string());
// 映射稳定化方法0: 自动裁剪, 1: 全帧稳定化)
let full_frame = if settings.method == 0 { 0 } else { 1 };
params.push(format!("full={}", full_frame));
// 映射平滑度(模板中为 0-100转换为 0-16 范围)
let smoothness = (settings.smooth as f64 / 100.0 * 16.0).min(16.0);
params.push(format!("smoothness={:.1}", smoothness));
// 滚动快门校正
if settings.rsc {
params.push("roll=1".to_string());
}
// 减少运动抖动
if settings.reduce_motion {
let reduce_level = settings.reduce_motion_iteration.min(5);
params.push(format!("reduce={}", reduce_level));
}
// 调试信息
println!("生成 tvai_stb 滤镜参数: {:?}", params);
println!("稳定化设置: smoothness={:.1}, full={}, roll={}, reduce={}",
smoothness, full_frame, settings.rsc,
if settings.reduce_motion { settings.reduce_motion_iteration } else { 0 });
Ok(format!("tvai_stb={}", params.join(":")))
}
/// 生成增强滤镜 (tvai_up)
fn generate_enhancement_filter(&self, settings: &crate::EnhanceSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// 映射模型
let model = self.model_mappings.get(&settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("未知的增强模型: {}", settings.model)))?;
params.push(format!("model={}", model));
// 添加缩放参数(默认不缩放,除非指定)
params.push("scale=0".to_string());
// 将增强参数映射到 tvai_up 参数(使用标准化值 0.0-1.0
if settings.compress != 0 {
let compression = settings.compress as f64 / 100.0;
params.push(format!("compression={:.2}", compression));
}
if settings.detail != 0 {
let details = settings.detail as f64 / 100.0;
params.push(format!("details={:.2}", details));
}
if settings.denoise != 0 {
let noise = settings.denoise as f64 / 100.0;
params.push(format!("noise={:.2}", noise));
}
if settings.dehalo != 0 {
let halo = settings.dehalo as f64 / 100.0;
params.push(format!("halo={:.2}", halo));
}
if settings.deblur != 0 {
let blur = settings.deblur as f64 / 100.0;
params.push(format!("blur={:.2}", blur));
}
if settings.sharpen != 0 {
let preblur = -(settings.sharpen as f64 / 100.0);
params.push(format!("preblur={:.2}", preblur));
}
// 如果不是逐行扫描,添加视频类型参数
if settings.video_type != 1 {
match settings.video_type {
0 => params.push("interlaced=1".to_string()),
2 => params.push("telecine=1".to_string()),
_ => {}
}
}
// 如果指定了场序,添加场序参数
if settings.field_order != 0 {
match settings.field_order {
1 => params.push("field_order=tff".to_string()),
2 => params.push("field_order=bff".to_string()),
_ => {}
}
}
// 如果指定了自动增强级别,添加参数
if settings.auto != 0 {
let auto_level = settings.auto as f64 / 100.0;
params.push(format!("auto={:.2}", auto_level));
}
// 如果指定了添加噪声参数,添加参数
if settings.add_noise != 0 {
let add_noise = settings.add_noise as f64 / 100.0;
params.push(format!("add_noise={:.2}", add_noise));
}
// 如果不是默认值,添加恢复原始细节参数
if settings.recover_original_detail_value != 20 {
let recover = settings.recover_original_detail_value as f64 / 100.0;
params.push(format!("recover_detail={:.2}", recover));
}
// 基于 AI 引擎标志添加模型特定优化
if settings.is_artemis {
// Artemis 动画/卡通内容优化
params.push("content_type=animation".to_string());
params.push("edge_enhance=1".to_string());
} else if settings.is_gaia {
// Gaia 自然场景优化
params.push("content_type=natural".to_string());
params.push("texture_enhance=1".to_string());
} else if settings.is_theia {
// Theia 细节恢复优化
params.push("detail_mode=high".to_string());
params.push("sharpening_boost=1".to_string());
} else if settings.is_iris {
// Iris 低光/噪声优化
params.push("noise_profile=aggressive".to_string());
params.push("low_light_boost=1".to_string());
} else if settings.is_proteus {
// Proteus 是默认的通用模型
params.push("content_type=general".to_string());
}
// 如果指定了焦点修复级别,添加参数
if let Some(ref focus_level) = settings.focus_fix_level {
if focus_level != "Off" {
let focus_value = match focus_level.as_str() {
"Low" => 0.25,
"Medium" => 0.5,
"High" => 0.75,
"Maximum" => 1.0,
_ => 0.0,
};
if focus_value > 0.0 {
params.push(format!("focus_fix={:.2}", focus_value));
}
}
}
Ok(format!("tvai_up={}", params.join(":")))
}
/// 生成帧插值滤镜 (tvai_fi)
fn generate_frame_interpolation_filter(&self, slowmo_settings: &crate::SlowMotionSettings, output_settings: &crate::OutputSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// 映射模型
let model = self.model_mappings.get(&slowmo_settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("未知的帧插值模型: {}", slowmo_settings.model)))?;
params.push(format!("model={}", model));
// 设置慢动作因子
if slowmo_settings.factor != 1.0 {
params.push(format!("slowmo={}", slowmo_settings.factor));
}
// 设置输出帧率video_rate 格式)
if output_settings.out_fps > 0.0 {
// 将浮点数转换为分数格式,例如 60.0 -> "60/1"
if output_settings.out_fps.fract() == 0.0 {
params.push(format!("fps={}/1", output_settings.out_fps as i32));
} else {
// 对于非整数帧率,使用更精确的分数表示
params.push(format!("fps={:.3}", output_settings.out_fps));
}
}
// 注意:当 out_fps 为 0 时,不添加 fps 参数,让 tvai_fi 使用默认的 "0" 值
// 重复帧阈值rdt 参数)
if !slowmo_settings.duplicate {
params.push("rdt=-0.01".to_string()); // 禁用重复帧移除
} else if slowmo_settings.duplicate_threshold != 10 { // 仅在非默认值时添加
let threshold = slowmo_settings.duplicate_threshold as f64 / 1000.0;
params.push(format!("rdt={:.3}", threshold));
}
// 调试信息
println!("生成 tvai_fi 滤镜参数: {:?}", params);
println!("输出 FPS: {}, 慢动作因子: {}", output_settings.out_fps, slowmo_settings.factor);
Ok(format!("tvai_fi={}", params.join(":")))
}
/// 生成运动模糊滤镜 (tvai_mb)
fn generate_motion_blur_filter(&self, settings: &crate::MotionBlurSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// 映射模型
let model = self.model_mappings.get(&settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("未知的运动模糊模型: {}", settings.model)))?;
params.push(format!("model={}", model));
Ok(format!("tvai_mb={}", params.join(":")))
}
/// 生成颗粒滤镜 (tvai_grain 或噪声近似)
fn generate_grain_filter(&self, settings: &crate::GrainSettings) -> TvaiResult<String> {
// 尝试使用 tvai_grain如果不可用则回退到噪声滤镜
let mut params = Vec::new();
// 颗粒量0-100 比例)
let amount = settings.grain as f64 / 100.0;
params.push(format!("amount={:.2}", amount));
// 颗粒大小1-10 比例)
params.push(format!("size={}", settings.grain_size));
// 使用 tvai_grain 滤镜
Ok(format!("tvai_grain={}", params.join(":")))
}
/// 生成第二次增强滤镜
fn generate_second_enhancement_filter(&self, enhance_settings: &crate::EnhanceSettings, filter_manager: &crate::FilterManagerSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// 使用与主要增强相同的模型
let model = self.model_mappings.get(&enhance_settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("未知的增强模型: {}", enhance_settings.model)))?;
params.push(format!("model={}", model));
// 设置中间分辨率缩放
let intermediate_scale = match filter_manager.second_enhancement_intermediate_resolution {
0 => 0.5, // 半分辨率
1 => 0.75, // 3/4 分辨率
2 => 1.0, // 相同分辨率
3 => 1.5, // 1.5x 分辨率
4 => 2.0, // 2x 分辨率
_ => 1.0, // 默认为相同分辨率
};
params.push(format!("scale={}", intermediate_scale));
// 为第二次处理应用较轻的增强参数
if enhance_settings.denoise != 0 {
let noise = (enhance_settings.denoise as f64 / 100.0) * 0.5; // 减半
params.push(format!("noise={:.2}", noise));
}
if enhance_settings.detail != 0 {
let details = (enhance_settings.detail as f64 / 100.0) * 0.5; // 减半
params.push(format!("details={:.2}", details));
}
Ok(format!("tvai_up={}", params.join(":")))
}
/// 根据编解码器名称获取音频编解码器配置
pub fn get_audio_codec(&self, name: &str) -> Option<&AudioCodec> {
self.audio_codecs.iter().find(|codec| codec.name.eq_ignore_ascii_case(name))
}
/// 根据编码器ID获取视频编码器配置
pub fn get_video_encoder(&self, id: &str) -> Option<&VideoEncoder> {
self.video_encoders.iter().find(|encoder| encoder.id == id)
}
/// 根据编码器名称获取视频编码器配置
pub fn get_video_encoder_by_name(&self, name: &str) -> Option<&VideoEncoder> {
self.video_encoders.iter().find(|encoder| encoder.encoder.eq_ignore_ascii_case(name))
}
/// 根据分辨率推荐模型
pub fn recommend_model_for_resolution(&self, height: u32) -> Vec<String> {
for rule in &self.model_recommendation_rules {
if rule.id == "target-resolution" && rule.property == "height" {
for condition in &rule.conditions {
let min = condition.min.unwrap_or(0);
let max = condition.max.unwrap_or(u32::MAX);
if height >= min && height <= max {
return condition.recommended_model_values.clone();
}
}
}
}
vec!["prob-4".to_string()] // 默认推荐 Proteus
}
/// 使用配置文件生成音频编解码器参数
pub fn generate_audio_codec_params(&self, codec_name: &str, bitrate: Option<u32>) -> String {
if let Some(codec) = self.get_audio_codec(codec_name) {
let mut params = format!("-c:a {}", codec.ffmpeg_opts);
if let (Some(bitrate_config), Some(bitrate_value)) = (&codec.bitrate, bitrate) {
let bitrate_param = bitrate_config.ffmpeg_opt.replace("<BITRATE>", &bitrate_value.to_string());
params.push_str(&format!(" {}", bitrate_param));
}
params
} else {
// 回退到默认设置
if let Some(bitrate) = bitrate {
format!("-c:a {} -b:a {}k", codec_name, bitrate)
} else {
format!("-c:a {}", codec_name)
}
}
}
/// 使用配置文件生成视频编码器参数
pub fn generate_video_encoder_params(&self, encoder_id: &str, quality_mode: &str, quality_value: Option<u32>) -> String {
if let Some(encoder) = self.get_video_encoder(encoder_id) {
let mut params = encoder.ffmpeg_opts.clone();
// 添加质量设置
if let (Some(bitrate_opts), Some(quality)) = (&encoder.bitrate_opts, quality_value) {
match quality_mode {
"cbr" => {
if let Some(cbr_template) = &bitrate_opts.cbr {
let cbr_param = cbr_template.replace("<CONST_BITRATE_VALUE>", &format!("{}k", quality));
params.push_str(&format!(" {}", cbr_param));
}
},
"vbr" | "cqp" => {
if let Some(vbr_template) = &bitrate_opts.vbr {
let mut vbr_param = vbr_template.clone();
// 处理不同的质量参数占位符
if let Some(cqp_values) = &encoder.cqp_values {
if let Some(values) = cqp_values.get("Mid") { // 默认使用中等质量
for (i, value) in values.iter().enumerate() {
match i {
0 => vbr_param = vbr_param.replace("<QP_VALUE>", &value.to_string())
.replace("<QPI_VALUE>", &value.to_string()),
1 => vbr_param = vbr_param.replace("<QPP_VALUE>", &value.to_string()),
2 => vbr_param = vbr_param.replace("<QPB_VALUE>", &value.to_string()),
_ => break,
}
}
}
}
// 如果没有预设值,使用传入的质量值
vbr_param = vbr_param.replace("<QV_VALUE>", &quality.to_string());
params.push_str(&format!(" {}", vbr_param));
}
},
_ => {}
}
}
params
} else {
// 回退到默认设置
format!("-c:v {}", encoder_id)
}
}
/// 生成输出设置
fn generate_output_settings(&self, settings: &crate::OutputSettings) -> TvaiResult<String> {
let mut output_params = Vec::new();
// 检查输出设置是否激活
if !settings.active {
// 如果输出设置未激活,使用配置文件中的默认设置
let audio_params = self.generate_audio_codec_params("AAC", Some(128));
output_params.push("-c:v libx264".to_string());
output_params.push(audio_params);
return Ok(format!(" {}", output_params.join(" ")));
}
// 硬件加速
if let Some(ref hwaccel) = settings.hwaccel {
output_params.push(format!("-hwaccel {}", hwaccel));
}
// 基于优先级设置处理分辨率
let use_custom_resolution = if let (Some(_width), Some(_height)) = (settings.width, settings.height) {
// 检查自定义分辨率优先级
let priority = settings.custom_resolution_priority.unwrap_or(0);
match priority {
0 => true, // 自定义分辨率具有正常优先级
1 => true, // 自定义分辨率具有高优先级
2 => false, // 尺寸方法优先于自定义分辨率
_ => true, // 默认使用自定义分辨率
}
} else {
false
};
if use_custom_resolution {
let width = settings.width.unwrap();
let height = settings.height.unwrap();
// 处理宽高比锁定
if settings.lock_aspect_ratio.unwrap_or(true) {
// 使用缩放滤镜保持宽高比
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease", width, height));
// 如果需要,添加填充以达到精确尺寸
if settings.crop_to_fit {
// 裁剪以适应精确尺寸
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=increase,crop={}:{}", width, height, width, height));
} else {
// 填充以适应精确尺寸
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease,pad={}:{}:(ow-iw)/2:(oh-ih)/2", width, height, width, height));
}
} else {
// 拉伸到精确尺寸(忽略宽高比)
output_params.push(format!("-s {}x{}", width, height));
}
} else {
// 处理输出尺寸方法(基于 Topaz Video AI 尺寸方法)
match settings.out_size_method {
0 => {
// 原始尺寸 - 不缩放
},
1 => {
// SD (480p)
output_params.push("-s 720x480".to_string());
},
2 => {
// DVD (576p)
output_params.push("-s 720x576".to_string());
},
3 => {
// HD Ready (720p)
output_params.push("-s 1280x720".to_string());
},
4 => {
// HD (1080p)
output_params.push("-s 1920x1080".to_string());
},
5 => {
// QHD (1440p)
output_params.push("-s 2560x1440".to_string());
},
6 => {
// FHD (1920x1080) - 与 4 相同
output_params.push("-s 1920x1080".to_string());
},
7 => {
// 4K UHD (2160p)
output_params.push("-s 3840x2160".to_string());
},
8 => {
// 8K UHD (4320p)
output_params.push("-s 7680x4320".to_string());
},
_ => {
// 未知方法 - 保持原始尺寸
}
}
}
// 为尺寸方法输出处理裁剪适应
if settings.crop_to_fit && !use_custom_resolution {
// 为尺寸方法输出应用裁剪适应
if let Some(last_param) = output_params.last_mut() {
if last_param.starts_with("-s ") {
let size = &last_param[3..];
// 用缩放+裁剪替换简单缩放
*last_param = format!("-vf scale={}:force_original_aspect_ratio=increase,crop={}", size, size);
}
}
}
// 处理像素宽高比
if settings.output_par != 0 {
match settings.output_par {
1 => output_params.push("-aspect 4:3".to_string()),
2 => output_params.push("-aspect 16:9".to_string()),
3 => output_params.push("-aspect 1:1".to_string()),
_ => {}
}
}
// 帧率
if settings.out_fps > 0.0 {
output_params.push(format!("-r {}", settings.out_fps));
}
// 视频编解码器 - 使用配置文件
if let Some(ref codec) = settings.codec {
// 尝试从配置文件中查找编码器
if let Some(encoder) = self.get_video_encoder_by_name(codec) {
let quality_mode = if settings.bitrate.is_some() { "cbr" } else { "vbr" };
let quality_value = settings.bitrate.map(|b| b as u32).or(settings.crf.map(|c| c as u32));
let encoder_params = self.generate_video_encoder_params(&encoder.id, quality_mode, quality_value);
output_params.push(encoder_params);
} else {
// 回退到传统方式
output_params.push(format!("-c:v {}", codec));
// 视频质量设置
if let Some(bitrate) = settings.bitrate {
output_params.push(format!("-b:v {}k", bitrate));
} else if let Some(crf) = settings.crf {
// 使用 CRF 进行基于质量的编码
match codec.as_str() {
"hevc_nvenc" | "h264_nvenc" => {
output_params.push(format!("-cq {}", crf));
},
"h264_amf" | "hevc_amf" => {
output_params.push(format!("-qp {}", crf));
},
"h264_qsv" | "hevc_qsv" => {
output_params.push(format!("-global_quality {}", crf));
},
_ => {
output_params.push(format!("-crf {}", crf));
}
}
} else {
output_params.push("-crf 23".to_string()); // 默认质量
}
}
} else {
// 使用默认的 H.264 编码器
output_params.push("-c:v libx264".to_string());
output_params.push("-crf 23".to_string());
}
// 编码预设
if let Some(ref preset) = settings.preset {
output_params.push(format!("-preset {}", preset));
} else {
output_params.push("-preset medium".to_string());
}
// 视频配置文件
if let Some(ref profile) = settings.profile {
output_params.push(format!("-profile:v {}", profile));
}
// 视频级别
if let Some(ref level) = settings.level {
output_params.push(format!("-level {}", level));
}
// GOP 大小
if let Some(gop_size) = settings.gop_size {
output_params.push(format!("-g {}", gop_size));
}
// B 帧
if let Some(b_frames) = settings.b_frames {
output_params.push(format!("-bf {}", b_frames));
}
// 像素格式
if let Some(ref pix_fmt) = settings.pixel_format {
output_params.push(format!("-pix_fmt {}", pix_fmt));
}
// 颜色设置
if let Some(ref colorspace) = settings.colorspace {
output_params.push(format!("-colorspace {}", colorspace));
}
if let Some(ref color_primaries) = settings.color_primaries {
output_params.push(format!("-color_primaries {}", color_primaries));
}
if let Some(ref color_trc) = settings.color_trc {
output_params.push(format!("-color_trc {}", color_trc));
}
// 音频设置 - 使用配置文件
if let Some(ref audio_codec) = settings.audio_codec {
if audio_codec != "copy" {
// 使用配置文件生成音频编解码器参数
let audio_params = self.generate_audio_codec_params(audio_codec, settings.audio_bitrate.map(|b| b as u32));
output_params.push(audio_params);
// 添加额外的音频参数
if let Some(audio_sample_rate) = settings.audio_sample_rate {
output_params.push(format!("-ar {}", audio_sample_rate));
}
if let Some(audio_channels) = settings.audio_channels {
output_params.push(format!("-ac {}", audio_channels));
}
} else {
output_params.push("-c:a copy".to_string());
}
} else {
// 使用默认音频编解码器
let default_audio = self.generate_audio_codec_params("AAC", Some(128));
output_params.push(default_audio);
}
// 自定义参数
if let Some(ref custom_params) = settings.custom_params {
for param in custom_params {
output_params.push(param.clone());
}
}
if output_params.is_empty() {
Ok(String::new())
} else {
Ok(format!(" {}", output_params.join(" ")))
}
}
/// 获取可用的模型映射
pub fn get_model_mappings(&self) -> &HashMap<String, String> {
&self.model_mappings
}
/// 验证模板用于 FFmpeg 生成
pub fn validate_template(&self, template: &Template) -> TvaiResult<()> {
let settings = &template.settings;
// 检查是否启用了任何处理模块
if !settings.enhance.active && !settings.slow_motion.active && !settings.stabilize.active && !settings.grain.active {
return Err(TvaiError::ValidationError("没有激活的处理模块".to_string()));
}
// 如果激活,验证增强模型
if settings.enhance.active && !self.model_mappings.contains_key(&settings.enhance.model) {
return Err(TvaiError::ModelMappingError(format!("未知的增强模型: {}", settings.enhance.model)));
}
// 如果激活,验证帧插值模型
if settings.slow_motion.active && !self.model_mappings.contains_key(&settings.slow_motion.model) {
return Err(TvaiError::ModelMappingError(format!("未知的帧插值模型: {}", settings.slow_motion.model)));
}
Ok(())
}
/// 生成带硬件加速的高级 FFmpeg 命令
pub fn generate_with_hardware_acceleration(&self, template: &Template, input_file: &str, output_file: &str, gpu_device: Option<&str>) -> TvaiResult<String> {
let mut command = self.generate(template, input_file, output_file)?;
// 为 TVAI 滤镜添加 GPU 设备规范
if let Some(device) = gpu_device {
command = command.replace("tvai_up=", &format!("tvai_up=device={}:", device));
command = command.replace("tvai_fi=", &format!("tvai_fi=device={}:", device));
command = command.replace("tvai_stb=", &format!("tvai_stb=device={}:", device));
}
Ok(command)
}
/// 生成带自定义输出编解码器的命令
pub fn generate_with_codec(&self, template: &Template, input_file: &str, output_file: &str, codec: &str, quality: Option<i32>) -> TvaiResult<String> {
// 创建带自定义编解码器设置的修改模板
let mut modified_template = template.clone();
modified_template.settings.output.codec = Some(codec.to_string());
if let Some(q) = quality {
// 设置质量时清除比特率
modified_template.settings.output.bitrate = None;
modified_template.settings.output.crf = Some(q);
}
// 使用修改的模板生成命令
self.generate(&modified_template, input_file, output_file)
}
/// 生成批处理命令
pub fn generate_batch_commands(&self, template: &Template, input_files: &[String], output_dir: &str) -> TvaiResult<Vec<String>> {
let mut commands = Vec::new();
for input_file in input_files {
let input_path = std::path::Path::new(input_file);
let filename = input_path.file_stem()
.ok_or_else(|| TvaiError::FfmpegError("无效的输入文件名".to_string()))?
.to_string_lossy();
let output_file = format!("{}/{}_processed.mp4", output_dir, filename);
let command = self.generate(template, input_file, &output_file)?;
commands.push(command);
}
Ok(commands)
}
/// 获取所有可用的音频编解码器
pub fn get_available_audio_codecs(&self) -> &Vec<AudioCodec> {
&self.audio_codecs
}
/// 获取所有可用的视频编码器
pub fn get_available_video_encoders(&self) -> &Vec<VideoEncoder> {
&self.video_encoders
}
/// 根据操作系统和GPU类型筛选视频编码器
pub fn get_compatible_video_encoders(&self, os: &str, gpu: Option<&str>) -> Vec<&VideoEncoder> {
self.video_encoders.iter().filter(|encoder| {
// 检查操作系统兼容性
let os_compatible = encoder.os.as_ref().map_or(true, |encoder_os| {
encoder_os.split('|').any(|supported_os| supported_os == os)
});
// 检查GPU兼容性
let gpu_compatible = if let Some(gpu_type) = gpu {
encoder.gpu.as_ref().map_or(true, |encoder_gpu| encoder_gpu == gpu_type)
} else {
encoder.gpu.is_none() // 如果没有指定GPU只返回不需要GPU的编码器
};
os_compatible && gpu_compatible
}).collect()
}
/// 根据文件扩展名获取兼容的编码器
pub fn get_encoders_for_extension(&self, extension: &str) -> Vec<&VideoEncoder> {
self.video_encoders.iter().filter(|encoder| {
encoder.ext.iter().any(|ext| ext.eq_ignore_ascii_case(extension))
}).collect()
}
/// 生成带有自动编码器选择的命令
pub fn generate_with_auto_encoder(&self, template: &Template, input_file: &str, output_file: &str, target_os: &str, gpu: Option<&str>) -> TvaiResult<String> {
// 从输出文件获取扩展名
let output_path = std::path::Path::new(output_file);
let extension = output_path.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("mp4");
// 获取兼容的编码器
let compatible_encoders = self.get_compatible_video_encoders(target_os, gpu);
let suitable_encoders: Vec<_> = compatible_encoders.into_iter()
.filter(|encoder| encoder.ext.iter().any(|ext| ext.eq_ignore_ascii_case(extension)))
.collect();
if suitable_encoders.is_empty() {
return Err(TvaiError::FfmpegError(format!("没有找到适用于 {} 格式和 {} 操作系统的编码器", extension, target_os)));
}
// 选择第一个合适的编码器
let selected_encoder = suitable_encoders[0];
// 创建修改的模板
let mut modified_template = template.clone();
modified_template.settings.output.codec = Some(selected_encoder.encoder.clone());
// 生成命令
self.generate(&modified_template, input_file, output_file)
}
/// 生成编码器信息报告
pub fn generate_encoder_report(&self) -> String {
let mut report = String::new();
report.push_str("=== 可用编码器报告 ===\n\n");
// 按编码器类型分组
let mut h264_encoders = Vec::new();
let mut h265_encoders = Vec::new();
let mut av1_encoders = Vec::new();
let mut other_encoders = Vec::new();
for encoder in &self.video_encoders {
match encoder.encoder.as_str() {
"H264" => h264_encoders.push(encoder),
"H265" => h265_encoders.push(encoder),
"AV1" => av1_encoders.push(encoder),
_ => other_encoders.push(encoder),
}
}
// 生成各类型编码器的报告
if !h264_encoders.is_empty() {
report.push_str("## H.264 编码器\n");
for encoder in h264_encoders {
report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts));
}
report.push('\n');
}
if !h265_encoders.is_empty() {
report.push_str("## H.265 编码器\n");
for encoder in h265_encoders {
report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts));
}
report.push('\n');
}
if !av1_encoders.is_empty() {
report.push_str("## AV1 编码器\n");
for encoder in av1_encoders {
report.push_str(&format!("- {} ({}): {}\n", encoder.id, encoder.profile.as_deref().unwrap_or("默认"), encoder.ffmpeg_opts));
}
report.push('\n');
}
if !other_encoders.is_empty() {
report.push_str("## 其他编码器\n");
for encoder in other_encoders {
report.push_str(&format!("- {} {}: {}\n", encoder.encoder, encoder.id, encoder.ffmpeg_opts));
}
report.push('\n');
}
// 音频编解码器报告
report.push_str("## 音频编解码器\n");
for codec in &self.audio_codecs {
report.push_str(&format!("- {}: {}\n", codec.name, codec.ffmpeg_opts));
}
report
}
}
/// 用于更复杂场景的 FFmpeg 命令构建器
#[derive(Debug, Default)]
pub struct FfmpegCommandBuilder {
input_file: String,
output_file: String,
filters: Vec<String>,
codec: String,
quality: Option<i32>,
hardware_accel: Option<String>,
custom_params: Vec<String>,
}
impl FfmpegCommandBuilder {
/// 创建新的命令构建器
pub fn new(input_file: &str, output_file: &str) -> Self {
Self {
input_file: input_file.to_string(),
output_file: output_file.to_string(),
filters: Vec::new(),
codec: "libx264".to_string(),
quality: Some(18),
hardware_accel: None,
custom_params: Vec::new(),
}
}
/// 添加滤镜
pub fn add_filter(mut self, filter: &str) -> Self {
self.filters.push(filter.to_string());
self
}
/// 设置编解码器
pub fn codec(mut self, codec: &str) -> Self {
self.codec = codec.to_string();
self
}
/// 设置质量
pub fn quality(mut self, quality: i32) -> Self {
self.quality = Some(quality);
self
}
/// 设置硬件加速
pub fn hardware_accel(mut self, accel: &str) -> Self {
self.hardware_accel = Some(accel.to_string());
self
}
/// 添加自定义参数
pub fn custom_param(mut self, param: &str) -> Self {
self.custom_params.push(param.to_string());
self
}
/// 构建命令
pub fn build(self) -> String {
let mut command = format!("ffmpeg -i \"{}\"", self.input_file);
if let Some(accel) = &self.hardware_accel {
command.push_str(&format!(" -hwaccel {}", accel));
}
if !self.filters.is_empty() {
command.push_str(" -vf \"");
command.push_str(&self.filters.join(","));
command.push('"');
}
command.push_str(&format!(" -c:v {}", self.codec));
if let Some(quality) = self.quality {
match self.codec.as_str() {
"hevc_nvenc" | "h264_nvenc" => {
command.push_str(&format!(" -cq {}", quality));
},
"h264_amf" | "hevc_amf" => {
command.push_str(&format!(" -qp {}", quality));
},
"h264_qsv" | "hevc_qsv" => {
command.push_str(&format!(" -global_quality {}", quality));
},
_ => {
command.push_str(&format!(" -crf {}", quality));
}
}
}
for param in &self.custom_params {
command.push_str(&format!(" {}", param));
}
command.push_str(&format!(" \"{}\"", self.output_file));
command
}
}