89 lines
2.6 KiB
Rust
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>,
|
|
}
|