use std::fs::File;
use std::io;
use std::io::{BufReader, BufWriter};
use std::process::Command;
use crate::DashMpdError;
use crate::fetch::DashDownloader;
fn mux_audio_video_ffmpeg(
downloader: &DashDownloader,
audio_path: &str,
video_path: &str) -> Result<(), DashMpdError> {
let output_path = downloader.output_path.as_ref()
.expect("muxer called without specifying output_path");
let container = match output_path.extension() {
Some(ext) => ext.to_str().unwrap_or("mp4"),
None => "mp4",
};
let tmpout = tempfile::Builder::new()
.prefix("dashmpdrs")
.suffix(&format!(".{container}"))
.rand_bytes(5)
.tempfile()
.map_err(|e| DashMpdError::Io(e, String::from("creating temporary output file")))?;
let tmppath = tmpout
.path()
.to_str()
.ok_or_else(|| DashMpdError::Io(
io::Error::new(io::ErrorKind::Other, "obtaining tmpfile name"),
String::from("")))?;
let ffmpeg = Command::new(&downloader.ffmpeg_location)
.args(["-hide_banner",
"-nostats",
"-loglevel", "error", "-y", "-i", audio_path,
"-i", video_path,
"-c:v", "copy",
"-c:a", "copy",
"-movflags", "+faststart", "-preset", "veryfast",
"-f", container,
tmppath])
.output()
.map_err(|e| DashMpdError::Io(e, String::from("spawning ffmpeg subprocess")))?;
let msg = String::from_utf8_lossy(&ffmpeg.stdout);
if msg.len() > 0 {
log::info!("ffmpeg stdout: {msg}");
}
let msg = String::from_utf8_lossy(&ffmpeg.stderr);
if msg.len() > 0 {
log::info!("ffmpeg stderr: {msg}");
}
if ffmpeg.status.success() {
let tmpfile = File::open(tmppath)
.map_err(|e| DashMpdError::Io(e, String::from("opening ffmpeg output")))?;
let mut muxed = BufReader::new(tmpfile);
let outfile = File::create(output_path)
.map_err(|e| DashMpdError::Io(e, String::from("creating output file")))?;
let mut sink = BufWriter::new(outfile);
io::copy(&mut muxed, &mut sink)
.map_err(|e| DashMpdError::Io(e, String::from("copying ffmpeg output to output file")))?;
Ok(())
} else {
Err(DashMpdError::Muxing(String::from("running ffmpeg")))
}
}
fn mux_audio_video_vlc(
downloader: &DashDownloader,
audio_path: &str,
video_path: &str) -> Result<(), DashMpdError> {
let output_path = downloader.output_path.as_ref()
.expect("muxer called without specifying output_path");
let tmpout = tempfile::Builder::new()
.prefix("dashmpdrs")
.suffix(".mp4")
.rand_bytes(5)
.tempfile()
.map_err(|e| DashMpdError::Io(e, String::from("creating temporary output file")))?;
let tmppath = tmpout
.path()
.to_str()
.ok_or_else(|| DashMpdError::Io(
io::Error::new(io::ErrorKind::Other, "obtaining tmpfile name"),
String::from("")))?;
let vlc = Command::new(&downloader.vlc_location)
.args(["-I", "dummy",
"--no-repeat", "--no-loop",
video_path,
"--input-slave", audio_path,
"--sout-mp4-faststart",
&format!("--sout=#std{{access=file,mux=mp4,dst={tmppath}}}"),
"--sout-keep",
"vlc://quit"])
.output()
.map_err(|e| DashMpdError::Io(e, String::from("spawning VLC subprocess")))?;
if vlc.status.success() {
let tmpfile = File::open(tmppath)
.map_err(|e| DashMpdError::Io(e, String::from("opening VLC output")))?;
let mut muxed = BufReader::new(tmpfile);
let outfile = File::create(output_path)
.map_err(|e| DashMpdError::Io(e, String::from("creating output file")))?;
let mut sink = BufWriter::new(outfile);
io::copy(&mut muxed, &mut sink)
.map_err(|e| DashMpdError::Io(e, String::from("copying VLC output to output file")))?;
Ok(())
} else {
let msg = String::from_utf8_lossy(&vlc.stderr);
Err(DashMpdError::Muxing(format!("running VLC: {msg}")))
}
}
#[cfg(target_os = "windows")]
fn temporary_outpath(suffix: &str) -> Result<String, DashMpdError> {
Ok(format!("dashmpdrs-tmp{}", suffix))
}
#[cfg(not(target_os = "windows"))]
fn temporary_outpath(suffix: &str) -> Result<String, DashMpdError> {
let tmpout = tempfile::Builder::new()
.prefix("dashmpdrs")
.suffix(suffix)
.rand_bytes(5)
.tempfile()
.map_err(|e| DashMpdError::Io(e, String::from("creating temporary output file")))?;
match tmpout.path().to_str() {
Some(s) => Ok(s.to_string()),
None => Ok(format!("/tmp/dashmpdrs-tmp{suffix}")),
}
}
fn mux_audio_video_mkvmerge(
downloader: &DashDownloader,
audio_path: &str,
video_path: &str) -> Result<(), DashMpdError> {
let output_path = downloader.output_path.as_ref()
.expect("muxer called without specifying output_path");
let tmppath = temporary_outpath(".mkv")?;
let mkv = Command::new(&downloader.mkvmerge_location)
.args(["--output", &tmppath,
"--no-video", audio_path,
"--no-audio", video_path])
.output()
.map_err(|e| DashMpdError::Io(e, String::from("spawning mkvmerge subprocess")))?;
if mkv.status.success() {
let tmpfile = File::open(&tmppath)
.map_err(|e| DashMpdError::Io(e, String::from("opening mkvmerge output")))?;
let mut muxed = BufReader::new(tmpfile);
let outfile = File::create(output_path)
.map_err(|e| DashMpdError::Io(e, String::from("opening output file")))?;
let mut sink = BufWriter::new(outfile);
io::copy(&mut muxed, &mut sink)
.map_err(|e| DashMpdError::Io(e, String::from("copying mkvmerge output to output file")))?;
#[cfg(target_os = "windows")]
::std::fs::remove_file(tmppath).ok();
Ok(())
} else {
let msg = String::from_utf8_lossy(&mkv.stdout);
Err(DashMpdError::Muxing(format!("running mkvmerge: {msg}")))
}
}
pub fn mux_audio_video(
downloader: &DashDownloader,
audio_path: &str,
video_path: &str) -> Result<(), DashMpdError> {
log::trace!("Muxing audio {audio_path}, video {video_path}");
let output_path = downloader.output_path.as_ref()
.expect("muxer called without specifying output_path");
let container = match output_path.extension() {
Some(ext) => ext.to_str().unwrap_or("mp4"),
None => "mp4",
};
let mut muxer_preference = vec![];
if container.eq("mkv") {
muxer_preference.push("mkvmerge");
muxer_preference.push("ffmpeg");
} else if container.eq("mp4") {
muxer_preference.push("ffmpeg");
muxer_preference.push("vlc");
} else {
muxer_preference.push("ffmpeg");
}
log::info!("Muxer preference for {container} is {muxer_preference:?}");
for muxer in muxer_preference {
log::info!("Trying muxer {}", muxer);
if muxer.eq("mkvmerge") {
if let Err(e) = mux_audio_video_mkvmerge(downloader, audio_path, video_path) {
log::warn!("Muxing with mkvmerge subprocess failed: {e}");
} else {
log::info!("Muxing with mkvmerge subprocess succeeded");
return Ok(());
}
} else if muxer.eq("ffmpeg") {
if let Err(e) = mux_audio_video_ffmpeg(downloader, audio_path, video_path) {
log::warn!("Muxing with ffmpeg subprocess failed: {e}");
} else {
log::info!("Muxing with ffmpeg subprocess succeeded");
return Ok(());
}
} else if muxer.eq("vlc") {
if let Err(e) = mux_audio_video_vlc(downloader, audio_path, video_path) {
log::warn!("Muxing with vlc subprocess failed: {e}");
} else {
log::info!("Muxing with vlc subprocess succeeded");
return Ok(());
}
}
}
log::warn!("All available muxers failed");
Err(DashMpdError::Muxing(String::from("all available muxers failed")))
}