Musik zu vermieten (Linux-Magazin, September 2014)

Der Musik-Streamingservice Spotify pumpt gegen eine monatliche Gebühr Musik auf den Desktop und aufs Smartphone. Mittels einer OAuth-geschützten WebAPI archiviert der Perlhacker die Daten seiner Playlists.

Da schau her -- in meiner Kristallkugel erscheint gerade eine Zukunftsvision der Medienlandschaft. Der Trend geht klar weg vom materiellen Datenträger, hin zu ausschließlich digital verfügbarer Information. Über kurz oder lang werden aber nicht nur die Papier- den Online-Zeitungen weichen, Silberscheiben von über die Datenleitung hereinströmender Musik und Film abgelöst, sondern es bahnt sich auch ein Wandel der Eigentumsverhältnisse an.

Eine gekaufte und einmal gesehene Blu-Ray Disk stellt sich doch realistisch gesehen später zum Beispiel oft als Staubfänger heraus, der in extra zu diesem Zweck in Designerläden teuer erworbenen Regalen wertvollen Wohnraum wegnimmt, über drei Umzüge hinweg in Kartons weitergeschleppt und viel später endlich für wenig Geld auf dem Flohmarkt verscherbelt wird. Die Wahrscheinlichkeit, einen Film zweimal anzusehen ist leider oft vernichtend gering, wozu also sollte man Kopien von Musik- oder Filmerzeugnissen tatsächlich besitzen wollen?

Schlummernde Musikbonzen

Abbildung 1: Im Web-Browser läuft die Mietmusik von Spotify nach vom Kunden erstellten Playlists.

Die Unterhaltungsindustrie hat den Trend erwartungsgemäß verschlafen, aber Drittanbieter wie der Video-Streamer Netflix in den USA (demnächst auch in Deutschland [8]) oder Musikangebote wie Pandora, Rhapsody oder Spotify verdienen mittlerweile ganz gut beim Online-Vermieten von Medien. Bei diesen Anbietern erwirbt der Kunde keine digitalen Medien mehr im Einzelkauf, sondern entrichtet eine monatliche Gebühr, gegen die der Anbieter sich verpflichtet, den Kunden per Internet mit beliebig vielen Film- oder Musikerzeugnissen aus dicken Online-Katalogen mit Millionen von Titeln zu bedienen. Diese laufen dann entweder auf dem PC, über kleine Kästchen wie dem Roku, Apple TV, Chromecast oder Amazon Fire im Fernsehkasten, oder gleich direkt auf dem Smartphone -- solange der Kunde die Abo-Gebühr entrichtet. Kündigt der Kunde, bleibt ihm nichts als die Erinnerung. Es überrascht wenig, dass die altbackenen Grammophongesellschaften natürlich lieber an ihrem hundert Jahre alten Geschäftsmodell festhalten würden und der Mietmusik Steine in den Weg legen, und auch manche Künstler haben was rumzumosern ([6]) -- geschenkt, meine Weichen sind bereits auf Zukunft gestellt.

Abbildung 2: Eine Spotify-Playlist des Autors auf dem iPhone.

Einige Anbieter dieser Mietmusik schalten ein kostenloses Radioprogramm, bei dem der Kunde nur ungefähr die Richtung vorgeben, aber keine Titel direkt auswählen kann. Außerdem trüben lästige Werbespots den Musikgenuss. Die zahlungspflichtigen Varianten hingegen bieten freie Titelwahl aus einem je nach Anbieter mehr oder weniger vollständigen Katalog an, und erlauben die Erstellung sogenannter Playlists, auf denen der Abonnent seinen Favoriten lauschen darf.

Cloud besser absichern

Da die Playlisten aber im Rechenzentrum des Anbieters liegen und nicht auf meinem Rechner zuhause, stellte sich bei mir ein leichtes Gefühl des Unwohlseins ein. Was würde mit meinen mühsam zusammengestellten Playlists passieren, falls Spotify den Fußball einpackte und heimginge? Dank der ebenfalls angebotenen Web-API gelang es mir aber, die Daten lokal zu sichern und so die Verlustangst zu überwinden.

