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}