use std::collections::BTreeMap;
use crate::{
ebml::webm::parse_webm,
error::ParsingError,
file::MediaMimeTrack,
mov::{extract_moov_body_from_buf, parse_isobmff},
EntryValue, GPSInfo,
};
#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum TrackInfoTag {
Make,
Model,
Software,
CreateDate,
DurationMs,
Width,
Height,
GpsIso6709,
Author,
}
#[derive(Debug, Clone, Default)]
pub struct TrackInfo {
entries: BTreeMap<TrackInfoTag, EntryValue>,
gps_info: Option<GPSInfo>,
}
impl TrackInfo {
pub fn get(&self, tag: TrackInfoTag) -> Option<&EntryValue> {
self.entries.get(&tag)
}
pub fn gps_info(&self) -> Option<&GPSInfo> {
self.gps_info.as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = (TrackInfoTag, &EntryValue)> {
self.entries.iter().map(|(k, v)| (*k, v))
}
#[deprecated(
since = "3.1.0",
note = "no concrete use case in v3.x; always returned false in 3.0.0. Kept as a no-op for source-compat; will be removed if no use case emerges by v4."
)]
pub fn has_embedded_media(&self) -> bool {
false
}
pub(crate) fn put(&mut self, tag: TrackInfoTag, value: EntryValue) {
self.entries.insert(tag, value);
}
}
#[tracing::instrument(skip(input))]
pub(crate) fn parse_track_info(
input: &[u8],
mime_video: MediaMimeTrack,
) -> Result<TrackInfo, ParsingError> {
let mut info: TrackInfo = match mime_video {
crate::file::MediaMimeTrack::QuickTime
| crate::file::MediaMimeTrack::_3gpp
| crate::file::MediaMimeTrack::Mp4 => {
let range = extract_moov_body_from_buf(input)?;
let moov_body = &input[range];
parse_isobmff(moov_body)?
}
crate::file::MediaMimeTrack::Webm | crate::file::MediaMimeTrack::Matroska => {
parse_webm(input)?.into()
}
};
if let Some(gps) = info.get(TrackInfoTag::GpsIso6709) {
info.gps_info = gps.as_str().and_then(|s| s.parse().ok());
}
Ok(info)
}
impl TrackInfoTag {
pub const fn name(self) -> &'static str {
match self {
TrackInfoTag::Make => "Make",
TrackInfoTag::Model => "Model",
TrackInfoTag::Software => "Software",
TrackInfoTag::CreateDate => "CreateDate",
TrackInfoTag::DurationMs => "DurationMs",
TrackInfoTag::Width => "Width",
TrackInfoTag::Height => "Height",
TrackInfoTag::GpsIso6709 => "GpsIso6709",
TrackInfoTag::Author => "Author",
}
}
}
impl std::fmt::Display for TrackInfoTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
impl std::str::FromStr for TrackInfoTag {
type Err = crate::ConvertError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"Make" => TrackInfoTag::Make,
"Model" => TrackInfoTag::Model,
"Software" => TrackInfoTag::Software,
"CreateDate" => TrackInfoTag::CreateDate,
"DurationMs" => TrackInfoTag::DurationMs,
"Width" => TrackInfoTag::Width,
"Height" => TrackInfoTag::Height,
"GpsIso6709" => TrackInfoTag::GpsIso6709,
"Author" => TrackInfoTag::Author,
other => return Err(crate::ConvertError::UnknownTagName(other.to_owned())),
})
}
}
#[cfg(test)]
mod p6_baseline {
use crate::{MediaParser, MediaSource, TrackInfoTag};
#[test]
fn p6_baseline_meta_mov_dump_snapshot() {
let mut parser = MediaParser::new();
let ms = MediaSource::open("testdata/meta.mov").unwrap();
let info = parser.parse_track(ms).unwrap();
let mut entries: Vec<String> = [
TrackInfoTag::Make,
TrackInfoTag::Model,
TrackInfoTag::GpsIso6709,
TrackInfoTag::DurationMs,
TrackInfoTag::Width,
TrackInfoTag::Height,
]
.into_iter()
.filter_map(|t| info.get(t).map(|v| format!("{t:?}={v}")))
.collect();
entries.sort();
assert!(
entries.len() >= 4,
"expected >=4 well-known tags, got {entries:?}"
);
assert!(
entries.iter().any(|s| s.starts_with("Make=")),
"expected Make tag in snapshot, got {entries:?}"
);
}
#[test]
fn track_info_tag_name_is_const_str() {
const _: &str = TrackInfoTag::Make.name();
assert_eq!(TrackInfoTag::Make.name(), "Make");
assert_eq!(TrackInfoTag::GpsIso6709.name(), "GpsIso6709");
assert_eq!(TrackInfoTag::DurationMs.name(), "DurationMs");
}
#[test]
fn track_info_tag_from_str_round_trip() {
use std::str::FromStr;
for t in [
TrackInfoTag::Make,
TrackInfoTag::Model,
TrackInfoTag::Software,
TrackInfoTag::CreateDate,
TrackInfoTag::DurationMs,
TrackInfoTag::Width,
TrackInfoTag::Height,
TrackInfoTag::GpsIso6709,
TrackInfoTag::Author,
] {
assert_eq!(TrackInfoTag::from_str(t.name()).unwrap(), t);
}
}
#[test]
fn track_info_tag_from_str_unknown_returns_convert_error() {
use crate::ConvertError;
use std::str::FromStr;
let err = TrackInfoTag::from_str("Bogus").unwrap_err();
assert!(matches!(err, ConvertError::UnknownTagName(s) if s == "Bogus"));
}
#[test]
#[allow(deprecated)]
fn track_info_deprecated_has_embedded_media_returns_false() {
let mut parser = crate::MediaParser::new();
let info = parser
.parse_track(crate::MediaSource::open("testdata/meta.mov").unwrap())
.unwrap();
assert!(!info.has_embedded_media());
}
}