1use crate::media::{Codec, TimeBase};
2use crate::mp4::TrackKind;
3
4#[derive(Clone, Debug, Default, PartialEq)]
5pub struct Mastering {
6 pub primaries: [(f64, f64); 3],
7 pub white_point: (f64, f64),
8 pub luminance_max: f64,
9 pub luminance_min: f64,
10}
11
12#[derive(Clone, Debug, Default, PartialEq)]
13pub struct Colour {
14 pub matrix: Option<u64>,
15 pub bits_per_channel: Option<u64>,
16 pub chroma_siting: (Option<u64>, Option<u64>),
17 pub range: Option<u64>,
18 pub transfer: Option<u64>,
19 pub primaries: Option<u64>,
20 pub max_cll: Option<u64>,
21 pub max_fall: Option<u64>,
22 pub mastering: Option<Mastering>,
23}
24
25impl Colour {
26 pub fn full_range(&self) -> bool {
27 self.range == Some(2)
28 }
29}
30
31#[derive(Clone, Debug, Default, PartialEq)]
32pub struct Projection {
33 pub kind: u64,
34 pub yaw: f64,
35 pub pitch: f64,
36 pub roll: f64,
37}
38
39#[derive(Clone, Debug, Default, PartialEq)]
40pub struct VideoInfo {
41 pub pixel_width: u32,
42 pub pixel_height: u32,
43 pub display_width: Option<u32>,
44 pub display_height: Option<u32>,
45 pub display_unit: u64,
46 pub crop: (u32, u32, u32, u32),
47 pub interlaced: u64,
48 pub colour: Option<Colour>,
49 pub projection: Option<Projection>,
50}
51
52impl VideoInfo {
53 pub fn rotation(&self) -> Option<u16> {
54 let Some(p) = &self.projection else {
55 return Some(0);
56 };
57 if p.kind != 0 || p.yaw != 0.0 || p.pitch != 0.0 || p.roll % 90.0 != 0.0 {
58 return None;
59 }
60 Some((-(p.roll / 90.0) as i64).rem_euclid(4) as u16 * 90)
61 }
62
63 pub fn pixel_aspect(&self) -> (u32, u32) {
64 let pw = self
65 .pixel_width
66 .saturating_sub(self.crop.2.saturating_add(self.crop.3)) as u64;
67 let ph = self
68 .pixel_height
69 .saturating_sub(self.crop.0.saturating_add(self.crop.1)) as u64;
70 let (Some(dw), Some(dh)) = (self.display_width, self.display_height) else {
71 return (1, 1);
72 };
73 if pw == 0 || ph == 0 || dw == 0 || dh == 0 || !matches!(self.display_unit, 0 | 3) {
74 return (1, 1);
75 }
76 let (n, d) = (dw as u64 * ph, dh as u64 * pw);
77 let g = gcd(n, d);
78 ((n / g) as u32, (d / g) as u32)
79 }
80}
81
82pub(crate) fn gcd(mut a: u64, mut b: u64) -> u64 {
83 while b != 0 {
84 (a, b) = (b, a % b);
85 }
86 a.max(1)
87}
88
89#[derive(Clone, Debug, PartialEq)]
90pub struct AudioInfo {
91 pub sampling_frequency: f64,
92 pub output_sampling_frequency: Option<f64>,
93 pub channels: u64,
94 pub bit_depth: Option<u64>,
95}
96
97impl Default for AudioInfo {
98 fn default() -> Self {
99 Self {
100 sampling_frequency: 8000.0,
101 output_sampling_frequency: None,
102 channels: 1,
103 bit_depth: None,
104 }
105 }
106}
107
108#[derive(Clone, Debug, PartialEq)]
109pub enum ContentEncoding {
110 HeaderStripping { bytes: Vec<u8>, scope: u64 },
111 Compression { algo: u64, scope: u64 },
112 Encryption { scope: u64 },
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct Sample {
117 pub offset: u64,
118 pub size: u32,
119 pub pts: i64,
120 pub duration: u32,
121 pub keyframe: bool,
122 pub cluster: u32,
123 pub block: u64,
124 pub lace: u16,
125}
126
127#[derive(Clone, Debug, PartialEq)]
128pub struct Track {
129 pub number: u64,
130 pub uid: u64,
131 pub kind: TrackKind,
132 pub enabled: bool,
133 pub default: bool,
134 pub name: String,
135 pub language: String,
136 pub codec_id: String,
137 pub codec_private: Vec<u8>,
138 pub default_duration: Option<u64>,
139 pub codec_delay: u64,
140 pub seek_pre_roll: u64,
141 pub width: u32,
142 pub height: u32,
143 pub video: Option<VideoInfo>,
144 pub audio: Option<AudioInfo>,
145 pub encodings: Vec<ContentEncoding>,
146 pub time_base: TimeBase,
147 pub samples: Vec<Sample>,
148 pub(crate) sync: Vec<u32>,
149 pub(crate) pts_order: Vec<u32>,
150}
151
152const AAC_RATES: [u32; 13] = [
153 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
154];
155
156pub fn codec_name(codec_id: &str) -> &str {
157 match codec_id {
158 "V_MPEG4/ISO/AVC" => "h264",
159 "V_MPEGH/ISO/HEVC" => "hevc",
160 "V_VP8" => "vp8",
161 "V_VP9" => "vp9",
162 "V_AV1" => "av1",
163 "A_OPUS" => "opus",
164 "A_VORBIS" => "vorbis",
165 "A_FLAC" => "flac",
166 "A_AC3" => "ac3",
167 "A_EAC3" => "eac3",
168 "A_MPEG/L3" => "mp3",
169 "A_MPEG/L2" => "mp2",
170 "A_MPEG/L1" => "mp1",
171 "S_TEXT/UTF8" => "subrip",
172 "S_TEXT/WEBVTT" => "webvtt",
173 "S_TEXT/ASS" => "ass",
174 id if id.starts_with("A_AAC") => "aac",
175 id if id.starts_with("A_PCM") => "pcm",
176 id => id,
177 }
178}
179
180impl Track {
181 pub fn codec(&self) -> Option<Codec> {
182 match codec_name(&self.codec_id) {
183 "h264" => Some(Codec::H264),
184 "hevc" => Some(Codec::Hevc),
185 "aac" => Some(Codec::Aac),
186 "vp9" => Some(Codec::Vp9),
187 "opus" => Some(Codec::Opus),
188 "ac3" => Some(Codec::Ac3),
189 "eac3" => Some(Codec::Eac3),
190 "mp3" | "mp2" | "mp1" => Some(Codec::Mp3),
191 _ => None,
192 }
193 }
194
195 pub fn codec_name(&self) -> &str {
196 codec_name(&self.codec_id)
197 }
198
199 pub fn extradata(&self) -> Option<Vec<u8>> {
200 if !self.codec_private.is_empty() {
201 return Some(self.codec_private.clone());
202 }
203 let id = self.codec_id.as_str();
204 if !id.starts_with("A_AAC/") {
205 return None;
206 }
207 let a = self.audio.clone().unwrap_or_default();
208 let object_type: u8 = if id.ends_with("/MAIN") {
209 1
210 } else if id.ends_with("/SSR") {
211 3
212 } else if id.ends_with("/LTP") {
213 4
214 } else {
215 2
216 };
217 let index = |r: f64| AAC_RATES.iter().position(|&x| x as f64 == r).unwrap_or(4) as u8;
218 let ri = index(a.sampling_frequency);
219 let ch = a.channels.min(7) as u8;
220 let mut v = vec![(object_type << 3) | (ri >> 1), ((ri & 1) << 7) | (ch << 3)];
221 if id.ends_with("/SBR") {
222 let ext = index(
223 a.output_sampling_frequency
224 .unwrap_or(a.sampling_frequency * 2.0),
225 );
226 v.extend_from_slice(&[0x56, 0xE5, 0x80 | (ext << 3)]);
227 }
228 Some(v)
229 }
230
231 pub fn channels(&self) -> u32 {
232 self.audio.as_ref().map_or(0, |a| a.channels as u32)
233 }
234
235 pub fn sample_rate(&self) -> u32 {
236 self.audio.as_ref().map_or(0, |a| {
237 a.output_sampling_frequency
238 .filter(|_| self.codec_name() == "aac")
239 .unwrap_or(a.sampling_frequency)
240 .round() as u32
241 })
242 }
243
244 pub fn rotation(&self) -> u16 {
245 self.video
246 .as_ref()
247 .and_then(VideoInfo::rotation)
248 .unwrap_or(0)
249 }
250
251 pub fn seconds(&self, ticks: i64) -> f64 {
252 self.time_base.seconds(ticks)
253 }
254
255 pub fn ticks(&self, seconds: f64) -> i64 {
256 (seconds * self.time_base.den as f64 / self.time_base.num as f64).round() as i64
257 }
258
259 pub fn nanos(&self, ticks: i64) -> i64 {
260 (ticks as i128 * self.time_base.num as i128 * 1_000_000_000 / self.time_base.den as i128)
261 as i64
262 }
263
264 pub fn end(&self) -> i64 {
265 self.samples
266 .iter()
267 .map(|s| s.pts.saturating_add(s.duration as i64))
268 .max()
269 .unwrap_or(0)
270 }
271
272 pub fn duration_seconds(&self) -> f64 {
273 let start = self.samples.iter().map(|s| s.pts).min().unwrap_or(0).min(0);
274 self.seconds(self.end().saturating_sub(start))
275 }
276
277 pub fn fps(&self) -> f64 {
278 if let Some(d) = self.default_duration.filter(|&d| d > 0) {
279 return 1e9 / d as f64;
280 }
281 let n = self.samples.len();
282 let span = self
283 .end()
284 .saturating_sub(self.samples.iter().map(|s| s.pts).min().unwrap_or(0));
285 if n == 0 || span <= 0 {
286 0.0
287 } else {
288 n as f64 / self.seconds(span)
289 }
290 }
291
292 pub fn keyframes(&self) -> &[u32] {
293 &self.sync
294 }
295
296 pub fn keyframe_before(&self, index: usize) -> usize {
297 let k = self.sync.partition_point(|&i| i as usize <= index);
298 if k == 0 { 0 } else { self.sync[k - 1] as usize }
299 }
300
301 pub fn sample_at(&self, pts: i64) -> Option<usize> {
302 if self.pts_order.is_empty() {
303 return None;
304 }
305 let k = self
306 .pts_order
307 .partition_point(|&i| self.samples[i as usize].pts <= pts);
308 Some(self.pts_order[k.saturating_sub(1)] as usize)
309 }
310
311 pub fn keyframe_at(&self, pts: i64) -> Option<usize> {
312 let mut best: Option<usize> = None;
313 for &k in &self.sync {
314 let s = &self.samples[k as usize];
315 if s.pts <= pts && best.is_none_or(|b| s.pts >= self.samples[b].pts) {
316 best = Some(k as usize);
317 }
318 }
319 best.or_else(|| self.sync.first().map(|&k| k as usize))
320 }
321
322 pub fn stripped_header(&self) -> Option<&[u8]> {
323 self.encodings.iter().find_map(|e| match e {
324 ContentEncoding::HeaderStripping { bytes, scope } if scope & 1 != 0 => {
325 Some(bytes.as_slice())
326 }
327 _ => None,
328 })
329 }
330
331 pub fn frames_readable(&self) -> bool {
332 self.encodings.iter().all(|e| match e {
333 ContentEncoding::HeaderStripping { .. } => true,
334 ContentEncoding::Compression { scope, .. } | ContentEncoding::Encryption { scope } => {
335 scope & 1 == 0
336 }
337 })
338 }
339
340 pub(crate) fn finish_indexes(&mut self) {
341 self.sync = self
342 .samples
343 .iter()
344 .enumerate()
345 .filter(|(_, s)| s.keyframe)
346 .map(|(i, _)| i as u32)
347 .collect();
348 let mut order: Vec<u32> = (0..self.samples.len() as u32).collect();
349 order.sort_by_key(|&i| (self.samples[i as usize].pts, i));
350 self.pts_order = order;
351 }
352}