1use lazy_static::lazy_static;
2use regex::Regex;
3
4pub const MIN_VOLUME: u8 = 0;
6
7pub const MAX_VOLUME: u8 = 200;
9
10pub type Playlist = Vec<Track>;
12
13pub type Subtitles = Vec<Subtitle>;
15
16pub(crate) trait FromParts: Sized {
18 fn from_parts(parts: &str) -> Option<Self>;
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Track {
25 index: i32,
26 title: String,
27 length: String,
28}
29
30impl Track {
31 pub fn index(&self) -> i32 {
33 self.index
34 }
35
36 pub fn title(&self) -> &str {
38 &self.title
39 }
40
41 pub fn length(&self) -> &str {
43 &self.length
44 }
45}
46
47impl std::fmt::Display for Track {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{} - {} ({})", self.index, self.title, self.length)
50 }
51}
52
53impl FromParts for Track {
54 fn from_parts(parts: &str) -> Option<Self> {
55 lazy_static! {
56 static ref REGEX: Regex = Regex::new(
57 r"(?x)
58 \| # List item delimiter.
59 \s+
60 [\*]?
61 (?P<index>[\d]+) # The track's index.
62 \s+
63 -
64 \s+
65 (?P<title>.+) # The track's title.
66 \s
67 \(
68 (?P<length>\d\d:\d\d:\d\d) # The track's length.
69 .*
70 ",
71 )
72 .unwrap();
73 };
74 let caps = REGEX.captures(parts)?;
75 Some(Self {
76 index: caps["index"].parse().ok()?,
77 title: caps["title"].to_owned(),
78 length: caps["length"].to_owned(),
79 })
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct Subtitle {
86 index: i32,
87 title: String,
88}
89
90impl std::fmt::Display for Subtitle {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 write!(f, "{} - {}", self.index, self.title)
93 }
94}
95
96impl Subtitle {
97 pub fn index(&self) -> i32 {
99 self.index
100 }
101
102 pub fn title(&self) -> &str {
104 &self.title
105 }
106}
107
108impl FromParts for Subtitle {
109 fn from_parts(parts: &str) -> Option<Self> {
110 lazy_static! {
111 static ref REGEX: Regex = Regex::new(
112 r"(?x)
113 \| # List item delimiter.
114 \s+
115 (?P<index>[\-]?[\d]+) # The subtitle's index.
116 \s+
117 -
118 \s+
119 (?P<title>.+) # The subtitle track's title.
120 ",
121 )
122 .unwrap();
123 };
124
125 let caps = REGEX.captures(parts)?;
126 Some(Self {
127 index: caps["index"].parse().ok()?,
128 title: caps["title"].to_owned(),
129 })
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use super::*;
136
137 macro_rules! test_from_parts {
139 ($in:expr, $out:expr) => {
140 assert_eq!(FromParts::from_parts($in), $out);
141 };
142
143 ($t:ident, $in:expr, $out:expr) => {
144 assert_eq!($t::from_parts($in), $out);
145 };
146 }
147
148 #[test]
149 fn track_from_parts_none() {
150 test_from_parts!(Track, "+----[ Playlist - playlist ]", None);
151 test_from_parts!(Track, "| 1 - Playlist", None);
152 test_from_parts!(Track, "| 2 - Media Library", None);
153 test_from_parts!(Track, "+----[ End of playlist ]", None);
154 }
155
156 #[test]
157 fn track_from_parts_some() {
158 test_from_parts!(
159 "| 8 - Chopin Nocturnes.mp3 (01:50:55)",
160 Some(Track {
161 index: 8,
162 title: "Chopin Nocturnes.mp3".into(),
163 length: "01:50:55".into()
164 })
165 );
166 test_from_parts!(
167 "| *1 - Bach (00:00:01).mp3 (01:50:55)",
168 Some(Track {
169 index: 1,
170 title: "Bach (00:00:01).mp3".into(),
171 length: "01:50:55".into()
172 })
173 );
174 }
175
176 #[test]
177 fn subtitle_from_parts_none() {
178 test_from_parts!(Subtitle, "+----[ spu-es ]", None);
179 test_from_parts!(Subtitle, "+----[ end of spu-es ]", None);
180 }
181
182 #[test]
183 fn subtitle_from_parts_some() {
184 test_from_parts!(
185 "| -1 - Disable *",
186 Some(Subtitle { index: -1, title: "Disable *".into() })
187 );
188 test_from_parts!(
189 "| 2 - Track 1 - [English]",
190 Some(Subtitle { index: 2, title: "Track 1 - [English]".into() })
191 );
192 }
193}