1#![doc = include_str!("../README.md")]
2#![warn(clippy::pedantic, clippy::nursery, clippy::enum_glob_use)]
16#![allow(
17 clippy::module_name_repetitions,
18 clippy::too_many_lines,
19 clippy::cognitive_complexity
20)]
21#![cfg_attr(docsrs, feature(doc_cfg))]
22
23use std::{collections::HashMap, io, num::NonZeroU8};
24
25pub mod playlist;
26pub mod tags;
27
28#[cfg_attr(docsrs, doc(cfg(feature = "steering-manifest")))]
29#[cfg(feature = "steering-manifest")]
30pub mod steering_manifest;
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum RenditionPlaybackPriority {
35 Default,
37
38 AutoSelect,
42
43 None,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum InStreamId {
50 Cc1,
52
53 Cc2,
55
56 Cc3,
58
59 Cc4,
61
62 Service(NonZeroU8),
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum AudioChannelInformation {
69 NumberOfChannelsOnly {
70 number_of_channels: u64,
72 },
73 WithAudioCodingIdentifiers {
74 number_of_channels: u64,
76
77 audio_coding_identifiers: Vec<String>,
79 },
80 WithSpecialUsageIdentifiers {
81 number_of_channels: u64,
83
84 audio_coding_identifiers: Vec<String>,
86
87 binaural: bool,
89
90 immersive: bool,
92
93 downmix: bool,
95 },
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub struct StreamInf {
101 pub bandwidth_bits_per_second: u64,
103
104 pub average_bandwidth_bits_per_second: Option<u64>,
106
107 pub score: Option<f64>,
110
111 pub codecs: Vec<String>,
114
115 pub supplemental_codecs: Vec<SupplementalCodec>,
118
119 pub resolution: Option<Resolution>,
122
123 pub hdcp_level: Option<HdcpLevel>,
126
127 pub allowed_cpc: Vec<ContentProtectionConfiguration>,
131 pub video_range: VideoRange,
132
133 pub required_video_layout: Vec<VideoChannelSpecifier>,
136
137 pub stable_variant_id: Option<String>,
140
141 pub pathway_id: Option<String>,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct SupplementalCodec {
150 supplemental_codec: String,
151 compatibility_brands: Vec<String>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
156pub enum HdcpLevel {
157 None,
159
160 Type0,
162
163 Type1,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct Resolution {
170 pub width: u64,
171 pub height: u64,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct ContentProtectionConfiguration {
177 pub key_format: String,
178
179 pub cpc_labels: Vec<String>,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum VideoRange {
186 Sdr,
187 Hlg,
188 Pq,
189 Other(String),
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum VideoChannelSpecifier {
195 Stereo,
196 Mono,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct SessionData {
202 pub data_id: String,
204
205 pub value: crate::SessionDataValue,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
211pub enum SessionDataValue {
212 Value {
214 value: String,
216
217 language: Option<String>,
219 },
220
221 Uri {
223 uri: String,
225
226 format: UriFormat,
228 },
229}
230
231#[derive(Debug, Clone, PartialEq, Eq, Default)]
233pub enum UriFormat {
234 #[default]
236 Json,
237
238 Raw,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum EncryptionMethod {
245 Aes128 {
246 uri: String,
248
249 iv: Option<u128>,
251
252 key_format: KeyFormat,
254
255 key_format_versions: Vec<u64>,
257 },
258 SampleAes {
259 uri: String,
261
262 iv: Option<u128>,
264
265 key_format_versions: Vec<u64>,
267 },
268 SampleAesCtr {
269 uri: String,
271
272 key_format_versions: Vec<u64>,
274 },
275}
276
277#[derive(Debug, Clone, PartialEq, Eq)]
279pub enum KeyFormat {
280 Identity,
281 Other(String),
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
286pub struct ContentSteering {
287 pub server_uri: String,
289 pub pathway_id: Option<String>,
290}
291
292#[derive(Debug, Clone, PartialEq)]
294pub struct DateRange {
295 pub id: String,
297
298 pub class: Option<String>,
301
302 pub start_date: chrono::DateTime<chrono::FixedOffset>,
304
305 pub cue: Option<DateRangeCue>,
307
308 pub end_date: Option<chrono::DateTime<chrono::FixedOffset>>,
310
311 pub duration_seconds: Option<f64>,
313
314 pub planned_duration_seconds: Option<f64>,
316
317 pub client_attributes: HashMap<String, AttributeValue>,
320
321 pub scte35_cmd: Vec<u8>,
323
324 pub scte35_in: Vec<u8>,
326
327 pub scte35_out: Vec<u8>,
329
330 pub end_on_next: bool,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq)]
338pub struct DateRangeCue {
339 pub once: bool,
341
342 pub position: DateRangeCuePosition,
344}
345
346#[derive(Debug, Clone, PartialEq, Eq)]
348pub enum DateRangeCuePosition {
349 Pre,
352
353 Post,
356
357 Neither,
358}
359
360#[derive(Debug, Clone, PartialEq)]
361pub enum AttributeValue {
362 String(String),
363 Bytes(Vec<u8>),
364 Float(f64),
365}
366
367#[derive(Debug, Clone, PartialEq, Eq)]
370pub struct PreloadHint {
371 pub hint_type: PreloadHintType,
373
374 pub uri: String,
376
377 pub start_byte_offset: u64,
380
381 pub length_in_bytes: Option<u64>,
385}
386
387#[derive(Debug, Clone, PartialEq, Eq)]
389pub enum PreloadHintType {
390 Part,
392
393 Map,
395}
396
397#[derive(Debug, Clone, PartialEq, Eq)]
399pub struct ByteRange {
400 pub length_bytes: u64,
402
403 pub start_offset_bytes: Option<u64>,
406}
407
408#[derive(Debug, Clone, PartialEq, Eq)]
410pub struct ByteRangeWithOffset {
411 pub length_bytes: u64,
413
414 pub start_offset_bytes: u64,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum PlaylistType {
423 Event,
424 Vod,
425}
426
427#[derive(Debug, Clone, PartialEq)]
429pub struct DeltaUpdateInfo {
430 pub skip_boundary_seconds: f64,
431
432 pub can_skip_dateranges: bool,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct RenditionReport {
441 pub uri: String,
443
444 pub last_sequence_number: Option<u64>,
447
448 pub last_part_index: Option<u64>,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
455pub enum DefinitionType {
456 Inline { name: String, value: String },
458
459 Import { name: String },
462
463 QueryParameter { name: String },
467}
468
469#[derive(Debug, Clone, PartialEq)]
470pub enum FloatOrInteger {
471 Float(f64),
472 Integer(u64),
473}
474
475impl ByteRange {
476 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
477 write!(output, "{}", self.length_bytes)?;
478 if let Some(start_offset_bytes) = self.start_offset_bytes {
479 write!(output, "@{start_offset_bytes}")?;
480 }
481
482 Ok(())
483 }
484}
485
486impl ByteRangeWithOffset {
487 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
488 write!(output, "{}@{}", self.length_bytes, self.start_offset_bytes)
489 }
490}
491
492impl EncryptionMethod {
493 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
494 match self {
495 Self::Aes128 { uri, .. } => write!(output, "METHOD=AES-128,URI=\"{uri}\"")?,
496 Self::SampleAes { uri, .. } => write!(output, "METHOD=SAMPLE-AES,URI=\"{uri}\"")?,
497 Self::SampleAesCtr { uri, .. } => {
498 write!(output, "METHOD=SAMPLE-AES-CTR,URI=\"{uri}\"")?;
499 }
500 }
501
502 match self {
503 Self::Aes128 { iv: Some(iv), .. } | Self::SampleAes { iv: Some(iv), .. } => {
504 write!(output, ",IV={iv:#X}")?;
505 }
506 _ => (),
507 }
508
509 if let Self::Aes128 {
510 key_format: KeyFormat::Other(key_format),
511 ..
512 } = self
513 {
514 write!(output, ",KEYFORMAT=\"{key_format}\"")?;
515 }
516
517 match self {
518 Self::Aes128 {
519 key_format_versions,
520 ..
521 }
522 | Self::SampleAes {
523 key_format_versions,
524 ..
525 }
526 | Self::SampleAesCtr {
527 key_format_versions,
528 ..
529 } => {
530 if !(key_format_versions.is_empty()
531 || key_format_versions.len() == 1 && key_format_versions[0] == 1)
532 {
533 write!(output, ",KEYFORMATVERSIONS=\"")?;
534
535 if key_format_versions.len() == 1 {
536 write!(output, "{}", key_format_versions[0])?;
537 } else {
538 for (i, version) in key_format_versions.iter().enumerate() {
539 if i == key_format_versions.len() - 1 {
540 write!(output, "{version}")?;
541 } else {
542 write!(output, "{version}/")?;
543 }
544 }
545 }
546
547 write!(output, "\"")?;
548 }
549 }
550 }
551
552 Ok(())
553 }
554}
555
556impl StreamInf {
557 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
558 write!(output, "BANDWIDTH={}", self.bandwidth_bits_per_second)?;
559
560 if let Some(average_bandwidth) = self.average_bandwidth_bits_per_second {
561 write!(output, ",AVERAGE-BANDWIDTH={average_bandwidth}")?;
562 }
563
564 if let Some(score) = self.score {
565 write!(output, ",SCORE={score}")?;
566 }
567
568 if !self.codecs.is_empty() {
569 write!(output, ",CODECS=\"")?;
570
571 if self.codecs.len() == 1 {
572 write!(output, "{}", self.codecs[0])?;
573 } else {
574 for (i, codec) in self.codecs.iter().enumerate() {
575 if i == self.codecs.len() - 1 {
576 write!(output, "{codec}")?;
577 } else {
578 write!(output, "{codec},")?;
579 }
580 }
581 }
582
583 write!(output, "\"")?;
584 }
585
586 if !self.supplemental_codecs.is_empty() {
587 write!(output, ",SUPPLEMENTAL-CODECS=\"")?;
588
589 if self.supplemental_codecs.len() == 1 {
590 self.supplemental_codecs[0].serialize(&mut output)?;
591 } else {
592 for (i, supplemental_codec) in self.supplemental_codecs.iter().enumerate() {
593 supplemental_codec.serialize(&mut output)?;
594 if i != self.supplemental_codecs.len() - 1 {
595 write!(output, ",")?;
596 }
597 }
598 }
599
600 write!(output, "\"")?;
601 }
602
603 if let Some(resolution) = &self.resolution {
604 write!(
605 output,
606 ",RESOLUTION={}x{}",
607 resolution.width, resolution.height
608 )?;
609 }
610
611 if let Some(level) = &self.hdcp_level {
612 match level {
613 HdcpLevel::None => write!(output, ",HDCP-LEVEL=NONE")?,
614 HdcpLevel::Type0 => write!(output, ",HDCP-LEVEL=TYPE-0")?,
615 HdcpLevel::Type1 => write!(output, ",HDCP-LEVEL=TYPE-1")?,
616 }
617 }
618
619 if !self.allowed_cpc.is_empty() {
620 write!(output, ",ALLOWED-CPC=\"")?;
621
622 if self.allowed_cpc.len() == 1 {
623 self.allowed_cpc[0].serialize(&mut output)?;
624 } else {
625 for (i, config) in self.allowed_cpc.iter().enumerate() {
626 config.serialize(&mut output)?;
627 if i != self.allowed_cpc.len() - 1 {
628 write!(output, ",")?;
629 }
630 }
631 }
632
633 write!(output, "\"")?;
634 }
635
636 match &self.video_range {
637 VideoRange::Sdr => (),
638 VideoRange::Hlg => write!(output, ",VIDEO-RANGE=HLG")?,
639 VideoRange::Pq => write!(output, ",VIDEO-RANGE=PQ")?,
640 VideoRange::Other(other) => write!(output, ",VIDEO-RANGE={other}")?,
641 }
642
643 if !(self.required_video_layout.is_empty()
644 || (self.required_video_layout.len() == 1
645 && matches!(self.required_video_layout[0], VideoChannelSpecifier::Mono)))
646 {
647 write!(output, ",REQ-VIDEO-LAYOUT=\"")?;
648
649 if self.required_video_layout.len() == 1 {
650 #[allow(clippy::match_on_vec_items)]
651 match self.required_video_layout[0] {
652 VideoChannelSpecifier::Stereo => write!(output, "CH-STEREO")?,
653 VideoChannelSpecifier::Mono => unreachable!(),
654 }
655 } else {
656 for (i, config) in self.required_video_layout.iter().enumerate() {
657 match config {
658 VideoChannelSpecifier::Stereo => write!(output, "CH-STEREO")?,
659 VideoChannelSpecifier::Mono => write!(output, "CH-MONO")?,
660 }
661 if i != self.required_video_layout.len() - 1 {
662 write!(output, ",")?;
663 }
664 }
665 }
666
667 write!(output, "\"")?;
668 }
669
670 if let Some(id) = &self.stable_variant_id {
671 write!(output, ",STABLE-VARIANT-ID=\"{id}\"")?;
672 }
673
674 if let Some(id) = &self.pathway_id {
675 write!(output, ",PATHWAY-ID=\"{id}\"")?;
676 }
677
678 Ok(())
679 }
680}
681
682impl SupplementalCodec {
683 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
684 write!(output, "{}", self.supplemental_codec)?;
685
686 for brand in &self.compatibility_brands {
687 write!(output, "/{brand}")?;
688 }
689
690 Ok(())
691 }
692}
693
694impl ContentProtectionConfiguration {
695 fn serialize(&self, mut output: impl io::Write) -> io::Result<()> {
696 write!(output, "{}:", self.key_format)?;
697
698 if self.cpc_labels.len() == 1 {
699 write!(output, "{}", self.cpc_labels[0])?;
700 } else {
701 for (i, label) in self.cpc_labels.iter().enumerate() {
702 if i == self.cpc_labels.len() - 1 {
703 write!(output, "{label}")?;
704 } else {
705 write!(output, "{label}/")?;
706 }
707 }
708 }
709
710 Ok(())
711 }
712}