Auch ohne Registrierung erlaubt Spotify mittels der Web-API generelle Suchabfragen zu Musikerzeugnissen in seinem Katalog. Wer zum Beispiel herausfinden möchte, welche Titel die kalifornische Band "Weezer" auf welchen Alben veröffentlicht hat, kann dies mit einer einfachen HTTP-Anfrage wie in Listing 1 tun.

Listing 1: search

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use LWP::UserAgent;
    04 use JSON qw( from_json );
    05 
    06 my $ua = LWP::UserAgent->new();
    07 
    08 my $resp = $ua->get( 
    09     "https://api.spotify.com/v1/search?" .
    10     "q=weezer&type=track"
    11 );
    12 
    13 my $data = from_json( $resp->content() );
    14 
    15 for my $item ( 
    16       @{ $data->{ tracks }->{ items } } ) {
    17     print "$item->{ name } ",
    18           "($item->{ album }->{ name })\n";
    19 }

Den Parameter q (für Query) setzt das Skript auf den Namen der gesuchten Band, und type steht auf "track", um dem Server zu signalisieren, dass der Client an Titeln der Band interessiert ist. Der Server schickt das Ergebnis im JSON-Format zurück, das die Methode from_json() aus dem CPAN-Modul JSON in eine verschachtelte Hash-Datenstruktur in Perl umwandelt. Die for-Schleife ab Zeile 15 iteriert über die Array-Einträge unter dem Schlüssel tracks->items und gibt sowohl den gefundenen Musiktitel als auch das zugehörige Album aus, das sich ebenfalls in der Datenstruktur unter dem Titeleintrag findet. Abbildung 3 zeigt die auf 20 Einträge begrenzte Ausgabe. Das Skript filtert den Großteil der im JSON-Format zurückkommenden Information aus, wer möchte, kann zusätzliche Happen wie Beliebtheit oder Spieldauer extrahieren und über Links zusätzliche Details wie das Erscheinungsdatum eines Albums einholen.

Abbildung 3: Ohne Registrierung erlaubt Spotify allgemeine Suchabfragen, wie hier nach den veröffentlichten Titeln der Band "Weezer".

Täglich grüßt das Murmeltier

Interessantere Abfragen bohren aber in den persönlich erstellten Daten eines Spotify-Users und erfordern dessen Einwilligung. Um zum Beispiel alle Playlists eines Nutzers mit deren Titeln einzuholen muss das anfragende Skript auf Spotifys Developer-Site als Applikation registiert sein und sich mittels gültiger OAuth-Tokens ausweisen, bevor Spotify die geschützten Informationen herausrückt. Die Anweisungen für Applikationsentwickler auf [7] führen durch die notwendigen Schritte bei der Registrierung und der anschließenden Verwendung der OAuth-Tokens. Allerdings gewährt Spotify nur zahlenden Nutzern Zugriff auf die API.

Abbildung 4: Eine Applikation, hier "PerlSnapshot", die auf private User-Daten wie Playlists zugreifen möchte, muss sich zunächst auf der Spotify-Developerseite registrieren.

Nachdem ich in dieser Rubrik schon verschiedene OAuth-geregelte Web-APIs vorgestellt habe, wie zum Beispiel die von Tumblr ([3]), Google Drive ([4]) oder Dropbox ([5]), und den ersten Access-Token jedesmal mit einem Mojolicious-Skript eingeholt habe, war es diesmal an der Zeit, zu diesem Zweck ein CPAN-Modul zu schreiben. Mit OAuth::Cmdline können nun Skripts wie Listing 2 schnell mal einen Webserver starten und die Einwilligung des Users in einem andockenden Browser einholen. spotify-token-init nimmt in den Zeilen 9 und 10 die zwei Hex-Werte "Client ID" und "Client Secret" entgegen, die Spotifys Developer-Seite an registrierte Applikationen vergibt (Abbildungen 5 und 6).

Abbildung 5: Nach dem Anmelden der Web-App ...

Abbildung 6: ... erscheinen die Client-ID und das Client-Secret.

