1
# Copyright (C) 2006 Adam Olsen
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 1, or (at your option)
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
import os, zipfile, md5, random, time, re, sys
18
import xl.plugins as plugins
19
from gettext import gettext as _
20
random.seed(time.time())
21
from xl import common, xlmisc
22
from xl.panels import radio
24
import xl.media as media
25
import time, gtk, gobject, gtk.glade
28
PLUGIN_NAME = _("LastFM Radio")
29
PLUGIN_AUTHORS = ['Adam Olsen <arolsen@gmail.com>']
30
PLUGIN_VERSION = "0.2.7"
31
PLUGIN_DESCRIPTION = _(r"""Allows for streaming via lastfm proxy.\n\nThis
32
plugin is very beta and still doesn't work perfectly.""")
33
PLUGIN_ENABLED = False
42
GLADE_XML_STRING = None
43
CONS = plugins.SignalContainer()
47
def lastfm_error(message):
48
common.error(APP.window, message)
50
class LastFMDriver(radio.RadioDriver):
51
def __init__(self, panel):
52
self.model = panel.model
53
self.folder_icon = panel.folder
54
self.note = panel.track
55
self.tree = panel.tree
59
self.no_new_page = True
61
self.custom_urls = self.exaile.settings.get_list('custom_urls',
62
plugin=plugins.name(__file__))
64
def command(self, command):
65
self.lfmcommand = command
66
self.exaile.status.set_first(_("Running command: %s...") %
72
HTTP_CLIENT.req(self.lfmcommand)
74
def get_menu(self, item, menu):
75
menu.append_separator()
76
menu.append(_("Add Station"), self.on_add, 'gtk-add')
77
if isinstance(item, radio.RadioGenre) and item.custom:
78
menu.append(_("Remove Station"), self.on_remove, 'gtk-delete')
79
self.last_menu_item = item
80
menu.append_separator()
81
menu.append(_("Play 'Similar Artist' Radio"), lambda *e:
85
"lastfm://artist/%s/similarartists"),
88
menu.append(_("Play 'Neighbour' Radio"), lambda *e:
92
"lastfm://user/%s/personal"),
95
menu.append(_("Play 'Group' Radio"), lambda *e:
102
menu.append(_('Play URL'), lambda *e:
111
def play_radio(self, title, field, url):
113
Opens a dialog and asks the user for a field, and plays the
116
dialog = common.MultiTextEntryDialog(APP.window, title)
117
dialog.add_field("%s: " % field)
118
result = dialog.run()
121
if result == gtk.RESPONSE_OK:
122
values = dialog.get_values()
124
value = urllib.quote(values[0].replace("lastfm://", ''))
126
item = radio.RadioGenre("%s: %s" % (field, display))
127
item.lastfm_url = url % value
128
self.load_genre(item)
130
def on_add(self, *e):
132
Called when the user wants to add a station
134
dialog = common.MultiTextEntryDialog(APP.window, _("Add Station"))
135
dialog.add_field(_("Name"))
136
dialog.add_field(_("URL"))
138
result = dialog.run()
139
values = dialog.get_values()
141
if result == gtk.RESPONSE_OK:
142
if values[1].find("://") == -1:
143
values[1] = "lastfm://%s" % values[1]
144
self.custom_urls.append(values)
145
self.exaile.settings.set_list('custom_urls',
146
self.custom_urls, plugin=plugins.name(__file__))
148
item = radio.RadioGenre(values[0], self)
149
item.lastfm_url = values[1]
151
node = self.model.append(self.last_node, [self.note, item])
154
def on_remove(self, *e):
155
item = self.last_menu_item
158
result = common.yes_no_dialog(APP.window, _("Are you sure you "
159
"want to remove this item?"))
160
if result == gtk.RESPONSE_YES:
161
self.model.remove(node)
164
for station, url in self.custom_urls:
165
if item.name == station and item.lastfm_url == url:
168
new_custom.append([station, url])
170
self.custom_urls = new_custom
171
self.exaile.settings.set_list('custom_urls',
172
self.custom_urls, plugin=plugins.name(__file__))
176
def load_streams(self, node, load_node, use_cache=True):
178
(_('Personal'), 'lastfm://user/%s/personal' % self.config.username),
179
(_('Recommended'), 'lastfm://user/%s/recommended/100' %
180
self.config.username),
181
(_('Neighbourhood'), 'lastfm://user/%s/neighbours' %
182
self.config.username),
183
(_('Loved Tracks'), 'lastfm://user/%s/loved' % self.config.username),
186
for station, url in stations:
187
item = radio.RadioGenre(station, self)
188
item.lastfm_url = url
190
n = self.model.append(node, [self.note, item])
194
for station, url in self.custom_urls:
195
item = radio.RadioGenre(station, self)
196
item.lastfm_url = url
198
n = self.model.append(node, [self.note, item])
201
self.model.remove(load_node)
202
self.last_node = node
204
def handles_url(self, url):
206
Check to see if this is a lastfm:// url, and if so, return True
208
if re.match(r"^lastfm://.*$", url):
211
def handle_url(self, url):
215
url = "lastfm://%s" % urllib.quote(url.replace('lastfm://', ''))
216
item = radio.RadioGenre(url, self)
217
item.lastfm_url = url
218
self.load_genre(item)
220
def load_genre(self, genre, rel=False):
221
if not PROXY.proxy_ready:
222
common.error(APP.window, _("The Last.FM Proxy has "
223
"not yet connected to last.fm. Please wait "
224
"a moment and try again"))
226
current = self.exaile.player.current
227
if hasattr(current, 'lastfm_track'):
228
current.album = "LastFM: %s" % str(genre)
229
new_loc = 'http://localhost:%d/lastfm.mp3' % self.listenport
230
self.exaile.tracks.refresh_row(current)
232
self.exaile.status.set_first(_("Changing stations..."), 3500)
233
if not current.loc == new_loc:
234
current.loc = new_loc
235
self.exaile.player.play_track(current)
236
if not current in self.exaile.tracks.songs:
237
self.exaile.tracks.append_song(current)
238
self.command("/changestation/%s" % genre.lastfm_url)
239
xlmisc.log("LastFM: playing %s" % genre.lastfm_url)
243
tr.artist = _('LastFM Radio!')
244
tr.album = "LastFM: %s" % str(genre)
245
tr.title = "LastFM: %s" % str(genre)
246
tr.loc = "http://localhost:%d/lastfm.mp3" % self.listenport
247
tr.lastfm_track = True
249
self.exaile.tracks.append_song(tr)
250
self.exaile.player.play_track(tr)
251
xlmisc.log("LastFM: playing %s" % genre.lastfm_url)
252
self.command('/changestation/%s' % genre.lastfm_url)
255
return _("LastFM Radio")
257
def unzip_file(file, odir):
259
Unzips "file" to "odir" (output directory)
261
z = zipfile.ZipFile(file)
262
for name in z.namelist():
263
m = re.match(r'^(.*)/([^/]*)$', name)
267
if not os.path.isdir(os.path.join(odir, dir)):
268
os.makedirs(os.path.join(odir, dir))
270
if not m.group(2): continue
272
h = open(os.path.join(odir, name), 'w')
273
h.write(z.read(name))
278
Loads the data from the zipfile
280
global TMP_DIR, PLUGIN_ICON, GLADE_XML_STRING
283
fname = os.sep.join(str(__file__).split(os.path.sep)[0:-1])
284
TMP_DIR = "/tmp/lfmdir%s" % md5.new(str(random.randrange(0,
285
123471287348834))).hexdigest()
287
unzip_file(fname, TMP_DIR)
289
GLADE_XML_STRING = zip.get_data('data/lastfmproxy.glade')
292
def run_proxy(config):
296
PLUGIN.listenport = config.listenport
297
PROXY.run(config.bind_address, config.listenport)
304
xlmisc.log("LastFM Proxy: Port %d in use, trying %d" %
305
(config.listenport, config.listenport + 1))
306
config.listenport += 1
311
(_('LastFM: Skip this track'), 'gtk-media-forward', '/skip'),
312
(_('LastFM: Mark this track as loved'), 'gtk-add', '/love'),
313
(_('LastFM: Ban this track'), 'gtk-delete', '/ban'),
317
def set_cover(value):
318
if not OLD_COVER_FUNC or not hasattr(APP.player.current,
319
'lastfm_track'): return
320
fname = "/tmp/lfmcover%s" % md5.new(str(random.randrange(0,
321
23423423442))).hexdigest()
323
value = value.replace('130x130', '300x300')
324
data = urllib.urlopen(value).read()
325
h = open(fname, 'w+')
328
APP.player.current._tmp_cover = fname
330
gobject.idle_add(APP.cover.set_lfmimage, fname)
332
def album_callback(value):
335
xlmisc.log("LastFM: Got cover: %s" % value)
337
def new_cover_func(self, *e):
339
Do absolutely nothing. Srs.
344
def play_track(exaile, track):
345
global SIGNAL_ID, OLD_COVER_FUNC
346
if not SHOW_COVER or not hasattr(APP.player.current,
347
'lastfm_track'): return
349
OLD_COVER_FUNC = APP.cover.set_image
350
APP.cover.set_image = new_cover_func
351
APP.cover.set_lfmimage = OLD_COVER_FUNC
353
APP.cover.set_lfmimage(xl.path.get_data('images', 'nocover.png'))
354
SIGNAL_ID = APP.connect('timer_update', lambda *e:
355
HTTP_CLIENT.req('/np'))
357
def stop_track(exaile, track):
358
global SIGNAL_ID, OLD_COVER_FUNC
359
if not SHOW_COVER: return
361
APP.disconnect(SIGNAL_ID)
364
APP.cover.set_lfmimage(xl.path.get_data('images', 'nocover.png'))
365
APP.cover.set_image = OLD_COVER_FUNC
366
OLD_COVER_FUNC = None
369
global PROXY, PLUGIN, HTTP_CLIENT, BUTTONS, SHOW_COVER
370
if not TMP_DIR in sys.path: sys.path.append(TMP_DIR)
376
settings = APP.settings
377
SHOW_COVER = settings.get_boolean('ui/show_cover', True)
379
port = settings.get_int('listenport', plugin=plugins.name(__file__),
381
config.listenport = port
382
# config.username = settings.get_str('lastfmuser',
383
# plugin=plugins.name(__file__),
384
# default=settings.get_str('lastfm/user', ''))
385
# config.password = settings.get_crypted('lastfmpass',
386
# plugin=plugins.name(__file__),
387
# default=settings.get_str('lastfm/pass', ''))
389
# if settings.get_boolean('lastfm_use_main', default=True,
390
# plugin=plugins.name(__file__)):
391
config.username = settings.get_str('lastfm/user', '')
392
config.password = settings.get_crypted('lastfm/pass', '')
395
PLUGIN = LastFMDriver(APP.pradio_panel)
396
PLUGIN.config = config
397
APP.pradio_panel.add_driver(PLUGIN, plugins.name(__file__))
398
HTTP_CLIENT = httpclient.httpclient('localhost', config.listenport)
399
APP.add_urlhandler(PLUGIN)
401
PROXY = lastfmmain.proxy(config.username, config.password)
402
PROXY.np_image_func = album_callback
403
PROXY.basedir = TMP_DIR
407
CONS.connect(APP.player, 'play-track', play_track)
408
CONS.connect(APP.player, 'stop-track', stop_track)
411
for tooltip, icon, command in BUTTON_ITEMS:
412
button = gtk.Button()
413
button.connect('clicked', lambda w, command=command: PLUGIN.command(command))
415
image.set_from_stock(icon, gtk.ICON_SIZE_MENU)
416
button.set_size_request(32, 32)
417
button.set_image(image)
418
TIPS.set_tip(button, tooltip)
419
APP.xml.get_widget('rating_toolbar').pack_start(button)
420
BUTTONS.append(button)
421
for button in BUTTONS:
427
global PLUGIN, MENU_ITEM, PROXY, BUTTONS, OLD_COVER_FUNC
429
sys.path.remove(TMP_DIR)
431
CONS.disconnect_all()
434
APP.cover.set_image = OLD_COVER_FUNC
435
OLD_COVER_FUNC = None
438
APP.pradio_panel.remove_driver(PLUGIN)
439
APP.remove_urlhandler(PLUGIN)
442
for button in BUTTONS:
449
PROXY.np_image_func = None
456
#def use_main_toggled(box, user, password):
457
# active = not box.get_active()
458
# user.set_sensitive(active)
459
# password.set_sensitive(active)
463
Runs initialize, but returns False so the timer doesn't continue to
474
settings = exaile.settings
476
xml = gtk.glade.xml_new_from_buffer(GLADE_XML_STRING,
477
len(GLADE_XML_STRING))
479
dialog = xml.get_widget('ConfigurationDialog')
480
# use_main = xml.get_widget('lastfm_use_main')
481
# lastfm_user = xml.get_widget('lastfm_user')
482
# lastfm_pass = xml.get_widget('lastfm_pass')
483
lastfm_listen_port = xml.get_widget('lastfm_listen_port')
485
# use_main.set_active(settings.get_boolean('use_main',
486
# plugin=plugins.name(__file__), default=True))
488
# lastfm_user.set_text(settings.get_str('lastfmuser',
489
# plugin=plugins.name(__file__),
490
# default=settings.get_str('lastfm/user', '')))
491
# lastfm_pass.set_text(settings.get_crypted('lastfmpass',
492
# plugin=plugins.name(__file__),
493
# default=settings.get_str('lastfm/pass', '')))
495
lastfm_listen_port.set_text(settings.get_str('listenport',
496
plugin=plugins.name(__file__), default=1881))
498
# use_main.connect('toggled', lambda *e: use_main_toggled(use_main,
499
# lastfm_user, lastfm_pass))
500
# use_main_toggled(use_main, lastfm_user, lastfm_pass)
502
result = dialog.run()
504
if result == gtk.RESPONSE_OK:
506
PROXY.np_image_func = None
510
# settings.set_boolean('use_main', use_main.get_active(),
511
# plugin=plugins.name(__file__))
512
# settings.set_str('lastfm_user', lastfm_user.get_text(),
513
# plugin=plugins.name(__file__))
514
# settings.set_crypted('lastfm_pass', lastfm_pass.get_text(),
515
# plugin=plugins.name(__file__))
517
settings.set_str('listenport', lastfm_listen_port.get_text(),
518
plugin=plugins.name(__file__))
521
gobject.timeout_add(5000, quick_init)
523
icon_data = ["16 16 72 1",
612
PLUGIN_ICON = gtk.gdk.pixbuf_new_from_xpm_data(icon_data)