Index: totem.spec.in
===================================================================
--- totem.spec.in	(révision 5806)
+++ totem.spec.in	(copie de travail)
@@ -137,6 +137,11 @@
 %{_libdir}/totem/plugins/pythonconsole/*.pyc
 %{_libdir}/totem/plugins/pythonconsole/*.pyo
 %{_libdir}/totem/plugins/pythonconsole/pythonconsole.totem-plugin
+%{_libdir}/totem/plugins/jamendo/*.py
+%{_libdir}/totem/plugins/jamendo/*.pyc
+%{_libdir}/totem/plugins/jamendo/*.pyo
+%{_libdir}/totem/plugins/jamendo/jamendo.totem-plugin
+%{_libdir}/totem/plugins/jamendo/jamendo.glade
 %{_libexecdir}/totem/totem-bugreport.*
 
 %files mozplugin
@@ -145,6 +150,9 @@
 %{_libexecdir}/totem-plugin-viewer
 
 %changelog
+* Tue Nov 25 2008 David JEAN LOUIS <izimobil@gmail.com>
+- Added jamendo python plugin
+
 * Sat Mar 1 2008 Christian Schaller <christian.schaller@collabora.co.uk>
 - Update for new plugins and removed pl-parser
 
Index: configure.in
===================================================================
--- configure.in	(révision 5806)
+++ configure.in	(copie de travail)
@@ -50,7 +50,7 @@
 AC_SUBST(TOTEM_VERSION_MICRO)
 
 # The full list of plugins
-allowed_plugins="thumbnail screensaver ontop galago gromit lirc media-player-keys mythtv properties sidebar-test skipto sample-python sample-vala bemused youtube publish tracker pythonconsole"
+allowed_plugins="thumbnail screensaver ontop galago gromit lirc media-player-keys mythtv properties sidebar-test skipto sample-python sample-vala bemused youtube publish tracker pythonconsole jamendo"
 
 PLUGINDIR='${libdir}/totem/plugins'
 AC_SUBST(PLUGINDIR)
@@ -873,6 +873,7 @@
 src/plugins/youtube/Makefile
 src/plugins/pythonconsole/Makefile
 src/plugins/publish/Makefile
+src/plugins/jamendo/Makefile
 src/backend/Makefile
 browser-plugin/Makefile
 data/Makefile
Index: src/plugins/jamendo/jamendo.py
===================================================================
--- src/plugins/jamendo/jamendo.py	(révision 0)
+++ src/plugins/jamendo/jamendo.py	(révision 0)
@@ -0,0 +1,650 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2008 David JL <izimobil@gmail.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+"""
+Jamendo totem plugin (http://www.jamendo.com).
+
+TODO:
+- store thumbnails in relevant XDG directories (?)
+- cleanup the notebook code
+- interface with jamendo write API (not documented yet):
+  favorites, comments, etc...
+"""
+
+import os
+import totem
+import gettext
+import gconf
+import gobject
+import gtk
+import gtk.glade
+import pango
+import socket
+import threading
+import time
+import urllib
+import urllib2
+import webbrowser
+from xml.sax.saxutils import escape
+try:
+    import json
+except ImportError:
+    try:
+        import simplejson as json
+    except ImportError:
+        dlg = gtk.MessageDialog(
+            type=gtk.MESSAGE_ERROR,
+            buttons=gtk.BUTTONS_OK
+        )
+        dlg.set_markup(_('You need to install the python simplejson module'))
+        dlg.run()
+        dlg.destroy()
+        raise
+
+socket.setdefaulttimeout(30)
+gobject.threads_init()
+_ = gettext.gettext
+gconf_key = '/apps/totem/plugins/jamendo'
+
+
+class JamendoPlugin(totem.Plugin):
+    """
+    Jamendo totem plugin GUI.
+    """
+    SEARCH_CRITERIUM = ['artist_name', 'tag_idstr']
+    AUDIO_FORMATS    = ['ogg2', 'mp31']
+    TAB_RESULTS      = 0
+    TAB_POPULAR      = 1
+    TAB_LATEST       = 2
+
+    def __init__(self):
+        totem.Plugin.__init__(self)
+        self.debug = True
+        self.gstreamer_plugins_present = True
+        self.totem = None
+        self.gconf = gconf.client_get_default()
+        self.init_settings()
+
+        # init glade interface
+        f = os.path.join(os.path.dirname(__file__), 'jamendo.glade')
+        self.glade = gtk.glade.XML(f)
+        self.config_dialog = self.glade.get_widget('config_dialog')
+        self.window = self.glade.get_widget('mainwindow')
+        self.container = self.glade.get_widget('mainwindow_container')
+        self.notebook = self.glade.get_widget('notebook')
+        self.search_entry = self.glade.get_widget('search_entry')
+        self.search_combo = self.glade.get_widget('search_combo')
+        # XXX active property set in glade file does not work
+        self.search_combo.set_active(0)
+        self.album_button = self.glade.get_widget('album_button')
+        self.previous_button = self.glade.get_widget('previous_button')
+        self.next_button = self.glade.get_widget('next_button')
+        self.progressbars = [
+            self.glade.get_widget('results_progressbar'),
+            self.glade.get_widget('popular_progressbar'),
+            self.glade.get_widget('latest_progressbar'),
+        ]
+        self.setup_treeviews()
+
+        # connect signals to slots
+        self.glade.signal_autoconnect({
+            'on_search_button_clicked': self.on_search_button_clicked,
+            'on_search_entry_activate': self.on_search_entry_activate,
+            'on_notebook_switch_page': self.on_notebook_switch_page,
+            'on_treeview_row_activated': self.on_treeview_row_activated,
+            'on_treeview_row_clicked': self.on_treeview_row_clicked,
+            'on_previous_button_clicked': self.on_previous_button_clicked,
+            'on_next_button_clicked': self.on_next_button_clicked,
+            'on_album_button_clicked': self.on_album_button_clicked,
+            'on_cancel_button_clicked': self.on_cancel_button_clicked,
+            'on_ok_button_clicked': self.on_ok_button_clicked,
+        })
+
+        self.reset()
+
+    def activate(self, totem_object):
+        """
+        Plugin activation.
+        """
+        self.totem = totem_object
+        # unparent the container to embed it into totem sidebar
+        self.container.unparent()
+        self.totem.add_sidebar_page("jamendo", _("Jamendo"), self.container)
+
+    def deactivate(self, totem_object):
+        """
+        Plugin deactivation.
+        """
+        totem_object.remove_sidebar_page("jamendo")
+
+    def create_configure_dialog(self, *args):
+        """
+        Plugin config dialog.
+        """
+        format = self.gconf.get_string('%s/format' % gconf_key)
+        num_per_page = self.gconf.get_int('%s/num_per_page' % gconf_key)
+        combo = self.glade.get_widget('preferred_format_combo')
+        combo.set_active(self.AUDIO_FORMATS.index(format))
+        spinbutton = self.glade.get_widget('album_num_spinbutton')
+        spinbutton.set_value(num_per_page)
+        return self.config_dialog
+
+    def reset(self):
+        """
+        XXX this will be refactored asap.
+        """
+        self.current_page = {
+            self.TAB_RESULTS: 1,
+            self.TAB_POPULAR: 1,
+            self.TAB_LATEST : 1
+        }
+        self.pages = {
+            self.TAB_RESULTS: [],
+            self.TAB_POPULAR: [],
+            self.TAB_LATEST : []
+        }
+        self.album_count = [0, 0, 0]
+        for tv in self.treeviews:
+            tv.get_model().clear()
+        self._update_buttons_state()
+
+    def init_settings(self):
+        """
+        Initialize plugin settings.
+        """
+        format = self.gconf.get_string('%s/format' % gconf_key)
+        if not format:
+            format = 'ogg2'
+            self.gconf.set_string('%s/format' % gconf_key, format)
+        num_per_page = self.gconf.get_int('%s/num_per_page' % gconf_key)
+        if not num_per_page:
+            num_per_page = 10
+            self.gconf.set_int('%s/num_per_page' % gconf_key, num_per_page)
+        JamendoService.AUDIO_FORMAT = format
+        JamendoService.NUM_PER_PAGE = num_per_page
+
+    def setup_treeviews(self):
+        """
+        Setup the 3 treeview: result, popular and latest
+        """
+        self.treeviews = [
+            self.glade.get_widget('results_treeview'),
+            self.glade.get_widget('popular_treeview'),
+            self.glade.get_widget('latest_treeview'),
+        ]
+        self.current_treeview = self.treeviews[0]
+        for w in self.treeviews:
+
+            # build a treestore
+            model = gtk.TreeStore(
+                gobject.TYPE_PYOBJECT, # album or track dict
+                gtk.gdk.Pixbuf,        # album cover or track icon
+                str,                   # album or track description
+                str,                   # album or track duration
+                str,                   # album or track tooltip
+            )
+            w.set_model(model)
+
+            # build pixbuf column
+            cell = gtk.CellRendererPixbuf()
+            col = gtk.TreeViewColumn()
+            col.pack_start(cell, True)
+            col.set_attributes(cell, pixbuf=1)
+            w.append_column(col)
+
+            # build description column
+            cell = gtk.CellRendererText()
+            cell.set_property('wrap-mode', pango.WRAP_WORD)
+            cell.set_property('wrap-width', 30)
+            col = gtk.TreeViewColumn()
+            col.pack_start(cell, True)
+            col.set_attributes(cell, markup=2)
+            col.set_expand(True)
+            w.append_column(col)
+            w.connect_after(
+                'size-allocate',
+                self.on_treeview_size_allocate,
+                col,
+                cell
+            )
+
+            # duration column
+            cell = gtk.CellRendererText()
+            cell.set_property('xalign', 1.0)
+            cell.set_property('size-points', 8)
+            col = gtk.TreeViewColumn()
+            col.pack_start(cell, True)
+            col.set_attributes(cell, markup=3)
+            col.set_alignment(1.0)
+            w.append_column(col)
+
+            # configure the treeview
+            w.set_show_expanders(False) # we manage internally expand/collapse
+            w.set_tooltip_column(4)     # set the tooltip column
+
+    def add_treeview_item(self, treeview, album):
+        if not isinstance(album['image'], gtk.gdk.Pixbuf):
+            # album image pixbuf is not yet built
+            try:
+                pb = gtk.gdk.pixbuf_new_from_file(album['image'])
+                os.unlink(album['image'])
+                album['image'] = pb
+            except:
+                # do not fail for this, just display a dummy pixbuf
+                album['image'] = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True,
+                    8, 1, 1)
+        # format title
+        title  = '<b>%s</b>\n' % self._format_str(album['name'])
+        title += _('Artist: %s') % self._format_str(album['artist_name'])
+        # format duration
+        dur = self._format_duration(album['duration'])
+        # format tooltip
+        try:
+            release = time.strptime(album['dates']['release'][0:10], '%Y-%m-%d')
+            release = time.strftime('%x', release)
+        except:
+            release = ''
+        tip = '\n'.join([
+            '<b>%s</b>' % self._format_str(album['name']),
+            _('Artist: %s') % self._format_str(album['artist_name']),
+            _('Genre: %s') % self._format_str(album['genre']),
+            _('Released on: %s') % release,
+            _('License: %s') % self._format_str(album['license'][0]),
+        ])
+        # append album row
+        parent = treeview.get_model().append(None,
+            [album, album['image'], title, dur, tip]
+        )
+        # append track rows
+        icon = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 1, 1)
+        for i, track in enumerate(album['tracks']):
+            # track title
+            tt = '<small>%02d. %s</small>' % \
+                (i+1, self._format_str(track['name']))
+            # track duration
+            td = self._format_duration(track['duration'])
+            # track tooltip
+            tip = '\n'.join([
+                '<b>%s</b>' %  self._format_str(track['name']),
+                _('Album: %s') % self._format_str(album['name']),
+                _('Artist: %s') % self._format_str(album['artist_name']),
+                _('Duration: %s') % td,
+            ])
+            # append track
+            treeview.get_model().append(parent, [track, icon, tt, td, tip])
+        # update current album count
+        pindex = self.treeviews.index(treeview)
+        self.album_count[pindex] += 1
+
+    def fetch_albums(self, pn=1):
+        """
+        Initialize the fetch thread.
+        """
+        tab_index = self.treeviews.index(self.current_treeview)
+        if tab_index == self.TAB_POPULAR:
+            params = {'order': 'rating_desc'}
+        elif tab_index == self.TAB_LATEST:
+            params = {'order': 'date_desc'}
+        else:
+            value = self.search_entry.get_text()
+            if not value:
+                return
+            prop = self.SEARCH_CRITERIUM[self.search_combo.get_active()]
+            params = {'order': 'date_desc', prop: value}
+        params['pn'] = pn
+        self.current_treeview.get_model().clear()
+        self.previous_button.set_sensitive(False)
+        self.next_button.set_sensitive(False)
+        self.album_button.set_sensitive(False)
+        self.progressbars[tab_index].show()
+        self.progressbars[tab_index].set_fraction(0.0)
+        self.progressbars[tab_index].set_text(
+            _('Fetching albums, please wait...')
+        )
+        lcb = (self.on_fetch_albums_loop, self.current_treeview)
+        dcb = (self.on_fetch_albums_done, self.current_treeview)
+        ecb = (self.on_fetch_albums_error, self.current_treeview)
+        thread = JamendoService(params, lcb, dcb, ecb)
+        thread.start()
+
+    def on_fetch_albums_loop(self, treeview, album):
+        """
+        Add an album item and its tracks to the current treeview.
+        """
+        self.add_treeview_item(treeview, album)
+        # pulse progressbar
+        pindex = self.treeviews.index(treeview)
+        self.progressbars[pindex].set_fraction(
+            float(self.album_count[pindex]) / float(JamendoService.NUM_PER_PAGE)
+        )
+
+    def on_fetch_albums_done(self, treeview, albums, save_state=True):
+        """
+        Called when the thread finished fetching albums.
+        """
+        pindex = self.treeviews.index(treeview)
+        model = treeview.get_model()
+        if save_state and len(albums):
+            self.pages[pindex].append(albums)
+            self.current_page[pindex] = len(self.pages[pindex])
+        self._update_buttons_state()
+        self.progressbars[pindex].set_fraction(0.0)
+        self.progressbars[pindex].hide()
+        self.album_count[pindex] = 0
+
+    def on_fetch_albums_error(self, treeview, exc):
+        """
+        Called when an error occured in the thread.
+        """
+        self.reset()
+        pindex = self.treeviews.index(treeview)
+        self.progressbars[pindex].set_fraction(0.0)
+        self.progressbars[pindex].hide()
+        dlg = gtk.MessageDialog(
+            type=gtk.MESSAGE_ERROR,
+            buttons=gtk.BUTTONS_OK
+        )
+        dlg.set_markup(
+            '<b>%s</b>' % _('An error occured while fetching albums.')
+        )
+        # managing exceptions with urllib is a real PITA... :(
+        if hasattr(exc, 'reason'):
+            try:
+                reason = exc.reason[1]
+            except:
+                try:
+                    reason = exc.reason[0]
+                except:
+                    reason = str(exc)
+            reason = reason.capitalize()
+            msg = _('Failed to connect to jamendo server.\n%s.') % reason
+        elif hasattr(exc, 'code'):
+            msg = _('The jamendo server returned code %s') % exc.code
+        else:
+            msg = str(exc)
+        dlg.format_secondary_text(msg)
+        dlg.run()
+        dlg.destroy()
+
+    def on_search_entry_activate(self, *args):
+        """
+        Called when the user typed <enter> in the search entry.
+        """
+        return self.on_search_button_clicked()
+
+    def on_search_button_clicked(self, *args):
+        """
+        Called when the user clicked on the search button.
+        """
+        if not self.search_entry.get_text():
+            return
+        if self.current_treeview != self.treeviews[self.TAB_RESULTS]:
+            self.current_treeview = self.treeviews[self.TAB_RESULTS]
+            self.notebook.set_current_page(self.TAB_RESULTS)
+        else:
+            self.on_notebook_switch_page(new_search=True)
+
+    def on_notebook_switch_page(self, nb=None, tab=None, tab_num=0,
+        new_search=False):
+        """
+        Called when the changed a notebook page.
+        """
+        self.current_treeview = self.treeviews[int(tab_num)]
+        self._update_buttons_state()
+        model = self.current_treeview.get_model()
+        # fetch popular and latest albums once only
+        if not new_search and len(model):
+            return
+        if new_search:
+            self.current_page[self.TAB_RESULTS] = 1
+            self.pages[self.TAB_RESULTS] = []
+            self.album_count[self.TAB_RESULTS] = 0
+            self._update_buttons_state()
+        model.clear()
+        self.fetch_albums()
+
+    def on_treeview_row_activated(self, tv, path, column):
+        """
+        Called when the user double-clicked on a treeview element.
+        """
+        sel = self._get_selection_at(0)
+        try:
+            prop = (len(path) == 1) and 'album_id' or 'id'
+            url = '%s/stream/track/redirect/?%s=%s&streamencoding=%s' %\
+                (JamendoService.API_URL, prop, sel['id'],
+                 JamendoService.AUDIO_FORMAT)
+            self.totem.action_set_mrl_and_play(url)
+            # update play icon
+            empty = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 1, 1)
+            icon = self.window.render_icon(gtk.STOCK_MEDIA_PLAY,
+                                           gtk.ICON_SIZE_MENU)
+            for treeview in self.treeviews:
+                model = treeview.get_model()
+                for row in model:
+                    if row.path == path:
+                        path = (path[0], 0)
+                    for subrow in row.iterchildren():
+                        subrow[1] = subrow.path == path and icon or empty
+        except:
+            pass
+
+    def on_treeview_row_clicked(self, tv, evt):
+        """
+        Called when the user clicked on a treeview element.
+        """
+        try:
+            coords = evt.get_coords()
+            path, c, x, y = tv.get_path_at_pos(int(coords[0]), int(coords[1]))
+            if (len(path) == 1):
+                if tv.row_expanded(path):
+                    tv.collapse_row(path)
+                else:
+                    tv.expand_row(path, False)
+            self.album_button.set_sensitive(True)
+        except:
+            pass
+
+    def on_treeview_size_allocate(self, tv, allocation, col, cell):
+        """
+        Hack to autowrap text of the title colum.
+        """
+        cols = (c for c in tv.get_columns() if c != col)
+        w = allocation.width - sum(c.get_width() for c in cols)
+        if cell.props.wrap_width == w or w <= 0:
+            return
+        cell.props.wrap_width = w
+
+    def on_previous_button_clicked(self, *args):
+        """
+        Called when the user clicked the previous button.
+        """
+        try:
+            self._update_buttons_state()
+            model = self.current_treeview.get_model()
+            model.clear()
+            pindex = self.treeviews.index(self.current_treeview)
+            self.current_page[pindex] -= 1
+            albums = self.pages[pindex][self.current_page[pindex]-1]
+            for album in albums:
+                self.add_treeview_item(self.current_treeview, album)
+            self.on_fetch_albums_done(self.current_treeview, albums, False)
+        except:
+            raise
+
+    def on_next_button_clicked(self, *args):
+        """
+        Called when the user clicked the next button.
+        """
+        try:
+            self._update_buttons_state()
+            model = self.current_treeview.get_model()
+            model.clear()
+            pindex = self.treeviews.index(self.current_treeview)
+            if self.current_page[pindex] == len(self.pages[pindex]):
+                self.fetch_albums(self.current_page[pindex]+1)
+            else:
+                self.current_page[pindex] += 1
+                albums = self.pages[pindex][self.current_page[pindex]-1]
+                for album in albums:
+                    self.add_treeview_item(self.current_treeview, album)
+                self.on_fetch_albums_done(self.current_treeview, albums, False)
+        except:
+            raise
+
+    def on_album_button_clicked(self, *args):
+        """
+        Called when the user clicked on the album button.
+        """
+        try:
+            album_id = self._get_selection_at(0, None, True)['id']
+            webbrowser.open('%s/url/album/redirect/?id=%s' % (
+                (JamendoService.API_URL, album_id)
+            ))
+        except:
+            pass
+
+    def on_cancel_button_clicked(self, *args):
+        """
+        Called when the user clicked cancel in the config dialog.
+        """
+        self.config_dialog.hide()
+
+    def on_ok_button_clicked(self, *args):
+        """
+        Called when the user clicked ok in the config dialog.
+        """
+        combo = self.glade.get_widget('preferred_format_combo')
+        spinbutton = self.glade.get_widget('album_num_spinbutton')
+        format = self.AUDIO_FORMATS[combo.get_active()]
+        self.gconf.set_string('%s/format' % gconf_key, format)
+        num_per_page = int(spinbutton.get_value())
+        self.gconf.set_int('%s/num_per_page' % gconf_key, num_per_page)
+        self.init_settings()
+        self.config_dialog.hide()
+        try:
+            self.reset()
+        except:
+            pass
+
+    def _update_buttons_state(self):
+        """
+        Update the state of the previous and next buttons.
+        """
+        sel = self.current_treeview.get_selection()
+        model, it = sel.get_selected()
+        pindex = self.treeviews.index(self.current_treeview)
+        self.previous_button.set_sensitive(self.current_page[pindex] > 1)
+        self.next_button.set_sensitive(len(model)==JamendoService.NUM_PER_PAGE)
+        self.album_button.set_sensitive(it is not None)
+
+    def _get_selection_at(self, at=0, sel=None, root=False):
+        """
+        Shortcut method to retrieve the value of the selected item at the
+        given column.
+        """
+        if sel is None:
+            sel = self.current_treeview.get_selection()
+        model, it = sel.get_selected()
+        if root:
+            while model.iter_parent(it) is not None:
+                it = model.iter_parent(it)
+        if it is not None:
+            return model.get(it, at)[0]
+        return None
+
+    def _format_str(self, st, truncate=False):
+        """
+        Escape entities for pango markup and force the string to utf-8.
+        """
+        if not st:
+            return ''
+        try:
+            return escape(st.encode('utf8'))
+        except:
+            return st
+
+    def _format_duration(self, secs):
+        """
+        Format the given number of seconds to a human readable duration.
+        """
+        try:
+            secs = int(secs)
+            if secs >= 3600:
+                return time.strftime('%H:%M:%S', time.gmtime(secs))
+            return time.strftime('%M:%S', time.gmtime(secs))
+        except:
+            return ''
+
+
+class JamendoService(threading.Thread):
+    """
+    Class that requests the jamendo REST service.
+    """
+
+    API_URL = 'http://api.jamendo.com/get2'
+    AUDIO_FORMAT = 'ogg2'
+    NUM_PER_PAGE = 10
+
+    def __init__(self, params, loop_cb, done_cb, error_cb):
+        self.params = params
+        self.loop_cb = loop_cb
+        self.done_cb = done_cb
+        self.error_cb = error_cb
+        self.lock = threading.Lock()
+        threading.Thread.__init__(self)
+
+    def run(self):
+        url = '%s/id+name+duration+image+genre+dates+artist_id+' \
+              'artist_name+artist_url/album/json/?n=%s&imagesize=50' % \
+              (self.API_URL, self.NUM_PER_PAGE)
+        if len(self.params):
+            url += '&%s' % urllib.urlencode(self.params)
+        try:
+            self.lock.acquire()
+            albums = json.loads(self._request(url))
+            ret = []
+            for i, album in enumerate(albums):
+                fname, headers = urllib.urlretrieve(album['image'])
+                album['image'] = fname
+                album['tracks'] = json.loads(self._request(
+                    '%s/id+name+duration/track/json/?album_id=%s'\
+                    '&order=numalbum_asc' % (self.API_URL, album['id'])
+                ))
+                album['license'] = json.loads(self._request(
+                    '%s/name/license/json/album_license/?album_id=%s'\
+                    % (self.API_URL, album['id'])
+                ))
+                gobject.idle_add(self.loop_cb[0], self.loop_cb[1], album)
+            gobject.idle_add(self.done_cb[0], self.done_cb[1], albums)
+        except Exception, exc:
+            gobject.idle_add(self.error_cb[0], self.error_cb[1], exc)
+        finally:
+            self.lock.release()
+
+    def _request(self, url):
+        opener = urllib2.build_opener()
+        opener.addheaders = [('User-agent', 'Totem Jamendo plugin')]
+        handle = opener.open(url)
+        data = handle.read()
+        handle.close()
+        return data
+
Index: src/plugins/jamendo/jamendo.totem-plugin.in
===================================================================
--- src/plugins/jamendo/jamendo.totem-plugin.in	(révision 0)
+++ src/plugins/jamendo/jamendo.totem-plugin.in	(révision 0)
@@ -0,0 +1,9 @@
+[Totem Plugin]
+Loader=python
+Module=jamendo
+IAge=1
+_Name=Jamendo
+_Description=Search, browse and listen to the large collection of creative commons licensed albums provided by jamendo.
+Authors=David JEAN LOUIS <izimobil@gmail.com>
+Copyright=Copyright © 2008 David JEAN LOUIS
+Website=http://www.jamendo.com
Index: src/plugins/jamendo/jamendo.glade
===================================================================
--- src/plugins/jamendo/jamendo.glade	(révision 0)
+++ src/plugins/jamendo/jamendo.glade	(révision 0)
@@ -0,0 +1,763 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="mainwindow">
+  <property name="border_width">5</property>
+  <property name="title" translatable="yes">Jamendo totem plugin</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_NONE</property>
+  <property name="modal">False</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+
+  <child>
+    <widget class="GtkVBox" id="mainwindow_container">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">5</property>
+
+      <child>
+	<widget class="GtkVBox" id="vbox1">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkHBox" id="search_hbox">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkEntry" id="search_entry">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="editable">True</property>
+		  <property name="visibility">True</property>
+		  <property name="max_length">0</property>
+		  <property name="text" translatable="yes"></property>
+		  <property name="has_frame">True</property>
+		  <property name="invisible_char">●</property>
+		  <property name="activates_default">False</property>
+		  <signal name="activate" handler="on_search_entry_activate"/>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkComboBox" id="search_combo">
+		  <property name="visible">True</property>
+		  <property name="can_default">True</property>
+		  <property name="has_default">True</property>
+		  <property name="items" translatable="yes">By artist
+By tag</property>
+		  <property name="add_tearoffs">False</property>
+		  <property name="focus_on_click">True</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkButton" id="search_button">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		  <signal name="clicked" handler="on_search_button_clicked"/>
+
+		  <child>
+		    <widget class="GtkImage" id="track_icon">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-find</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkNotebook" id="notebook">
+	  <property name="visible">True</property>
+	  <property name="can_focus">True</property>
+	  <property name="show_tabs">True</property>
+	  <property name="show_border">True</property>
+	  <property name="tab_pos">GTK_POS_TOP</property>
+	  <property name="scrollable">False</property>
+	  <property name="enable_popup">False</property>
+	  <signal name="switch_page" handler="on_notebook_switch_page"/>
+
+	  <child>
+	    <widget class="GtkVBox" id="vbox1">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkScrolledWindow" id="results_scrolledwindow">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="shadow_type">GTK_SHADOW_NONE</property>
+		  <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+		  <child>
+		    <widget class="GtkViewport" id="viewport1">
+		      <property name="visible">True</property>
+		      <property name="shadow_type">GTK_SHADOW_IN</property>
+
+		      <child>
+			<widget class="GtkVBox" id="results_vbox">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">0</property>
+
+			  <child>
+			    <widget class="GtkTreeView" id="results_treeview">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="headers_visible">False</property>
+			      <property name="rules_hint">False</property>
+			      <property name="reorderable">False</property>
+			      <property name="enable_search">True</property>
+			      <property name="fixed_height_mode">False</property>
+			      <property name="hover_selection">False</property>
+			      <property name="hover_expand">False</property>
+			      <signal name="button_press_event" handler="on_treeview_row_clicked"/>
+			      <signal name="row_activated" handler="on_treeview_row_activated"/>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">True</property>
+			      <property name="fill">True</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkProgressBar" id="results_progressbar">
+		  <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
+		  <property name="fraction">0</property>
+		  <property name="pulse_step">0.10000000149</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="tab_expand">False</property>
+	      <property name="tab_fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="results_tab_label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Search results</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">True</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="type">tab</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkVBox" id="vbox2">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkScrolledWindow" id="popular_scrolledwindow">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="shadow_type">GTK_SHADOW_NONE</property>
+		  <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+		  <child>
+		    <widget class="GtkViewport" id="viewport2">
+		      <property name="visible">True</property>
+		      <property name="shadow_type">GTK_SHADOW_IN</property>
+
+		      <child>
+			<widget class="GtkVBox" id="popular_vbox">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">0</property>
+
+			  <child>
+			    <widget class="GtkTreeView" id="popular_treeview">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="headers_visible">False</property>
+			      <property name="rules_hint">False</property>
+			      <property name="reorderable">False</property>
+			      <property name="enable_search">True</property>
+			      <property name="fixed_height_mode">False</property>
+			      <property name="hover_selection">False</property>
+			      <property name="hover_expand">False</property>
+			      <signal name="button_press_event" handler="on_treeview_row_clicked"/>
+			      <signal name="row_activated" handler="on_treeview_row_activated"/>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">True</property>
+			      <property name="fill">True</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkProgressBar" id="popular_progressbar">
+		  <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
+		  <property name="fraction">0</property>
+		  <property name="pulse_step">0.10000000149</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="tab_expand">False</property>
+	      <property name="tab_fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="popular_tab_label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Popular</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">True</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="type">tab</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkVBox" id="vbox3">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkScrolledWindow" id="latest_scrolledwindow">
+		  <property name="visible">True</property>
+		  <property name="can_focus">True</property>
+		  <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+		  <property name="shadow_type">GTK_SHADOW_NONE</property>
+		  <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+		  <child>
+		    <widget class="GtkViewport" id="viewport3">
+		      <property name="visible">True</property>
+		      <property name="shadow_type">GTK_SHADOW_IN</property>
+
+		      <child>
+			<widget class="GtkVBox" id="latest_vbox">
+			  <property name="visible">True</property>
+			  <property name="homogeneous">False</property>
+			  <property name="spacing">0</property>
+
+			  <child>
+			    <widget class="GtkTreeView" id="latest_treeview">
+			      <property name="visible">True</property>
+			      <property name="can_focus">True</property>
+			      <property name="headers_visible">False</property>
+			      <property name="rules_hint">False</property>
+			      <property name="reorderable">False</property>
+			      <property name="enable_search">True</property>
+			      <property name="fixed_height_mode">False</property>
+			      <property name="hover_selection">False</property>
+			      <property name="hover_expand">False</property>
+			      <signal name="button_press_event" handler="on_treeview_row_clicked"/>
+			      <signal name="row_activated" handler="on_treeview_row_activated"/>
+			    </widget>
+			    <packing>
+			      <property name="padding">0</property>
+			      <property name="expand">True</property>
+			      <property name="fill">True</property>
+			    </packing>
+			  </child>
+			</widget>
+		      </child>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">True</property>
+		  <property name="fill">True</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkProgressBar" id="latest_progressbar">
+		  <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
+		  <property name="fraction">0</property>
+		  <property name="pulse_step">0.10000000149</property>
+		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="tab_expand">False</property>
+	      <property name="tab_fill">True</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="latest_tab_label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Latest releases</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">True</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0.5</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="type">tab</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">True</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkHBox" id="hbox1">
+	  <property name="visible">True</property>
+	  <property name="homogeneous">False</property>
+	  <property name="spacing">0</property>
+
+	  <child>
+	    <widget class="GtkButton" id="album_button">
+	      <property name="visible">True</property>
+	      <property name="sensitive">False</property>
+	      <property name="can_focus">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <signal name="clicked" handler="on_album_button_clicked"/>
+
+	      <child>
+		<widget class="GtkHBox" id="album_button_hbox">
+		  <property name="visible">True</property>
+		  <property name="homogeneous">False</property>
+		  <property name="spacing">0</property>
+
+		  <child>
+		    <widget class="GtkImage" id="album_button_image">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-jump-to</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">True</property>
+		      <property name="fill">True</property>
+		    </packing>
+		  </child>
+
+		  <child>
+		    <widget class="GtkLabel" id="download_button_label">
+		      <property name="visible">True</property>
+		      <property name="label" translatable="yes">Jamendo album page</property>
+		      <property name="use_underline">False</property>
+		      <property name="use_markup">False</property>
+		      <property name="justify">GTK_JUSTIFY_LEFT</property>
+		      <property name="wrap">False</property>
+		      <property name="selectable">False</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+		      <property name="width_chars">-1</property>
+		      <property name="single_line_mode">False</property>
+		      <property name="angle">0</property>
+		    </widget>
+		    <packing>
+		      <property name="padding">0</property>
+		      <property name="expand">True</property>
+		      <property name="fill">True</property>
+		    </packing>
+		  </child>
+		</widget>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">False</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkHBox" id="hbox2">
+	      <property name="visible">True</property>
+	      <property name="homogeneous">False</property>
+	      <property name="spacing">0</property>
+
+	      <child>
+		<widget class="GtkButton" id="previous_button">
+		  <property name="visible">True</property>
+		  <property name="sensitive">False</property>
+		  <property name="can_focus">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		  <signal name="clicked" handler="on_previous_button_clicked"/>
+
+		  <child>
+		    <widget class="GtkImage" id="previous_button_image">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-go-back</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+
+	      <child>
+		<widget class="GtkButton" id="next_button">
+		  <property name="visible">True</property>
+		  <property name="sensitive">False</property>
+		  <property name="can_focus">True</property>
+		  <property name="relief">GTK_RELIEF_NORMAL</property>
+		  <property name="focus_on_click">True</property>
+		  <signal name="clicked" handler="on_next_button_clicked"/>
+
+		  <child>
+		    <widget class="GtkImage" id="next_button_image">
+		      <property name="visible">True</property>
+		      <property name="stock">gtk-go-forward</property>
+		      <property name="icon_size">4</property>
+		      <property name="xalign">0.5</property>
+		      <property name="yalign">0.5</property>
+		      <property name="xpad">0</property>
+		      <property name="ypad">0</property>
+		    </widget>
+		  </child>
+		</widget>
+		<packing>
+		  <property name="padding">0</property>
+		  <property name="expand">False</property>
+		  <property name="fill">False</property>
+		</packing>
+	      </child>
+	    </widget>
+	    <packing>
+	      <property name="padding">0</property>
+	      <property name="expand">False</property>
+	      <property name="fill">True</property>
+	      <property name="pack_type">GTK_PACK_END</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">False</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+<widget class="GtkDialog" id="config_dialog">
+  <property name="border_width">5</property>
+  <property name="title" translatable="yes">Jamendo plugin configuration</property>
+  <property name="type">GTK_WINDOW_TOPLEVEL</property>
+  <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+  <property name="modal">True</property>
+  <property name="resizable">True</property>
+  <property name="destroy_with_parent">False</property>
+  <property name="decorated">True</property>
+  <property name="skip_taskbar_hint">False</property>
+  <property name="skip_pager_hint">False</property>
+  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+  <property name="focus_on_map">True</property>
+  <property name="urgency_hint">False</property>
+  <property name="has_separator">False</property>
+
+  <child internal-child="vbox">
+    <widget class="GtkVBox" id="config_dialog_container">
+      <property name="visible">True</property>
+      <property name="homogeneous">False</property>
+      <property name="spacing">2</property>
+
+      <child internal-child="action_area">
+	<widget class="GtkHButtonBox" id="buttons">
+	  <property name="visible">True</property>
+	  <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+	  <child>
+	    <widget class="GtkButton" id="cancel_button">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-cancel</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">0</property>
+	      <signal name="clicked" handler="on_cancel_button_clicked"/>
+	    </widget>
+	  </child>
+
+	  <child>
+	    <widget class="GtkButton" id="ok_button">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="label">gtk-ok</property>
+	      <property name="use_stock">True</property>
+	      <property name="relief">GTK_RELIEF_NORMAL</property>
+	      <property name="focus_on_click">True</property>
+	      <property name="response_id">0</property>
+	      <signal name="clicked" handler="on_ok_button_clicked"/>
+	    </widget>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	  <property name="pack_type">GTK_PACK_END</property>
+	</packing>
+      </child>
+
+      <child>
+	<widget class="GtkTable" id="table">
+	  <property name="visible">True</property>
+	  <property name="n_rows">2</property>
+	  <property name="n_columns">2</property>
+	  <property name="homogeneous">False</property>
+	  <property name="row_spacing">0</property>
+	  <property name="column_spacing">2</property>
+
+	  <child>
+	    <widget class="GtkLabel" id="album_num_label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Number of albums to retrieve</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">0</property>
+	      <property name="right_attach">1</property>
+	      <property name="top_attach">1</property>
+	      <property name="bottom_attach">2</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkSpinButton" id="album_num_spinbutton">
+	      <property name="visible">True</property>
+	      <property name="can_focus">True</property>
+	      <property name="climb_rate">1</property>
+	      <property name="digits">0</property>
+	      <property name="numeric">False</property>
+	      <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+	      <property name="snap_to_ticks">False</property>
+	      <property name="wrap">False</property>
+	      <property name="adjustment">10 1 100 1 0 0</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">1</property>
+	      <property name="right_attach">2</property>
+	      <property name="top_attach">1</property>
+	      <property name="bottom_attach">2</property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkComboBox" id="preferred_format_combo">
+	      <property name="visible">True</property>
+	      <property name="can_default">True</property>
+	      <property name="has_default">True</property>
+	      <property name="items" translatable="yes">Ogg
+MP3</property>
+	      <property name="add_tearoffs">False</property>
+	      <property name="focus_on_click">True</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">1</property>
+	      <property name="right_attach">2</property>
+	      <property name="top_attach">0</property>
+	      <property name="bottom_attach">1</property>
+	      <property name="y_options"></property>
+	    </packing>
+	  </child>
+
+	  <child>
+	    <widget class="GtkLabel" id="preferred_format_label">
+	      <property name="visible">True</property>
+	      <property name="label" translatable="yes">Preferred audio format</property>
+	      <property name="use_underline">False</property>
+	      <property name="use_markup">False</property>
+	      <property name="justify">GTK_JUSTIFY_LEFT</property>
+	      <property name="wrap">False</property>
+	      <property name="selectable">False</property>
+	      <property name="xalign">0</property>
+	      <property name="yalign">0.5</property>
+	      <property name="xpad">0</property>
+	      <property name="ypad">0</property>
+	      <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+	      <property name="width_chars">-1</property>
+	      <property name="single_line_mode">False</property>
+	      <property name="angle">0</property>
+	    </widget>
+	    <packing>
+	      <property name="left_attach">0</property>
+	      <property name="right_attach">1</property>
+	      <property name="top_attach">0</property>
+	      <property name="bottom_attach">1</property>
+	    </packing>
+	  </child>
+	</widget>
+	<packing>
+	  <property name="padding">0</property>
+	  <property name="expand">False</property>
+	  <property name="fill">True</property>
+	</packing>
+      </child>
+    </widget>
+  </child>
+</widget>
+
+</glade-interface>
Index: src/plugins/jamendo/Makefile.am
===================================================================
--- src/plugins/jamendo/Makefile.am	(révision 0)
+++ src/plugins/jamendo/Makefile.am	(révision 0)
@@ -0,0 +1,14 @@
+plugindir = $(PLUGINDIR)/jamendo
+uidir = $(plugindir)
+plugin_PYTHON = jamendo.py
+plugin_in_files = jamendo.totem-plugin.in
+
+%.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin)
+ui_DATA = jamendo.glade
+
+EXTRA_DIST = $(plugin_in_files) $(ui_DATA) jamendo.py
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
