feat: Add ffmpeg remuxing for faststart
This commit is contained in:
@@ -3,6 +3,7 @@ pub mod config;
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
pub mod routes;
|
||||
pub mod transcribe;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::process::Command;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FfmpegError {
|
||||
Spawn(std::io::Error),
|
||||
NonZeroExit { code: Option<i32>, stderr: String },
|
||||
TempFile(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FfmpegError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Spawn(e) => write!(f, "failed to spawn ffmpeg: {e}"),
|
||||
Self::NonZeroExit { code, stderr } => {
|
||||
write!(f, "ffmpeg exited with {code:?}: {stderr}")
|
||||
}
|
||||
Self::TempFile(e) => write!(f, "failed to create temp file: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FfmpegError {}
|
||||
|
||||
/// Remux an m4a file with `-movflags faststart` so the `moov` atom sits at the
|
||||
/// start of the file. Returns a NamedTempFile that is auto-deleted on drop.
|
||||
///
|
||||
/// Lossless — just a container-level rewrite, typically <1s for dictation-sized files.
|
||||
pub async fn remux_faststart(input: &Path) -> Result<NamedTempFile, FfmpegError> {
|
||||
let output = tempfile::Builder::new()
|
||||
.prefix("doctate-remux-")
|
||||
.suffix(".m4a")
|
||||
.tempfile()
|
||||
.map_err(FfmpegError::TempFile)?;
|
||||
|
||||
let output_path = output.path().to_owned();
|
||||
|
||||
let result = Command::new("ffmpeg")
|
||||
.arg("-hide_banner")
|
||||
.arg("-loglevel")
|
||||
.arg("error")
|
||||
.arg("-y")
|
||||
.arg("-i")
|
||||
.arg(input)
|
||||
.arg("-c")
|
||||
.arg("copy")
|
||||
.arg("-movflags")
|
||||
.arg("faststart")
|
||||
.arg(&output_path)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.output()
|
||||
.await
|
||||
.map_err(FfmpegError::Spawn)?;
|
||||
|
||||
if !result.status.success() {
|
||||
return Err(FfmpegError::NonZeroExit {
|
||||
code: result.status.code(),
|
||||
stderr: String::from_utf8_lossy(&result.stderr).into_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
debug!(?output_path, "ffmpeg remux completed");
|
||||
Ok(output)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod ffmpeg;
|
||||
Reference in New Issue
Block a user