1use std::{
2 path::Path,
3 process::{Command, ExitStatus},
4};
5
6use serde::{Deserialize, Serialize};
7use serde_with::{DisplayFromStr, serde_as};
8
9#[derive(Debug, Deserialize, Serialize)]
10pub struct FFProbe {
11 pub streams: Vec<Stream>,
12 pub format: Format,
13}
14
15#[derive(Debug, Deserialize, Serialize)]
16pub struct Stream {}
17
18#[serde_as]
19#[derive(Debug, Deserialize, Serialize)]
20pub struct Format {
21 #[serde_as(as = "DisplayFromStr")]
22 pub duration: f64,
23}
24
25#[derive(Debug, thiserror::Error)]
26pub enum FFProbeError {
27 #[error("{0}")]
28 Io(#[from] std::io::Error),
29 #[error("{0}")]
30 Json(#[from] serde_json::Error),
31 #[error("{0}")]
32 Process(ExitStatus),
33}
34
35const ARGS: &[&str] = &[
36 "-v",
37 "quiet",
38 "-print_format",
39 "json",
40 "-show_format",
41 "-show_streams",
42];
43
44pub fn ffprobe<P: AsRef<Path>>(path: P) -> Result<FFProbe, FFProbeError> {
45 let output = Command::new("ffprobe")
46 .args(ARGS)
47 .arg(path.as_ref())
48 .output()?;
49
50 if output.status.success() {
51 return serde_json::from_slice(&output.stdout).map_err(FFProbeError::Json);
52 }
53
54 Err(FFProbeError::Process(output.status))
55}
56
57#[cfg(feature = "tokio")]
58pub async fn async_ffprobe<P: AsRef<Path>>(path: P) -> Result<FFProbe, FFProbeError> {
59 let output = tokio::process::Command::new("ffprobe")
60 .args(ARGS)
61 .arg(path.as_ref())
62 .output()
63 .await?;
64
65 if output.status.success() {
66 return serde_json::from_slice(&output.stdout).map_err(FFProbeError::Json);
67 }
68
69 Err(FFProbeError::Process(output.status))
70}