Package: jamaendo
Architecture: all
-Depends: ${python:Depends}, python2.5-gtk2, python2.5-gstreamer, python2.5-dbus, python2.5-hildon, python-lxml, python2.5-osso
+Depends: ${python:Depends}, python2.5-gtk2, python2.5-gstreamer, python2.5-dbus, python2.5-hildon, python-lxml, python2.5-osso, python-pycurl
Description:
XB-Maemo-Display-Name: Jamaendo Player
XB-Maemo-Icon-26:
# Image / cover downloads.. and more?
import urllib, threading, os, time, simplejson, re
import logging, hashlib
+import pycurl, StringIO
_CACHEDIR = None
_COVERDIR = None
_RADIO_FIELDS = ['id', 'name', 'idstr', 'image']
_TAG_FIELDS = ['id', 'name']
+def curlGET(url):
+ c = pycurl.Curl()
+ s = StringIO.StringIO()
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+ c.setopt(pycurl.URL, url)
+ c.setopt(pycurl.WRITEFUNCTION, s.write)
+ try:
+ c.perform()
+ finally:
+ c.close()
+ s.seek(0)
+ return s.read()
+
class LazyQuery(object):
def set_from_json(self, json):
for key, value in json.iteritems():
log.info("%s", url)
Query._ratelimit()
try:
- f = urllib.urlopen(url)
- ret = simplejson.load(f)
- f.close()
+ ret = simplejson.loads(curlGET(url))
except Exception, e:
return None
return ret
self.cond = threading.Condition()
self.work = []
+ def _retrieve(self, url, fname):
+ f = open(fname, 'wb')
+ c = pycurl.Curl()
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+ c.setopt(pycurl.URL, str(url))
+ c.setopt(pycurl.WRITEFUNCTION, f.write)
+ try:
+ c.perform()
+ except:
+ fname = None
+ finally:
+ c.close()
+ f.close()
+ log.debug("Coverfetch: %s -> %s", url, fname)
+ return fname
+
def _fetch_cover(self, albumid, size):
try:
coverdir = _COVERDIR if _COVERDIR else '/tmp'
to = os.path.join(coverdir, '%d-%d.jpg'%(albumid, size))
if not os.path.isfile(to):
url = _GET2+'image/album/redirect/?id=%d&imagesize=%d'%(albumid, size)
- urllib.urlretrieve(url, to)
+ to = self._retrieve(url, to)
return to
except Exception, e:
return None
coverdir = _COVERDIR if _COVERDIR else '/tmp'
to = os.path.join(coverdir, h+'.jpg')
if not os.path.isfile(to):
- urllib.urlretrieve(url, to)
+ to = self._retrieve(url, to)
return to
except Exception, e:
return None
def request_images(self, urls, cb):
"""cb([(url, image)])"""
self.cond.acquire()
- self.work.insert(0, ('images', urls, cb))
+ self.work = [('image', url, cb) for url in urls] + self.work
self.cond.notify()
self.cond.release()
multi = len(work) > 1
for job in work:
- if job[0] == 'images':
- self.process_images(job[1], job[2])
+ if job[0] == 'image':
+ self.process_image(job[1], job[2])
else:
self.process_cover(*job)
- if multi:
- time.sleep(1.0)
def process_cover(self, albumid, size, cb):
cover = self._fetch_cover(albumid, size)
if cover:
cb(albumid, size, cover)
- def process_images(self, urls, cb):
- results = [(url, image) for url, image in ((url, self._fetch_image(url)) for url in urls) if image is not None]
- if results:
- cb(results)
+ def process_image(self, url, cb):
+ image = self._fetch_image(url)
+ if image:
+ cb([(url, image)])
class CoverCache(object):
"""
+import os
import gtk
+import gobject
import hildon
import jamaendo
+import util
from settings import settings
from postoffice import postoffice
import logging
log = logging.getLogger(__name__)
-class ImageDownloader(object):
+class _BaseList(gtk.TreeView):
"""
- TODO: background downloader of images
- for album lists, track lists, etc
+ TODO: unify the different lists into one
"""
+ ICON_SIZE = 50
-class AlbumList(gtk.TreeView):
def __init__(self):
gtk.TreeView.__init__(self)
- self.__store = gtk.ListStore(str, int)
+ self.__store = None
+ self.default_pixbuf = util.find_resource('album.png')
+ self.connect('destroy', self.on_destroy)
+
+ def get_pixbuf(self, img):
+ try:
+ return gtk.gdk.pixbuf_new_from_file_at_size(img,
+ self.ICON_SIZE,
+ self.ICON_SIZE)
+ except gobject.GError:
+ log.error("Broken image in cache: %s", img)
+ try:
+ os.unlink(img)
+ except OSError, e:
+ log.warning("Failed to unlink broken image.")
+ if img != self.default_pixbuf:
+ return self.get_default_pixbuf()
+ else:
+ return None
+
+ def get_default_pixbuf(self):
+ if self.default_pixbuf:
+ return self.get_pixbuf(self.default_pixbuf)
+
+ def on_destroy(self, wnd):
+ pass
+
+class MusicList(_BaseList):
+ def __init__(self):
+ _BaseList.__init__(self)
+ (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4)
+ self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str)
+
self.set_model(self.__store)
+ icon = gtk.TreeViewColumn('Icon')
+ self.append_column(icon)
+ cell = gtk.CellRendererPixbuf()
+ icon.pack_start(cell, True)
+ icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
+
col = gtk.TreeViewColumn('Name')
self.append_column(col)
cell = gtk.CellRendererText()
col.pack_start(cell, True)
- col.add_attribute(cell, 'text', 0)
- self.set_search_column(0)
- col.set_sort_column_id(0)
-
+ col.add_attribute(cell, 'text', self.COL_NAME)
+ self.set_search_column(self.COL_NAME)
+ col.set_sort_column_id(self.COL_NAME)
+
+ postoffice.connect('images', self, self.on_images)
+
+ def get_item_id(self, path):
+ return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0]
+
+ def on_destroy(self, wnd):
+ postoffice.disconnect('images', self)
+
+ def on_images(self, images):
+ for url, image in images:
+ for row in self.__store:
+ if row[self.COL_IMAGE] == url:
+ pb = self.get_pixbuf(image)
+ if pb:
+ row[self.COL_ICON] = pb
+
+ def add_items(self, items):
+ images = [x for x in (self.get_item_image(item) for item in items) if x]
+ for item in items:
+ txt = self.get_item_text(item)
+ self.__store.append([self.get_default_pixbuf(), txt, item.ID, self.get_item_image(item)])
+ if images:
+ postoffice.notify('request-images', images)
+
+ def get_item_text(self, item):
+ if isinstance(item, jamaendo.Album):
+ return "%s - %s" % (item.artist_name, item.name)
+ elif isinstance(item, jamaendo.Track):
+ return "%s - %s" % (item.artist_name, item.name)
+ else:
+ return item.name
+
+ def get_item_image(self, item):
+ ret = None
+ if isinstance(item, jamaendo.Track):
+ ret = item.album_image
+ elif hasattr(item, 'image'):
+ ret = item.image
+ if ret:
+ ret = ret.replace('1.100.jpg', '1.%d.jpg'%(self.ICON_SIZE))
+ return ret
+
+class AlbumList(_BaseList):
+ def __init__(self):
+ _BaseList.__init__(self)
+ (self.COL_ICON, self.COL_NAME, self.COL_ID) = range(3)
+ self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int)
self.__show_artist = True
+ self.set_model(self.__store)
+
+ icon = gtk.TreeViewColumn('Icon')
+ self.append_column(icon)
+ cell = gtk.CellRendererPixbuf()
+ icon.pack_start(cell, True)
+ icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
+
+ col = gtk.TreeViewColumn('Name')
+ self.append_column(col)
+ cell = gtk.CellRendererText()
+ col.pack_start(cell, True)
+ col.add_attribute(cell, 'text', self.COL_NAME)
+ self.set_search_column(self.COL_NAME)
+ col.set_sort_column_id(self.COL_NAME)
+
+ postoffice.connect('album-cover', self, self.on_album_cover)
+
+ def on_destroy(self, wnd):
+ _BaseList.on_destroy(self, wnd)
+ postoffice.disconnect('album-cover', self)
+
+ def on_album_cover(self, albumid, size, cover):
+ if size == self.ICON_SIZE:
+ for row in self.__store:
+ if row[self.COL_ID] == albumid:
+ row[self.COL_ICON] = self.get_pixbuf(cover)
+
def add_album(self, album):
if self.__show_artist:
txt = "%s - %s" % (album.artist_name, album.name)
else:
txt = album.name
- self.__store.append([txt, album.ID])
+ self.__store.append([self.get_default_pixbuf(), txt, album.ID])
+ postoffice.notify('request-album-cover', album.ID, self.ICON_SIZE)
def get_album_id(self, path):
- treeiter = self.__store.get_iter(path)
- _, _id = self.__store.get(treeiter, 0, 1)
- return _id
+ return self.__store.get(self.__store.get_iter(path), self.COL_ID)[0]
def show_artist(self, show):
self.__show_artist = show
-class TrackList(gtk.TreeView):
+class TrackList(_BaseList):
def __init__(self, numbers = True):
- gtk.TreeView.__init__(self)
+ _BaseList.__init__(self)
self.track_numbers = numbers
self.__store = gtk.ListStore(int, str, int)
self.set_model(self.__store)
_, _, _id = self.__store.get(treeiter, 0, 1, 2)
return _id
-class RadioList(gtk.TreeView):
+class RadioList(_BaseList):
def __init__(self):
- gtk.TreeView.__init__(self)
- self.__store = gtk.ListStore(str, int)
+ _BaseList.__init__(self)
+ (self.COL_ICON, self.COL_NAME, self.COL_ID, self.COL_IMAGE) = range(4)
+ self.__store = gtk.ListStore(gtk.gdk.Pixbuf, str, int, str)
self.set_model(self.__store)
+ icon = gtk.TreeViewColumn('Icon')
+ self.append_column(icon)
+ cell = gtk.CellRendererPixbuf()
+ icon.pack_start(cell, True)
+ icon.add_attribute(cell, 'pixbuf', self.COL_ICON)
+
col = gtk.TreeViewColumn('Name')
self.append_column(col)
cell = gtk.CellRendererText()
col.pack_start(cell, True)
- col.add_attribute(cell, 'text', 0)
+ col.add_attribute(cell, 'text', self.COL_NAME)
+ self.set_search_column(self.COL_NAME)
+ col.set_sort_column_id(self.COL_NAME)
- self.set_search_column(0)
- col.set_sort_column_id(0)
+ postoffice.connect('images', self, self.on_images)
+
+ def on_destroy(self, wnd):
+ postoffice.disconnect('images', self)
+
+ def add_radios(self, radios):
+ for radio in radios:
+ self.__store.append([self.get_default_pixbuf(), self.radio_name(radio), radio.ID, radio.image])
+ postoffice.notify('request-images', [radio.image for radio in radios])
- def add_radio(self, radio):
- self.__store.append([self.radio_name(radio), radio.ID])
def get_radio_id(self, path):
treeiter = self.__store.get_iter(path)
- name, _id = self.__store.get(treeiter, 0, 1)
+ name, _id = self.__store.get(treeiter, self.COL_NAME, self.COL_ID)
return name, _id
+ def on_images(self, images):
+ for url, image in images:
+ for row in self.__store:
+ if row[self.COL_IMAGE] == url:
+ pb = self.get_pixbuf(image)
+ if pb:
+ row[self.COL_ICON] = pb
+
def radio_name(self, radio):
if radio.idstr:
return radio.idstr.capitalize()
from playerwindow import open_playerwindow
from showartist import ShowArtist
from showalbum import ShowAlbum
+from albumlist import MusicList
+from player import Playlist
def _alist(l, match):
for key, value in l:
def __init__(self, feature):
hildon.StackableWindow.__init__(self)
- self.set_title("Featured")
+ self.set_title(feature)
self.featurefn = _alist(self.features, feature)
# Results list
self.panarea = hildon.PannableArea()
- self.result_store = gtk.ListStore(str, int)
- #self.result_store.append(['red'])
- self.result_view = gtk.TreeView(self.result_store)
- col = gtk.TreeViewColumn('Name')
- self.result_view.append_column(col)
- cell = gtk.CellRendererText()
- col.pack_start(cell, True)
- col.add_attribute(cell, 'text', 0)
- self.result_view.set_search_column(0)
- col.set_sort_column_id(0)
- self.result_view.connect('row-activated', self.row_activated)
-
- self.panarea.add(self.result_view)
+ self.musiclist = MusicList()
+ self.musiclist.connect('row-activated', self.row_activated)
+ self.panarea.add(self.musiclist)
self.idmap = {}
- for item in self.featurefn():
+ self.items = self.featurefn()
+ for item in self.items:
self.idmap[item.ID] = item
- self.result_store.append([self.get_item_text(item), item.ID])
+ self.musiclist.add_items(self.items)
self.add(self.panarea)
self.menu.show_all()
self.set_app_menu(self.menu)
- def get_item_text(self, item):
- if isinstance(item, jamaendo.Album):
- return "%s - %s" % (item.artist_name, item.name)
- elif isinstance(item, jamaendo.Track):
- return "%s - %s" % (item.artist_name, item.name)
- else:
- return item.name
-
- def make_button(self, text, subtext, callback):
- button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- button.set_text(text, subtext)
-
- if callback:
- button.connect('clicked', callback)
-
- #image = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON)
- #button.set_image(image)
- #button.set_image_position(gtk.POS_RIGHT)
-
- return button
-
def row_activated(self, treeview, path, view_column):
- treeiter = self.result_store.get_iter(path)
- title, _id = self.result_store.get(treeiter, 0, 1)
+ _id = self.musiclist.get_item_id(path)
item = self.idmap[_id]
- #print _id, item
self.open_item(item)
def open_item(self, item):
wnd = ShowArtist(item)
wnd.show_all()
elif isinstance(item, jamaendo.Track):
+ playlist = Playlist(self.items)
+ playlist.jump_to(item.ID)
wnd = open_playerwindow()
- wnd.play_tracks([item])
+ wnd.play_tracks(playlist)
elif isinstance(item, jamaendo.Tag):
wnd = open_playerwindow()
wnd.play_tracks(jamaendo.get_tag_tracks(item.ID))
def set_default_cover(self):
tmp = util.find_resource('album.png')
if tmp:
+ log.debug("Setting cover to %s", tmp)
self.cover.set_from_file(tmp)
def update_state(self):
if size == 300:
playing = self.get_album_id()
if playing and albumid and (int(playing) == int(albumid)):
+ log.debug("Setting cover to %s", cover)
self.cover.set_from_file(cover)
def play_radio(self, radio_name, radio_id):
self.radios = {}
hildon.hildon_gtk_window_set_progress_indicator(self, 1)
- for item in jamaendo.starred_radios():
+ radios = jamaendo.starred_radios()
+ for item in radios:
self.radios[item.ID] = item
- self.radiolist.add_radio(item)
+ self.radiolist.add_radios(radios)
hildon.hildon_gtk_window_set_progress_indicator(self, 0)
self.add(self.panarea)
from playerwindow import open_playerwindow
from showartist import ShowArtist
from showalbum import ShowAlbum
+from albumlist import MusicList
class SearchWindow(hildon.StackableWindow):
def __init__(self):
# Results list
self.panarea = hildon.PannableArea()
+ self.musiclist = MusicList()
self.result_store = gtk.ListStore(str, int)
#self.result_store.append(['red'])
self.result_view = gtk.TreeView(self.result_store)
# shows the current song position (looking a bit nicer than a default widget, hopefully)
class SongPosition(gtk.DrawingArea):
- WIDTH = 8.0
+ WIDTH = 32.0
HEIGHT = 8.0
def __init__(self):
darkclr.add_color_stop_rgba(1.0, 0.25, 0.25, 0.25, 1.0)
markerclr = cairo.LinearGradient(0.0, 0.0, 0.0, self.HEIGHT)
- markerclr.add_color_stop_rgba(0.0, *orange1)
- markerclr.add_color_stop_rgba(0.5, *orange0)
- markerclr.add_color_stop_rgba(1.0, *orange0)
+ markerclr.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 0.0)
+ markerclr.add_color_stop_rgba(0.5, 1.0, 1.0, 1.0, 0.75)
+ markerclr.add_color_stop_rgba(1.0, 1.0, 1.0, 1.0, 1.0)
self.lightclr = lightclr
self.darkclr = darkclr
context.fill()
def set_position(self, pos):
- assert 0 <= pos <= 1
+ if pos < 0.0:
+ pos = 0.0
+ elif pos > 1.0:
+ pos = 1.0
self.pos = pos
self.invalidate()
log = logging.getLogger(__name__)
-VERSION = '0.1'
+VERSION = '0.2'
try:
import hildon
settings.load()
postoffice.connect('request-album-cover', self, self.on_request_cover)
+ postoffice.connect('request-images', self, self.on_request_images)
log.debug("Created main window.")
def save_settings(self):
def on_request_cover(self, albumid, size):
jamaendo.get_album_cover_async(self.got_album_cover, int(albumid), size)
+ def on_request_images(self, urls):
+ jamaendo.get_images_async(self.got_images, urls)
+
def got_album_cover(self, albumid, size, cover):
postoffice.notify('album-cover', albumid, size, cover)
+ def got_images(self, images):
+ postoffice.notify('images', images)
+
#def add_featured_button(self):
# self.featured_sel = hildon.TouchSelector(text=True)
# self.featured_sel.append_text("Albums of the week")
# self.bbox.add(btn)
def destroy(self, widget):
- postoffice.disconnect('request-album-cover', self)
+ postoffice.disconnect(['request-album-cover', 'request-images'], self)
self.save_settings()
from player import the_player
if the_player:
def on_featured(self, button):
dialog = hildon.PickerDialog(self.window)
- sel = hildon.TouchSelector(text=True)
+ sel = hildon.TouchSelectorEntry(text=True)
for feature, _ in FeaturedWindow.features:
sel.append_text(feature)
dialog.set_selector(sel)
dialog.set_title("Featured")
+ sel.unselect_all(0)
if dialog.run() == gtk.RESPONSE_OK:
txt = sel.get_current_text()
self.featuredwnd = FeaturedWindow(txt)