- 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
1444 lines
52 KiB
Rust
1444 lines
52 KiB
Rust
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
|
||
}
|
||
}
|