m3u8_reader/
multi_variant.rs1use crate::error::{Context as _, Result};
2use memchr::memchr;
3use std::str;
4
5#[derive(Debug)]
7pub struct MultiVariantPlaylist {
8 pub variant_streams: Vec<VariantStream>,
12 pub frame_streams: Vec<FrameStream>,
16}
17
18#[derive(Debug)]
19pub struct VariantStream {
20 pub program_id: Option<u8>,
22 pub bandwidth: u32,
24 pub resolution: (u16, u16),
26 pub frame_rate: Option<f32>,
28 pub codecs: Option<String>,
30 pub uri: String,
32}
33
34#[derive(Debug)]
35pub struct FrameStream {
36 pub bandwidth: u32,
38 pub resolution: (u16, u16),
40 pub codecs: String,
42 pub uri: String,
44}
45
46pub fn parse(bytes: &[u8]) -> Result<MultiVariantPlaylist> {
47 let mut variant_streams = Vec::new();
48 let mut frame_streams = Vec::new();
49
50 let mut position = 0;
51 while position < bytes.len() {
52 let newline_pos =
53 memchr(b'\n', &bytes[position..]).unwrap_or(bytes.len() - position) + position;
54 let line = &bytes[position..newline_pos];
55
56 if line.starts_with(b"#EXT-X-STREAM-INF") {
57 variant_streams.push(parse_variant_stream(line)?);
58 } else if line.trim_ascii().ends_with(b".m3u8") {
59 if let Some(last_stream) = variant_streams.last_mut() {
60 last_stream.uri = str::from_utf8(line)?.trim().to_string();
61 }
62 } else if line.starts_with(b"#EXT-X-I-FRAME-STREAM-INF") {
63 frame_streams.push(parse_frame_stream(line)?);
64 }
65
66 position = newline_pos + 1;
67 }
68
69 Ok(MultiVariantPlaylist {
70 variant_streams,
71 frame_streams,
72 })
73}
74
75fn parse_variant_stream(line: &[u8]) -> Result<VariantStream> {
76 let mut in_quotes = false;
77 let mut attributes = Vec::new();
78 let mut current_attribute = Vec::new();
79
80 for &byte in &line[18..] {
81 if byte == b'"' {
82 in_quotes = !in_quotes;
83 current_attribute.push(byte);
84 } else if byte == b',' && !in_quotes {
85 attributes.push(current_attribute.clone());
86 current_attribute.clear();
87 } else {
88 current_attribute.push(byte);
89 }
90 }
91 attributes.push(current_attribute);
92
93 let mut stream = VariantStream {
94 program_id: None,
95 bandwidth: 0,
96 resolution: (0, 0),
97 frame_rate: None,
98 codecs: None,
99 uri: String::new(),
100 };
101
102 for attribute in attributes {
103 let mut parts = attribute.splitn(2, |&byte| byte == b'=');
104 let key = parts.next().context("Missing key")?;
105 let value = parts.next().context("Missing value")?;
106
107 match key {
108 b"PROGRAM-ID" => stream.program_id = str::from_utf8(value)?.parse().ok(),
109 b"BANDWIDTH" => stream.bandwidth = str::from_utf8(value)?.parse()?,
110 b"RESOLUTION" => {
111 let mut res_parts = value.trim_ascii().splitn(2, |&byte| byte == b'x');
112 stream.resolution.0 =
113 str::from_utf8(res_parts.next().context("Missing width")?)?.parse()?;
114 stream.resolution.1 =
115 str::from_utf8(res_parts.next().context("Missing height")?)?.parse()?;
116 }
117 b"FRAME-RATE" => stream.frame_rate = str::from_utf8(value)?.parse().ok(),
118 b"CODECS" => {
119 stream.codecs = Some(str::from_utf8(value)?.replace('\"', "").trim().to_string())
120 }
121 _ => eprintln!(
122 "Unknown attribute: {}",
123 str::from_utf8(key).unwrap_or("Invalid UTF-8")
124 ),
125 }
126 }
127
128 Ok(stream)
129}
130
131fn parse_frame_stream(line: &[u8]) -> Result<FrameStream> {
132 let attributes = line[26..].split(|&byte| byte == b',').collect::<Vec<_>>();
133 let mut stream = FrameStream {
134 bandwidth: 0,
135 resolution: (0, 0),
136 codecs: String::new(),
137 uri: String::new(),
138 };
139
140 for attribute in attributes {
141 let mut parts = attribute.splitn(2, |&byte| byte == b'=');
142 let key = parts.next().context("Missing key")?;
143 let value = parts.next().context("Missing value")?;
144
145 match key {
146 b"BANDWIDTH" => stream.bandwidth = str::from_utf8(value)?.parse()?,
147 b"RESOLUTION" => {
148 let mut res_parts = value.trim_ascii().splitn(2, |&byte| byte == b'x');
149 stream.resolution.0 =
150 str::from_utf8(res_parts.next().context("Missing width")?)?.parse()?;
151 stream.resolution.1 =
152 str::from_utf8(res_parts.next().context("Missing height")?)?.parse()?;
153 }
154 b"CODECS" => {
155 stream.codecs = str::from_utf8(value)?.replace('\"', "").trim().to_string()
156 }
157 b"URI" => stream.uri = str::from_utf8(value)?.replace('\"', "").trim().to_string(),
158 _ => eprintln!(
159 "Unknown attribute: {}",
160 str::from_utf8(key).unwrap_or("Invalid UTF-8")
161 ),
162 }
163 }
164
165 Ok(stream)
166}