1use std::cmp::Ordering;
4
5#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
7#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
8pub enum MediaType {
9 Audio,
11 Video,
13 Subtitles,
15 ClosedCaptions,
17}
18
19#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum ExtractorType {
23 MpegDash,
25 Hls,
27 HttpLive,
29 Mss,
31}
32
33#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum Choice {
37 No,
39 Yes,
41}
42
43#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum RoleType {
47 Subtitle,
48 Main,
49 Alternate,
50 Supplementary,
51 Commentary,
52 Dub,
53 Description,
54 Sign,
55 Metadata,
56 ForcedSubtitle,
57 Numeric(i32),
59}
60
61impl RoleType {
62 pub const fn from_number(value: i32) -> Self {
64 match value {
65 0 => Self::Subtitle,
66 1 => Self::Main,
67 2 => Self::Alternate,
68 3 => Self::Supplementary,
69 4 => Self::Commentary,
70 5 => Self::Dub,
71 6 => Self::Description,
72 7 => Self::Sign,
73 8 => Self::Metadata,
74 9 => Self::ForcedSubtitle,
75 other => Self::Numeric(other),
76 }
77 }
78
79 pub fn parse_enum_token(value: &str) -> Option<Self> {
81 let trimmed = value.trim();
82 parse_role_name(trimmed).or_else(|| trimmed.parse::<i32>().ok().map(Self::from_number))
83 }
84}
85
86fn parse_role_name(value: &str) -> Option<RoleType> {
87 match value.to_ascii_lowercase().as_str() {
88 "subtitle" => Some(RoleType::Subtitle),
89 "main" => Some(RoleType::Main),
90 "alternate" => Some(RoleType::Alternate),
91 "supplementary" => Some(RoleType::Supplementary),
92 "commentary" => Some(RoleType::Commentary),
93 "dub" => Some(RoleType::Dub),
94 "description" => Some(RoleType::Description),
95 "sign" => Some(RoleType::Sign),
96 "metadata" => Some(RoleType::Metadata),
97 "forcedsubtitle" => Some(RoleType::ForcedSubtitle),
98 _ => None,
99 }
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
104#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
105pub enum EncryptionMethod {
106 #[default]
108 None,
109 Aes128,
110 Aes128Ecb,
111 SampleAes,
112 SampleAesCtr,
113 Cenc,
114 Chacha20,
115 Unknown,
116}
117
118impl EncryptionMethod {
119 pub fn parse(value: Option<&str>) -> Self {
121 match value.map(normalize_token).as_deref() {
122 Some("none") => Self::None,
123 Some("aes128") => Self::Aes128,
124 Some("aes128ecb") => Self::Aes128Ecb,
125 Some("sampleaes") => Self::SampleAes,
126 Some("sampleaesctr") => Self::SampleAesCtr,
127 Some("cenc") => Self::Cenc,
128 Some("chacha20") => Self::Chacha20,
129 Some(_) => Self::Unknown,
130 None => Self::Unknown,
131 }
132 }
133}
134
135#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
137#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
138pub enum KeySource {
139 #[default]
141 None,
142 Inline,
144 Uri,
146 File,
148 KeyTextFile,
150 Custom,
152}
153
154#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
156#[derive(Clone, Debug, Default, Eq, PartialEq)]
157pub struct EncryptionInfo {
158 pub method: EncryptionMethod,
160 pub key: Option<Vec<u8>>,
162 pub iv: Option<Vec<u8>>,
164 pub kid: Option<Vec<u8>>,
166 pub scheme: Option<String>,
168 pub protection_data: Option<Vec<u8>>,
170 pub source: KeySource,
172}
173
174impl EncryptionInfo {
175 pub fn is_encrypted(&self) -> bool {
177 self.method != EncryptionMethod::None
178 }
179}
180
181#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
183#[derive(Clone, Copy, Debug, Eq, PartialEq)]
184pub struct ByteRange {
185 pub start: i64,
187 pub length: i64,
189}
190
191impl ByteRange {
192 pub fn end(&self) -> Option<i64> {
194 Some(self.start.wrapping_add(self.length).wrapping_sub(1))
195 }
196}
197
198#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
200#[derive(Clone, Debug)]
201pub struct MediaSegment {
202 pub index: i64,
204 pub duration: f64,
206 pub title: Option<String>,
208 pub program_date_time: Option<String>,
210 pub start_range: Option<i64>,
212 pub expected_length: Option<i64>,
214 pub encryption: EncryptionInfo,
216 pub url: String,
218 pub name_from_var: Option<String>,
220}
221
222impl Default for MediaSegment {
223 fn default() -> Self {
224 Self {
225 index: 0,
226 duration: 0.0,
227 title: None,
228 program_date_time: None,
229 start_range: None,
230 expected_length: None,
231 encryption: EncryptionInfo::default(),
232 url: String::new(),
233 name_from_var: None,
234 }
235 }
236}
237
238impl MediaSegment {
239 pub fn stop_range(&self) -> Option<i64> {
241 match (self.start_range, self.expected_length) {
242 (Some(start), Some(length)) => Some(start.wrapping_add(length).wrapping_sub(1)),
243 _ => None,
244 }
245 }
246
247 pub fn is_encrypted(&self) -> bool {
249 self.encryption.is_encrypted()
250 }
251}
252
253impl PartialEq for MediaSegment {
254 fn eq(&self, other: &Self) -> bool {
255 self.index == other.index
256 && (self.duration - other.duration).abs() < 0.001
257 && self.title == other.title
258 && self.start_range == other.start_range
259 && self.stop_range() == other.stop_range()
260 && self.expected_length == other.expected_length
261 && self.url == other.url
262 }
263}
264
265#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
267#[derive(Clone, Debug, Default, PartialEq)]
268pub struct MediaPart {
269 pub media_segments: Vec<MediaSegment>,
271}
272
273impl MediaPart {
274 pub fn total_duration(&self) -> f64 {
276 self.media_segments
277 .iter()
278 .map(|segment| segment.duration)
279 .sum()
280 }
281}
282
283#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
285#[derive(Clone, Debug, PartialEq)]
286pub struct Playlist {
287 pub url: String,
289 pub is_live: bool,
291 pub refresh_interval_ms: f64,
293 pub target_duration: Option<f64>,
295 pub media_init: Option<MediaSegment>,
297 pub media_parts: Vec<MediaPart>,
299}
300
301impl Default for Playlist {
302 fn default() -> Self {
303 Self {
304 url: String::new(),
305 is_live: false,
306 refresh_interval_ms: 15_000.0,
307 target_duration: None,
308 media_init: None,
309 media_parts: Vec::new(),
310 }
311 }
312}
313
314impl Playlist {
315 pub fn total_duration(&self) -> f64 {
317 self.media_parts.iter().map(MediaPart::total_duration).sum()
318 }
319
320 pub fn segments_count(&self) -> usize {
322 self.media_parts
323 .iter()
324 .map(|part| part.media_segments.len())
325 .sum()
326 }
327}
328
329#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
331#[derive(Clone, Debug, Default, Eq, PartialEq)]
332pub struct MssData {
333 pub four_cc: String,
334 pub codec_private_data: String,
335 pub stream_type: String,
336 pub timescale: i32,
337 pub sampling_rate: i32,
338 pub channels: i32,
339 pub bits_per_sample: i32,
340 pub nal_unit_length_field: i32,
341 pub duration: i64,
342 pub is_protection: bool,
343 pub protection_system_id: String,
344 pub protection_data: String,
345}
346
347#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
349#[derive(Clone, Debug, Default, PartialEq)]
350pub struct Stream {
351 pub id: String,
353 pub media_type: Option<MediaType>,
355 pub group_id: Option<String>,
357 pub language: Option<String>,
359 pub name: Option<String>,
361 pub default: Option<Choice>,
363 pub forced: Option<Choice>,
365 pub skipped_duration: Option<f64>,
367 pub mss_data: Option<MssData>,
369 pub bandwidth: Option<i64>,
371 pub codecs: Option<String>,
373 pub resolution: Option<String>,
375 pub frame_rate: Option<f64>,
377 pub channels: Option<String>,
379 pub extension: Option<String>,
381 pub role: Option<RoleType>,
383 pub video_range: Option<String>,
385 pub characteristics: Option<String>,
387 pub publish_time: Option<String>,
389 pub audio_id: Option<String>,
391 pub video_id: Option<String>,
393 pub subtitle_id: Option<String>,
395 pub period_id: Option<String>,
397 pub url: String,
399 pub original_url: String,
401 pub playlist: Option<Playlist>,
403}
404
405impl Stream {
406 pub fn segments_count(&self) -> usize {
408 self.playlist.as_ref().map_or(0, Playlist::segments_count)
409 }
410
411 pub fn total_duration(&self) -> Option<f64> {
413 self.playlist.as_ref().map(Playlist::total_duration)
414 }
415}
416
417#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
419#[derive(Clone, Debug, Default, PartialEq)]
420pub struct Manifest {
421 pub extractor_type: Option<ExtractorType>,
423 pub source: Option<String>,
425 pub streams: Vec<Stream>,
427 pub warnings: Vec<String>,
429 pub is_live: bool,
431}
432
433pub fn sort_streams_compatible(streams: &mut [Stream]) {
435 streams.sort_by(compare_streams_compatible);
436}
437
438pub fn compare_streams_compatible(left: &Stream, right: &Stream) -> Ordering {
440 media_rank(left.media_type)
441 .cmp(&media_rank(right.media_type))
442 .then_with(|| compare_option_i64_desc(left.bandwidth, right.bandwidth))
443 .then_with(|| audio_channel_order(right).cmp(&audio_channel_order(left)))
444}
445
446pub fn audio_channel_order(stream: &Stream) -> i32 {
448 let Some(channels) = &stream.channels else {
449 return 0;
450 };
451 let first = channels.split('/').next().unwrap_or_default();
452 first.parse::<i32>().unwrap_or_default()
453}
454
455fn media_rank(media_type: Option<MediaType>) -> i32 {
456 match media_type {
457 None => -1,
458 Some(MediaType::Audio) => 0,
459 Some(MediaType::Video) => 1,
460 Some(MediaType::Subtitles) => 2,
461 Some(MediaType::ClosedCaptions) => 3,
462 }
463}
464
465fn compare_option_i64_desc(left: Option<i64>, right: Option<i64>) -> Ordering {
466 match (left, right) {
467 (Some(left), Some(right)) => right.cmp(&left),
468 (Some(_), None) => Ordering::Less,
469 (None, Some(_)) => Ordering::Greater,
470 (None, None) => Ordering::Equal,
471 }
472}
473
474fn normalize_token(value: &str) -> String {
475 value
476 .chars()
477 .filter(|ch| *ch != '-' && *ch != '_' && *ch != ' ')
478 .flat_map(char::to_lowercase)
479 .collect()
480}
481
482#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
484#[derive(Clone, Debug, Default, Eq, PartialEq)]
485pub enum StreamSelector {
486 #[default]
488 Interactive,
489 Auto,
491 SubtitlesOnly,
493 ExplicitIds(Vec<String>),
495}