playlist_decoder/
lib.rs

1//! This is a very simple url extractor for different kinds of playlist formats: M3U, PLS, ASX, XSPF
2//!
3//! It is not optimized yet and does create a lot of strings on the way.
4
5extern crate quick_xml;
6
7mod pls;
8mod m3u;
9mod asx;
10mod xspf;
11mod decode_error;
12
13use std::collections::HashSet;
14
15pub use decode_error::PlaylistDecodeError;
16
17/// Decode playlist content string. It checks for M3U, PLS, XSPF and ASX content in the string.
18/// # Example
19/// ```rust
20/// let list = playlist_decoder::decode(r##"<?xml version="1.0" encoding="UTF-8"?>
21///    <playlist version="1" xmlns="http://xspf.org/ns/0/">
22///      <trackList>
23///        <track>
24///          <title>Nobody Move, Nobody Get Hurt</title>
25///          <creator>We Are Scientists</creator>
26///          <location>file:///mp3s/titel_1.mp3</location>
27///        </track>
28///        <track>
29///          <title>See The World</title>
30///          <creator>The Kooks</creator>
31///          <location>http://www.example.org/musik/world.ogg</location>
32///        </track>
33///      </trackList>
34///    </playlist>"##).unwrap();
35/// assert!(list.len() == 2, "Did not find 2 urls in example");
36/// for item in list {
37///     println!("{:?}", item);
38/// }
39/// ```
40/// # Arguments
41/// * `content` - A string slice containing a playlist
42pub fn decode(content: &str) -> Result<Vec<String>, PlaylistDecodeError> {
43    let mut set = HashSet::new();
44    let content_small = content.to_lowercase();
45    match content_small.find("<playlist"){
46        Some(_)=>{
47            let xspf_items = xspf::decode(content)?;
48            for item in xspf_items {
49                if item.url != "" {
50                    set.insert(item.url);
51                }
52                if item.identifier != "" {
53                    set.insert(item.identifier);
54                }
55            }
56        }
57        None =>{
58            match content_small.find("<asx"){
59                Some(_)=>{
60                    let pls_items = asx::decode(content)?;
61                    for item in pls_items {
62                        set.insert(item.url);
63                    }
64                }
65                None =>{
66                    match content_small.find("[playlist]"){
67                        Some(_) => {
68                            let pls_items = pls::decode(content);
69                            for item in pls_items {
70                                set.insert(item.url);
71                            }
72                        }
73                        None => {
74                            let m3u_items = m3u::decode(content);
75                            for item in m3u_items {
76                                set.insert(item.url);
77                            }
78                        }
79                    }
80                }
81            }
82        }
83    }
84    let v: Vec<String> = set.into_iter().collect();
85    Ok(v)
86}
87
88pub fn is_content_hls(content: &str) -> bool {
89    if content.contains("EXT-X-STREAM-INF"){
90        return true;
91    }
92    if content.contains("EXT-X-TARGETDURATION"){
93        return true;
94    }
95    return false;
96}
97
98#[cfg(test)]
99mod tests {
100    #[test]
101    fn xspf() {
102        let s = r#"<?xml version="1.0" encoding="UTF-8"?>
103<playlist version="1" xmlns="http://xspf.org/ns/0/">
104    <trackList>
105    <track>
106        <title>Title</title>
107        <identifier>Identifier</identifier>
108        <location>http://this.is.an.example</location>
109    </track>
110    <track>
111        <title>Title2</title>
112        <identifier>Identifier2</identifier>
113        <location>http://this.is.an.example2</location>
114    </track>
115    </trackList>
116</playlist>"#;
117        let items = crate::xspf::decode(s);
118        assert!(items.is_ok());
119        let items = items.unwrap();
120        assert!(items.len() == 2);
121        assert!(items[0].url == "http://this.is.an.example");
122        assert!(items[0].title == "Title");
123        assert!(items[0].identifier == "Identifier");
124        assert!(items[1].url == "http://this.is.an.example2");
125        assert!(items[1].title == "Title2");
126        assert!(items[1].identifier == "Identifier2");
127    }
128
129    #[test]
130    fn asx() {
131        let s = r#"<asx version="3.0">
132  <title>Test-Liste</title>
133  <entry>
134    <title>title1</title>
135    <ref href="ref1"/>
136  </entry>
137  <entry>
138    <title>title2</title>
139    <ref href="ref2"/>
140  </entry>
141</asx>"#;
142        let items = crate::asx::decode(s);
143        assert!(items.is_ok());
144        let items = items.unwrap();
145        assert!(items.len() == 2);
146        assert!(items[0].url == "ref1");
147        assert!(items[0].title == "title1");
148        assert!(items[1].url == "ref2");
149        assert!(items[1].title == "title2");
150    }
151
152    #[test]
153    fn m3u() {
154        let items = crate::m3u::decode("http://this.is.an.example");
155        assert!(items.len() == 1);
156        assert!(items[0].url == "http://this.is.an.example");
157    }
158
159    #[test]
160    fn pls() {
161        let items = crate::pls::decode("[playlist]
162File1=http://this.is.an.example
163Title1=mytitle
164        ");
165        assert!(items.len() == 1);
166        assert!(items[0].url == "http://this.is.an.example");
167        assert!(items[0].title == "mytitle");
168    }
169
170    #[test]
171    fn pls2() {
172        let items = crate::pls::decode("[playlist]
173File1=http://this.is.an.example
174Title=mytitle
175        ");
176        assert!(items.len() == 1);
177        assert!(items[0].url == "http://this.is.an.example");
178        assert!(items[0].title == "mytitle");
179    }
180
181    #[test]
182    fn pls3() {
183        let items = crate::pls::decode("[Playlist]
184File1=http://this.is.an.example
185Title=mytitle
186        ");
187        assert!(items.len() == 1);
188        assert!(items[0].url == "http://this.is.an.example");
189        assert!(items[0].title == "mytitle");
190    }
191}