feat: fix bug

This commit is contained in:
imeepos
2025-08-15 14:12:16 +08:00
parent 81b80eb911
commit d8d5fdedce
4 changed files with 481 additions and 47 deletions

View File

@@ -38,3 +38,7 @@ path = "examples/ffmpeg_generation.rs"
[[example]]
name = "advanced_output_settings"
path = "examples/advanced_output_settings.rs"
[[example]]
name = "output_settings_demo"
path = "examples/output_settings_demo.rs"

View File

@@ -0,0 +1,159 @@
use tvai_sdk::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Topaz Video AI SDK - Output Settings Demo");
println!("========================================");
let sdk = TvaiSdk::new();
// Example 1: Social Media Square with Crop to Fit
println!("\n1. Social Media Square Template (Crop to Fit):");
let social_template = TemplatePresets::social_media_square()?;
println!(" Template: {}", social_template.name);
println!(" Description: {}", social_template.description);
println!(" Settings:");
println!(" Resolution: {}x{}",
social_template.settings.output.width.unwrap_or(0),
social_template.settings.output.height.unwrap_or(0)
);
println!(" Crop to fit: {}", social_template.settings.output.crop_to_fit);
println!(" Lock aspect ratio: {}", social_template.settings.output.lock_aspect_ratio.unwrap_or(true));
println!(" Custom resolution priority: {}", social_template.settings.output.custom_resolution_priority.unwrap_or(0));
let social_command = sdk.generate_ffmpeg_command(&social_template, "input_16x9.mp4", "output_square.mp4")?;
println!(" Command:");
println!(" {}", social_command);
// Example 2: Letterbox Preserve Aspect Ratio
println!("\n2. Letterbox Preserve Template (Padding):");
let letterbox_template = TemplatePresets::letterbox_preserve()?;
println!(" Template: {}", letterbox_template.name);
println!(" Settings:");
println!(" Resolution: {}x{}",
letterbox_template.settings.output.width.unwrap_or(0),
letterbox_template.settings.output.height.unwrap_or(0)
);
println!(" Crop to fit: {}", letterbox_template.settings.output.crop_to_fit);
println!(" Lock aspect ratio: {}", letterbox_template.settings.output.lock_aspect_ratio.unwrap_or(true));
let letterbox_command = sdk.generate_ffmpeg_command(&letterbox_template, "input_4x3.mp4", "output_letterbox.mp4")?;
println!(" Command:");
println!(" {}", letterbox_command);
// Example 3: Minimal Processing (Output Settings Disabled)
println!("\n3. Minimal Processing Template (Output Settings Disabled):");
let minimal_template = TemplatePresets::minimal_processing()?;
println!(" Template: {}", minimal_template.name);
println!(" Settings:");
println!(" Output active: {}", minimal_template.settings.output.active);
let minimal_command = sdk.generate_ffmpeg_command(&minimal_template, "input.mp4", "output_minimal.mp4")?;
println!(" Command:");
println!(" {}", minimal_command);
// Example 4: Custom Template with Advanced Output Settings
println!("\n4. Custom Template with Advanced Output Settings:");
let custom_template = TemplateBuilder::new("Advanced Output Demo")
.description("Demonstrates all output settings parameters")
.enable_enhancement("prob-4")
.enhancement_params(25, 20, 10)
.resolution(1280, 720) // 720p target
.video_codec("libx264", Some(24))
.preset("medium")
.custom_resolution_priority(1) // High priority for custom resolution
.lock_aspect_ratio(true) // Preserve aspect ratio
.crop_to_fit(false) // Use padding
.pixel_aspect_ratio(2) // 16:9 aspect ratio
.audio_settings("aac", 128, 2)
.build()?;
println!(" Template: {}", custom_template.name);
println!(" Settings:");
println!(" Resolution: {}x{}",
custom_template.settings.output.width.unwrap_or(0),
custom_template.settings.output.height.unwrap_or(0)
);
println!(" Custom resolution priority: {}", custom_template.settings.output.custom_resolution_priority.unwrap_or(0));
println!(" Lock aspect ratio: {}", custom_template.settings.output.lock_aspect_ratio.unwrap_or(true));
println!(" Crop to fit: {}", custom_template.settings.output.crop_to_fit);
println!(" Pixel aspect ratio: {}", custom_template.settings.output.output_par);
let custom_command = sdk.generate_ffmpeg_command(&custom_template, "input.mp4", "output_custom.mp4")?;
println!(" Command:");
println!(" {}", custom_command);
// Example 5: Comparison of Different Resolution Priorities
println!("\n5. Resolution Priority Comparison:");
// Priority 0: Normal (custom resolution used)
let priority0_template = TemplateBuilder::new("Priority 0")
.enable_enhancement("prob-4")
.resolution(1920, 1080)
.output_settings(6, 0.0) // FHD size method
.custom_resolution_priority(0) // Normal priority
.build()?;
// Priority 2: Size method has priority
let priority2_template = TemplateBuilder::new("Priority 2")
.enable_enhancement("prob-4")
.resolution(1920, 1080) // This will be ignored
.output_settings(5, 0.0) // QHD size method takes priority
.custom_resolution_priority(2) // Size method priority
.build()?;
let priority0_command = sdk.generate_ffmpeg_command(&priority0_template, "input.mp4", "output_p0.mp4")?;
let priority2_command = sdk.generate_ffmpeg_command(&priority2_template, "input.mp4", "output_p2.mp4")?;
println!(" Priority 0 (Custom resolution used):");
println!(" {}", priority0_command);
println!("\n Priority 2 (Size method used - 2560x1440):");
println!(" {}", priority2_command);
// Example 6: Aspect Ratio Handling Comparison
println!("\n6. Aspect Ratio Handling Comparison:");
// Stretch to fit (ignore aspect ratio)
let stretch_template = TemplateBuilder::new("Stretch to Fit")
.enable_enhancement("prob-4")
.resolution(1920, 1080)
.lock_aspect_ratio(false) // Allow stretching
.build()?;
// Crop to fit (maintain aspect ratio, crop excess)
let crop_template = TemplateBuilder::new("Crop to Fit")
.enable_enhancement("prob-4")
.resolution(1920, 1080)
.lock_aspect_ratio(true)
.crop_to_fit(true) // Crop excess
.build()?;
// Pad to fit (maintain aspect ratio, add padding)
let pad_template = TemplateBuilder::new("Pad to Fit")
.enable_enhancement("prob-4")
.resolution(1920, 1080)
.lock_aspect_ratio(true)
.crop_to_fit(false) // Add padding
.build()?;
let stretch_command = sdk.generate_ffmpeg_command(&stretch_template, "input.mp4", "output_stretch.mp4")?;
let crop_command = sdk.generate_ffmpeg_command(&crop_template, "input.mp4", "output_crop.mp4")?;
let pad_command = sdk.generate_ffmpeg_command(&pad_template, "input.mp4", "output_pad.mp4")?;
println!(" Stretch to fit (ignore aspect ratio):");
println!(" {}", stretch_command);
println!("\n Crop to fit (maintain aspect ratio, crop):");
println!(" {}", crop_command);
println!("\n Pad to fit (maintain aspect ratio, pad):");
println!(" {}", pad_command);
println!("\n✅ Output settings demo completed successfully!");
println!("\nKey Features Demonstrated:");
println!(" • Output settings active/inactive control");
println!(" • Custom resolution priority handling");
println!(" • Aspect ratio locking and preservation");
println!(" • Crop vs pad behavior for aspect ratio mismatches");
println!(" • Pixel aspect ratio configuration");
println!(" • Resolution priority vs size method priority");
Ok(())
}

