feat: remote unuse json
This commit is contained in:
@@ -34,3 +34,7 @@ path = "examples/template_management.rs"
|
||||
[[example]]
|
||||
name = "ffmpeg_generation"
|
||||
path = "examples/ffmpeg_generation.rs"
|
||||
|
||||
[[example]]
|
||||
name = "advanced_output_settings"
|
||||
path = "examples/advanced_output_settings.rs"
|
||||
|
||||
@@ -70,7 +70,11 @@ let custom_template = TemplateBuilder::new("My Custom Template")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(30, 20, 10) // denoise, detail, sharpen
|
||||
.enable_stabilization(60, 1) // smoothness, method
|
||||
.output_settings(6, 60.0) // FHD, 60fps
|
||||
.resolution(1920, 1080) // Custom resolution
|
||||
.video_codec("hevc_nvenc", Some(20)) // H.265 NVENC with CRF 20
|
||||
.preset("slow") // Encoding preset
|
||||
.audio_settings("aac", 192, 2) // AAC 192kbps stereo
|
||||
.hardware_acceleration("cuda", Some("0")) // GPU acceleration
|
||||
.build()?;
|
||||
|
||||
sdk.add_template(custom_template)?;
|
||||
@@ -166,6 +170,7 @@ Templates are JSON files with the following structure:
|
||||
|
||||
The SDK includes several built-in template presets:
|
||||
|
||||
### Basic Presets
|
||||
- `TemplatePresets::upscale_to_4k()` - Upscale video to 4K resolution
|
||||
- `TemplatePresets::convert_to_60fps()` - Convert video to 60 FPS
|
||||
- `TemplatePresets::remove_noise()` - Remove noise using AI
|
||||
@@ -173,6 +178,61 @@ The SDK includes several built-in template presets:
|
||||
- `TemplatePresets::slow_motion_4x()` - Create 4x slow motion effect
|
||||
- `TemplatePresets::comprehensive_enhancement()` - Complete enhancement
|
||||
|
||||
### Advanced Presets
|
||||
- `TemplatePresets::high_quality_nvenc()` - High-quality NVIDIA NVENC encoding
|
||||
- `TemplatePresets::hdr_processing()` - HDR content processing with proper color settings
|
||||
- `TemplatePresets::streaming_optimized()` - Optimized for live streaming
|
||||
- `TemplatePresets::mobile_optimized()` - Small file sizes for mobile devices
|
||||
- `TemplatePresets::archival_quality()` - Maximum quality for archival purposes
|
||||
|
||||
## Extended Output Settings
|
||||
|
||||
The SDK supports comprehensive FFmpeg output configuration:
|
||||
|
||||
### Video Settings
|
||||
- **Custom Resolution**: Set exact width and height
|
||||
- **Video Codecs**: H.264, H.265, AV1, and hardware-accelerated variants
|
||||
- **Quality Control**: CRF (Constant Rate Factor) or bitrate-based encoding
|
||||
- **Encoding Presets**: ultrafast, fast, medium, slow, veryslow
|
||||
- **Profiles & Levels**: baseline, main, high, main10, etc.
|
||||
- **Advanced Parameters**: GOP size, B-frames, pixel format
|
||||
|
||||
### Audio Settings
|
||||
- **Audio Codecs**: AAC, MP3, FLAC, Opus, etc.
|
||||
- **Quality**: Bitrate, sample rate, channel configuration
|
||||
- **Lossless Support**: FLAC and ALAC with proper validation
|
||||
|
||||
### Color & HDR Support
|
||||
- **Color Spaces**: bt709, bt2020nc, etc.
|
||||
- **Color Primaries**: bt709, bt2020, etc.
|
||||
- **Transfer Characteristics**: bt709, smpte2084 (HDR10), etc.
|
||||
- **Pixel Formats**: yuv420p, yuv420p10le (10-bit), etc.
|
||||
|
||||
### Hardware Acceleration
|
||||
- **Methods**: CUDA, OpenCL, QSV, AMF
|
||||
- **GPU Selection**: Specify device index for multi-GPU systems
|
||||
- **Codec-Specific**: NVENC, AMF, QSV variants
|
||||
|
||||
### Example: Advanced Template
|
||||
```rust
|
||||
let advanced_template = TemplateBuilder::new("Professional 4K HDR")
|
||||
.description("Professional 4K HDR encoding")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(3840, 2160) // 4K
|
||||
.video_codec("hevc_nvenc", Some(16)) // High quality H.265
|
||||
.preset("slow") // Best compression
|
||||
.profile_level("main10", Some("5.1")) // 10-bit profile
|
||||
.pixel_format("yuv420p10le") // 10-bit pixel format
|
||||
.color_settings("bt2020nc", "bt2020", "smpte2084") // HDR10
|
||||
.audio_settings("aac", 256, 2) // High quality audio
|
||||
.hardware_acceleration("cuda", Some("0")) // GPU 0
|
||||
.custom_params(vec![
|
||||
"-color_range tv".to_string(),
|
||||
"-max_muxing_queue_size 1024".to_string(),
|
||||
])
|
||||
.build()?;
|
||||
```
|
||||
|
||||
## Hardware Acceleration Support
|
||||
|
||||
### NVIDIA (NVENC/NVDEC)
|
||||
@@ -241,6 +301,9 @@ cargo run --example template_management
|
||||
|
||||
# FFmpeg command generation
|
||||
cargo run --example ffmpeg_generation
|
||||
|
||||
# Advanced output settings
|
||||
cargo run --example advanced_output_settings
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
214
cargos/tvai-v2/examples/advanced_output_settings.rs
Normal file
214
cargos/tvai-v2/examples/advanced_output_settings.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use tvai_sdk::*;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Topaz Video AI SDK - Advanced Output Settings Example");
|
||||
println!("====================================================");
|
||||
|
||||
let sdk = TvaiSdk::new();
|
||||
|
||||
// Example 1: High-quality NVENC encoding
|
||||
println!("\n1. High-Quality NVENC Template:");
|
||||
let nvenc_template = TemplatePresets::high_quality_nvenc()?;
|
||||
println!(" Template: {}", nvenc_template.name);
|
||||
println!(" Description: {}", nvenc_template.description);
|
||||
|
||||
let nvenc_command = sdk.generate_ffmpeg_command(&nvenc_template, "input.mp4", "output_nvenc.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", nvenc_command);
|
||||
|
||||
// Example 2: HDR Processing
|
||||
println!("\n2. HDR Processing Template:");
|
||||
let hdr_template = TemplatePresets::hdr_processing()?;
|
||||
println!(" Template: {}", hdr_template.name);
|
||||
|
||||
let hdr_command = sdk.generate_ffmpeg_command(&hdr_template, "input_hdr.mp4", "output_hdr.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", hdr_command);
|
||||
|
||||
// Example 3: Streaming Optimized
|
||||
println!("\n3. Streaming Optimized Template:");
|
||||
let streaming_template = TemplatePresets::streaming_optimized()?;
|
||||
println!(" Template: {}", streaming_template.name);
|
||||
|
||||
let streaming_command = sdk.generate_ffmpeg_command(&streaming_template, "input.mp4", "output_streaming.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", streaming_command);
|
||||
|
||||
// Example 4: Mobile Optimized
|
||||
println!("\n4. Mobile Optimized Template:");
|
||||
let mobile_template = TemplatePresets::mobile_optimized()?;
|
||||
println!(" Template: {}", mobile_template.name);
|
||||
|
||||
let mobile_command = sdk.generate_ffmpeg_command(&mobile_template, "input.mp4", "output_mobile.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", mobile_command);
|
||||
|
||||
// Example 5: Archival Quality
|
||||
println!("\n5. Archival Quality Template:");
|
||||
let archival_template = TemplatePresets::archival_quality()?;
|
||||
println!(" Template: {}", archival_template.name);
|
||||
|
||||
let archival_command = sdk.generate_ffmpeg_command(&archival_template, "input.mp4", "output_archival.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", archival_command);
|
||||
|
||||
// Example 6: Custom template with advanced settings
|
||||
println!("\n6. Custom Advanced Template:");
|
||||
let custom_template = TemplateBuilder::new("Custom Advanced")
|
||||
.description("Custom template with advanced output settings")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(25, 30, 15)
|
||||
.resolution(2560, 1440) // 1440p
|
||||
.video_codec("hevc_nvenc", Some(20))
|
||||
.preset("slow")
|
||||
.profile_level("main10", Some("5.0"))
|
||||
.pixel_format("yuv420p10le")
|
||||
.audio_settings("aac", 192, 2)
|
||||
.hardware_acceleration("cuda", Some("0"))
|
||||
.custom_params(vec![
|
||||
"-rc vbr".to_string(),
|
||||
"-rc-lookahead 32".to_string(),
|
||||
"-spatial_aq 1".to_string(),
|
||||
"-aq-strength 8".to_string(),
|
||||
])
|
||||
.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!(" Codec: {}", custom_template.settings.output.codec.as_ref().unwrap());
|
||||
println!(" CRF: {}", custom_template.settings.output.crf.unwrap_or(0));
|
||||
println!(" Preset: {}", custom_template.settings.output.preset.as_ref().unwrap());
|
||||
println!(" Audio: {} @ {}kbps",
|
||||
custom_template.settings.output.audio_codec.as_ref().unwrap(),
|
||||
custom_template.settings.output.audio_bitrate.unwrap_or(0)
|
||||
);
|
||||
|
||||
let custom_command = sdk.generate_ffmpeg_command(&custom_template, "input.mp4", "output_custom.mp4")?;
|
||||
println!(" Command:");
|
||||
println!(" {}", custom_command);
|
||||
|
||||
// Example 7: Different codec comparisons
|
||||
println!("\n7. Codec Comparison Templates:");
|
||||
|
||||
// H.264 template
|
||||
let h264_template = TemplateBuilder::new("H.264 Comparison")
|
||||
.description("H.264 encoding for compatibility")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("libx264", Some(23))
|
||||
.preset("medium")
|
||||
.profile_level("high", Some("4.0"))
|
||||
.audio_settings("aac", 128, 2)
|
||||
.build()?;
|
||||
|
||||
// H.265 template
|
||||
let h265_template = TemplateBuilder::new("H.265 Comparison")
|
||||
.description("H.265 encoding for efficiency")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("libx265", Some(25)) // Higher CRF for similar quality
|
||||
.preset("medium")
|
||||
.profile_level("main", Some("4.0"))
|
||||
.audio_settings("aac", 128, 2)
|
||||
.build()?;
|
||||
|
||||
// AV1 template
|
||||
let av1_template = TemplateBuilder::new("AV1 Comparison")
|
||||
.description("AV1 encoding for future-proofing")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("libaom-av1", Some(30)) // Much higher CRF for AV1
|
||||
.preset("6") // AV1 uses numeric presets
|
||||
.audio_settings("opus", 128, 2) // Opus for AV1
|
||||
.custom_params(vec![
|
||||
"-cpu-used 4".to_string(),
|
||||
"-row-mt 1".to_string(),
|
||||
])
|
||||
.build()?;
|
||||
|
||||
println!(" H.264 Command:");
|
||||
let h264_command = sdk.generate_ffmpeg_command(&h264_template, "input.mp4", "output_h264.mp4")?;
|
||||
println!(" {}", h264_command);
|
||||
|
||||
println!("\n H.265 Command:");
|
||||
let h265_command = sdk.generate_ffmpeg_command(&h265_template, "input.mp4", "output_h265.mp4")?;
|
||||
println!(" {}", h265_command);
|
||||
|
||||
println!("\n AV1 Command:");
|
||||
let av1_command = sdk.generate_ffmpeg_command(&av1_template, "input.mp4", "output_av1.mp4")?;
|
||||
println!(" {}", av1_command);
|
||||
|
||||
// Example 8: Hardware-specific templates
|
||||
println!("\n8. Hardware-Specific Templates:");
|
||||
|
||||
// NVIDIA template
|
||||
let nvidia_template = TemplateBuilder::new("NVIDIA Optimized")
|
||||
.description("Optimized for NVIDIA GPUs")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("hevc_nvenc", Some(21))
|
||||
.preset("p4") // NVENC preset
|
||||
.hardware_acceleration("cuda", Some("0"))
|
||||
.custom_params(vec![
|
||||
"-rc vbr".to_string(),
|
||||
"-multipass fullres".to_string(),
|
||||
"-spatial_aq 1".to_string(),
|
||||
])
|
||||
.build()?;
|
||||
|
||||
// AMD template
|
||||
let amd_template = TemplateBuilder::new("AMD Optimized")
|
||||
.description("Optimized for AMD GPUs")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("hevc_amf", Some(23))
|
||||
.preset("balanced") // AMF preset
|
||||
.custom_params(vec![
|
||||
"-rc cqp".to_string(),
|
||||
"-quality quality".to_string(),
|
||||
])
|
||||
.build()?;
|
||||
|
||||
// Intel template
|
||||
let intel_template = TemplateBuilder::new("Intel Optimized")
|
||||
.description("Optimized for Intel Quick Sync")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("hevc_qsv", Some(25))
|
||||
.preset("medium")
|
||||
.hardware_acceleration("qsv", None)
|
||||
.custom_params(vec![
|
||||
"-look_ahead 1".to_string(),
|
||||
"-look_ahead_depth 40".to_string(),
|
||||
])
|
||||
.build()?;
|
||||
|
||||
println!(" NVIDIA Command:");
|
||||
let nvidia_command = sdk.generate_ffmpeg_command(&nvidia_template, "input.mp4", "output_nvidia.mp4")?;
|
||||
println!(" {}", nvidia_command);
|
||||
|
||||
println!("\n AMD Command:");
|
||||
let amd_command = sdk.generate_ffmpeg_command(&amd_template, "input.mp4", "output_amd.mp4")?;
|
||||
println!(" {}", amd_command);
|
||||
|
||||
println!("\n Intel Command:");
|
||||
let intel_command = sdk.generate_ffmpeg_command(&intel_template, "input.mp4", "output_intel.mp4")?;
|
||||
println!(" {}", intel_command);
|
||||
|
||||
println!("\n✅ Advanced output settings example completed successfully!");
|
||||
println!("\nKey Features Demonstrated:");
|
||||
println!(" • Custom resolution settings");
|
||||
println!(" • Multiple codec support (H.264, H.265, AV1)");
|
||||
println!(" • Hardware acceleration (NVIDIA, AMD, Intel)");
|
||||
println!(" • Quality control (CRF, bitrate)");
|
||||
println!(" • Audio settings configuration");
|
||||
println!(" • HDR and color space handling");
|
||||
println!(" • Custom FFmpeg parameters");
|
||||
println!(" • Preset-based encoding optimization");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -26,6 +26,7 @@ impl FfmpegCommandGenerator {
|
||||
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());
|
||||
self.model_mappings.insert("hyp-1".to_string(), "hyp-1".to_string()); // Hyperion for HDR
|
||||
|
||||
// Frame interpolation models
|
||||
self.model_mappings.insert("apo-8".to_string(), "chr-2".to_string());
|
||||
@@ -48,25 +49,31 @@ impl FfmpegCommandGenerator {
|
||||
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
|
||||
// Generate enhancement filter (usually first in chain)
|
||||
if settings.enhance.active {
|
||||
let up_filter = self.generate_enhancement_filter(&settings.enhance)?;
|
||||
filters.push(up_filter);
|
||||
}
|
||||
|
||||
// Generate frame interpolation filter
|
||||
// Generate frame interpolation filter (after enhancement)
|
||||
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)
|
||||
// Generate stabilization filter (after interpolation)
|
||||
if settings.stabilize.active {
|
||||
let stb_filter = self.generate_stabilization_filter(&settings.stabilize)?;
|
||||
filters.push(stb_filter);
|
||||
}
|
||||
|
||||
// Generate motion blur filter (if active)
|
||||
if settings.motion_blur.active {
|
||||
let mb_filter = self.generate_motion_blur_filter(&settings.motion_blur)?;
|
||||
filters.push(mb_filter);
|
||||
}
|
||||
|
||||
// Generate grain filter (usually last in chain)
|
||||
if settings.grain.active {
|
||||
let grain_filter = self.generate_grain_filter(&settings.grain)?;
|
||||
filters.push(grain_filter);
|
||||
@@ -83,7 +90,7 @@ impl FfmpegCommandGenerator {
|
||||
|
||||
// Add output settings
|
||||
command.push_str(&self.generate_output_settings(&settings.output)?);
|
||||
|
||||
|
||||
command.push_str(&format!(" \"{}\"", output_file));
|
||||
|
||||
Ok(command)
|
||||
@@ -199,36 +206,143 @@ impl FfmpegCommandGenerator {
|
||||
/// 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
|
||||
|
||||
// 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));
|
||||
} else {
|
||||
// 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
|
||||
|
||||
// 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());
|
||||
|
||||
|
||||
// Video codec
|
||||
if let Some(ref codec) = settings.codec {
|
||||
output_params.push(format!("-c:v {}", codec));
|
||||
} else {
|
||||
output_params.push("-c:v libx264".to_string());
|
||||
}
|
||||
|
||||
// Video quality settings
|
||||
if let Some(bitrate) = settings.bitrate {
|
||||
output_params.push(format!("-b:v {}k", bitrate));
|
||||
} else if let Some(crf) = settings.crf {
|
||||
// Use CRF for quality-based encoding
|
||||
let codec = settings.codec.as_deref().unwrap_or("libx264");
|
||||
match codec {
|
||||
"hevc_nvenc" | "h264_nvenc" => {
|
||||
output_params.push(format!("-cq {}", crf));
|
||||
},
|
||||
"h264_amf" | "hevc_amf" => {
|
||||
output_params.push(format!("-qp {}", crf));
|
||||
},
|
||||
"h264_qsv" | "hevc_qsv" => {
|
||||
output_params.push(format!("-global_quality {}", crf));
|
||||
},
|
||||
_ => {
|
||||
output_params.push(format!("-crf {}", crf));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output_params.push("-crf 23".to_string()); // Default quality
|
||||
}
|
||||
|
||||
// Encoding preset
|
||||
if let Some(ref preset) = settings.preset {
|
||||
output_params.push(format!("-preset {}", preset));
|
||||
} else {
|
||||
output_params.push("-preset medium".to_string());
|
||||
}
|
||||
|
||||
// Video profile
|
||||
if let Some(ref profile) = settings.profile {
|
||||
output_params.push(format!("-profile:v {}", profile));
|
||||
}
|
||||
|
||||
// Video level
|
||||
if let Some(ref level) = settings.level {
|
||||
output_params.push(format!("-level {}", level));
|
||||
}
|
||||
|
||||
// GOP size
|
||||
if let Some(gop_size) = settings.gop_size {
|
||||
output_params.push(format!("-g {}", gop_size));
|
||||
}
|
||||
|
||||
// B-frames
|
||||
if let Some(b_frames) = settings.b_frames {
|
||||
output_params.push(format!("-bf {}", b_frames));
|
||||
}
|
||||
|
||||
// Pixel format
|
||||
if let Some(ref pix_fmt) = settings.pixel_format {
|
||||
output_params.push(format!("-pix_fmt {}", pix_fmt));
|
||||
}
|
||||
|
||||
// Color settings
|
||||
if let Some(ref colorspace) = settings.colorspace {
|
||||
output_params.push(format!("-colorspace {}", colorspace));
|
||||
}
|
||||
if let Some(ref color_primaries) = settings.color_primaries {
|
||||
output_params.push(format!("-color_primaries {}", color_primaries));
|
||||
}
|
||||
if let Some(ref color_trc) = settings.color_trc {
|
||||
output_params.push(format!("-color_trc {}", color_trc));
|
||||
}
|
||||
|
||||
// Audio settings
|
||||
if let Some(ref audio_codec) = settings.audio_codec {
|
||||
if audio_codec != "copy" {
|
||||
output_params.push(format!("-c:a {}", audio_codec));
|
||||
|
||||
if let Some(audio_bitrate) = settings.audio_bitrate {
|
||||
output_params.push(format!("-b:a {}k", audio_bitrate));
|
||||
}
|
||||
|
||||
if let Some(audio_sample_rate) = settings.audio_sample_rate {
|
||||
output_params.push(format!("-ar {}", audio_sample_rate));
|
||||
}
|
||||
|
||||
if let Some(audio_channels) = settings.audio_channels {
|
||||
output_params.push(format!("-ac {}", audio_channels));
|
||||
}
|
||||
} else {
|
||||
output_params.push("-c:a copy".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Custom parameters
|
||||
if let Some(ref custom_params) = settings.custom_params {
|
||||
for param in custom_params {
|
||||
output_params.push(param.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if output_params.is_empty() {
|
||||
Ok(String::new())
|
||||
} else {
|
||||
@@ -279,29 +393,18 @@ impl FfmpegCommandGenerator {
|
||||
|
||||
/// 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));
|
||||
// Create a modified template with custom codec settings
|
||||
let mut modified_template = template.clone();
|
||||
modified_template.settings.output.codec = Some(codec.to_string());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
// Clear bitrate when setting quality
|
||||
modified_template.settings.output.bitrate = None;
|
||||
modified_template.settings.output.crf = Some(q);
|
||||
}
|
||||
|
||||
Ok(command)
|
||||
// Generate command with modified template
|
||||
self.generate(&modified_template, input_file, output_file)
|
||||
}
|
||||
|
||||
/// Generate batch processing commands
|
||||
|
||||
@@ -244,6 +244,87 @@ impl TemplateBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom resolution
|
||||
pub fn resolution(mut self, width: i32, height: i32) -> Self {
|
||||
self.template.settings.output.width = Some(width);
|
||||
self.template.settings.output.height = Some(height);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set video codec and quality
|
||||
pub fn video_codec(mut self, codec: &str, crf: Option<i32>) -> Self {
|
||||
self.template.settings.output.codec = Some(codec.to_string());
|
||||
if let Some(quality) = crf {
|
||||
self.template.settings.output.crf = Some(quality);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set video bitrate (alternative to CRF)
|
||||
pub fn video_bitrate(mut self, bitrate: i32) -> Self {
|
||||
self.template.settings.output.bitrate = Some(bitrate);
|
||||
self.template.settings.output.crf = None; // Clear CRF when using bitrate
|
||||
self
|
||||
}
|
||||
|
||||
/// Set encoding preset
|
||||
pub fn preset(mut self, preset: &str) -> Self {
|
||||
self.template.settings.output.preset = Some(preset.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set video profile and level
|
||||
pub fn profile_level(mut self, profile: &str, level: Option<&str>) -> Self {
|
||||
self.template.settings.output.profile = Some(profile.to_string());
|
||||
if let Some(l) = level {
|
||||
self.template.settings.output.level = Some(l.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set audio settings
|
||||
pub fn audio_settings(mut self, codec: &str, bitrate: i32, channels: i32) -> Self {
|
||||
self.template.settings.output.audio_codec = Some(codec.to_string());
|
||||
self.template.settings.output.audio_bitrate = Some(bitrate);
|
||||
self.template.settings.output.audio_channels = Some(channels);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set hardware acceleration
|
||||
pub fn hardware_acceleration(mut self, hwaccel: &str, device: Option<&str>) -> Self {
|
||||
self.template.settings.output.hwaccel = Some(hwaccel.to_string());
|
||||
if let Some(dev) = device {
|
||||
self.template.settings.output.gpu_device = Some(dev.to_string());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set pixel format
|
||||
pub fn pixel_format(mut self, format: &str) -> Self {
|
||||
self.template.settings.output.pixel_format = Some(format.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set color settings for HDR content
|
||||
pub fn color_settings(mut self, colorspace: &str, primaries: &str, trc: &str) -> Self {
|
||||
self.template.settings.output.colorspace = Some(colorspace.to_string());
|
||||
self.template.settings.output.color_primaries = Some(primaries.to_string());
|
||||
self.template.settings.output.color_trc = Some(trc.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add custom FFmpeg parameters
|
||||
pub fn custom_params(mut self, params: Vec<String>) -> Self {
|
||||
self.template.settings.output.custom_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable two-pass encoding
|
||||
pub fn two_pass(mut self, enabled: bool) -> Self {
|
||||
self.template.settings.output.two_pass = Some(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable grain effect
|
||||
pub fn enable_grain(mut self, amount: i32, size: i32) -> Self {
|
||||
self.template.settings.grain.active = true;
|
||||
@@ -316,6 +397,86 @@ impl TemplatePresets {
|
||||
.output_settings(6, 0.0) // FHD output
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a high-quality NVENC template
|
||||
pub fn high_quality_nvenc() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("High Quality NVENC")
|
||||
.description("High quality encoding with NVIDIA NVENC")
|
||||
.enable_enhancement("prob-4")
|
||||
.resolution(3840, 2160) // 4K
|
||||
.video_codec("hevc_nvenc", Some(18)) // H.265 with high quality
|
||||
.preset("slow")
|
||||
.profile_level("main10", Some("5.1"))
|
||||
.pixel_format("yuv420p10le") // 10-bit
|
||||
.audio_settings("aac", 256, 2) // High quality audio
|
||||
.hardware_acceleration("cuda", Some("0"))
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create an HDR template
|
||||
pub fn hdr_processing() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("HDR Processing")
|
||||
.description("Process HDR content with proper color settings")
|
||||
.enable_enhancement("hyp-1") // Hyperion model for HDR
|
||||
.resolution(3840, 2160)
|
||||
.video_codec("hevc_nvenc", Some(16))
|
||||
.pixel_format("yuv420p10le")
|
||||
.color_settings("bt2020nc", "bt2020", "smpte2084")
|
||||
.custom_params(vec![
|
||||
"-color_range tv".to_string(),
|
||||
"-max_muxing_queue_size 1024".to_string(),
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a streaming optimized template
|
||||
pub fn streaming_optimized() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Streaming Optimized")
|
||||
.description("Optimized for live streaming and fast encoding")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(15, 10, 20) // Light processing for speed
|
||||
.resolution(1920, 1080)
|
||||
.video_codec("h264_nvenc", Some(23))
|
||||
.preset("fast")
|
||||
.profile_level("high", Some("4.0"))
|
||||
.audio_settings("aac", 128, 2)
|
||||
.custom_params(vec![
|
||||
"-tune zerolatency".to_string(),
|
||||
"-rc vbr".to_string(),
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create a mobile-optimized template
|
||||
pub fn mobile_optimized() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Mobile Optimized")
|
||||
.description("Optimized for mobile devices with small file sizes")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(20, 15, 5)
|
||||
.resolution(1280, 720) // 720p
|
||||
.video_codec("libx264", Some(28)) // Higher CRF for smaller files
|
||||
.preset("fast")
|
||||
.profile_level("baseline", Some("3.1")) // Compatible profile
|
||||
.audio_settings("aac", 96, 2) // Lower audio bitrate
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Create an archival quality template
|
||||
pub fn archival_quality() -> Result<Template, TvaiError> {
|
||||
TemplateBuilder::new("Archival Quality")
|
||||
.description("Maximum quality for archival purposes")
|
||||
.enable_enhancement("prob-4")
|
||||
.enhancement_params(40, 50, 15) // High quality settings
|
||||
.enable_stabilization(80, 1)
|
||||
.resolution(3840, 2160)
|
||||
.video_codec("libx265", Some(12)) // Very high quality
|
||||
.preset("veryslow") // Maximum compression efficiency
|
||||
.profile_level("main10", Some("5.1"))
|
||||
.pixel_format("yuv420p10le")
|
||||
.audio_settings("flac", 0, 2) // Lossless audio
|
||||
.two_pass(true)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TvaiSdk {
|
||||
|
||||
@@ -616,14 +616,104 @@ impl OutputSettings {
|
||||
self.lock_aspect_ratio = Some(true);
|
||||
}
|
||||
|
||||
// Validate extended parameters
|
||||
if let Some(width) = self.width {
|
||||
if width <= 0 || width > 16384 {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid width: {}", width)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(height) = self.height {
|
||||
if height <= 0 || height > 16384 {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid height: {}", height)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bitrate) = self.bitrate {
|
||||
if bitrate <= 0 || bitrate > 100000 {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid bitrate: {}", bitrate)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(crf) = self.crf {
|
||||
if crf < 0 || crf > 51 {
|
||||
self.crf = Some(crf.clamp(0, 51));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(gop_size) = self.gop_size {
|
||||
if gop_size <= 0 {
|
||||
self.gop_size = Some(30); // Default GOP size
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(b_frames) = self.b_frames {
|
||||
if b_frames < 0 || b_frames > 16 {
|
||||
self.b_frames = Some(b_frames.clamp(0, 16));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(audio_bitrate) = self.audio_bitrate {
|
||||
// Allow 0 bitrate for lossless codecs like FLAC
|
||||
let is_lossless = self.audio_codec.as_ref()
|
||||
.map(|codec| codec == "flac" || codec == "alac" || codec == "pcm_s16le")
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_lossless && (audio_bitrate <= 0 || audio_bitrate > 1024) {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid audio bitrate: {}", audio_bitrate)));
|
||||
} else if is_lossless && audio_bitrate < 0 {
|
||||
return Err(TvaiError::ValidationError(format!("Invalid audio bitrate: {}", audio_bitrate)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(audio_channels) = self.audio_channels {
|
||||
if audio_channels <= 0 || audio_channels > 8 {
|
||||
self.audio_channels = Some(audio_channels.clamp(1, 8));
|
||||
}
|
||||
}
|
||||
|
||||
// Set default codec if not specified
|
||||
if self.codec.is_none() {
|
||||
self.codec = Some("libx264".to_string());
|
||||
}
|
||||
|
||||
// Set default audio codec if not specified
|
||||
if self.audio_codec.is_none() {
|
||||
self.audio_codec = Some("aac".to_string());
|
||||
}
|
||||
|
||||
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
|
||||
// Basic validation
|
||||
if self.out_size_method < 0 || self.out_size_method > 10 ||
|
||||
self.output_par < 0 ||
|
||||
self.out_fps < 0.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extended validation
|
||||
if let Some(width) = self.width {
|
||||
if width <= 0 || width > 16384 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(height) = self.height {
|
||||
if height <= 0 || height > 16384 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(crf) = self.crf {
|
||||
if crf < 0 || crf > 51 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,6 +727,31 @@ impl Default for OutputSettings {
|
||||
out_fps: 0.0,
|
||||
custom_resolution_priority: Some(0),
|
||||
lock_aspect_ratio: Some(true),
|
||||
|
||||
// Extended parameters with sensible defaults
|
||||
width: None,
|
||||
height: None,
|
||||
codec: Some("libx264".to_string()),
|
||||
bitrate: None,
|
||||
crf: Some(23), // Balanced quality
|
||||
preset: Some("medium".to_string()),
|
||||
profile: None,
|
||||
level: None,
|
||||
gop_size: Some(30),
|
||||
b_frames: Some(3),
|
||||
audio_codec: Some("aac".to_string()),
|
||||
audio_bitrate: Some(128), // 128 kbps
|
||||
audio_sample_rate: Some(48000), // 48 kHz
|
||||
audio_channels: Some(2), // Stereo
|
||||
format: Some("mp4".to_string()),
|
||||
colorspace: None,
|
||||
color_primaries: None,
|
||||
color_trc: None,
|
||||
pixel_format: Some("yuv420p".to_string()),
|
||||
hwaccel: None,
|
||||
gpu_device: None,
|
||||
two_pass: Some(false),
|
||||
custom_params: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,9 +232,9 @@ fn test_hardware_acceleration_command() {
|
||||
fn test_custom_codec_command() {
|
||||
let template = TemplatePresets::upscale_to_4k().unwrap();
|
||||
let sdk = TvaiSdk::new();
|
||||
|
||||
|
||||
let command = sdk.generate_ffmpeg_command_with_codec(&template, "input.mp4", "output.mp4", "hevc_nvenc", Some(20)).unwrap();
|
||||
|
||||
|
||||
assert!(command.contains("hevc_nvenc"));
|
||||
assert!(command.contains("-cq 20"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user