comparison src/browser/radiocol.py @ 449:981ed669d3b3

/!\ reorganize all the file hierarchy, move the code and launching script to src: - browser_side --> src/browser - public --> src/browser_side/public - libervia.py --> src/browser/libervia_main.py - libervia_server --> src/server - libervia_server/libervia.sh --> src/libervia.sh - twisted --> src/twisted - new module src/common - split constants.py in 3 files: - src/common/constants.py - src/browser/constants.py - src/server/constants.py - output --> html (generated by pyjsbuild during the installation) - new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css - setup.py installs libervia to the following paths: - src/common --> <LIB>/libervia/common - src/server --> <LIB>/libervia/server - src/twisted --> <LIB>/twisted - html --> <SHARE>/libervia/html - server_side --> <SHARE>libervia/server_side - LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation: - clean: remove previous installation directories - purge: remove building and previous installation directories You may need to update your sat.conf and/or launching script to update the following options/parameters: - ssl_certificate - data_dir
author souliane <souliane@mailoo.org>
date Tue, 20 May 2014 06:41:16 +0200
parents browser_side/radiocol.py@d52f529a6d42
children 1a0cec9b0f1e
comparison
equal deleted inserted replaced
448:14c35f7f1ef5 449:981ed669d3b3
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 import pyjd # this is dummy in pyjs
21 from sat.core.log import getLogger
22 log = getLogger(__name__)
23 from sat_frontends.tools.misc import DEFAULT_MUC
24 from sat.core.i18n import _
25
26 from pyjamas.ui.VerticalPanel import VerticalPanel
27 from pyjamas.ui.HorizontalPanel import HorizontalPanel
28 from pyjamas.ui.FlexTable import FlexTable
29 from pyjamas.ui.FormPanel import FormPanel
30 from pyjamas.ui.Label import Label
31 from pyjamas.ui.Button import Button
32 from pyjamas.ui.ClickListener import ClickHandler
33 from pyjamas.ui.Hidden import Hidden
34 from pyjamas.ui.CaptionPanel import CaptionPanel
35 from pyjamas.media.Audio import Audio
36 from pyjamas import Window
37 from pyjamas.Timer import Timer
38
39 from html_tools import html_sanitize
40 from file_tools import FilterFileUpload
41
42
43 class MetadataPanel(FlexTable):
44
45 def __init__(self):
46 FlexTable.__init__(self)
47 title_lbl = Label("title:")
48 title_lbl.setStyleName('radiocol_metadata_lbl')
49 artist_lbl = Label("artist:")
50 artist_lbl.setStyleName('radiocol_metadata_lbl')
51 album_lbl = Label("album:")
52 album_lbl.setStyleName('radiocol_metadata_lbl')
53 self.title = Label("")
54 self.title.setStyleName('radiocol_metadata')
55 self.artist = Label("")
56 self.artist.setStyleName('radiocol_metadata')
57 self.album = Label("")
58 self.album.setStyleName('radiocol_metadata')
59 self.setWidget(0, 0, title_lbl)
60 self.setWidget(1, 0, artist_lbl)
61 self.setWidget(2, 0, album_lbl)
62 self.setWidget(0, 1, self.title)
63 self.setWidget(1, 1, self.artist)
64 self.setWidget(2, 1, self.album)
65 self.setStyleName("radiocol_metadata_pnl")
66
67 def setTitle(self, title):
68 self.title.setText(title)
69
70 def setArtist(self, artist):
71 self.artist.setText(artist)
72
73 def setAlbum(self, album):
74 self.album.setText(album)
75
76
77 class ControlPanel(FormPanel):
78 """Panel used to show controls to add a song, or vote for the current one"""
79
80 def __init__(self, parent):
81 FormPanel.__init__(self)
82 self.setEncoding(FormPanel.ENCODING_MULTIPART)
83 self.setMethod(FormPanel.METHOD_POST)
84 self.setAction("upload_radiocol")
85 self.timer_on = False
86 self._parent = parent
87 vPanel = VerticalPanel()
88
89 types = [('audio/ogg', '*.ogg', 'Ogg Vorbis Audio'),
90 ('video/ogg', '*.ogv', 'Ogg Vorbis Video'),
91 ('application/ogg', '*.ogx', 'Ogg Vorbis Multiplex'),
92 ('audio/mpeg', '*.mp3', 'MPEG-Layer 3'),
93 ('audio/mp3', '*.mp3', 'MPEG-Layer 3'),
94 ]
95 self.file_upload = FilterFileUpload("song", 10, types)
96 vPanel.add(self.file_upload)
97
98 hPanel = HorizontalPanel()
99 self.upload_btn = Button("Upload song", getattr(self, "onBtnClick"))
100 hPanel.add(self.upload_btn)
101 self.status = Label()
102 self.updateStatus()
103 hPanel.add(self.status)
104 #We need to know the filename and the referee
105 self.filename_field = Hidden('filename', '')
106 hPanel.add(self.filename_field)
107 referee_field = Hidden('referee', self._parent.referee)
108 hPanel.add(self.filename_field)
109 hPanel.add(referee_field)
110 vPanel.add(hPanel)
111
112 self.add(vPanel)
113 self.addFormHandler(self)
114
115 def updateStatus(self):
116 if self.timer_on:
117 return
118 # TODO: the status should be different if a song is being played or not
119 queue = self._parent.getQueueSize()
120 queue_data = self._parent.queue_data
121 if queue < queue_data[0]:
122 left = queue_data[0] - queue
123 self.status.setText("[we need %d more song%s]" % (left, "s" if left > 1 else ""))
124 elif queue < queue_data[1]:
125 left = queue_data[1] - queue
126 self.status.setText("[%d available spot%s]" % (left, "s" if left > 1 else ""))
127 elif queue >= queue_data[1]:
128 self.status.setText("[The queue is currently full]")
129 self.status.setStyleName('radiocol_status')
130
131 def onBtnClick(self):
132 if self.file_upload.check():
133 self.status.setText('[Submitting, please wait...]')
134 self.filename_field.setValue(self.file_upload.getFilename())
135 if self.file_upload.getFilename().lower().endswith('.mp3'):
136 self._parent._parent.host.showWarning('STATUS', 'For a better support, it is recommended to submit Ogg Vorbis file instead of MP3. You can convert your files easily, ask for help if needed!', 5000)
137 self.submit()
138 self.file_upload.setFilename("")
139
140 def onSubmit(self, event):
141 pass
142
143 def blockUpload(self):
144 self.file_upload.setVisible(False)
145 self.upload_btn.setEnabled(False)
146
147 def unblockUpload(self):
148 self.file_upload.setVisible(True)
149 self.upload_btn.setEnabled(True)
150
151 def setTemporaryStatus(self, text, style):
152 self.status.setText(text)
153 self.status.setStyleName('radiocol_upload_status_%s' % style)
154 self.timer_on = True
155
156 def cb(timer):
157 self.timer_on = False
158 self.updateStatus()
159
160 Timer(5000, cb)
161
162 def onSubmitComplete(self, event):
163 result = event.getResults()
164 if result == "OK":
165 # the song can still be rejected (not readable, full queue...)
166 self.setTemporaryStatus('[Your song has been submitted to the radio]', "ok")
167 elif result == "KO":
168 self.setTemporaryStatus('[Something went wrong during your song upload]', "ko")
169 self._parent.radiocolSongRejected(_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."))
170 # TODO: would be great to re-use the original Exception class and message
171 # but it is lost in the middle of the traceback and encapsulated within
172 # a DBusException instance --> extract the data from the traceback?
173 else:
174 Window.alert('Submit error: %s' % result)
175 self.status.setText('')
176
177
178 class Player(Audio):
179
180 def __init__(self, player_id, metadata_panel):
181 Audio.__init__(self)
182 self._id = player_id
183 self.metadata = metadata_panel
184 self.timestamp = ""
185 self.title = ""
186 self.artist = ""
187 self.album = ""
188 self.filename = None
189 self.played = False # True when the song is playing/has played, becomes False on preload
190 self.setAutobuffer(True)
191 self.setAutoplay(False)
192 self.setVisible(False)
193
194
195 def preload(self, timestamp, filename, title, artist, album):
196 """preload the song but doesn't play it"""
197 self.timestamp = timestamp
198 self.filename = filename
199 self.title = title
200 self.artist = artist
201 self.album = album
202 self.played = False
203 self.setSrc("radiocol/%s" % html_sanitize(filename))
204 log.debug("preloading %s in %s" % (title, self._id))
205
206 def play(self, play=True):
207 """Play or pause the song
208 @param play: set to True to play or to False to pause
209 """
210 if play:
211 self.played = True
212 self.metadata.setTitle(self.title)
213 self.metadata.setArtist(self.artist)
214 self.metadata.setAlbum(self.album)
215 Audio.play(self)
216 else:
217 self.pause()
218
219
220 class RadioColPanel(HorizontalPanel, ClickHandler):
221
222 def __init__(self, parent, referee, player_nick, players, queue_data):
223 """
224 @param parent
225 @param referee
226 @param player_nick
227 @param players
228 @param queue_data: list of integers (queue to start, queue limit)
229 """
230 # We need to set it here and not in the CSS :(
231 HorizontalPanel.__init__(self, Height="90px")
232 ClickHandler.__init__(self)
233 self._parent = parent
234 self.referee = referee
235 self.queue_data = queue_data
236 self.setStyleName("radiocolPanel")
237
238 # Now we set up the layout
239 self.metadata_panel = MetadataPanel()
240 self.add(CaptionPanel("Now playing", self.metadata_panel))
241 self.playlist_panel = VerticalPanel()
242 self.add(CaptionPanel("Songs queue", self.playlist_panel))
243 self.control_panel = ControlPanel(self)
244 self.add(CaptionPanel("Controls", self.control_panel))
245
246 self.next_songs = []
247 self.players = [Player("player_%d" % i, self.metadata_panel) for i in xrange(queue_data[1] + 1)]
248 self.current_player = None
249 for player in self.players:
250 self.add(player)
251 self.addClickListener(self)
252
253 help_msg = """Accepted file formats: Ogg Vorbis (recommended), MP3.<br />
254 Please do not submit files that are protected by copyright.<br />
255 Click <a style="color: red;">here</a> if you need some support :)"""
256 link_cb = lambda: self._parent.host.bridge.call('joinMUC', None, DEFAULT_MUC, self._parent.nick)
257 self._parent.printInfo(help_msg, type_='link', link_cb=link_cb)
258
259 def pushNextSong(self, title):
260 """Add a song to the left panel's next songs queue"""
261 next_song = Label(title)
262 next_song.setStyleName("radiocol_next_song")
263 self.next_songs.append(next_song)
264 self.playlist_panel.append(next_song)
265 self.control_panel.updateStatus()
266
267 def popNextSong(self):
268 """Remove the first song of next songs list
269 should be called when the song is played"""
270 #FIXME: should check that the song we remove is the one we play
271 next_song = self.next_songs.pop(0)
272 self.playlist_panel.remove(next_song)
273 self.control_panel.updateStatus()
274
275 def getQueueSize(self):
276 return len(self.playlist_panel.getChildren())
277
278 def radiocolCheckPreload(self, timestamp):
279 for player in self.players:
280 if player.timestamp == timestamp:
281 return False
282 return True
283
284 def radiocolPreload(self, timestamp, filename, title, artist, album, sender):
285 if not self.radiocolCheckPreload(timestamp):
286 return # song already preloaded
287 preloaded = False
288 for player in self.players:
289 if not player.filename or \
290 (player.played and player != self.current_player):
291 #if player has no file loaded, or it has already played its song
292 #we use it to preload the next one
293 player.preload(timestamp, filename, title, artist, album)
294 preloaded = True
295 break
296 if not preloaded:
297 log.warning("Can't preload song, we are getting too many songs to preload, we shouldn't have more than %d at once" % self.queue_data[1])
298 else:
299 self.pushNextSong(title)
300 self._parent.printInfo(_('%(user)s uploaded %(artist)s - %(title)s') % {'user': sender, 'artist': artist, 'title': title})
301
302 def radiocolPlay(self, filename):
303 found = False
304 for player in self.players:
305 if not found and player.filename == filename:
306 player.play()
307 self.popNextSong()
308 self.current_player = player
309 found = True
310 else:
311 player.play(False) # in case the previous player was not sync
312 if not found:
313 log.error("Song not found in queue, can't play it. This should not happen")
314
315 def radiocolNoUpload(self):
316 self.control_panel.blockUpload()
317
318 def radiocolUploadOk(self):
319 self.control_panel.unblockUpload()
320
321 def radiocolSongRejected(self, reason):
322 Window.alert("Song rejected: %s" % reason)