use std::path::{Path, PathBuf};
use crate::tools::{
hlskit_error::GStreamerCommandBuilderError,
internals::hls_output_config::{HlsOutputConfig, HlsOutputEncryptionConfig},
};
#[derive(Debug, Default)]
pub struct GStreamerCommand {
input_path: PathBuf,
output_path: PathBuf,
width: i32,
height: i32,
bitrate: i32,
hls_config: Option<HlsOutputConfig>,
}
#[derive(Debug, Default)]
pub struct GStreamerCommandBuilder {
command: GStreamerCommand,
has_input: bool,
has_output: bool,
has_dimensions: bool,
has_bitrate: bool,
errors: Vec<GStreamerCommandBuilderError>,
}
impl GStreamerCommandBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn input<P: AsRef<Path>>(mut self, path: P) -> Self {
self.command.input_path = path.as_ref().to_path_buf();
self.has_input = true;
self
}
pub fn output<P: AsRef<Path>>(mut self, path: P) -> Self {
self.command.output_path = path.as_ref().to_path_buf();
self.has_output = true;
self
}
pub fn dimensions(mut self, width: i32, height: i32) -> Self {
if width <= 0 || height <= 0 {
self.errors
.push(GStreamerCommandBuilderError::InvalidDimensions(
"Width and height must be positive.".to_string(),
));
}
self.command.width = width;
self.command.height = height;
self.has_dimensions = true;
self
}
pub fn bitrate(mut self, kbps: i32) -> Self {
if kbps <= 0 {
self.errors
.push(GStreamerCommandBuilderError::InvalidBitrate(
"Bitrate must be a positive value.".to_string(),
));
}
self.command.bitrate = kbps;
self.has_bitrate = true;
self
}
pub fn enable_hls(
mut self,
segment_pattern: &str,
playlist_type: Option<&str>,
base_url: Option<&str>,
encryption: Option<HlsOutputEncryptionConfig>,
hls_time: i32,
) -> Self {
if !segment_pattern.contains('%') {
self.errors
.push(GStreamerCommandBuilderError::InvalidConfig(
"Segment pattern must contain a printf-style specifier (e.g., %05d)."
.to_string(),
));
}
self.command.hls_config = Some(HlsOutputConfig {
segment_filename_pattern: segment_pattern.to_string(),
playlist_type: playlist_type.map(String::from),
base_url: base_url.map(String::from),
encryption_config: encryption,
hls_time,
});
self
}
pub fn build(&mut self) -> Result<Vec<String>, GStreamerCommandBuilderError> {
if !self.errors.is_empty() {
return Err(self.errors.remove(0));
}
if !self.has_input {
return Err(GStreamerCommandBuilderError::MissingInput);
}
if !self.has_output && self.command.hls_config.is_none() {
return Err(GStreamerCommandBuilderError::MissingOutput);
}
if !self.has_dimensions {
return Err(GStreamerCommandBuilderError::InvalidDimensions(
"Must specify video dimensions.".to_string(),
));
}
if !self.has_bitrate {
return Err(GStreamerCommandBuilderError::InvalidBitrate(
"Must specify video bitrate.".to_string(),
));
}
Ok(self.command.to_args())
}
}
impl GStreamerCommand {
pub fn to_args(&self) -> Vec<String> {
let mut args = vec!["gst-launch-1.0".to_string()];
args.push("filesrc".to_string());
args.push(format!("location={}", self.input_path.display()));
args.push("! decodebin".to_string());
args.push("! videoconvert ! videoscale".to_string());
args.push(format!(
"! video/x-raw,width={},height={}",
self.width, self.height
));
args.push(format!(
"! x264enc bitrate={} speed-preset=medium tune=zerolatency",
self.bitrate
));
args.push("! mpegtsmux".to_string());
if let Some(hls) = &self.hls_config {
args.push("! hlssink".to_string());
args.push(format!("playlist-location={}", self.output_path.display()));
args.push(format!("location={}", hls.segment_filename_pattern));
args.push(format!("target-duration={}", hls.hls_time));
if let Some(enc) = &hls.encryption_config {
args.push(format!("key-file={}", enc.encryption_key_path));
let key_filename = std::path::Path::new(&enc.encryption_key_path)
.file_name()
.unwrap_or_default()
.to_string_lossy();
if let Some(base_url) = &hls.base_url {
let mut key_uri = base_url.clone();
if !key_uri.ends_with('/') {
key_uri.push('/');
}
key_uri.push_str(&key_filename);
args.push(format!("key-uri={key_uri}"));
} else {
args.push(format!("key-uri={key_filename}"));
}
if let Some(iv) = &enc.iv {
args.push(format!("iv={iv}"));
}
}
} else {
args.push("! filesink".to_string());
args.push(format!("location={}", self.output_path.display()));
}
args
}
}