feat: add template json

This commit is contained in:
imeepos
2025-08-15 13:41:17 +08:00
parent c37fff0d09
commit 247eebef9e
261 changed files with 89699 additions and 0 deletions

View 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>;

View 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
View 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);
}
}

View 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
}
}

View 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,
}
}
}