Tag Archives: Python

Mehr Liebe für Exaile

Das ständige Aufrufen meines Last.fm-Profils im Browser um ein Stück meinen Lieblingen hinzuzufügen nervte mich schon lange und eine Integration dieser Funktion in Exaile lag auf der Hand. Geplant hatte ich dies schon seit langer Zeit und vergangenes Wochenende befasste ich mich nun damit. Heraus kam das Plugin Last.fm Lieblinge:

Exaile-Wiedergabeliste mit Plugin-Integration als Spalte und im Kontextmenü

Integration in Exailes Wiedergabelisten

Durch die hervorragende Dokumentation der Last.fm-API waren mir bereits die API-Aufrufe track.love und track.unlove bekannt. Allerdings grübelte ich lange darüber nach, wie ich den derzeitigen Lieblings-Status eines Stücks abfragen könnte. Aus früheren Experimenten meinte ich mich zu erinnern, dass track.getInfo unter anderem einen Eintrag dafür lieferte. Dem ist allerdings nicht so und nach einem kurzen Besuch in #audioscrobbler wurde mir nahe gelegt, den Aufruf user.getLovedTracks zu nutzen. Eingangs scheute ich noch die Iteration über alle Stücke in dieser Liste, da die lokalen Tag-Informationen ja durchaus von denen auf Last.fm aufgrund automatischer Korrekturen abweichen können. Schnell wurde jedoch klar, dass dies der einzig gangbare Weg ist, da es 1. sowieso keine andere Möglichkeit gibt, den Status abzufragen und 2. ein einmaliges Abrufen aller Lieblings-Stücke deutlich weniger Datenverkehr verursacht als eine ständige Abfrage pro Stück.

Nachdem dies geklärt war ging es an den zweiten wichtigen Aspekt: Schreibzugriffe auf Profile. Die Aufrufe track.love und track.unlove erfordern wie zu erwarten ist Schreibzugriff auf das Profil des jeweiligen Nutzers. Ein Einbetten meines persönlichen API-Schlüssels und Geheimnisses stand außer Frage; hierdurch hätte  jeder Schreibrechte auf die Profile aller Nutzer, die diesen Zugriff auf ihr Profil erlaubt hätten. Mehr als ein Kopieren des API-Schlüssels und Geheimnisses aus dem Quellcode des Plugins wäre hierfür nicht notwendig gewesen.

Aus diesem Grund entschied ich mich für das einzig Logische: jeder Nutzer müsste dem Plugin zur Nutzung seinen ganz persönlichen  API-Schlüssel und das zugehörige Geheimnis mitteilen. Damit obliegt dem Nutzer weiterhin die Entscheidung, ob Exaile auf sein Profil zugreifen darf und die erforderlichen Authentifizierungsdaten bleiben privat. Grafisch sieht das ganze daher nun so aus:

Dialog mit Einstellungen des Plugins Last.fm Lieblinge

Einstellungsdialog

Ein Klick auf die Schaltfläche „Zugriffsgenehmigung anfragen“ öffnet die Seite zum Verbinden von Apps auf Last.fm im Browser.

Bei der Umsetzung habe ich besonders Wert auf Multithreading gelegt, denn nichts nervt mehr als eine Anwendung, die nicht mehr reagiert, weil sie irgendetwas Aufwändiges im Hintergrund macht. Die Integration in Exailes grafische Oberfläche gestaltete sich dank des bereits vorhandenen Providers-Frameworks simpel. Der zum Umschalten des Lieblings-Status verwendete CellRendererToggleImage ist gekapselt und andernorts uneingeschränkt nutzbar. Die Kommunikation mit Last.fm (und in Zukunft auch Libre.fm, sollte dieser Dienst die AudioScrobbler 2.0 API implementieren) findet durch die wunderbare pylast-Bibliothek statt. Praktisch alle API-Aufrufe werden von dieser gekapselt und komfortabel zur Verfügung gestellt. Lediglich den track.unlove-Aufruf musste ich manuell hinzufügen.

Nunmehr genügt ein einzelner Klick auf das Herz-Symbol in der durch das Plugin hinzugefügten Spalte für Wiedergabelisten oder wahlweise der passende Eintrag im Kontextmenü von Stücken um diese als Lieblinge zu markieren oder diese Markierung zu entfernen. Für Letzteres hatte ich bisher allerdings noch keinen Bedarf.

