Skip to main content

pito_codecs/mkv/
track.rs

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}