feat: remote unuse json
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user