Weiter gibt der Skriptnutzer im Konstruktor von OAuth::Cmdline die von Spotify vorgegebenen URLs zum Einloggen und Tokenerneuerung als login_uri und token_uri an. Das Ausmaß des erlaubten Zugriffs gibt der Parameter scope mit "user-read-private" vor, lässt also das Lesen privater Daten zu. Käme die Schreibberechtigung hinzu, dürften Skripts auch neue Playlists anlegen und dort Titel einhängen. Startet der User das Skript in Listing 2, zeigt es mit

   Server available at http://localhost:8082

die Webadresse an, die ins URL-Feld des Browserfensters einzugeben ist. Daraufhin zeigt der Browser einen Link an, der mit "Login on Spotify" betitelt ist. Klickt der User darauf, springt der Browser auf die Spotify-Seite und fordert den User auf, sich dort mit seinem User-Namen und dem gültigen Passwort einzuloggen.

Listing 2: spotify-token-init

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use lib 'lib';
    04 
    05 use OAuth::Cmdline;
    06 use OAuth::Cmdline::Mojo;
    07 
    08 my $oauth = OAuth::Cmdline->new(
    09     client_id     => "XXX",
    10     client_secret => "YYY",
    11     login_uri     => 
    12       "https://accounts.spotify.com/authorize",
    13     token_uri     => 
    14       "https://accounts.spotify.com/api/token",
    15     site          => "spotify",
    16     scope         => "user-read-private",
    17 );
    18 
    19 my $app = OAuth::Cmdline::Mojo->new(
    20     oauth => $oauth,
    21 );
    22 
    23 $app->start( 'daemon', '-l', $oauth->local_uri );

Klickt der User auf "Okay", verzweigt der Spotify-Server zurück zu der in Abbildung 6 registrierten "Redirect URI". Diese setzt der Testserver in OAuth::Cmdline::Mojo auf http://localhost:8082/callback und genauso muss sie auch beim Ausfüllen des Antrags auf der Spotify-Developerseite eingetragen werden. Vorsicht: Nach dem Hinzufügen einer Redirect-URL (Abbildung 6) besteht Spotify ulkigerweise auf dem Pressen des "Save"-Buttons am unteren Rand der Seite, was man leicht vergisst, und sich anschließend über die mit "Bad Request" und "Invalid Redirect URI" betitelten Fehlerseiten des Servers wundert.

Zeigt sich der User im Webflow einsichtig, lenkt Spotify den Browser wieder zurück zum lokalen Testserver in Listing 2 auf "localhost:8082", und lässt diesem einen gültigen Access-Token sowie einen Refresh-Token zukommen. Mit letzterem kann OAuth::Cmdline später abgelaufene Access-Tokens erneuern. Das Skript in Listing 2 legt diese Zugangsdaten in der Datei .spotify.yml im Home-Verzeichnis ab.

Zugriff bis auf Widerruf

Nur der jeweilige Unix-User darf die Daten lesen, aber natürlich kann auch ein neugieriger System-Administrator (oder auch ein Einbrecher) mit root-Berechtigung darauf zugreifen, also Vorsicht. Über diesen Umweg hat also der User dem Server die Erlaubnis erteilt, bis auf expliziten Widerruf die Userdaten an die Applikation "Perlsnapshot" weiter zu geben, die sich bei jeder Anfrage im HTTP-Header über einen gültigen Access-Token ausweist.

Abbildung 7: Der User bestätigt, dass die Applikation "Perlsnapshot" bis auf Widerruf seine privaten Playlist-Daten lesen darf.

Einfach wiederverwerten

Weitere Skripts wie Listing 3 können nun den Konstruktor der Klasse OAuth::Cmdline mit dem site-Parameter "spotify" aufrufen und einen gültigen Access-Token aus der Cache-Datei ~/.spotify.yml auslesen. Zum Auslesen der Playlists eines Users setzt Zeile 16 mit der Methode authorization_headers() aus OAuth::Cmdline den vom Server geforderten Access-Token in der darauffolgenden GET-Anfrage an die Web-API. Ist der Access-Token mittlerweile verfallen, erneuert ihn OAuth::Cmdline automatisch hinter den Kulissen mittels des ebenfalls in der Cache-Datei gespeicherten Refresh-Tokens durch eine Anfrage an den Refresh-Service am OAuth-Handler des Servers.

