feat: add template json
This commit is contained in:
63
cargos/tvai-v2/src/error.rs
Normal file
63
cargos/tvai-v2/src/error.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std::fmt;
|
||||
|
||||
/// Error types for Topaz Video AI SDK
|
||||
#[derive(Debug)]
|
||||
pub enum TvaiError {
|
||||
/// IO error
|
||||
IoError(std::io::Error),
|
||||
/// JSON parsing error
|
||||
JsonParseError(serde_json::Error),
|
||||
/// JSON serialization error
|
||||
JsonSerializeError(serde_json::Error),
|
||||
/// Template validation error
|
||||
ValidationError(String),
|
||||
/// Template not found error
|
||||
TemplateNotFound(String),
|
||||
/// FFmpeg command generation error
|
||||
FfmpegError(String),
|
||||
/// Model mapping error
|
||||
ModelMappingError(String),
|
||||
/// Configuration error
|
||||
ConfigError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for TvaiError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TvaiError::IoError(err) => write!(f, "IO error: {}", err),
|
||||
TvaiError::JsonParseError(err) => write!(f, "JSON parsing error: {}", err),
|
||||
TvaiError::JsonSerializeError(err) => write!(f, "JSON serialization error: {}", err),
|
||||
TvaiError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
|
||||
TvaiError::TemplateNotFound(name) => write!(f, "Template not found: {}", name),
|
||||
TvaiError::FfmpegError(msg) => write!(f, "FFmpeg error: {}", msg),
|
||||
TvaiError::ModelMappingError(msg) => write!(f, "Model mapping error: {}", msg),
|
||||
TvaiError::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for TvaiError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
TvaiError::IoError(err) => Some(err),
|
||||
TvaiError::JsonParseError(err) => Some(err),
|
||||
TvaiError::JsonSerializeError(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for TvaiError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
TvaiError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for TvaiError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
TvaiError::JsonParseError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result type for Topaz Video AI SDK operations
|
||||
pub type TvaiResult<T> = Result<T, TvaiError>;
|
||||
423
cargos/tvai-v2/src/ffmpeg.rs
Normal file
423
cargos/tvai-v2/src/ffmpeg.rs
Normal file
@@ -0,0 +1,423 @@
|
||||
use crate::{Template, TvaiError, TvaiResult};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// FFmpeg command generator for Topaz Video AI templates
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FfmpegCommandGenerator {
|
||||
/// Model mappings from template models to FFmpeg filter models
|
||||
model_mappings: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl FfmpegCommandGenerator {
|
||||
/// Create a new FFmpeg command generator
|
||||
pub fn new() -> Self {
|
||||
let mut generator = Self {
|
||||
model_mappings: HashMap::new(),
|
||||
};
|
||||
|
||||
// Initialize default model mappings
|
||||
generator.init_model_mappings();
|
||||
generator
|
||||
}
|
||||
|
||||
/// Initialize model mappings from template models to FFmpeg filter models
|
||||
fn init_model_mappings(&mut self) {
|
||||
// Enhancement models
|
||||
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());
|
||||
|
||||
// Frame interpolation models
|
||||
self.model_mappings.insert("apo-8".to_string(), "chr-2".to_string());
|
||||
self.model_mappings.insert("chf-3".to_string(), "chr-2".to_string());
|
||||
|
||||
// Motion blur models
|
||||
self.model_mappings.insert("thm-2".to_string(), "thm-2".to_string());
|
||||
|
||||
// Stabilization models (mapped to ref-2 for tvai_stb)
|
||||
// Note: Template stabilization uses different approach than tvai_stb filter
|
||||
}
|
||||
|
||||
/// Add custom model mapping
|
||||
pub fn add_model_mapping(&mut self, template_model: String, ffmpeg_model: String) {
|
||||
self.model_mappings.insert(template_model, ffmpeg_model);
|
||||
}
|
||||
|
||||
/// Generate FFmpeg command from template
|
||||
pub fn generate(&self, template: &Template, input_file: &str, output_file: &str) -> TvaiResult<String> {
|
||||
let mut filters = Vec::new();
|
||||
let settings = &template.settings;
|
||||
|
||||
// Generate stabilization filter
|
||||
if settings.stabilize.active {
|
||||
let stb_filter = self.generate_stabilization_filter(&settings.stabilize)?;
|
||||
filters.push(stb_filter);
|
||||
}
|
||||
|
||||
// Generate enhancement filter
|
||||
if settings.enhance.active {
|
||||
let up_filter = self.generate_enhancement_filter(&settings.enhance)?;
|
||||
filters.push(up_filter);
|
||||
}
|
||||
|
||||
// Generate frame interpolation filter
|
||||
if settings.slow_motion.active {
|
||||
let fi_filter = self.generate_frame_interpolation_filter(&settings.slow_motion, &settings.output)?;
|
||||
filters.push(fi_filter);
|
||||
}
|
||||
|
||||
// Generate grain filter (using FFmpeg's noise filter as approximation)
|
||||
if settings.grain.active {
|
||||
let grain_filter = self.generate_grain_filter(&settings.grain)?;
|
||||
filters.push(grain_filter);
|
||||
}
|
||||
|
||||
// Build the complete FFmpeg command
|
||||
let mut command = format!("ffmpeg -i \"{}\"", input_file);
|
||||
|
||||
if !filters.is_empty() {
|
||||
command.push_str(" -vf \"");
|
||||
command.push_str(&filters.join(","));
|
||||
command.push('"');
|
||||
}
|
||||
|
||||
// Add output settings
|
||||
command.push_str(&self.generate_output_settings(&settings.output)?);
|
||||
|
||||
command.push_str(&format!(" \"{}\"", output_file));
|
||||
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
/// Generate stabilization filter (tvai_stb)
|
||||
fn generate_stabilization_filter(&self, settings: &crate::StabilizeSettings) -> TvaiResult<String> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
// Use default model for stabilization
|
||||
params.push("model=ref-2".to_string());
|
||||
|
||||
// Map smoothness (0-100 in template to 0-16 in filter)
|
||||
let smoothness = (settings.smooth as f64 / 100.0 * 16.0).round() as i32;
|
||||
params.push(format!("smoothness={}", smoothness));
|
||||
|
||||
// Map method (0: auto crop, 1: full frame)
|
||||
let full = if settings.method == 1 { 1 } else { 0 };
|
||||
params.push(format!("full={}", full));
|
||||
|
||||
// Rolling shutter correction
|
||||
if settings.rsc {
|
||||
params.push("roll=1".to_string());
|
||||
}
|
||||
|
||||
// Reduce motion
|
||||
if settings.reduce_motion {
|
||||
params.push(format!("reduce={}", settings.reduce_motion_iteration));
|
||||
}
|
||||
|
||||
Ok(format!("tvai_stb={}", params.join(":")))
|
||||
}
|
||||
|
||||
/// Generate enhancement filter (tvai_up)
|
||||
fn generate_enhancement_filter(&self, settings: &crate::EnhanceSettings) -> TvaiResult<String> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
// Map model
|
||||
let model = self.model_mappings.get(&settings.model)
|
||||
.ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", settings.model)))?;
|
||||
params.push(format!("model={}", model));
|
||||
|
||||
// Map enhancement parameters to tvai_up parameters
|
||||
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));
|
||||
}
|
||||
|
||||
Ok(format!("tvai_up={}", params.join(":")))
|
||||
}
|
||||
|
||||
/// Generate frame interpolation filter (tvai_fi)
|
||||
fn generate_frame_interpolation_filter(&self, slowmo_settings: &crate::SlowMotionSettings, output_settings: &crate::OutputSettings) -> TvaiResult<String> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
// Map model
|
||||
let model = self.model_mappings.get(&slowmo_settings.model)
|
||||
.ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown frame interpolation model: {}", slowmo_settings.model)))?;
|
||||
params.push(format!("model={}", model));
|
||||
|
||||
// Set slowmo factor
|
||||
if slowmo_settings.factor != 1.0 {
|
||||
params.push(format!("slowmo={}", slowmo_settings.factor));
|
||||
}
|
||||
|
||||
// Set output FPS if specified
|
||||
if output_settings.out_fps > 0.0 {
|
||||
params.push(format!("fps={}", output_settings.out_fps));
|
||||
}
|
||||
|
||||
// Duplicate frame threshold
|
||||
if !slowmo_settings.duplicate {
|
||||
params.push("rdt=-0.01".to_string()); // Disable duplicate frame removal
|
||||
} else {
|
||||
let threshold = slowmo_settings.duplicate_threshold as f64 / 1000.0;
|
||||
params.push(format!("rdt={:.3}", threshold));
|
||||
}
|
||||
|
||||
Ok(format!("tvai_fi={}", params.join(":")))
|
||||
}
|
||||
|
||||
/// Generate grain filter (approximation using FFmpeg noise filter)
|
||||
fn generate_grain_filter(&self, settings: &crate::GrainSettings) -> TvaiResult<String> {
|
||||
let strength = settings.grain as f64 / 100.0 * 20.0; // Scale to reasonable noise level
|
||||
Ok(format!("noise=alls={}:allf=t", strength))
|
||||
}
|
||||
|
||||
/// Generate output settings
|
||||
fn generate_output_settings(&self, settings: &crate::OutputSettings) -> TvaiResult<String> {
|
||||
let mut output_params = Vec::new();
|
||||
|
||||
// Handle output size method
|
||||
match settings.out_size_method {
|
||||
7 => {
|
||||
// 4K output
|
||||
output_params.push("-s 3840x2160".to_string());
|
||||
},
|
||||
6 => {
|
||||
// FHD output
|
||||
output_params.push("-s 1920x1080".to_string());
|
||||
},
|
||||
5 => {
|
||||
// HD output
|
||||
output_params.push("-s 1280x720".to_string());
|
||||
},
|
||||
_ => {
|
||||
// Keep original size (method 0) or other methods
|
||||
}
|
||||
}
|
||||
|
||||
// Handle frame rate
|
||||
if settings.out_fps > 0.0 {
|
||||
output_params.push(format!("-r {}", settings.out_fps));
|
||||
}
|
||||
|
||||
// Add default encoding settings for quality
|
||||
output_params.push("-c:v libx264".to_string());
|
||||
output_params.push("-crf 18".to_string());
|
||||
output_params.push("-preset slow".to_string());
|
||||
|
||||
if output_params.is_empty() {
|
||||
Ok(String::new())
|
||||
} else {
|
||||
Ok(format!(" {}", output_params.join(" ")))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get available model mappings
|
||||
pub fn get_model_mappings(&self) -> &HashMap<String, String> {
|
||||
&self.model_mappings
|
||||
}
|
||||
|
||||
/// Validate template for FFmpeg generation
|
||||
pub fn validate_template(&self, template: &Template) -> TvaiResult<()> {
|
||||
let settings = &template.settings;
|
||||
|
||||
// Check if any processing is enabled
|
||||
if !settings.enhance.active && !settings.slow_motion.active && !settings.stabilize.active && !settings.grain.active {
|
||||
return Err(TvaiError::ValidationError("No processing modules are active".to_string()));
|
||||
}
|
||||
|
||||
// Validate enhancement model if active
|
||||
if settings.enhance.active && !self.model_mappings.contains_key(&settings.enhance.model) {
|
||||
return Err(TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", settings.enhance.model)));
|
||||
}
|
||||
|
||||
// Validate frame interpolation model if active
|
||||
if settings.slow_motion.active && !self.model_mappings.contains_key(&settings.slow_motion.model) {
|
||||
return Err(TvaiError::ModelMappingError(format!("Unknown frame interpolation model: {}", settings.slow_motion.model)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate advanced FFmpeg command with hardware acceleration
|
||||
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)?;
|
||||
|
||||
// Add GPU device specification for TVAI filters
|
||||
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)
|
||||
}
|
||||
|
||||
/// Generate command with custom output codec
|
||||
pub fn generate_with_codec(&self, template: &Template, input_file: &str, output_file: &str, codec: &str, quality: Option<i32>) -> TvaiResult<String> {
|
||||
let mut command = self.generate(template, input_file, output_file)?;
|
||||
|
||||
// Replace default codec settings
|
||||
command = command.replace("-c:v libx264", &format!("-c:v {}", codec));
|
||||
|
||||
if let Some(q) = quality {
|
||||
match codec {
|
||||
"hevc_nvenc" | "h264_nvenc" => {
|
||||
command = command.replace("-crf 18", &format!("-cq {}", q));
|
||||
},
|
||||
"h264_amf" | "hevc_amf" => {
|
||||
command = command.replace("-crf 18", &format!("-qp {}", q));
|
||||
},
|
||||
"h264_qsv" | "hevc_qsv" => {
|
||||
command = command.replace("-crf 18", &format!("-global_quality {}", q));
|
||||
},
|
||||
_ => {
|
||||
command = command.replace("-crf 18", &format!("-crf {}", q));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
/// Generate batch processing commands
|
||||
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("Invalid input filename".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)
|
||||
}
|
||||
}
|
||||
|
||||
/// FFmpeg command builder for more complex scenarios
|
||||
#[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 {
|
||||
/// Create new command builder
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add filter
|
||||
pub fn add_filter(mut self, filter: &str) -> Self {
|
||||
self.filters.push(filter.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set codec
|
||||
pub fn codec(mut self, codec: &str) -> Self {
|
||||
self.codec = codec.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set quality
|
||||
pub fn quality(mut self, quality: i32) -> Self {
|
||||
self.quality = Some(quality);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set hardware acceleration
|
||||
pub fn hardware_accel(mut self, accel: &str) -> Self {
|
||||
self.hardware_accel = Some(accel.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add custom parameter
|
||||
pub fn custom_param(mut self, param: &str) -> Self {
|
||||
self.custom_params.push(param.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the command
|
||||
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
|
||||
}
|
||||
}
|
||||
336
cargos/tvai-v2/src/lib.rs
Normal file
336
cargos/tvai-v2/src/lib.rs
Normal file
@@ -0,0 +1,336 @@
|
||||
pub mod template;
|
||||
pub mod ffmpeg;
|
||||
pub mod manager;
|
||||
pub mod error;
|
||||
|
||||
pub use template::*;
|
||||
pub use ffmpeg::*;
|
||||
pub use manager::*;
|
||||
pub use error::*;
|
||||
|
||||
/// Topaz Video AI Rust SDK
|
||||
///
|
||||
/// This SDK provides functionality to:
|
||||
/// - Load and manage Topaz Video AI templates
|
||||
/// - Convert templates to FFmpeg commands
|
||||
/// - Validate and complete template structures
|
||||
/// - Support database storage and runtime loading
|
||||
pub struct TvaiSdk {
|
||||
manager: TemplateManager,
|
||||
ffmpeg_generator: FfmpegCommandGenerator,
|
||||
}
|
||||
|
||||
impl TvaiSdk {
|
||||
/// Create a new SDK instance
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
manager: TemplateManager::new(),
|
||||
ffmpeg_generator: FfmpegCommandGenerator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create SDK instance with custom model mappings
|
||||
pub fn with_model_mappings(mappings: std::collections::HashMap<String, String>) -> Self {
|
||||
let mut generator = FfmpegCommandGenerator::new();
|
||||
for (template_model, ffmpeg_model) in mappings {
|
||||
generator.add_model_mapping(template_model, ffmpeg_model);
|
||||
}
|
||||
|
||||
Self {
|
||||
manager: TemplateManager::new(),
|
||||
ffmpeg_generator: generator,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load templates from a directory
|
||||
pub fn load_templates_from_dir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), TvaiError> {
|
||||
self.manager.load_from_directory(path)
|
||||
}
|
||||
|
||||
/// Load a single template from file
|
||||
pub fn load_template_from_file<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Template, TvaiError> {
|
||||
self.manager.load_from_file(path)
|
||||
}
|
||||
|
||||
/// Add a template to the SDK
|
||||
pub fn add_template(&mut self, template: Template) -> Result<(), TvaiError> {
|
||||
self.manager.add_template(template)
|
||||
}
|
||||
|
||||
/// Remove a template by name
|
||||
pub fn remove_template(&mut self, name: &str) -> Option<Template> {
|
||||
self.manager.remove_template(name)
|
||||
}
|
||||
|
||||
/// Get all loaded templates
|
||||
pub fn get_templates(&self) -> Vec<&Template> {
|
||||
self.manager.get_templates()
|
||||
}
|
||||
|
||||
/// Get template names
|
||||
pub fn get_template_names(&self) -> Vec<&String> {
|
||||
self.manager.get_template_names()
|
||||
}
|
||||
|
||||
/// Find template by name
|
||||
pub fn find_template(&self, name: &str) -> Option<&Template> {
|
||||
self.manager.find_by_name(name)
|
||||
}
|
||||
|
||||
/// Check if template exists
|
||||
pub fn has_template(&self, name: &str) -> bool {
|
||||
self.manager.contains_template(name)
|
||||
}
|
||||
|
||||
/// Get template count
|
||||
pub fn template_count(&self) -> usize {
|
||||
self.manager.template_count()
|
||||
}
|
||||
|
||||
/// Generate FFmpeg command from template
|
||||
pub fn generate_ffmpeg_command(&self, template: &Template, input_file: &str, output_file: &str) -> Result<String, TvaiError> {
|
||||
self.ffmpeg_generator.generate(template, input_file, output_file)
|
||||
}
|
||||
|
||||
/// Generate FFmpeg command with hardware acceleration
|
||||
pub fn generate_ffmpeg_command_with_gpu(&self, template: &Template, input_file: &str, output_file: &str, gpu_device: &str) -> Result<String, TvaiError> {
|
||||
self.ffmpeg_generator.generate_with_hardware_acceleration(template, input_file, output_file, Some(gpu_device))
|
||||
}
|
||||
|
||||
/// Generate FFmpeg command with custom codec
|
||||
pub fn generate_ffmpeg_command_with_codec(&self, template: &Template, input_file: &str, output_file: &str, codec: &str, quality: Option<i32>) -> Result<String, TvaiError> {
|
||||
self.ffmpeg_generator.generate_with_codec(template, input_file, output_file, codec, quality)
|
||||
}
|
||||
|
||||
/// Generate batch processing commands
|
||||
pub fn generate_batch_commands(&self, template: &Template, input_files: &[String], output_dir: &str) -> Result<Vec<String>, TvaiError> {
|
||||
self.ffmpeg_generator.generate_batch_commands(template, input_files, output_dir)
|
||||
}
|
||||
|
||||
/// Validate template for FFmpeg generation
|
||||
pub fn validate_template_for_ffmpeg(&self, template: &Template) -> Result<(), TvaiError> {
|
||||
self.ffmpeg_generator.validate_template(template)
|
||||
}
|
||||
|
||||
/// Validate and complete template structure
|
||||
pub fn validate_and_complete_template(&self, template: &mut Template) -> Result<(), TvaiError> {
|
||||
template.validate_and_complete()
|
||||
}
|
||||
|
||||
/// Create a new template with default settings
|
||||
pub fn create_template(&self, name: &str, description: &str) -> Template {
|
||||
Template {
|
||||
name: name.to_string(),
|
||||
description: description.to_string(),
|
||||
author: "User".to_string(),
|
||||
date: chrono::Utc::now().format("%a %b %d %H:%M:%S %Y GMT%z").to_string(),
|
||||
veai_version: "5.1.2".to_string(),
|
||||
editable: true,
|
||||
enabled: true,
|
||||
save_output_settings: false,
|
||||
path: None,
|
||||
settings: TemplateSettings::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Export all templates to JSON
|
||||
pub fn export_templates_to_json(&self) -> Result<String, TvaiError> {
|
||||
self.manager.export_templates_to_json()
|
||||
}
|
||||
|
||||
/// Import templates from JSON
|
||||
pub fn import_templates_from_json(&mut self, json: &str) -> Result<usize, TvaiError> {
|
||||
self.manager.import_templates_from_json(json)
|
||||
}
|
||||
|
||||
/// Save all templates to directory
|
||||
pub fn save_templates_to_dir<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), TvaiError> {
|
||||
self.manager.save_templates_to_directory(path)
|
||||
}
|
||||
|
||||
/// Clear all templates
|
||||
pub fn clear_templates(&mut self) {
|
||||
self.manager.clear();
|
||||
}
|
||||
|
||||
/// Validate all loaded templates
|
||||
pub fn validate_all_templates(&mut self) -> Vec<(String, TvaiError)> {
|
||||
self.manager.validate_all_templates()
|
||||
}
|
||||
|
||||
/// Get FFmpeg model mappings
|
||||
pub fn get_model_mappings(&self) -> &std::collections::HashMap<String, String> {
|
||||
self.ffmpeg_generator.get_model_mappings()
|
||||
}
|
||||
|
||||
/// Add custom model mapping
|
||||
pub fn add_model_mapping(&mut self, template_model: String, ffmpeg_model: String) {
|
||||
self.ffmpeg_generator.add_model_mapping(template_model, ffmpeg_model);
|
||||
}
|
||||
}
|
||||
|
||||
/// Template builder for creating templates programmatically
|
||||
#[derive(Debug)]
|
||||
pub struct TemplateBuilder {
|
||||
template: Template,
|
||||
}
|
||||
|
||||
impl TemplateBuilder {
|
||||
/// Create a new template builder
|
||||
pub fn new(name: &str) -> Self {
|
||||
let template = Template {
|
||||
name: name.to_string(),
|
||||
description: String::new(),
|
||||
author: "User".to_string(),
|
||||
date: chrono::Utc::now().format("%a %b %d %H:%M:%S %Y GMT%z").to_string(),
|
||||
veai_version: "5.1.2".to_string(),
|
||||
editable: true,
|
||||
enabled: true,
|
||||
save_output_settings: false,
|
||||
path: None,
|
||||
settings: TemplateSettings::default(),
|
||||
};
|
||||
|
||||
Self { template }
|
||||
}
|
||||
|
||||
/// Set description
|
||||
pub fn description(mut self, description: &str) -> Self {
|
||||
self.template.description = description.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set author
|
||||
pub fn author(mut self, author: &str) -> Self {
|
||||
self.template.author = author.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable video enhancement
|
||||
pub fn enable_enhancement(mut self, model: &str) -> Self {
|
||||
self.template.settings.enhance.active = true;
|
||||
self.template.settings.enhance.model = model.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure enhancement parameters
|
||||
pub fn enhancement_params(mut self, denoise: i32, detail: i32, sharpen: i32) -> Self {
|
||||
self.template.settings.enhance.denoise = denoise;
|
||||
self.template.settings.enhance.detail = detail;
|
||||
self.template.settings.enhance.sharpen = sharpen;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable frame interpolation
|
||||
pub fn enable_frame_interpolation(mut self, model: &str, factor: f64) -> Self {
|
||||
self.template.settings.slow_motion.active = true;
|
||||
self.template.settings.slow_motion.model = model.to_string();
|
||||
self.template.settings.slow_motion.factor = factor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable stabilization
|
||||
pub fn enable_stabilization(mut self, smoothness: i32, method: i32) -> Self {
|
||||
self.template.settings.stabilize.active = true;
|
||||
self.template.settings.stabilize.smooth = smoothness;
|
||||
self.template.settings.stabilize.method = method;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set output settings
|
||||
pub fn output_settings(mut self, size_method: i32, fps: f64) -> Self {
|
||||
self.template.settings.output.out_size_method = size_method;
|
||||
self.template.settings.output.out_fps = fps;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable grain effect
|
||||
pub fn enable_grain(mut self, amount: i32, size: i32) -> Self {
|
||||
self.template.settings.grain.active = true;
|
||||
self.template.settings.grain.grain = amount;
|
||||
self.template.settings.grain.grain_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the template
|
||||
pub fn build(mut self) -> Result<Template, TvaiError> {
|
||||
self.template.validate_and_complete()?;
|
||||
Ok(self.template)
|
||||
}
|
||||
}
|
||||
|
||||
/// Predefined template presets
|
||||
pub struct TemplatePresets;
|
||||
|
||||
impl TemplatePresets {
|
||||
/// Create a 4K upscaling template
|
||||
pub fn upscale_to_4k() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Upscale to 4K")
|
||||
.description("Upscale video to 4K resolution with AI enhancement")
|
||||
.enable_enhancement("prob-4")
|
||||
.output_settings(7, 0.0) // 4K output
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a 60fps conversion template
|
||||
pub fn convert_to_60fps() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Convert to 60 FPS")
|
||||
.description("Convert video to 60 FPS using frame interpolation")
|
||||
.enable_frame_interpolation("chf-3", 1.0)
|
||||
.output_settings(0, 60.0) // Original size, 60 FPS
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a noise removal template
|
||||
pub fn remove_noise() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Remove Noise")
|
||||
.description("Remove noise from video using AI")
|
||||
.enable_enhancement("nyx-3")
|
||||
.enhancement_params(50, 0, 0) // High denoise, no detail/sharpen
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a stabilization template
|
||||
pub fn stabilize_video() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Stabilize Video")
|
||||
.description("Stabilize shaky video with auto crop")
|
||||
.enable_stabilization(50, 0) // Medium smoothness, auto crop
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a slow motion template
|
||||
pub fn slow_motion_4x() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("4x Slow Motion")
|
||||
.description("Create 4x slow motion effect")
|
||||
.enable_frame_interpolation("apo-8", 4.0)
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a comprehensive enhancement template
|
||||
pub fn comprehensive_enhancement() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Comprehensive Enhancement")
|
||||
.description("Complete video enhancement with upscaling, denoising, and stabilization")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(30, 20, 10) // Moderate denoise, detail, and sharpen
|
||||
.enable_stabilization(60, 1) // High smoothness, full frame
|
||||
.output_settings(6, 0.0) // FHD output
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TvaiSdk {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sdk_creation() {
|
||||
let sdk = TvaiSdk::new();
|
||||
assert_eq!(sdk.get_templates().len(), 0);
|
||||
}
|
||||
}
|
||||
238
cargos/tvai-v2/src/manager.rs
Normal file
238
cargos/tvai-v2/src/manager.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use crate::{Template, TvaiError, TvaiResult};
|
||||
|
||||
/// Template manager for loading, storing and managing templates
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TemplateManager {
|
||||
/// Loaded templates indexed by name
|
||||
templates: HashMap<String, Template>,
|
||||
/// Template file paths
|
||||
template_paths: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl TemplateManager {
|
||||
/// Create a new template manager
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
templates: HashMap::new(),
|
||||
template_paths: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load templates from a directory
|
||||
pub fn load_from_directory<P: AsRef<Path>>(&mut self, dir_path: P) -> TvaiResult<()> {
|
||||
let dir_path = dir_path.as_ref();
|
||||
|
||||
if !dir_path.exists() {
|
||||
return Err(TvaiError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Directory not found: {}", dir_path.display())
|
||||
)));
|
||||
}
|
||||
|
||||
let entries = std::fs::read_dir(dir_path)?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") {
|
||||
match self.load_from_file(&path) {
|
||||
Ok(template) => {
|
||||
self.template_paths.insert(template.name.clone(), path.to_string_lossy().to_string());
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to load template from {}: {}", path.display(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load a single template from file
|
||||
pub fn load_from_file<P: AsRef<Path>>(&mut self, file_path: P) -> TvaiResult<Template> {
|
||||
let mut template = Template::from_file(&file_path)?;
|
||||
|
||||
// Validate and complete the template
|
||||
template.validate_and_complete()?;
|
||||
|
||||
// Store the template
|
||||
self.templates.insert(template.name.clone(), template.clone());
|
||||
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
/// Add a template to the manager
|
||||
pub fn add_template(&mut self, mut template: Template) -> TvaiResult<()> {
|
||||
// Validate and complete the template
|
||||
template.validate_and_complete()?;
|
||||
|
||||
self.templates.insert(template.name.clone(), template);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a template by name
|
||||
pub fn remove_template(&mut self, name: &str) -> Option<Template> {
|
||||
self.template_paths.remove(name);
|
||||
self.templates.remove(name)
|
||||
}
|
||||
|
||||
/// Get all templates
|
||||
pub fn get_templates(&self) -> Vec<&Template> {
|
||||
self.templates.values().collect()
|
||||
}
|
||||
|
||||
/// Get template names
|
||||
pub fn get_template_names(&self) -> Vec<&String> {
|
||||
self.templates.keys().collect()
|
||||
}
|
||||
|
||||
/// Find template by name
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&Template> {
|
||||
self.templates.get(name)
|
||||
}
|
||||
|
||||
/// Find template by name (mutable)
|
||||
pub fn find_by_name_mut(&mut self, name: &str) -> Option<&mut Template> {
|
||||
self.templates.get_mut(name)
|
||||
}
|
||||
|
||||
/// Check if template exists
|
||||
pub fn contains_template(&self, name: &str) -> bool {
|
||||
self.templates.contains_key(name)
|
||||
}
|
||||
|
||||
/// Get template count
|
||||
pub fn template_count(&self) -> usize {
|
||||
self.templates.len()
|
||||
}
|
||||
|
||||
/// Clear all templates
|
||||
pub fn clear(&mut self) {
|
||||
self.templates.clear();
|
||||
self.template_paths.clear();
|
||||
}
|
||||
|
||||
/// Export templates to JSON
|
||||
pub fn export_templates_to_json(&self) -> TvaiResult<String> {
|
||||
let templates: Vec<&Template> = self.templates.values().collect();
|
||||
serde_json::to_string_pretty(&templates).map_err(TvaiError::JsonSerializeError)
|
||||
}
|
||||
|
||||
/// Import templates from JSON
|
||||
pub fn import_templates_from_json(&mut self, json: &str) -> TvaiResult<usize> {
|
||||
let templates: Vec<Template> = serde_json::from_str(json)?;
|
||||
let mut count = 0;
|
||||
|
||||
for mut template in templates {
|
||||
if let Err(e) = template.validate_and_complete() {
|
||||
eprintln!("Warning: Failed to validate template '{}': {}", template.name, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
self.templates.insert(template.name.clone(), template);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
/// Save all templates to directory
|
||||
pub fn save_templates_to_directory<P: AsRef<Path>>(&self, dir_path: P) -> TvaiResult<()> {
|
||||
let dir_path = dir_path.as_ref();
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if !dir_path.exists() {
|
||||
std::fs::create_dir_all(dir_path)?;
|
||||
}
|
||||
|
||||
for template in self.templates.values() {
|
||||
let filename = format!("{}.json", template.name.replace("/", "_").replace("\\", "_"));
|
||||
let file_path = dir_path.join(filename);
|
||||
template.save_to_file(file_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get template file path
|
||||
pub fn get_template_path(&self, name: &str) -> Option<&String> {
|
||||
self.template_paths.get(name)
|
||||
}
|
||||
|
||||
/// Validate all templates
|
||||
pub fn validate_all_templates(&mut self) -> Vec<(String, TvaiError)> {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for (name, template) in &mut self.templates {
|
||||
if let Err(e) = template.validate_and_complete() {
|
||||
errors.push((name.clone(), e));
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
||||
/// Database interface trait for template storage
|
||||
pub trait TemplateDatabase {
|
||||
/// Save template to database
|
||||
fn save_template(&self, template: &Template) -> TvaiResult<()>;
|
||||
|
||||
/// Load template from database
|
||||
fn load_template(&self, name: &str) -> TvaiResult<Template>;
|
||||
|
||||
/// Load all templates from database
|
||||
fn load_all_templates(&self) -> TvaiResult<Vec<Template>>;
|
||||
|
||||
/// Delete template from database
|
||||
fn delete_template(&self, name: &str) -> TvaiResult<()>;
|
||||
|
||||
/// Check if template exists in database
|
||||
fn template_exists(&self, name: &str) -> TvaiResult<bool>;
|
||||
}
|
||||
|
||||
/// Template manager with database support
|
||||
pub struct DatabaseTemplateManager<D: TemplateDatabase> {
|
||||
manager: TemplateManager,
|
||||
database: D,
|
||||
}
|
||||
|
||||
impl<D: TemplateDatabase> DatabaseTemplateManager<D> {
|
||||
/// Create new database template manager
|
||||
pub fn new(database: D) -> Self {
|
||||
Self {
|
||||
manager: TemplateManager::new(),
|
||||
database,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load all templates from database
|
||||
pub fn load_from_database(&mut self) -> TvaiResult<()> {
|
||||
let templates = self.database.load_all_templates()?;
|
||||
|
||||
for template in templates {
|
||||
self.manager.add_template(template)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Save template to database
|
||||
pub fn save_template_to_database(&self, template: &Template) -> TvaiResult<()> {
|
||||
self.database.save_template(template)
|
||||
}
|
||||
|
||||
/// Get the underlying template manager
|
||||
pub fn manager(&self) -> &TemplateManager {
|
||||
&self.manager
|
||||
}
|
||||
|
||||
/// Get the underlying template manager (mutable)
|
||||
pub fn manager_mut(&mut self) -> &mut TemplateManager {
|
||||
&mut self.manager
|
||||
}
|
||||
}
|
||||
668
cargos/tvai-v2/src/template.rs
Normal file
668
cargos/tvai-v2/src/template.rs
Normal file
@@ -0,0 +1,668 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::error::TvaiError;
|
||||
|
||||
/// Topaz Video AI Template
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Template {
|
||||
/// Template name
|
||||
pub name: String,
|
||||
/// Template description
|
||||
pub description: String,
|
||||
/// Template author
|
||||
pub author: String,
|
||||
/// Creation date
|
||||
pub date: String,
|
||||
/// Topaz Video AI version
|
||||
#[serde(rename = "veaiversion")]
|
||||
pub veai_version: String,
|
||||
/// Whether template is editable
|
||||
pub editable: bool,
|
||||
/// Whether template is enabled
|
||||
pub enabled: bool,
|
||||
/// Whether to save output settings
|
||||
#[serde(rename = "saveOutputSettings")]
|
||||
pub save_output_settings: bool,
|
||||
/// Template file path (optional)
|
||||
pub path: Option<String>,
|
||||
/// Template settings
|
||||
pub settings: TemplateSettings,
|
||||
}
|
||||
|
||||
/// Template settings containing all processing modules
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TemplateSettings {
|
||||
/// Video stabilization settings
|
||||
pub stabilize: StabilizeSettings,
|
||||
/// Motion blur settings
|
||||
#[serde(rename = "motionblur")]
|
||||
pub motion_blur: MotionBlurSettings,
|
||||
/// Slow motion/frame interpolation settings
|
||||
#[serde(rename = "slowmo")]
|
||||
pub slow_motion: SlowMotionSettings,
|
||||
/// Video enhancement settings
|
||||
pub enhance: EnhanceSettings,
|
||||
/// Grain effect settings
|
||||
pub grain: GrainSettings,
|
||||
/// Output settings
|
||||
pub output: OutputSettings,
|
||||
/// Filter manager settings (optional, for newer versions)
|
||||
#[serde(rename = "filterManager", skip_serializing_if = "Option::is_none")]
|
||||
pub filter_manager: Option<FilterManagerSettings>,
|
||||
}
|
||||
|
||||
/// Video stabilization settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StabilizeSettings {
|
||||
/// Whether stabilization is active
|
||||
pub active: bool,
|
||||
/// Smoothness level (0-100)
|
||||
pub smooth: i32,
|
||||
/// Stabilization method (0: auto crop, 1: full frame)
|
||||
pub method: i32,
|
||||
/// Rolling shutter correction
|
||||
pub rsc: bool,
|
||||
/// Reduce motion
|
||||
#[serde(rename = "reduceMotion")]
|
||||
pub reduce_motion: bool,
|
||||
/// Reduce motion iteration count
|
||||
#[serde(rename = "reduceMotionIteration")]
|
||||
pub reduce_motion_iteration: i32,
|
||||
}
|
||||
|
||||
/// Motion blur settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MotionBlurSettings {
|
||||
/// Whether motion blur is active
|
||||
pub active: bool,
|
||||
/// Motion blur model
|
||||
pub model: String,
|
||||
}
|
||||
|
||||
/// Slow motion/frame interpolation settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SlowMotionSettings {
|
||||
/// Whether slow motion is active
|
||||
pub active: bool,
|
||||
/// Slow motion model
|
||||
pub model: String,
|
||||
/// Slow motion factor
|
||||
pub factor: f64,
|
||||
/// Whether to duplicate frames
|
||||
pub duplicate: bool,
|
||||
/// Duplicate frame threshold
|
||||
#[serde(rename = "duplicateThreshold")]
|
||||
pub duplicate_threshold: i32,
|
||||
}
|
||||
|
||||
/// Video enhancement settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EnhanceSettings {
|
||||
/// Whether enhancement is active
|
||||
pub active: bool,
|
||||
/// Enhancement model
|
||||
pub model: String,
|
||||
/// Video type
|
||||
#[serde(rename = "videoType")]
|
||||
pub video_type: i32,
|
||||
/// Auto enhancement level
|
||||
pub auto: i32,
|
||||
/// Field order
|
||||
#[serde(rename = "fieldOrder")]
|
||||
pub field_order: i32,
|
||||
/// Compression artifact reduction
|
||||
pub compress: i32,
|
||||
/// Detail enhancement
|
||||
pub detail: i32,
|
||||
/// Sharpening
|
||||
pub sharpen: i32,
|
||||
/// Noise reduction
|
||||
pub denoise: i32,
|
||||
/// Halo reduction
|
||||
pub dehalo: i32,
|
||||
/// Deblur
|
||||
pub deblur: i32,
|
||||
/// Add noise
|
||||
#[serde(rename = "addNoise")]
|
||||
pub add_noise: i32,
|
||||
/// Recover original detail value
|
||||
#[serde(rename = "recoverOriginalDetailValue")]
|
||||
pub recover_original_detail_value: i32,
|
||||
/// Model flags
|
||||
#[serde(rename = "isArtemis")]
|
||||
pub is_artemis: bool,
|
||||
#[serde(rename = "isGaia")]
|
||||
pub is_gaia: bool,
|
||||
#[serde(rename = "isTheia")]
|
||||
pub is_theia: bool,
|
||||
#[serde(rename = "isProteus")]
|
||||
pub is_proteus: bool,
|
||||
#[serde(rename = "isIris")]
|
||||
pub is_iris: bool,
|
||||
/// Focus fix level (optional, for newer versions)
|
||||
#[serde(rename = "focusFixLevel", skip_serializing_if = "Option::is_none")]
|
||||
pub focus_fix_level: Option<String>,
|
||||
/// Second enhancement flag (optional)
|
||||
#[serde(rename = "isSecondEnhancement", skip_serializing_if = "Option::is_none")]
|
||||
pub is_second_enhancement: Option<bool>,
|
||||
}
|
||||
|
||||
/// Grain effect settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GrainSettings {
|
||||
/// Whether grain is active
|
||||
pub active: bool,
|
||||
/// Grain amount
|
||||
pub grain: i32,
|
||||
/// Grain size
|
||||
#[serde(rename = "grainSize")]
|
||||
pub grain_size: i32,
|
||||
}
|
||||
|
||||
/// Output settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OutputSettings {
|
||||
/// Whether output settings are active
|
||||
pub active: bool,
|
||||
/// Output size method
|
||||
#[serde(rename = "outSizeMethod")]
|
||||
pub out_size_method: i32,
|
||||
/// Whether to crop to fit
|
||||
#[serde(rename = "cropToFit")]
|
||||
pub crop_to_fit: bool,
|
||||
/// Output pixel aspect ratio
|
||||
#[serde(rename = "outputPAR")]
|
||||
pub output_par: i32,
|
||||
/// Output frame rate (0 = same as input)
|
||||
#[serde(rename = "outFPS")]
|
||||
pub out_fps: f64,
|
||||
/// Custom resolution priority (optional)
|
||||
#[serde(rename = "customResolutionPriority", skip_serializing_if = "Option::is_none")]
|
||||
pub custom_resolution_priority: Option<i32>,
|
||||
/// Lock aspect ratio (optional)
|
||||
#[serde(rename = "lockAspectRatio", skip_serializing_if = "Option::is_none")]
|
||||
pub lock_aspect_ratio: Option<bool>,
|
||||
|
||||
// Extended FFmpeg output parameters
|
||||
/// Custom output width (overrides size method if set)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub width: Option<i32>,
|
||||
/// Custom output height (overrides size method if set)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub height: Option<i32>,
|
||||
/// Video codec (e.g., "libx264", "hevc_nvenc", "h264_amf")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub codec: Option<String>,
|
||||
/// Video bitrate in kbps (e.g., 5000 for 5Mbps)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bitrate: Option<i32>,
|
||||
/// Constant Rate Factor for quality-based encoding (0-51, lower = better quality)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub crf: Option<i32>,
|
||||
/// Encoding preset (e.g., "ultrafast", "fast", "medium", "slow", "veryslow")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preset: Option<String>,
|
||||
/// Video profile (e.g., "baseline", "main", "high", "main10")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<String>,
|
||||
/// Video level (e.g., "3.1", "4.0", "5.1")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub level: Option<String>,
|
||||
/// GOP size (keyframe interval)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gop_size: Option<i32>,
|
||||
/// B-frames count
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub b_frames: Option<i32>,
|
||||
/// Audio codec (e.g., "aac", "mp3", "copy")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audio_codec: Option<String>,
|
||||
/// Audio bitrate in kbps
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audio_bitrate: Option<i32>,
|
||||
/// Audio sample rate in Hz
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audio_sample_rate: Option<i32>,
|
||||
/// Audio channels count
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub audio_channels: Option<i32>,
|
||||
/// Container format (e.g., "mp4", "mkv", "avi")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub format: Option<String>,
|
||||
/// Color space (e.g., "bt709", "bt2020nc")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub colorspace: Option<String>,
|
||||
/// Color primaries (e.g., "bt709", "bt2020")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color_primaries: Option<String>,
|
||||
/// Transfer characteristics (e.g., "bt709", "smpte2084")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color_trc: Option<String>,
|
||||
/// Pixel format (e.g., "yuv420p", "yuv420p10le")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pixel_format: Option<String>,
|
||||
/// Hardware acceleration method (e.g., "cuda", "opencl", "qsv")
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hwaccel: Option<String>,
|
||||
/// GPU device index for hardware acceleration
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gpu_device: Option<String>,
|
||||
/// Two-pass encoding
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub two_pass: Option<bool>,
|
||||
/// Custom FFmpeg parameters
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub custom_params: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Filter manager settings (for newer template versions)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FilterManagerSettings {
|
||||
/// Second enhancement enabled
|
||||
#[serde(rename = "SecondEnhancementEnabled")]
|
||||
pub second_enhancement_enabled: bool,
|
||||
/// Second enhancement intermediate resolution
|
||||
#[serde(rename = "SecondEnhancementIntermediateResolution")]
|
||||
pub second_enhancement_intermediate_resolution: i32,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
/// Load template from JSON string
|
||||
pub fn from_json(json: &str) -> Result<Self, TvaiError> {
|
||||
serde_json::from_str(json).map_err(TvaiError::JsonParseError)
|
||||
}
|
||||
|
||||
/// Load template from file
|
||||
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self, TvaiError> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
Self::from_json(&content)
|
||||
}
|
||||
|
||||
/// Convert template to JSON string
|
||||
pub fn to_json(&self) -> Result<String, TvaiError> {
|
||||
serde_json::to_string_pretty(self).map_err(TvaiError::JsonSerializeError)
|
||||
}
|
||||
|
||||
/// Save template to file
|
||||
pub fn save_to_file<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), TvaiError> {
|
||||
let json = self.to_json()?;
|
||||
std::fs::write(path, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate and complete template structure with default values
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate required fields
|
||||
if self.name.is_empty() {
|
||||
return Err(TvaiError::ValidationError("Template name cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
// Set default values for missing optional fields
|
||||
if self.author.is_empty() {
|
||||
self.author = "Unknown".to_string();
|
||||
}
|
||||
|
||||
if self.date.is_empty() {
|
||||
self.date = chrono::Utc::now().format("%a %b %d %H:%M:%S %Y GMT%z").to_string();
|
||||
}
|
||||
|
||||
if self.veai_version.is_empty() {
|
||||
self.veai_version = "5.1.2".to_string();
|
||||
}
|
||||
|
||||
// Validate and complete settings
|
||||
self.settings.validate_and_complete()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if template is valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.name.is_empty() && self.settings.is_valid()
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateSettings {
|
||||
/// Validate and complete template settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate and complete stabilize settings
|
||||
self.stabilize.validate_and_complete()?;
|
||||
|
||||
// Validate and complete motion blur settings
|
||||
self.motion_blur.validate_and_complete()?;
|
||||
|
||||
// Validate and complete slow motion settings
|
||||
self.slow_motion.validate_and_complete()?;
|
||||
|
||||
// Validate and complete enhance settings
|
||||
self.enhance.validate_and_complete()?;
|
||||
|
||||
// Validate and complete grain settings
|
||||
self.grain.validate_and_complete()?;
|
||||
|
||||
// Validate and complete output settings
|
||||
self.output.validate_and_complete()?;
|
||||
|
||||
// Validate and complete filter manager settings if present
|
||||
if let Some(ref mut fm) = self.filter_manager {
|
||||
fm.validate_and_complete()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.stabilize.is_valid() &&
|
||||
self.motion_blur.is_valid() &&
|
||||
self.slow_motion.is_valid() &&
|
||||
self.enhance.is_valid() &&
|
||||
self.grain.is_valid() &&
|
||||
self.output.is_valid()
|
||||
}
|
||||
|
||||
/// Create default template settings
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
stabilize: StabilizeSettings::default(),
|
||||
motion_blur: MotionBlurSettings::default(),
|
||||
slow_motion: SlowMotionSettings::default(),
|
||||
enhance: EnhanceSettings::default(),
|
||||
grain: GrainSettings::default(),
|
||||
output: OutputSettings::default(),
|
||||
filter_manager: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StabilizeSettings {
|
||||
/// Validate and complete stabilize settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate smooth range
|
||||
if self.smooth < 0 || self.smooth > 100 {
|
||||
self.smooth = self.smooth.clamp(0, 100);
|
||||
}
|
||||
|
||||
// Validate method
|
||||
if self.method < 0 || self.method > 1 {
|
||||
self.method = 1; // Default to full frame
|
||||
}
|
||||
|
||||
// Validate reduce motion iteration
|
||||
if self.reduce_motion_iteration < 1 {
|
||||
self.reduce_motion_iteration = 2;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.smooth >= 0 && self.smooth <= 100 &&
|
||||
(self.method == 0 || self.method == 1) &&
|
||||
self.reduce_motion_iteration >= 1
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StabilizeSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: false,
|
||||
smooth: 50,
|
||||
method: 1,
|
||||
rsc: false,
|
||||
reduce_motion: false,
|
||||
reduce_motion_iteration: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MotionBlurSettings {
|
||||
/// Validate and complete motion blur settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Set default model if empty
|
||||
if self.model.is_empty() {
|
||||
self.model = "thm-2".to_string();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
!self.model.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MotionBlurSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: false,
|
||||
model: "thm-2".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlowMotionSettings {
|
||||
/// Validate and complete slow motion settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate model when active
|
||||
if self.active && self.model.is_empty() {
|
||||
self.model = "apo-8".to_string();
|
||||
}
|
||||
|
||||
// Validate factor range
|
||||
if self.factor <= 0.0 {
|
||||
self.factor = 1.0;
|
||||
}
|
||||
|
||||
// Validate duplicate threshold
|
||||
if self.duplicate_threshold < 0 {
|
||||
self.duplicate_threshold = 10;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
(!self.active || !self.model.is_empty()) &&
|
||||
self.factor > 0.0 &&
|
||||
self.duplicate_threshold >= 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SlowMotionSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: false,
|
||||
model: "apo-8".to_string(),
|
||||
factor: 1.0,
|
||||
duplicate: true,
|
||||
duplicate_threshold: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EnhanceSettings {
|
||||
/// Validate and complete enhance settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate model when active
|
||||
if self.active && self.model.is_empty() {
|
||||
self.model = "prob-4".to_string();
|
||||
}
|
||||
|
||||
// Validate video type
|
||||
if self.video_type < 0 || self.video_type > 2 {
|
||||
self.video_type = 1; // Default to progressive
|
||||
}
|
||||
|
||||
// Validate field order
|
||||
if self.field_order < 0 || self.field_order > 2 {
|
||||
self.field_order = 0; // Default to auto
|
||||
}
|
||||
|
||||
// Validate parameter ranges (-100 to 100 typically)
|
||||
self.compress = self.compress.clamp(-100, 100);
|
||||
self.detail = self.detail.clamp(-100, 100);
|
||||
self.sharpen = self.sharpen.clamp(-100, 100);
|
||||
self.denoise = self.denoise.clamp(-100, 100);
|
||||
self.dehalo = self.dehalo.clamp(-100, 100);
|
||||
self.deblur = self.deblur.clamp(-100, 100);
|
||||
self.add_noise = self.add_noise.clamp(-100, 100);
|
||||
|
||||
// Validate recover original detail value
|
||||
self.recover_original_detail_value = self.recover_original_detail_value.clamp(0, 100);
|
||||
|
||||
// Set default focus fix level if None
|
||||
if self.focus_fix_level.is_none() {
|
||||
self.focus_fix_level = Some("Off".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
(!self.active || !self.model.is_empty()) &&
|
||||
self.video_type >= 0 && self.video_type <= 2 &&
|
||||
self.field_order >= 0 && self.field_order <= 2
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EnhanceSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: false,
|
||||
model: "prob-4".to_string(),
|
||||
video_type: 1,
|
||||
auto: 0,
|
||||
field_order: 0,
|
||||
compress: 0,
|
||||
detail: 0,
|
||||
sharpen: 0,
|
||||
denoise: 0,
|
||||
dehalo: 0,
|
||||
deblur: 0,
|
||||
add_noise: 0,
|
||||
recover_original_detail_value: 20,
|
||||
is_artemis: false,
|
||||
is_gaia: false,
|
||||
is_theia: false,
|
||||
is_proteus: true,
|
||||
is_iris: false,
|
||||
focus_fix_level: Some("Off".to_string()),
|
||||
is_second_enhancement: Some(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GrainSettings {
|
||||
/// Validate and complete grain settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate grain amount
|
||||
if self.grain < 0 || self.grain > 100 {
|
||||
self.grain = self.grain.clamp(0, 100);
|
||||
}
|
||||
|
||||
// Validate grain size
|
||||
if self.grain_size < 1 || self.grain_size > 10 {
|
||||
self.grain_size = self.grain_size.clamp(1, 10);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.grain >= 0 && self.grain <= 100 &&
|
||||
self.grain_size >= 1 && self.grain_size <= 10
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GrainSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: false,
|
||||
grain: 5,
|
||||
grain_size: 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputSettings {
|
||||
/// Validate and complete output settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate output size method
|
||||
if self.out_size_method < 0 || self.out_size_method > 10 {
|
||||
self.out_size_method = 0; // Default to original size
|
||||
}
|
||||
|
||||
// Validate output PAR
|
||||
if self.output_par < 0 {
|
||||
self.output_par = 0; // Default to auto
|
||||
}
|
||||
|
||||
// Validate output FPS
|
||||
if self.out_fps < 0.0 {
|
||||
self.out_fps = 0.0; // Default to original FPS
|
||||
}
|
||||
|
||||
// Set default values for optional fields
|
||||
if self.custom_resolution_priority.is_none() {
|
||||
self.custom_resolution_priority = Some(0);
|
||||
}
|
||||
|
||||
if self.lock_aspect_ratio.is_none() {
|
||||
self.lock_aspect_ratio = Some(true);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.out_size_method >= 0 && self.out_size_method <= 10 &&
|
||||
self.output_par >= 0 &&
|
||||
self.out_fps >= 0.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OutputSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: true,
|
||||
out_size_method: 0,
|
||||
crop_to_fit: false,
|
||||
output_par: 0,
|
||||
out_fps: 0.0,
|
||||
custom_resolution_priority: Some(0),
|
||||
lock_aspect_ratio: Some(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterManagerSettings {
|
||||
/// Validate and complete filter manager settings
|
||||
pub fn validate_and_complete(&mut self) -> Result<(), TvaiError> {
|
||||
// Validate intermediate resolution
|
||||
if self.second_enhancement_intermediate_resolution < 0 || self.second_enhancement_intermediate_resolution > 10 {
|
||||
self.second_enhancement_intermediate_resolution = 3;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if settings are valid
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.second_enhancement_intermediate_resolution >= 0 && self.second_enhancement_intermediate_resolution <= 10
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FilterManagerSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
second_enhancement_enabled: false,
|
||||
second_enhancement_intermediate_resolution: 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user