use std::{
collections::{btree_map::IntoIter, BTreeMap},
fmt::Display,
};
use thiserror::Error;
use crate::{
ebml::webm::parse_webm,
error::ParsingError,
file::MimeVideo,
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,
ImageWidth,
ImageHeight,
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 get_gps_info(&self) -> Option<&GPSInfo> {
self.gps_info.as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = (&TrackInfoTag, &EntryValue)> {
self.entries.iter()
}
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: MimeVideo,
) -> Result<TrackInfo, ParsingError> {
let mut info: TrackInfo = match mime_video {
crate::file::MimeVideo::QuickTime
| crate::file::MimeVideo::_3gpp
| crate::file::MimeVideo::Mp4 => {
let range = extract_moov_body_from_buf(input)?;
let moov_body = &input[range];
parse_isobmff(moov_body)?.into()
}
crate::file::MimeVideo::Webm | crate::file::MimeVideo::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 IntoIterator for TrackInfo {
type Item = (TrackInfoTag, EntryValue);
type IntoIter = IntoIter<TrackInfoTag, EntryValue>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
impl From<BTreeMap<TrackInfoTag, EntryValue>> for TrackInfo {
fn from(entries: BTreeMap<TrackInfoTag, EntryValue>) -> Self {
Self {
entries,
gps_info: None,
}
}
}
impl Display for TrackInfoTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s: &str = (*self).into();
s.fmt(f)
}
}
impl From<TrackInfoTag> for &str {
fn from(value: TrackInfoTag) -> Self {
match value {
TrackInfoTag::Make => "Make",
TrackInfoTag::Model => "Model",
TrackInfoTag::Software => "Software",
TrackInfoTag::CreateDate => "CreateDate",
TrackInfoTag::DurationMs => "DurationMs",
TrackInfoTag::ImageWidth => "ImageWidth",
TrackInfoTag::ImageHeight => "ImageHeight",
TrackInfoTag::GpsIso6709 => "GpsIso6709",
TrackInfoTag::Author => "Author",
}
}
}
#[derive(Debug, Error)]
#[error("unknown TrackInfoTag: {0}")]
pub struct UnknownTrackInfoTag(pub String);
impl TryFrom<&str> for TrackInfoTag {
type Error = UnknownTrackInfoTag;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let tag = match value {
"Make" => TrackInfoTag::Make,
"Model" => TrackInfoTag::Model,
"Software" => TrackInfoTag::Software,
"CreateDate" => TrackInfoTag::CreateDate,
"DurationMs" => TrackInfoTag::DurationMs,
"ImageWidth" => TrackInfoTag::ImageWidth,
"ImageHeight" => TrackInfoTag::ImageHeight,
"GpsIso6709" => TrackInfoTag::GpsIso6709,
"Author" => TrackInfoTag::Author,
x => return Err(UnknownTrackInfoTag(x.to_owned())),
};
Ok(tag)
}
}