use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::Stdio;
use tempfile::NamedTempFile;
use tokio::process::Command;
use crate::models::hls_video::HlsVideoSegment;
use crate::tools::ffmpeg_command_builder::HlsOutputEncryptionConfig;
use crate::{
models::hls_video::HlsVideoResolution,
tools::{ffmpeg_command_builder::FfmpegCommandBuilder, hlskit_error::HlsKitError},
};
fn write_input_to_tempfile(input_bytes: &[u8]) -> Result<NamedTempFile, HlsKitError> {
let mut temp_file = NamedTempFile::new()?;
temp_file.write_all(input_bytes)?;
temp_file.flush()?;
Ok(temp_file)
}
#[allow(clippy::too_many_arguments)]
fn build_ffmpeg_command(
input_path: &str,
width: i32,
height: i32,
crf: i32,
preset: &str,
segment_filename: &str,
playlist_filename: &str,
encryption_key_url: Option<&str>,
encryption_key_path: Option<String>,
iv: Option<String>,
) -> Result<Vec<String>, HlsKitError> {
let encryption_settings: Option<HlsOutputEncryptionConfig> =
encryption_key_path.map(|key_path| HlsOutputEncryptionConfig {
encryption_key_path: key_path,
iv,
});
let command = FfmpegCommandBuilder::new()
.input(input_path)
.dimensions(width, height)
.crf(crf)
.preset(preset)
.enable_hls(
segment_filename,
None,
encryption_key_url,
encryption_settings,
10,
)
.output(playlist_filename)
.build()?;
Ok(command)
}
async fn run_ffmpeg_command(command: &[String]) -> Result<(), HlsKitError> {
let process = Command::new(&command[0])
.args(&command[1..])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|error| HlsKitError::FfmpegError {
error: error.to_string(),
})?;
let output = process
.wait_with_output()
.await
.map_err(|error| HlsKitError::FfmpegError {
error: format!("Failed to write to ffmpeg output: {}", error),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(HlsKitError::FfmpegError {
error: format!("FFmpeg error: {}", stderr),
});
}
Ok(())
}
fn read_playlist_and_segments(
playlist_filename: &str,
segment_filename: &str,
resolution: (i32, i32),
stream_index: i32,
) -> Result<HlsVideoResolution, HlsKitError> {
let mut hls_resolution = HlsVideoResolution {
resolution,
playlist_name: format!("playlist_{}.m3u8", stream_index),
playlist_data: vec![],
segments: vec![],
};
let mut playlist_handler = File::open(playlist_filename)?;
playlist_handler.read_to_end(&mut hls_resolution.playlist_data)?;
let mut segment_index = 0;
loop {
let segment_path = segment_filename.replace("%03d", &format!("{:03}", segment_index));
if !PathBuf::from(&segment_path).exists() {
break;
}
let mut segment_file_handler = File::open(&segment_path)?;
let mut segment_file_read_buffer: Vec<u8> = Vec::new();
segment_file_handler.read_to_end(&mut segment_file_read_buffer)?;
let segment_name = format!("data_{}_%03d.ts", stream_index)
.replace("%03d", &format!("{:03}", segment_index));
let segment = HlsVideoSegment {
segment_name,
segment_data: segment_file_read_buffer,
};
hls_resolution.segments.push(segment);
segment_index += 1;
}
Ok(hls_resolution)
}
pub async fn process_video_profile(
input_bytes: Vec<u8>,
resolution: (i32, i32),
crf: i32,
preset: &str,
output_dir: &Path,
stream_index: i32,
) -> Result<HlsVideoResolution, HlsKitError> {
let (width, height) = resolution;
let segment_filename = format!(
"{}/data_{}_%03d.ts",
output_dir.to_str().unwrap(),
stream_index
);
let playlist_filename = format!(
"{}/playlist_{}.m3u8",
output_dir.to_str().unwrap(),
stream_index
);
let temp_file = write_input_to_tempfile(&input_bytes)?;
let input_path = temp_file.path().to_str().unwrap();
let command = build_ffmpeg_command(
input_path,
width,
height,
crf,
preset,
&segment_filename,
&playlist_filename,
None,
None,
None,
)?;
run_ffmpeg_command(&command).await?;
read_playlist_and_segments(
&playlist_filename,
&segment_filename,
resolution,
stream_index,
)
}
#[allow(clippy::too_many_arguments)]
pub async fn process_video_profile_with_encryption(
input_bytes: Vec<u8>,
resolution: (i32, i32),
crf: i32,
preset: &str,
output_dir: &Path,
stream_index: i32,
encryption_key_url: String,
encryption_key_path: String,
iv: Option<String>,
) -> Result<HlsVideoResolution, HlsKitError> {
let (width, height) = resolution;
let segment_filename = format!(
"{}/data_{}_%03d.ts",
output_dir.to_str().unwrap(),
stream_index
);
let playlist_filename = format!(
"{}/playlist_{}.m3u8",
output_dir.to_str().unwrap(),
stream_index
);
let temp_file = write_input_to_tempfile(&input_bytes)?;
let input_path = temp_file.path().to_str().unwrap();
let command = build_ffmpeg_command(
input_path,
width,
height,
crf,
preset,
&segment_filename,
&playlist_filename,
Some(&encryption_key_url),
Some(encryption_key_path),
iv,
)?;
run_ffmpeg_command(&command).await?;
read_playlist_and_segments(
&playlist_filename,
&segment_filename,
resolution,
stream_index,
)
}