feat: add initial ffmpeg implementation
This commit is contained in:
parent
1d93482209
commit
8f3c7e4052
6 changed files with 237 additions and 32 deletions
126
src/ffmpeg.rs
Normal file
126
src/ffmpeg.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
};
|
||||
|
||||
use color_eyre::eyre::{ContextCompat, Result};
|
||||
use relm4::{
|
||||
Sender,
|
||||
tokio::{
|
||||
self,
|
||||
io::{AsyncBufReadExt, BufReader},
|
||||
process::Command,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::ProgressMessage;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct FfprobeOut {
|
||||
streams: Vec<StreamInfo>,
|
||||
format: FormatInfo,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct StreamInfo {
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct FormatInfo {
|
||||
duration: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VideoMeta {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub duration_us: usize,
|
||||
}
|
||||
|
||||
pub async fn get_video_meta(path: &Path) -> Result<VideoMeta> {
|
||||
let output = Command::new("ffprobe")
|
||||
.args([
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"stream=width,height : format=duration",
|
||||
"-of",
|
||||
"json",
|
||||
])
|
||||
.arg(path)
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
let str = String::from_utf8(output.stdout)?;
|
||||
let output: FfprobeOut = serde_json::from_str(&str)?;
|
||||
|
||||
let stream = output
|
||||
.streams
|
||||
.first()
|
||||
.wrap_err("could not get stream information from ffprobe")?;
|
||||
|
||||
let duration_sec = output.format.duration.parse::<f64>()?;
|
||||
let duration_us = (duration_sec * 1_000_000.0).ceil() as usize;
|
||||
|
||||
Ok(VideoMeta {
|
||||
width: stream.width,
|
||||
height: stream.height,
|
||||
duration_us,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn convert_video(path: &Path, sender: Sender<ProgressMessage>) -> Result<PathBuf> {
|
||||
let out_path = PathBuf::from("/tmp/out.webm");
|
||||
|
||||
let mut child = Command::new("ffmpeg")
|
||||
.arg("-i")
|
||||
.arg(path)
|
||||
.args([
|
||||
"-c:a",
|
||||
"libopus",
|
||||
"-b:a",
|
||||
"96k",
|
||||
"-c:v",
|
||||
"libsvtav1",
|
||||
"-loglevel",
|
||||
"error",
|
||||
"-progress",
|
||||
"-",
|
||||
"-nostats",
|
||||
])
|
||||
.arg(&out_path)
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let mut reader = BufReader::new(stdout).lines();
|
||||
|
||||
// make sure the process is actually started and awaited
|
||||
tokio::spawn(async move {
|
||||
child
|
||||
.wait()
|
||||
.await
|
||||
.expect("ffmpeg process encountered an error");
|
||||
});
|
||||
|
||||
while let Some(line) = reader.next_line().await? {
|
||||
if line.starts_with("out_time_us") {
|
||||
let (_, current_duration) = line
|
||||
.split_once("=")
|
||||
.wrap_err_with(|| format!("could not parse ffmpeg output: {line}"))?;
|
||||
|
||||
if current_duration != "N/A" {
|
||||
sender.emit(ProgressMessage::AbsProgress(
|
||||
current_duration.parse::<usize>()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(out_path)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue