Skip to main content

nom_exif/
video.rs

1use std::{
2    collections::{btree_map::IntoIter, BTreeMap},
3    fmt::Display,
4};
5
6use crate::{
7    ebml::webm::parse_webm,
8    error::ParsingError,
9    file::MimeVideo,
10    mov::{extract_moov_body_from_buf, parse_mp4, parse_qt},
11    EntryValue, GPSInfo,
12};
13
14/// Try to keep the tag name consistent with [`crate::ExifTag`], and add some
15/// unique to video/audio, such as `DurationMs`.
16///
17/// Different variants of `TrackInfoTag` may have different value types, please
18/// refer to the documentation of each variant.
19#[derive(Debug, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, Hash)]
20#[non_exhaustive]
21pub enum TrackInfoTag {
22    /// Its value is an `EntryValue::Text`.
23    Make,
24
25    /// Its value is an `EntryValue::Text`.
26    Model,
27
28    /// Its value is an `EntryValue::Text`.
29    Software,
30
31    /// Its value is an [`EntryValue::Time`].
32    CreateDate,
33
34    /// Duration in millisecond, its value is an `EntryValue::U64`.
35    DurationMs,
36
37    /// Its value is an `EntryValue::U32`.
38    ImageWidth,
39
40    /// Its value is an `EntryValue::U32`.
41    ImageHeight,
42
43    /// Its value is an `EntryValue::Text`, location presented in ISO6709.
44    ///
45    /// If you need a parsed [`GPSInfo`] which provides more detailed GPS info,
46    /// please use [`TrackInfo::get_gps_info`].
47    GpsIso6709,
48}
49
50/// Represents parsed track info.
51#[derive(Debug, Clone, Default)]
52pub struct TrackInfo {
53    entries: BTreeMap<TrackInfoTag, EntryValue>,
54    gps_info: Option<GPSInfo>,
55}
56
57impl TrackInfo {
58    /// Get value for `tag`. Different variants of `TrackInfoTag` may have
59    /// different value types, please refer to [`TrackInfoTag`].
60    pub fn get(&self, tag: TrackInfoTag) -> Option<&EntryValue> {
61        self.entries.get(&tag)
62    }
63
64    /// Get parsed `GPSInfo`.
65    pub fn get_gps_info(&self) -> Option<&GPSInfo> {
66        self.gps_info.as_ref()
67    }
68
69    /// Get an iterator for `(&TrackInfoTag, &EntryValue)`. The parsed
70    /// `GPSInfo` is not included.
71    pub fn iter(&self) -> impl Iterator<Item = (&TrackInfoTag, &EntryValue)> {
72        self.entries.iter()
73    }
74
75    pub(crate) fn put(&mut self, tag: TrackInfoTag, value: EntryValue) {
76        self.entries.insert(tag, value);
77    }
78}
79
80/// Parse video/audio info from `reader`. The file format will be detected
81/// automatically by parser, if the format is not supported, an `Err` will be
82/// returned.
83///
84/// Currently supported file formats are:
85///
86/// - ISO base media file format (ISOBMFF): *.mp4, *.mov, *.3gp, etc.
87/// - Matroska based file format: *.webm, *.mkv, *.mka, etc.
88///
89/// ## Explanation of the generic parameters of this function:
90///
91/// - In order to improve parsing efficiency, the parser will internally skip
92///   some useless bytes during parsing the byte stream, which is called
93///   [`Skip`] internally.
94///
95/// - In order to support both `Read` and `Read` + `Seek` types, the interface
96///   of input parameters is defined as `Read`.
97///   
98/// - Since Rust does not support specialization, the parser cannot internally
99///   distinguish between `Read` and `Seek` and provide different `Skip`
100///   implementations for them.
101///   
102/// Therefore, We chose to let the user specify how `Skip` works:
103///
104/// - `parse_track_info::<SkipSeek, _>(reader)` means the `reader` supports
105///   `Seek`, so `Skip` will use the `Seek` trait to implement efficient skip
106///   operations.
107///   
108/// - `parse_track_info::<SkipRead, _>(reader)` means the `reader` dosn't
109///   support `Seek`, so `Skip` will fall back to using `Read` to implement the
110///   skip operations.
111///
112/// ## Performance impact
113///
114/// If your `reader` only supports `Read`, it may cause performance loss when
115/// processing certain large files. For example, *.mov files place metadata at
116/// the end of the file, therefore, when parsing such files, locating metadata
117/// will be slightly slower.
118///
119/// ## Examples
120///
121/// ```rust
122/// use nom_exif::*;
123/// use std::fs::File;
124/// use chrono::DateTime;
125///
126/// let ms = MediaSource::file_path("./testdata/meta.mov").unwrap();
127/// let mut parser = MediaParser::new();
128/// let info: TrackInfo = parser.parse(ms).unwrap();
129///
130/// assert_eq!(info.get(TrackInfoTag::Make), Some(&"Apple".into()));
131/// assert_eq!(info.get(TrackInfoTag::Model), Some(&"iPhone X".into()));
132/// assert_eq!(info.get(TrackInfoTag::GpsIso6709), Some(&"+27.1281+100.2508+000.000/".into()));
133/// assert_eq!(info.get_gps_info().unwrap().latitude_ref, 'N');
134/// assert_eq!(
135///     info.get_gps_info().unwrap().latitude,
136///     [(27, 1), (7, 1), (68, 100)].into(),
137/// );
138/// ```
139pub(crate) fn parse_track_info(
140    input: &[u8],
141    mime_video: MimeVideo,
142) -> Result<TrackInfo, ParsingError> {
143    let mut info: TrackInfo = match mime_video {
144        crate::file::MimeVideo::QuickTime
145        | crate::file::MimeVideo::_3gpp
146        | crate::file::MimeVideo::Mp4 => {
147            let range = extract_moov_body_from_buf(input)?;
148            let moov_body = &input[range];
149
150            match mime_video {
151                MimeVideo::QuickTime => parse_qt(moov_body)?.into(),
152
153                MimeVideo::Mp4 | MimeVideo::_3gpp => parse_mp4(moov_body)?.into(),
154                _ => unreachable!(),
155            }
156        }
157        crate::file::MimeVideo::Webm | crate::file::MimeVideo::Matroska => {
158            parse_webm(input)?.into()
159        }
160    };
161
162    if let Some(gps) = info.get(TrackInfoTag::GpsIso6709) {
163        info.gps_info = gps.as_str().and_then(|s| s.parse().ok());
164    }
165
166    Ok(info)
167}
168
169impl IntoIterator for TrackInfo {
170    type Item = (TrackInfoTag, EntryValue);
171    type IntoIter = IntoIter<TrackInfoTag, EntryValue>;
172
173    fn into_iter(self) -> Self::IntoIter {
174        self.entries.into_iter()
175    }
176}
177
178impl From<BTreeMap<TrackInfoTag, EntryValue>> for TrackInfo {
179    fn from(entries: BTreeMap<TrackInfoTag, EntryValue>) -> Self {
180        Self {
181            entries,
182            gps_info: None,
183        }
184    }
185}
186
187impl Display for TrackInfoTag {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        let s: &str = (*self).into();
190        s.fmt(f)
191    }
192}
193
194impl From<TrackInfoTag> for &str {
195    fn from(value: TrackInfoTag) -> Self {
196        match value {
197            TrackInfoTag::Make => "Make",
198            TrackInfoTag::Model => "Model",
199            TrackInfoTag::Software => "Software",
200            TrackInfoTag::CreateDate => "CreateDate",
201            TrackInfoTag::DurationMs => "DurationMs",
202            TrackInfoTag::ImageWidth => "ImageWidth",
203            TrackInfoTag::ImageHeight => "ImageHeight",
204            TrackInfoTag::GpsIso6709 => "GpsIso6709",
205        }
206    }
207}