Files
arr/crates/arr-probe/src/ffprobe.rs
T
naps62-yolo 1cb7f1b2fa
ci / rust (push) Successful in 1m20s
ci / web (push) Successful in 7s
e2e / e2e (push) Successful in 1m28s
feat(probe): ffprobe wrapper (#59)
2026-08-22 20:52:09 +01:00

89 lines
2.6 KiB
Rust

//! The slice of `ffprobe`'s JSON output this crate reads.
//!
//! Everything is optional because `ffprobe` omits fields rather than nulling
//! them, and which ones it omits depends on the container.
use std::collections::BTreeMap;
use serde::Deserialize;
/// One `ffprobe -print_format json -show_format -show_streams` document.
#[derive(Debug, Deserialize)]
pub(crate) struct Output {
#[serde(default)]
pub format: Format,
#[serde(default)]
pub streams: Vec<Stream>,
}
/// Container-level facts.
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Format {
#[serde(rename = "format_name")]
pub name: Option<String>,
pub duration: Option<String>,
pub size: Option<String>,
}
/// One stream of any kind. `codec_type` says which.
#[derive(Debug, Deserialize)]
pub(crate) struct Stream {
pub codec_type: Option<String>,
pub codec_name: Option<String>,
pub width: Option<u32>,
pub height: Option<u32>,
pub color_transfer: Option<String>,
pub duration: Option<String>,
#[serde(default)]
pub disposition: Disposition,
#[serde(default)]
pub tags: Tags,
#[serde(default)]
pub side_data_list: Vec<SideData>,
}
impl Stream {
pub fn is_kind(&self, kind: &str) -> bool {
self.codec_type.as_deref() == Some(kind)
}
/// A real video stream, not the cover art bolted onto an audio file.
pub fn is_video(&self) -> bool {
self.is_kind("video") && self.disposition.attached_pic == 0
}
}
/// Stream flags. Only `attached_pic` is read: an MP3 or FLAC with embedded
/// artwork reports it as a video stream, and taking that for video would make
/// an audio file look importable.
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Disposition {
#[serde(default)]
pub attached_pic: u8,
}
/// Stream metadata. Matroska writes `HANDLER_NAME`, MP4 writes `handler_name`,
/// so every lookup here is case-insensitive.
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Tags(BTreeMap<String, String>);
impl Tags {
pub fn get(&self, key: &str) -> Option<&str> {
self.0
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(key))
.map(|(_, value)| value.as_str())
.filter(|value| !value.is_empty())
}
}
/// A stream side-data block. Only the Dolby Vision configuration record and
/// the HDR10+ marker are read; the rest are ignored.
#[derive(Debug, Deserialize)]
pub(crate) struct SideData {
#[serde(rename = "side_data_type")]
pub kind: Option<String>,
pub dv_profile: Option<u8>,
pub dv_bl_signal_compatibility_id: Option<u8>,
}