1use crate::{
2 bail,
3 error::{Context as _, Error, Result},
4};
5use logos::Logos;
6
7#[derive(Logos, Debug, PartialEq)]
8#[logos(skip r"[ \t\n\f]+")]
9#[logos(error = String)]
10pub enum Token<'a> {
11 #[token("#EXTM3U")]
12 ExtM3U,
13 #[token("#EXT-X-STREAM-INF")]
14 StreamInf,
15 #[token("#EXT-X-I-FRAME-STREAM-INF")]
16 IFrameStreamInf,
17
18 #[token("PROGRAM-ID")]
19 ProgramId,
20 #[token("BANDWIDTH")]
21 Bandwidth,
22 #[token("RESOLUTION")]
23 Resolution,
24 #[token("FRAME-RATE")]
25 FrameRate,
26 #[token("CODECS")]
27 Codecs,
28 #[token("URI")]
29 Uri,
30
31 #[token("=")]
32 Equal,
33 #[token(",")]
34 Comma,
35 #[token(":")]
36 Colon,
37
38 #[regex(r"[0-9]+\.[0-9]+", |lex| lexical::parse(lex.slice()).ok())]
39 Float(f64),
40 #[regex(r"[0-9]+", |lex| lexical::parse(lex.slice()).ok())]
41 Integer(usize),
42 #[regex(r#""([^"]*)""#, |lex| lex.slice()[1..lex.slice().len() - 1].as_ref())]
43 String(&'a str),
44
45 #[regex(r"[0-9]+x[0-9]+", |lex| {
46 let mut parts = lex.slice().split('x');
47 let width = parts.next().unwrap().parse().unwrap();
48 let height = parts.next().unwrap().parse().unwrap();
49 (width, height)
50 })]
51 ResolutionValue((usize, usize)),
52 #[regex(r"[a-zA-Z0-9\-_]+\.m3u8")]
53 UriValue(&'a str),
54}
55
56#[derive(Debug, PartialEq)]
57pub struct MultiVariantPlaylist {
58 pub variant_streams: Vec<VariantStream>,
62 pub frame_streams: Vec<FrameStream>,
66}
67
68#[derive(Debug, PartialEq)]
69pub struct VariantStream {
70 pub program_id: Option<u8>,
72 pub bandwidth: u32,
74 pub resolution: (u16, u16),
76 pub frame_rate: Option<f32>,
78 pub codecs: Option<String>,
80 pub uri: String,
82}
83
84#[derive(Debug, PartialEq)]
85pub struct FrameStream {
86 pub bandwidth: u32,
88 pub resolution: (u16, u16),
90 pub codecs: String,
92 pub uri: String,
94}
95
96pub fn parse(input: &str) -> Result<MultiVariantPlaylist> {
97 let mut lexer = Token::lexer(input);
98 let mut variant_streams = Vec::new();
99 let mut frame_streams = Vec::new();
100
101 while let Some(token) = lexer.next() {
102 match token? {
103 Token::ExtM3U => (),
104 Token::StreamInf => {
105 variant_streams.push(parse_variant_stream(&mut lexer)?);
106 }
107 Token::IFrameStreamInf => {
108 frame_streams.push(parse_frame_stream(&mut lexer)?);
109 }
110 _ => (),
111 }
112 }
113
114 Ok(MultiVariantPlaylist {
115 variant_streams,
116 frame_streams,
117 })
118}
119
120fn parse_variant_stream<'a>(lexer: &mut logos::Lexer<'a, Token<'a>>) -> Result<VariantStream> {
121 let mut program_id = None;
122 let mut bandwidth = 0;
123 let mut resolution = (0, 0);
124 let mut frame_rate = None;
125 let mut codecs = None;
126 let mut uri = String::new();
127
128 while let Some(token) = lexer.next() {
129 match token? {
130 Token::Colon => (),
131 Token::Equal => (),
132 Token::Comma => (),
133 Token::ProgramId => {
134 program_id = match lexer.nth(1).context("program id")?? {
135 Token::Integer(value) => Some(value as u8),
136 _ => bail!("Invalid program id"),
137 };
138 }
139 Token::Bandwidth => {
140 bandwidth = match lexer.nth(1).context("bandwidth")?? {
141 Token::Integer(value) => value as u32,
142 _ => bail!("Invalid bandwidth"),
143 };
144 }
145 Token::Resolution => {
146 resolution = match lexer.nth(1).context("resolution")?? {
147 Token::ResolutionValue(res) => (res.0 as u16, res.1 as u16),
148 _ => bail!("Invalid resolution"),
149 };
150 }
151 Token::FrameRate => {
152 frame_rate = match lexer.nth(1).context("frame rate")?? {
153 Token::Float(rate) => Some(rate as f32),
154 _ => bail!("Invalid frame rate"),
155 };
156 }
157 Token::Codecs => {
158 codecs = Some(match lexer.nth(1).context("codecs")?? {
159 Token::String(codec) => codec.to_string(),
160 _ => bail!("Invalid codecs"),
161 });
162 }
163 Token::UriValue(value) => {
164 uri = value.to_string();
165 break;
166 }
167 _ => bail!("Invalid variant stream"),
168 }
169 }
170
171 Ok(VariantStream {
172 program_id,
173 bandwidth,
174 resolution,
175 frame_rate,
176 codecs,
177 uri,
178 })
179}
180
181fn parse_frame_stream<'a>(lexer: &mut logos::Lexer<'a, Token<'a>>) -> Result<FrameStream> {
182 let mut bandwidth = 0;
183 let mut resolution = (0, 0);
184 let mut codecs = String::new();
185 let mut uri = String::new();
186
187 while let Some(token) = lexer.next() {
188 match token? {
189 Token::Colon => (),
190 Token::Equal => (),
191 Token::Comma => (),
192 Token::Bandwidth => {
193 bandwidth = match lexer.nth(1).context("bandwidth")?? {
194 Token::Integer(value) => value as u32,
195 _ => bail!("Invalid bandwidth"),
196 };
197 }
198 Token::Resolution => {
199 resolution = match lexer.nth(1).context("resolution")?? {
200 Token::ResolutionValue(res) => (res.0 as u16, res.1 as u16),
201 _ => bail!("Invalid resolution"),
202 };
203 }
204 Token::Codecs => {
205 codecs = match lexer.nth(1).context("codecs")?? {
206 Token::String(codec) => codec.to_string(),
207 _ => bail!("Invalid codecs"),
208 };
209 }
210 Token::Uri => {
211 uri = match lexer.nth(1).context("uri")?? {
212 Token::String(uri) => uri.to_string(),
213 _ => bail!("Invalid uri"),
214 };
215 break;
216 }
217 _ => bail!("Invalid frame stream"),
218 }
219 }
220
221 Ok(FrameStream {
222 bandwidth,
223 resolution,
224 codecs,
225 uri,
226 })
227}
228
229#[test]
230fn test_variant_stream_token() {
231 let input = "
232 #EXTM3U
233 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2553505,RESOLUTION=1920x1080,FRAME-RATE=25.000,CODECS=\"avc1.640032,mp4a.40.2\"
234 index-f1-v1-a1.m3u8
235 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1420969,RESOLUTION=1280x720,FRAME-RATE=25.000,CODECS=\"avc1.64001f,mp4a.40.2\"
236 index-f2-v1-a1.m3u8
237 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=641061,RESOLUTION=640x360,FRAME-RATE=25.000,CODECS=\"avc1.64001e,mp4a.40.2\"
238 index-f3-v1-a1.m3u8
239
240 #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=217533,RESOLUTION=1920x1080,CODECS=\"avc1.640032\",URI=\"iframes-f1-v1-a1.m3u8\"
241 #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=140609,RESOLUTION=1280x720,CODECS=\"avc1.64001f\",URI=\"iframes-f2-v1-a1.m3u8\"
242 #EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=58096,RESOLUTION=640x360,CODECS=\"avc1.64001e\",URI=\"iframes-f3-v1-a1.m3u8\"
243 ";
244
245 let multi_variant_playlist = parse(input).unwrap();
246 assert_eq!(
247 multi_variant_playlist,
248 MultiVariantPlaylist {
249 variant_streams: vec![
250 VariantStream {
251 program_id: Some(1),
252 bandwidth: 2553505,
253 resolution: (1920, 1080),
254 frame_rate: Some(25.0),
255 codecs: Some("avc1.640032,mp4a.40.2".to_string()),
256 uri: "index-f1-v1-a1.m3u8".to_string(),
257 },
258 VariantStream {
259 program_id: Some(1),
260 bandwidth: 1420969,
261 resolution: (1280, 720),
262 frame_rate: Some(25.0),
263 codecs: Some("avc1.64001f,mp4a.40.2".to_string()),
264 uri: "index-f2-v1-a1.m3u8".to_string(),
265 },
266 VariantStream {
267 program_id: Some(1),
268 bandwidth: 641061,
269 resolution: (640, 360),
270 frame_rate: Some(25.0),
271 codecs: Some("avc1.64001e,mp4a.40.2".to_string()),
272 uri: "index-f3-v1-a1.m3u8".to_string(),
273 },
274 ],
275 frame_streams: vec![
276 FrameStream {
277 bandwidth: 217533,
278 resolution: (1920, 1080),
279 codecs: "avc1.640032".to_string(),
280 uri: "iframes-f1-v1-a1.m3u8".to_string(),
281 },
282 FrameStream {
283 bandwidth: 140609,
284 resolution: (1280, 720),
285 codecs: "avc1.64001f".to_string(),
286 uri: "iframes-f2-v1-a1.m3u8".to_string(),
287 },
288 FrameStream {
289 bandwidth: 58096,
290 resolution: (640, 360),
291 codecs: "avc1.64001e".to_string(),
292 uri: "iframes-f3-v1-a1.m3u8".to_string(),
293 },
294 ],
295 }
296 );
297}