Die Integration der Lieblingsstücke als benutzerdefinierte oder dynamische Wiedergabeliste ist schon vorgesehen. Langfristig ist übrigens auch geplant, dieses und alle anderen AudioScrobbler-bezogenen Plugins zu vereinen. Bis dahin gibt es noch viele interessante Funktionen, die es zu integrieren heißt.

Mausklicks in GtkLabel

Entgegen der landläufigen Behauptung ist es durchaus möglich, Mausklicks auf GtkLabel-Instanzen zu verarbeiten. Der Schlüssel liegt darin, GTK anzuweisen, dass das jeweilige Label ein GdkWindow haben soll. Aufmerksam gemacht wurde ich hierauf durch diesen Beitrag von Tadej Borovšak. Hier ein Beispiel:

#! /usr/bin/env python
 
import gtk
 
window = gtk.Window()
window.set_title('Label Button Press Test')
window.set_size_request(400, 300)
window.connect('destroy', lambda w: gtk.main_quit())
 
box = gtk.VBox(spacing=6)
window.add(box)
 
class ClickableLabel(gtk.Label):
    __gsignals__ = {'button-press-event': 'override'}
 
    def __init__(self, *args):
        gtk.Label.__init__(self, *args)
        # This is the important line to enable more signals
        self.set_has_window(True)
        self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
 
    def do_button_press_event(self, event):
        print 'Clickable: %s' % event.type
 
clickable_label = ClickableLabel()
clickable_label.set_markup('<span size="25000">Clickable label</span>')
box.pack_start(clickable_label, False)
 
def on_button_press_event(widget, event):
    print 'Regular: %s' % event.type
 
label = gtk.Label()
label.set_markup('<span size="25000">Regular label</span>')
# Does not work and renders the label invisible
label.set_has_window(True)
label.add_events(gtk.gdk.BUTTON_PRESS_MASK)
label.connect('button-press-event', on_button_press_event)
box.pack_start(label, False)
 
window.show_all()
gtk.main()

Wie zu sehen ist, lassen sich unter Nutzung einer einzelnen Zeile weitere Signale für ein GtkLabel „freischalten“. Jedoch nur für abgeleitete Typen, nicht für die Standard-GtkLabels, wie am zweiten Label zu erkennen ist. Warum dies so ist ist mir trotz Blick auf gtkwidget.c noch nicht ersichtlich. Erklärungen zur Methode set_has_window() erwähnen, dass diese nur in der Initialisierung eines Widgets aufgerufen werden sollte. Intern wird hier nur das GTK_NO_WINDOW-Flag gesetzt oder gelöscht und infolge dessen unter anderem das Zeichnen von Widgets anders gehandhabt. Aus diesem Grund hat ein GtkLabel bspw. auch keinen Hintergrund welchen man ändern könnte.

Iteration durch gtk.TreeModel

Zur Verarbeitung von Einträgen in einem gtk.TreeModel bedient man sich in Python üblicherweise der Iteration. Hierbei bietet gtk.TreeModel einige Methoden zur Arbeit damit. Ein übliches Konstrukt sieht damit so aus:

iter = model.get_iter_first()
while True:
    value = model.get_value(iter, 0)
    # Do something with value
    iter = model.iter_next(iter)
    if iter is None:
        break

Ziemlich umständlich und daher oft auch in dieser etwas vereinfachten Version vorzufinden:

iter = model.get_iter_first()
while iter:
    value = model.get_value(iter, 0)
    # Do something with value
    iter = model.iter_next(iter)

Doch wie so oft in Python gibt es eine viel intuitivere und simplere Lösung; gtk.TreeModel implementiert __iter__ und __next__, womit sich das obige Konstrukt auf folgenden Zweizeiler reduzieren lässt.

for row in model:
    value = row[0]

Bei row handelt es sich um eine gtk.TreeModelRow, welche intuitiven Zugriff auf die Daten der jeweiligen Spalten ermöglicht.

Operatoren-Verkettung in Python

Eher durch Zufall bin ich heute auf etwas gestoßen, was ich mir seit jeher in einer vernünftigen Programmiersprache gewünscht aber bis jetzt nie vorgefunden habe:

if a < b < c
    # …

Ein völlig intuitives und sehr gut lesbares Konstrukt zum einfachen Vergleich dreier oder mehr Operanden. In praktisch jeder Sprache muss man sich jedoch mit diesem Konstrukt behelfen:

if a < b and b < c
    # …

Nicht so jedoch in Python. Dort ist genau obiges möglich. Hierbei können offenbar beliebig viele Operanden verkettet werden. Eine feine Sache.