1use serde::{Deserialize, Serialize};
2
3const EXTM3U: &str = "#EXTM3U";
4const EXTINF: &str = "#EXTINF";
5
6#[derive(Serialize, Deserialize, Debug, Clone)]
7pub struct Entry {
8 pub title: Option<String>,
9 pub url: String,
10 pub time: Option<i32>,
11}
12impl core::fmt::Display for Entry {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 let info = match (self.time, self.title.clone()) {
15 (Some(d), Some(t)) => {
16 format!("{EXTINF}:{},{}\n{}", d, t, self.url)
17 }
18 (None, Some(t)) => {
19 format!("{EXTINF}:0,{}\n{}", t, self.url)
20 }
21 (Some(d), None) => {
22 format!("{EXTINF}:{}", d)
23 }
24 (None, None) => self.url.to_string(),
25 };
26 f.write_str(info.as_str())
27 }
28}
29
30#[derive(Serialize, Deserialize, Debug, Clone)]
31pub struct Playlist {
32 pub list: Vec<Entry>,
33}
34
35impl core::fmt::Display for Playlist {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 let s = format!(
38 "{EXTM3U}\n{}",
39 self.list
40 .iter()
41 .map(|i| i.to_string())
42 .collect::<Vec<_>>()
43 .join("\n")
44 );
45 f.write_str(s.as_str())
46 }
47}
48
49impl<'a> From<&'a str> for Playlist {
50 fn from(value: &'a str) -> Self {
51 let mut list: Vec<Entry> = vec![];
52 let mut lines = value.lines();
53
54 let first = lines.next();
55 if let Some(s) = first {
56 if s != EXTM3U {
57 list.push(Entry {
58 url: s.to_string(),
59 title: None,
60 time: None,
61 });
62 for i in lines {
63 list.push(Entry {
64 url: i.to_string(),
65 title: None,
66 time: None,
67 })
68 }
69 } else {
70 while let (Some(info), Some(url)) = (lines.next(), lines.next()) {
71 if let (Some(header_index), Some(title_index)) =
72 (info.find(':'), info.find(','))
73 {
74 let time: i32 = info[header_index + 1..title_index]
75 .parse()
76 .unwrap_or_default();
77 let title = info[title_index + 1..].to_string();
78 list.push(Entry {
79 url: url.to_string(),
80 title: Some(title),
81 time: Some(time),
82 })
83 }
84 }
85 }
86 }
87 Self { list }
88 }
89}
90
91#[cfg(test)]
92mod test {
93 use crate::Playlist;
94
95 #[test]
96 pub fn base() {
97 let s = r#"
98#EXTM3U
99#EXTINF:419,Alice in Chains - Rotten Apple
100Alice in Chains_Jar of Flies_01_Rotten Apple.mp3
101#EXTINF:260,Alice in Chains - Nutshell
102Alice in Chains_Jar of Flies_02_Nutshell.mp3
103"#
104 .trim();
105
106 let playlist = Playlist::from(s);
107
108 assert_eq!(playlist.list.len(), 2);
109 assert_eq!(playlist.to_string(), s);
110 }
111}