Merge lp:~ahayzen/cappella/cappella10_play_stream_2 into lp:cappella
- cappella10_play_stream_2
- Merge into 1.0
Proposed by
Andrew Hayzen
Status: | Merged |
---|---|
Approved by: | Andrew Hayzen |
Approved revision: | 99 |
Merged at revision: | 99 |
Proposed branch: | lp:~ahayzen/cappella/cappella10_play_stream_2 |
Merge into: | lp:cappella |
Diff against target: |
314 lines (+145/-28) 8 files modified
cappella/data/default/format.js (+2/-0) cappella/data/glade/open_file_dialogs.ui (+1/-1) cappella/gui/widget/play_queue.py (+3/-3) cappella/library/podcast_web.py (+1/-13) cappella/library/stream.py (+90/-6) cappella/management/media.py (+3/-3) cappella/tool/media.py (+8/-2) cappella/tool/parser.py (+37/-0) |
To merge this branch: | bzr merge lp:~ahayzen/cappella/cappella10_play_stream_2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Hayzen | Approve | ||
Review via email: mp+161356@code.launchpad.net |
Commit message
Description of the change
* Added basic support for .asx and .m3u formats
To post a comment you must log in.
Revision history for this message
Andrew Hayzen (ahayzen) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'cappella/data/default/format.js' | |||
2 | --- cappella/data/default/format.js 2013-04-28 18:40:19 +0000 | |||
3 | +++ cappella/data/default/format.js 2013-04-29 03:13:27 +0000 | |||
4 | @@ -1,8 +1,10 @@ | |||
5 | 1 | { | 1 | { |
6 | 2 | "aac": {"audio": true, "video": false}, | 2 | "aac": {"audio": true, "video": false}, |
7 | 3 | "asx": {"audio": true, "video": false}, | ||
8 | 3 | "avi": {"audio": false, "video": true}, | 4 | "avi": {"audio": false, "video": true}, |
9 | 4 | "flac": {"audio": true, "video": false}, | 5 | "flac": {"audio": true, "video": false}, |
10 | 5 | "flv": {"audio": false, "video": true}, | 6 | "flv": {"audio": false, "video": true}, |
11 | 7 | "m3u": {"audio": true, "video": false}, | ||
12 | 6 | "m4a": {"audio": true, "video": false}, | 8 | "m4a": {"audio": true, "video": false}, |
13 | 7 | "mp3": {"audio": true, "video": false}, | 9 | "mp3": {"audio": true, "video": false}, |
14 | 8 | "mp4": {"audio": false, "video": true}, | 10 | "mp4": {"audio": false, "video": true}, |
15 | 9 | 11 | ||
16 | === modified file 'cappella/data/glade/open_file_dialogs.ui' | |||
17 | --- cappella/data/glade/open_file_dialogs.ui 2013-04-28 19:00:58 +0000 | |||
18 | +++ cappella/data/glade/open_file_dialogs.ui 2013-04-29 03:13:27 +0000 | |||
19 | @@ -188,7 +188,7 @@ | |||
20 | 188 | <property name="skip_taskbar_hint">True</property> | 188 | <property name="skip_taskbar_hint">True</property> |
21 | 189 | <property name="buttons">ok-cancel</property> | 189 | <property name="buttons">ok-cancel</property> |
22 | 190 | <property name="text" translatable="yes">Please enter the address of the stream</property> | 190 | <property name="text" translatable="yes">Please enter the address of the stream</property> |
24 | 191 | <property name="secondary_text" translatable="yes">This should either be a .pls file or a direct stream</property> | 191 | <property name="secondary_text" translatable="yes">This should either be a .asx, .m3u, .pls or a direct stream</property> |
25 | 192 | <child internal-child="vbox"> | 192 | <child internal-child="vbox"> |
26 | 193 | <object class="GtkBox" id="messagedialog-vbox"> | 193 | <object class="GtkBox" id="messagedialog-vbox"> |
27 | 194 | <property name="can_focus">False</property> | 194 | <property name="can_focus">False</property> |
28 | 195 | 195 | ||
29 | === modified file 'cappella/gui/widget/play_queue.py' | |||
30 | --- cappella/gui/widget/play_queue.py 2013-04-28 18:40:19 +0000 | |||
31 | +++ cappella/gui/widget/play_queue.py 2013-04-29 03:13:27 +0000 | |||
32 | @@ -22,7 +22,7 @@ | |||
33 | 22 | from ...config.cappella import settings | 22 | from ...config.cappella import settings |
34 | 23 | from ...signalhandler import signal | 23 | from ...signalhandler import signal |
35 | 24 | from ...tool import skipx, thread_safe | 24 | from ...tool import skipx, thread_safe |
37 | 25 | from ...tool.media import process_pls | 25 | from ...tool.media import is_playlist, process_playlist |
38 | 26 | 26 | ||
39 | 27 | 27 | ||
40 | 28 | class PlayQueue(): | 28 | class PlayQueue(): |
41 | @@ -129,8 +129,8 @@ | |||
42 | 129 | 129 | ||
43 | 130 | @thread_safe | 130 | @thread_safe |
44 | 131 | def on_playqueue_add_int(self, uri, library, view): | 131 | def on_playqueue_add_int(self, uri, library, view): |
47 | 132 | if uri.endswith(".pls"): | 132 | if is_playlist(uri): |
48 | 133 | uri = process_pls(uri) | 133 | uri = process_playlist(uri) |
49 | 134 | 134 | ||
50 | 135 | if type(uri) is list: | 135 | if type(uri) is list: |
51 | 136 | # Add uris to play queue | 136 | # Add uris to play queue |
52 | 137 | 137 | ||
53 | === modified file 'cappella/library/podcast_web.py' | |||
54 | --- cappella/library/podcast_web.py 2013-04-02 22:48:35 +0000 | |||
55 | +++ cappella/library/podcast_web.py 2013-04-29 03:13:27 +0000 | |||
56 | @@ -20,7 +20,6 @@ | |||
57 | 20 | 20 | ||
58 | 21 | from gi.repository import GdkPixbuf | 21 | from gi.repository import GdkPixbuf |
59 | 22 | from html.parser import HTMLParser | 22 | from html.parser import HTMLParser |
60 | 23 | from xml.dom import minidom | ||
61 | 24 | from xml.etree import ElementTree | 23 | from xml.etree import ElementTree |
62 | 25 | from urllib.request import urlopen | 24 | from urllib.request import urlopen |
63 | 26 | 25 | ||
64 | @@ -28,21 +27,10 @@ | |||
65 | 28 | from ..management.library import (LibraryType, LibraryPurpose, LibraryMode, | 27 | from ..management.library import (LibraryType, LibraryPurpose, LibraryMode, |
66 | 29 | LibraryFileType) | 28 | LibraryFileType) |
67 | 30 | from ..tool.metatag import MetaParser | 29 | from ..tool.metatag import MetaParser |
68 | 30 | from ..tool.parser import FeedToXml | ||
69 | 31 | 31 | ||
70 | 32 | 32 | ||
71 | 33 | #-------------------------------------- Helper classes for RSS->Dict Conversion | 33 | #-------------------------------------- Helper classes for RSS->Dict Conversion |
72 | 34 | class FeedToXml(): | ||
73 | 35 | """ Read XML and parse from URL """ | ||
74 | 36 | def __init__(self, url): | ||
75 | 37 | self.stream = urlopen(url) | ||
76 | 38 | |||
77 | 39 | self.obj = minidom.parse(self.stream) | ||
78 | 40 | |||
79 | 41 | *proc, dom = self.obj.childNodes | ||
80 | 42 | |||
81 | 43 | self.xml = dom.toprettyxml() | ||
82 | 44 | |||
83 | 45 | |||
84 | 46 | class XmlToInfo(): | 34 | class XmlToInfo(): |
85 | 47 | def __init__(self, xml_stream, uri): | 35 | def __init__(self, xml_stream, uri): |
86 | 48 | self.root = ElementTree.fromstring(xml_stream) | 36 | self.root = ElementTree.fromstring(xml_stream) |
87 | 49 | 37 | ||
88 | === modified file 'cappella/library/stream.py' | |||
89 | --- cappella/library/stream.py 2013-04-28 18:49:59 +0000 | |||
90 | +++ cappella/library/stream.py 2013-04-29 03:13:27 +0000 | |||
91 | @@ -20,7 +20,10 @@ | |||
92 | 20 | 20 | ||
93 | 21 | from ..management.library import LibraryType, LibraryPurpose, LibraryMode | 21 | from ..management.library import LibraryType, LibraryPurpose, LibraryMode |
94 | 22 | from ..signalhandler import signal | 22 | from ..signalhandler import signal |
95 | 23 | from ..tool.media import is_playlist | ||
96 | 24 | from ..tool.parser import FeedToXml | ||
97 | 23 | from urllib.request import urlopen | 25 | from urllib.request import urlopen |
98 | 26 | from xml.etree import ElementTree | ||
99 | 24 | 27 | ||
100 | 25 | 28 | ||
101 | 26 | class StreamLibrary(): | 29 | class StreamLibrary(): |
102 | @@ -53,9 +56,10 @@ | |||
103 | 53 | def copy(self, uri): | 56 | def copy(self, uri): |
104 | 54 | """ Get a stream for the uri in the library (name) specified """ | 57 | """ Get a stream for the uri in the library (name) specified """ |
105 | 55 | 58 | ||
109 | 56 | if (not uri.startswith("http://") and | 59 | if (not uri.startswith("http://") and not uri.startswith("mms://") and |
110 | 57 | (not uri.startswith("file://") and not ".pls" not in uri)): | 60 | (not uri.startswith("file://") and is_playlist(uri))): |
111 | 58 | raise ValueError("URI should start with http:// or ends with .pls") | 61 | raise ValueError("URI should start with http:// or contains .pls, " |
112 | 62 | ".asx, .m3u") | ||
113 | 59 | 63 | ||
114 | 60 | f = urlopen(uri) | 64 | f = urlopen(uri) |
115 | 61 | 65 | ||
116 | @@ -86,9 +90,10 @@ | |||
117 | 86 | def get(self, uri): | 90 | def get(self, uri): |
118 | 87 | """ Get the info about a uri in the library (name) specified """ | 91 | """ Get the info about a uri in the library (name) specified """ |
119 | 88 | 92 | ||
123 | 89 | if (not uri.startswith("http://") and | 93 | if (not uri.startswith("http://") and not uri.startswith("mms://") and |
124 | 90 | (not uri.startswith("file://") and ".pls" not in uri)): | 94 | (not uri.startswith("file://") and is_playlist(uri))): |
125 | 91 | raise ValueError("URI should start with http:// or ends with .pls") | 95 | raise ValueError("URI should start with http:// or contains .pls, " |
126 | 96 | ".asx, .m3u") | ||
127 | 92 | 97 | ||
128 | 93 | files = {} | 98 | files = {} |
129 | 94 | 99 | ||
130 | @@ -134,6 +139,85 @@ | |||
131 | 134 | return {"title": uri, "uri": uri} | 139 | return {"title": uri, "uri": uri} |
132 | 135 | else: | 140 | else: |
133 | 136 | return output | 141 | return output |
134 | 142 | elif ".asx" in uri: | ||
135 | 143 | # Read .asx file | ||
136 | 144 | if uri.startswith("http://"): | ||
137 | 145 | stream = urlopen(uri) | ||
138 | 146 | else: | ||
139 | 147 | stream = open(uri[7:]) | ||
140 | 148 | |||
141 | 149 | xml_stream = FeedToXml(data=stream).xml | ||
142 | 150 | |||
143 | 151 | root = ElementTree.fromstring(xml_stream) | ||
144 | 152 | |||
145 | 153 | try: | ||
146 | 154 | title = root.find("TITLE").text | ||
147 | 155 | except AttributeError: | ||
148 | 156 | title = None | ||
149 | 157 | |||
150 | 158 | try: | ||
151 | 159 | artist = root.find("AUTHOR").text | ||
152 | 160 | except AttributeError: | ||
153 | 161 | artist = None | ||
154 | 162 | |||
155 | 163 | output = [] | ||
156 | 164 | |||
157 | 165 | for item in root.findall("Entry"): | ||
158 | 166 | info = {"title": title, "artist": artist} | ||
159 | 167 | |||
160 | 168 | try: | ||
161 | 169 | ref = item.find("ref") | ||
162 | 170 | |||
163 | 171 | try: | ||
164 | 172 | info["uri"] = ref.attrib["href"] | ||
165 | 173 | except KeyError: | ||
166 | 174 | continue | ||
167 | 175 | except AttributeError: | ||
168 | 176 | continue | ||
169 | 177 | |||
170 | 178 | output.append(info) | ||
171 | 179 | |||
172 | 180 | self.cache[info["uri"]] = {"raw_uri": uri, "title": title, | ||
173 | 181 | "artist": artist} | ||
174 | 182 | |||
175 | 183 | if len(output) > 0: | ||
176 | 184 | return output | ||
177 | 185 | elif ".m3u" in uri: | ||
178 | 186 | # Read .pls file | ||
179 | 187 | if uri.startswith("http://"): | ||
180 | 188 | data = urlopen(uri).read().decode("utf") | ||
181 | 189 | else: | ||
182 | 190 | data = open(uri[7:]).read() | ||
183 | 191 | |||
184 | 192 | # Read data from the .pls file | ||
185 | 193 | title, artist = None, None | ||
186 | 194 | output = [] | ||
187 | 195 | |||
188 | 196 | for line in data.split("\n"): | ||
189 | 197 | if line.startswith("#EXTINF"): | ||
190 | 198 | value = line.split(",")[1].split("-") | ||
191 | 199 | |||
192 | 200 | if len(value) == 2: | ||
193 | 201 | artist, title = value | ||
194 | 202 | else: | ||
195 | 203 | title = value[0] | ||
196 | 204 | elif line.startswith("#"): | ||
197 | 205 | continue | ||
198 | 206 | elif line.strip() == "": | ||
199 | 207 | continue | ||
200 | 208 | else: | ||
201 | 209 | info = {"title": title or uri, "artist": artist, | ||
202 | 210 | "uri": line} | ||
203 | 211 | |||
204 | 212 | output.append(info) | ||
205 | 213 | self.cache[line] = {"title": title or uri, | ||
206 | 214 | "artist": artist, | ||
207 | 215 | "raw_uri": uri} | ||
208 | 216 | |||
209 | 217 | title, artist = None, None | ||
210 | 218 | |||
211 | 219 | if len(output) > 0: | ||
212 | 220 | return output | ||
213 | 137 | 221 | ||
214 | 138 | if uri in self.cache: | 222 | if uri in self.cache: |
215 | 139 | info = self.cache[uri] | 223 | info = self.cache[uri] |
216 | 140 | 224 | ||
217 | === modified file 'cappella/management/media.py' | |||
218 | --- cappella/management/media.py 2013-04-28 18:56:14 +0000 | |||
219 | +++ cappella/management/media.py 2013-04-29 03:13:27 +0000 | |||
220 | @@ -21,7 +21,7 @@ | |||
221 | 21 | from ..config.cappella import settings | 21 | from ..config.cappella import settings |
222 | 22 | from ..signalhandler import signal | 22 | from ..signalhandler import signal |
223 | 23 | from ..tool import skipx | 23 | from ..tool import skipx |
225 | 24 | from ..tool.media import process_pls | 24 | from ..tool.media import is_playlist, process_playlist |
226 | 25 | 25 | ||
227 | 26 | 26 | ||
228 | 27 | class Management(): | 27 | class Management(): |
229 | @@ -273,8 +273,8 @@ | |||
230 | 273 | signal.emit("_musicengine_stop_") # Stop playing items | 273 | signal.emit("_musicengine_stop_") # Stop playing items |
231 | 274 | 274 | ||
232 | 275 | # Check is playable file (eg get uri from pls) | 275 | # Check is playable file (eg get uri from pls) |
235 | 276 | if ".pls" in uri: | 276 | if is_playlist(uri): |
236 | 277 | uri = process_pls(uri) | 277 | uri = process_playlist(uri) |
237 | 278 | library = "stream_library" | 278 | library = "stream_library" |
238 | 279 | 279 | ||
239 | 280 | if type(uri) is list: | 280 | if type(uri) is list: |
240 | 281 | 281 | ||
241 | === modified file 'cappella/tool/media.py' | |||
242 | --- cappella/tool/media.py 2013-04-28 18:49:59 +0000 | |||
243 | +++ cappella/tool/media.py 2013-04-29 03:13:27 +0000 | |||
244 | @@ -73,6 +73,12 @@ | |||
245 | 73 | return False | 73 | return False |
246 | 74 | 74 | ||
247 | 75 | 75 | ||
248 | 76 | def is_playlist(uri): | ||
249 | 77 | """ Check if the uri is a playlist """ | ||
250 | 78 | |||
251 | 79 | return ".pls" in uri or ".asx" in uri or ".m3u" in uri | ||
252 | 80 | |||
253 | 81 | |||
254 | 76 | def is_video(uri): | 82 | def is_video(uri): |
255 | 77 | """ Return True is uri is video """ | 83 | """ Return True is uri is video """ |
256 | 78 | 84 | ||
257 | @@ -92,12 +98,12 @@ | |||
258 | 92 | return False | 98 | return False |
259 | 93 | 99 | ||
260 | 94 | 100 | ||
262 | 95 | def process_pls(uri): | 101 | def process_playlist(uri): |
263 | 96 | # Get files from .pls | 102 | # Get files from .pls |
264 | 97 | library = "stream_library" | 103 | library = "stream_library" |
265 | 98 | info = signal.get_object("library").get(library, uri) | 104 | info = signal.get_object("library").get(library, uri) |
266 | 99 | 105 | ||
268 | 100 | if type(info) is list: # Some pls are playlists | 106 | if type(info) is list: # Some multisong |
269 | 101 | return info | 107 | return info |
270 | 102 | else: | 108 | else: |
271 | 103 | uri = info["uri"] | 109 | uri = info["uri"] |
272 | 104 | 110 | ||
273 | === added file 'cappella/tool/parser.py' | |||
274 | --- cappella/tool/parser.py 1970-01-01 00:00:00 +0000 | |||
275 | +++ cappella/tool/parser.py 2013-04-29 03:13:27 +0000 | |||
276 | @@ -0,0 +1,37 @@ | |||
277 | 1 | #!/usr/bin/env python3 | ||
278 | 2 | ''' | ||
279 | 3 | Copyright (C) 2011-2013 Andrew Hayzen and Simon Bull. | ||
280 | 4 | |||
281 | 5 | Cappella is a simple media player based on PyGObject for Ubuntu. | ||
282 | 6 | |||
283 | 7 | This file is free software: you can redistribute it and/or modify it | ||
284 | 8 | under the terms of the GNU General Public License as published by the | ||
285 | 9 | Free Software Foundation, either version 3 of the License, | ||
286 | 10 | or (at your option) any later version. | ||
287 | 11 | |||
288 | 12 | This file is distributed in the hope that it will be useful, but WITHOUT | ||
289 | 13 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
290 | 14 | FITNESS FOR A PARTICULAR PURPOSE. See the | ||
291 | 15 | GNU General Public License for more details. | ||
292 | 16 | |||
293 | 17 | You should have received a copy of the GNU General Public License along with | ||
294 | 18 | this file. If not, see <http://www.gnu.org/licenses/>. | ||
295 | 19 | ''' | ||
296 | 20 | |||
297 | 21 | from urllib.request import urlopen | ||
298 | 22 | from xml.dom import minidom | ||
299 | 23 | |||
300 | 24 | |||
301 | 25 | class FeedToXml(): | ||
302 | 26 | """ Read XML and parse from URL """ | ||
303 | 27 | def __init__(self, url=None, data=None): | ||
304 | 28 | if url is not None: | ||
305 | 29 | self.stream = urlopen(url) | ||
306 | 30 | else: | ||
307 | 31 | self.stream = data | ||
308 | 32 | |||
309 | 33 | self.obj = minidom.parse(self.stream) | ||
310 | 34 | |||
311 | 35 | *proc, dom = self.obj.childNodes | ||
312 | 36 | |||
313 | 37 | self.xml = dom.toprettyxml() | ||
314 | 0 | \ No newline at end of file | 38 | \ No newline at end of file |