View File

@@ -22,12 +22,26 @@ impl FfmpegCommandGenerator {
/// Initialize model mappings from template models to FFmpeg filter models
fn init_model_mappings(&mut self) {
// Enhancement models
// Enhancement models - Proteus series (general purpose)
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());
// Enhancement models - Specialized series
self.model_mappings.insert("nyx-3".to_string(), "nyx-3".to_string()); // Iris series (low light/noise)
self.model_mappings.insert("hyp-1".to_string(), "hyp-1".to_string()); // Hyperion for HDR
// Artemis series (animation/cartoon)
self.model_mappings.insert("art-1".to_string(), "art-1".to_string());
self.model_mappings.insert("art-2".to_string(), "art-2".to_string());
// Gaia series (natural scenes)
self.model_mappings.insert("gai-1".to_string(), "gai-1".to_string());
self.model_mappings.insert("gai-2".to_string(), "gai-2".to_string());
// Theia series (detail recovery)
self.model_mappings.insert("the-1".to_string(), "the-1".to_string());
self.model_mappings.insert("the-2".to_string(), "the-2".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());
@@ -79,6 +93,14 @@ impl FfmpegCommandGenerator {
filters.push(grain_filter);
}
// Handle second enhancement if filter manager is present
if let Some(ref filter_manager) = settings.filter_manager {
if filter_manager.second_enhancement_enabled && settings.enhance.active {
let second_enhance_filter = self.generate_second_enhancement_filter(&settings.enhance, filter_manager)?;
filters.push(second_enhance_filter);
}
}
// Build the complete FFmpeg command
let mut command = format!("ffmpeg -i \"{}\"", input_file);
@@ -103,22 +125,21 @@ impl FfmpegCommandGenerator {
// 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: no crop/full frame)
let method = if settings.method == 0 { "auto_crop" } else { "no_crop" };
params.push(format!("method={}", method));
// Map method (0: auto crop, 1: full frame)
let full = if settings.method == 1 { 1 } else { 0 };
params.push(format!("full={}", full));
// Map smoothness (0-100 in template)
params.push(format!("smooth={}", settings.smooth));
// Rolling shutter correction
if settings.rsc {
params.push("roll=1".to_string());
params.push("rsc=1".to_string());
}
// Reduce motion
if settings.reduce_motion {
params.push(format!("reduce={}", settings.reduce_motion_iteration));
params.push(format!("reduce_motion={}", settings.reduce_motion_iteration));
}
Ok(format!("tvai_stb={}", params.join(":")))
@@ -133,7 +154,10 @@ impl FfmpegCommandGenerator {
.ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", settings.model)))?;
params.push(format!("model={}", model));
// Map enhancement parameters to tvai_up parameters
// Add scale parameter (default to no scaling unless specified)
params.push("scale=0".to_string());
// Map enhancement parameters to tvai_up parameters (using normalized values 0.0-1.0)
if settings.compress != 0 {
let compression = settings.compress as f64 / 100.0;
params.push(format!("compression={:.2}", compression));
@@ -164,6 +188,42 @@ impl FfmpegCommandGenerator {
params.push(format!("preblur={:.2}", preblur));
}
// Add video type parameter if not progressive
if settings.video_type != 1 {
match settings.video_type {
0 => params.push("interlaced=1".to_string()),
2 => params.push("telecine=1".to_string()),
_ => {}
}
}
// Add field order if specified
if settings.field_order != 0 {
match settings.field_order {
1 => params.push("field_order=tff".to_string()),
2 => params.push("field_order=bff".to_string()),
_ => {}
}
}
// Add auto enhancement level if specified
if settings.auto != 0 {
let auto_level = settings.auto as f64 / 100.0;
params.push(format!("auto={:.2}", auto_level));
}
// Add add_noise parameter if specified
if settings.add_noise != 0 {
let add_noise = settings.add_noise as f64 / 100.0;
params.push(format!("add_noise={:.2}", add_noise));
}
// Add recover original detail if not default
if settings.recover_original_detail_value != 20 {
let recover = settings.recover_original_detail_value as f64 / 100.0;
params.push(format!("recover_detail={:.2}", recover));
}
Ok(format!("tvai_up={}", params.join(":")))
}
@@ -186,10 +246,10 @@ impl FfmpegCommandGenerator {
params.push(format!("fps={}", output_settings.out_fps));
}
// Duplicate frame threshold
// Duplicate frame threshold (rdt parameter)
if !slowmo_settings.duplicate {
params.push("rdt=-0.01".to_string()); // Disable duplicate frame removal
} else {
} else if slowmo_settings.duplicate_threshold != 10 { // Only add if not default
let threshold = slowmo_settings.duplicate_threshold as f64 / 1000.0;
params.push(format!("rdt={:.3}", threshold));
}
@@ -197,45 +257,186 @@ impl FfmpegCommandGenerator {
Ok(format!("tvai_fi={}", params.join(":")))
}
/// Generate grain filter (approximation using FFmpeg noise filter)
/// Generate motion blur filter (tvai_mb)
fn generate_motion_blur_filter(&self, settings: &crate::MotionBlurSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// Map model
let model = self.model_mappings.get(&settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown motion blur model: {}", settings.model)))?;
params.push(format!("model={}", model));
Ok(format!("tvai_mb={}", params.join(":")))
}
/// Generate grain filter (tvai_grain or noise approximation)
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))
// Try to use tvai_grain if available, otherwise fall back to noise filter
let mut params = Vec::new();
// Grain amount (0-100 scale)
let amount = settings.grain as f64 / 100.0;
params.push(format!("amount={:.2}", amount));
// Grain size (1-10 scale)
params.push(format!("size={}", settings.grain_size));
// Use tvai_grain filter
Ok(format!("tvai_grain={}", params.join(":")))
}
/// Generate second enhancement filter
fn generate_second_enhancement_filter(&self, enhance_settings: &crate::EnhanceSettings, filter_manager: &crate::FilterManagerSettings) -> TvaiResult<String> {
let mut params = Vec::new();
// Use the same model as primary enhancement
let model = self.model_mappings.get(&enhance_settings.model)
.ok_or_else(|| TvaiError::ModelMappingError(format!("Unknown enhancement model: {}", enhance_settings.model)))?;
params.push(format!("model={}", model));
// Set intermediate resolution scale
let intermediate_scale = match filter_manager.second_enhancement_intermediate_resolution {
0 => 0.5, // Half resolution
1 => 0.75, // 3/4 resolution
2 => 1.0, // Same resolution
3 => 1.5, // 1.5x resolution
4 => 2.0, // 2x resolution
_ => 1.0, // Default to same resolution
};
params.push(format!("scale={}", intermediate_scale));
// Apply lighter enhancement parameters for second pass
if enhance_settings.denoise != 0 {
let noise = (enhance_settings.denoise as f64 / 100.0) * 0.5; // Reduce by half
params.push(format!("noise={:.2}", noise));
}
if enhance_settings.detail != 0 {
let details = (enhance_settings.detail as f64 / 100.0) * 0.5; // Reduce by half
params.push(format!("details={:.2}", details));
}
Ok(format!("tvai_up={}", params.join(":")))
}
/// Generate output settings
fn generate_output_settings(&self, settings: &crate::OutputSettings) -> TvaiResult<String> {
let mut output_params = Vec::new();
// Check if output settings are active
if !settings.active {
// If output settings are not active, use minimal default settings
output_params.push("-c:v libx264".to_string());
output_params.push("-c:a aac".to_string());
return Ok(format!(" {}", output_params.join(" ")));
}
// Hardware acceleration
if let Some(ref hwaccel) = settings.hwaccel {
output_params.push(format!("-hwaccel {}", hwaccel));
}
// Custom resolution takes priority over size method
if let (Some(width), Some(height)) = (settings.width, settings.height) {
output_params.push(format!("-s {}x{}", width, height));
// Handle resolution based on priority settings
let use_custom_resolution = if let (Some(_width), Some(_height)) = (settings.width, settings.height) {
// Check custom resolution priority
let priority = settings.custom_resolution_priority.unwrap_or(0);
match priority {
0 => true, // Custom resolution has normal priority
1 => true, // Custom resolution has high priority
2 => false, // Size method has priority over custom resolution
_ => true, // Default to using custom resolution
}
} else {
// Handle output size method
false
};
if use_custom_resolution {
let width = settings.width.unwrap();
let height = settings.height.unwrap();
// Handle aspect ratio locking
if settings.lock_aspect_ratio.unwrap_or(true) {
// Use scale filter to maintain aspect ratio
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease", width, height));
// Add padding if needed to reach exact dimensions
if settings.crop_to_fit {
// Crop to fit exact dimensions
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=increase,crop={}:{}", width, height, width, height));
} else {
// Pad to fit exact dimensions
output_params.push(format!("-vf scale={}:{}:force_original_aspect_ratio=decrease,pad={}:{}:(ow-iw)/2:(oh-ih)/2", width, height, width, height));
}
} else {
// Stretch to exact dimensions (ignore aspect ratio)
output_params.push(format!("-s {}x{}", width, height));
}
} else {
// Handle output size method (based on Topaz Video AI size methods)
match settings.out_size_method {
7 => {
// 4K output
output_params.push("-s 3840x2160".to_string());
0 => {
// Original size - no scaling
},
6 => {
// FHD output
1 => {
// SD (480p)
output_params.push("-s 720x480".to_string());
},
2 => {
// DVD (576p)
output_params.push("-s 720x576".to_string());
},
3 => {
// HD Ready (720p)
output_params.push("-s 1280x720".to_string());
},
4 => {
// HD (1080p)
output_params.push("-s 1920x1080".to_string());
},
5 => {
// HD output
output_params.push("-s 1280x720".to_string());
// QHD (1440p)
output_params.push("-s 2560x1440".to_string());
},
6 => {
// FHD (1920x1080) - same as 4
output_params.push("-s 1920x1080".to_string());
},
7 => {
// 4K UHD (2160p)
output_params.push("-s 3840x2160".to_string());
},
8 => {
// 8K UHD (4320p)
output_params.push("-s 7680x4320".to_string());
},
_ => {
// Keep original size (method 0) or other methods
// Unknown method - keep original size
}
}
}
// Handle crop to fit for size method outputs
if settings.crop_to_fit && !use_custom_resolution {
// Apply crop to fit for size method outputs
if let Some(last_param) = output_params.last_mut() {
if last_param.starts_with("-s ") {
let size = &last_param[3..];
// Replace simple scale with scale+crop
*last_param = format!("-vf scale={}:force_original_aspect_ratio=increase,crop={}", size, size);
}
}
}
// Handle pixel aspect ratio
if settings.output_par != 0 {
match settings.output_par {
1 => output_params.push("-aspect 4:3".to_string()),
2 => output_params.push("-aspect 16:9".to_string()),
3 => output_params.push("-aspect 1:1".to_string()),
_ => {}
}
}
// Frame rate
if settings.out_fps > 0.0 {
output_params.push(format!("-r {}", settings.out_fps));

View File

@@ -325,6 +325,36 @@ impl TemplateBuilder {
self
}
/// Set output settings active state
pub fn output_active(mut self, active: bool) -> Self {
self.template.settings.output.active = active;
self
}
/// Set custom resolution priority
pub fn custom_resolution_priority(mut self, priority: i32) -> Self {
self.template.settings.output.custom_resolution_priority = Some(priority);
self
}
/// Set aspect ratio locking
pub fn lock_aspect_ratio(mut self, lock: bool) -> Self {
self.template.settings.output.lock_aspect_ratio = Some(lock);
self
}
/// Enable crop to fit
pub fn crop_to_fit(mut self, crop: bool) -> Self {
self.template.settings.output.crop_to_fit = crop;
self
}
/// Set pixel aspect ratio
pub fn pixel_aspect_ratio(mut self, par: i32) -> Self {
self.template.settings.output.output_par = par;
self
}
/// Enable grain effect
pub fn enable_grain(mut self, amount: i32, size: i32) -> Self {
self.template.settings.grain.active = true;
@@ -477,6 +507,46 @@ impl TemplatePresets {
.two_pass(true)
.build()
}
/// Create a social media template with aspect ratio handling
pub fn social_media_square() -> Result<Template, TvaiError> {
TemplateBuilder::new("Social Media Square")
.description("Square format for social media with smart cropping")
.enable_enhancement("prob-4")
.enhancement_params(20, 25, 15) // Moderate enhancement
.resolution(1080, 1080) // Square format
.video_codec("libx264", Some(25))
.preset("fast") // Quick encoding for social media
.crop_to_fit(true) // Crop to exact square
.lock_aspect_ratio(false) // Allow cropping
.custom_resolution_priority(1) // High priority for custom resolution
.pixel_aspect_ratio(2) // 16:9 source handling
.audio_settings("aac", 128, 2)
.build()
}
/// Create a letterbox template that preserves aspect ratio
pub fn letterbox_preserve() -> Result<Template, TvaiError> {
TemplateBuilder::new("Letterbox Preserve")
.description("Preserve aspect ratio with letterboxing")
.enable_enhancement("prob-4")
.resolution(1920, 1080) // Target FHD
.video_codec("libx264", Some(23))
.lock_aspect_ratio(true) // Preserve aspect ratio
.crop_to_fit(false) // Use padding instead of cropping
.custom_resolution_priority(0) // Normal priority
.build()
}
/// Create a template with output settings disabled
pub fn minimal_processing() -> Result<Template, TvaiError> {
TemplateBuilder::new("Minimal Processing")
.description("Minimal processing with basic output")
.enable_enhancement("prob-4")
.enhancement_params(10, 5, 0) // Very light processing
.output_active(false) // Disable advanced output settings
.build()
}
}
impl Default for TvaiSdk {