Listing 3: user-playlists

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use OAuth::Cmdline;
    04 use LWP::UserAgent;
    05 use JSON qw( from_json );
    06 
    07 my( $user ) = @ARGV;
    08 die "usage: $0 user" if !defined $user;
    09 
    10 my $oauth = OAuth::Cmdline->new(
    11     site => "spotify"
    12 );
    13 
    14 my $ua = LWP::UserAgent->new();
    15 $ua->default_header( 
    16     $oauth->authorization_headers );
    17 
    18 my $resp = $ua->get( 
    19     "https://api.spotify.com/v1" .
    20     "/users/$user/playlists" );
    21 
    22 if( $resp->is_error ) {
    23     die "Error: ", $resp->message();
    24 }
    25 
    26 my $result = from_json( $resp->content() );
    27 
    28 for my $item ( @{ $result->{ items } } ) {
    29     print "$item->{ name }\n";
    30 }

Die im JSON-Format zurückkommende Antwort enthält die Namen aller Playlists des Users und deren Hex-IDs, mit denen Skripts wie Listing 4 später die dort referenzierten Musikstücke einholen können. Dank OAuth::Cmdline müssen sie sich nicht mehr mit dem OAuth-Tanz befassen, sondern können sich auf das Lesen der Daten konzentrieren.

Listing 4: songs-in-playlist

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use warnings;
    04 use OAuth::Cmdline;
    05 use LWP::UserAgent;
    06 use JSON qw( from_json );
    07 use YAML qw( Dump );
    08 
    09 my( $user ) = @ARGV;
    10 die "usage: $0 user" if !defined $user;
    11 
    12 my $oauth = OAuth::Cmdline->new(
    13     site => "spotify"
    14 );
    15 
    16 my $ua = LWP::UserAgent->new();
    17 $ua->default_header( 
    18     $oauth->authorization_headers );
    19 
    20 my $resp = $ua->get( 
    21     "https://api.spotify.com/v1" .
    22     "/users/$user/playlists/" .
    23     "022PMTci8phXA4CTJjwWEF" );
    24 
    25 if( $resp->is_error ) {
    26     die "Error: ", $resp->message();
    27 }
    28 
    29 my $result = from_json( $resp->content() );
    30 
    31 my @playlists = ();
    32 
    33 for my $item ( 
    34     @{ $result->{ tracks }->{ items } } ) {
    35     my $track = $item->{ track };
    36 
    37     push @playlists, {
    38       track  => $track->{ name },
    39       artist => 
    40         $track->{ artists }->[0]->{ name },
    41       album  => $track->{ album }->{ name }
    42     };
    43 }
    44 
    45 print Dump( \@playlists );

Abbildung 8: In der YAML-Datei stehen nun die Playlist-Daten als lokale Sicherungskopie.

Listing 4 legt nach dem Einholen der Playlist-Daten diese im sowohl von Menschen also auch Maschinen leicht lesbaren YAML-Format ab. So kann der besorgte Musikfreund sie regelmäßig privat sichern und ruhig schlafen, denn die hineingesteckte Arbeit bei der Auswahl der Musikstücke geht nun niemals verloren, unabhängig vom gerade gewählten Mietmusik-Anbieter.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2014/09/Perl

[2]

https://developer.spotify.com/technologies/libspotify/

[3]

"Zimmer mit Aussicht", Tumblr API im Perl Snapshot, Michael Schilli, http://www.linux-magazin.de/Ausgaben/2013/12/Perl-Snapshot

[4]

"Papierbuch am Ende", Google Drive API im Perl Snapshot, Michael Schilli, http://www.linux-magazin.de/Ausgaben/2012/12/Perl-Snapshot

[5]

"Ab in die Kiste", Dropbox API im Perl Snapshot, Michael Schilli, http://www.linux-magazin.de/Ausgaben/2011/07/Perl-Snapshot

[6]

"David Byrne: 'The internet will suck all creative content out of the world'", The Guardian, 2013, http://www.theguardian.com/music/2013/oct/11/david-byrne-internet-content-world

[7]

"Spotify Web API Authorization Guide", https://developer.spotify.com/web-api/authorization-guide/

[8]

"Netflix to expand to Germany, France and Switzerland", http://www.bbc.com/news/technology-27496055

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.