Je nach Stimmungslage haben Musikliebhaber mal Lust auf Rock, mal auf Schmusepop. Ein MP3-Player mit grafischer GTK-Oberfläche wählt aus der privaten Sammlung passende Lieder aus, erstellt eine Playliste und spielt sie ab. Das Perl Object Environment sorgt dafür, dass alles flüssig abläuft.
In jeder größeren MP3-Sammlung aus gerippten CDs finden sich immer wieder ungeahnte Schätze: Eine Computer-gesteuerte Zufallsauswahl fördert aus Tausenden von MP3-Dateien erstaunliche Kostbarkeiten zutage und erlaubt überraschende Zusammenstellungen aufgrund einfacher Kriterien.
Das hier vorgestellte Perl-Skript »rateplay« zeigt dem Benutzer alle Musikstücke in zufälliger Reihenfolge. Er bewertet dann jedes einzelne nach zwei Kriterien: dem so genannten Energize- und dem Schmoop-Faktor. Energize bestimmt die Dynamik des Stücks, Schmoop die Schmusigkeit. Auf einer Skala von eins bis fünf hätte etwa der Song “Thunderstruck” von AC/DC einen Energize-Faktor von fünf und einen Schmoop-Faktor von eins. “Don\’t Know Why” von Norah Jones hätte einen Energize-Faktor von eins und einen Schmoop-Faktor von fünf.
Jedes Mal, wenn der Benutzer seine Stimme abgibt, speichert eine Datenbank den Dateisystempfad zu dem Song und die beiden Faktoren. Haben sich so einige Bewertungen angesammelt, erstellt das Skript auf Anfragen wie “Spiele mir schnelle Songs, aber keine, die meine Freundin verscheuchen” Playlisten und spielt sie ab.
Grafisches Interface
Abbildung 1 zeigt das Skript in Aktion. Zum Abspielen bereits bewerteter Songs wählt der Musikfreund die gewünschten Energize- und Schmoop-Faktoren in den oberen zwei Knopfreihen aus. Ein Mausklick auf »Play Rated« erstellt eine Playliste aus jenen Songs der Datenbank, zu denen die Werte passen, und spielt sie der Reihe nach ab. Über »Play Next« und »Play Previous« springt das Programm zum nächsten und vorigen Stück.
Um neue Lieder zu bewerten, klickt der Benutzer auf den Button »Random Rate«. Rateplay erstellt daraufhin eine Playliste aus bisher unbewerteten Songs und spielt sie ab. Währenddessen lassen sich für jedes Stück die einzelnen Level einstellen, und zwar über die Buttonleiste am unteren Rand. Nur jeweils ein Energize- und Schmoop-Level pro Stück ist erlaubt. Ein Klick auf »Rate« speichert die Werte in der Datenbank und Rateplay springt zur nächsten Datei.

Abbildung 1: Hier spielt Rateplay nur Songs mit den Energize-Werten drei bis fünf und Schmoop eins bis drei ab. Im Moment dröhnt ein Heavy-Metal-Song aus den Lautsprechern, mit einem Energize-Level von fünf und einem Schmoop-Wert von eins.
Vom Duo zum Trio
Rateplay nutzt gleich mehrere Perl-Module: Zu dem aus[2] bekannten Erfolgsduo POE und GTK für ruckfreie GUIs gesellt sich diesmal noch der Kommandozeilen-MP3-Spieler Musicus[3] von Robert Muth dazu, ein C++-Programm, das auf den dynamischen Libraries des bekannten Players Xmms aufsetzt. Das Modul »POE::Component::Player::Musicus« (im Folgenden durch »PoCo::Player::Musicus« abgekürzt) von Curtis Hawthorne bindet den MP3-Spieler in den POE-Reigen ein – so steuert das GUI den Player ruckelfrei.
Zum Speichern der Bewertungen nutzt Rateplay die in[4] vorgestellte objektorientierte »Class::DBI«-Abstraktion, die diesmal eine SQLite-Datenbank nutzt (siehe[5]). SQLite legt in einer einzigen Datei eine professionelle Datenbank mit SQL-Abfragemöglichkeit ab. Natürlich gibt es dafür auch ein Perl-Modul aus der DBI-Reihe auf dem CPAN. SQLite ist im Grunde eine ganz normale SQL-Datenbank. Um festzustellen wie viele bewertete Songs in der definierten Tabelle »rated_songs« stehen, dockt der Anwender einfach mit dem Kommandozeilen-Tool »sqlite« an die von Rateplay erzeugte Datenbankdatei »rp.dat« an und setzt einen SQL-Befehl ab:
$ sqlite rp.dat SQLite version 2.8.12 Enter ".help" for instructions sqlite> select count(*) from rated_songs; 887
In diesem Beispiel sind also 887 Songs schon bewertet. Genug Stücke, um erstaunliche Playlisten zu erstellen!
Rateplay im Detail
Das Programm Rateplay ist sehr umfangreich. Listing 1 zeigt den Source-Code, den es auf[1] zum Download gibt. Die Konfigurationszeilen acht bis zehn legen den Pfad zur Datenbankdatei fest (in der Variablen »$DB_NAME«) und das Verzeichnis (in »$SONG_DIR«), in dem das nachstehend definierte »find«-Programm rekursiv nach Dateien mit der Endung ».mp3« suchen soll.
Die globalen Arrays »@PLAY_ENERG« und »@PLAY_SCHMO« speichern die Werte der Checkbuttons für die Songauswahl am oberen Rand des GUI. Die Skalare »$RATE_ENERG« und »$RATE_SCHMO« hingegen enthalten die Einstellungen der Radiobuttons am unteren Rand des Fensters und nehmen Werte von eins bis fünf für Energize- und Schmoop-Faktor entgegen. Die beiden Arrays »@RATE_ENERG_BUTTONS« sowie »@RATE_SCHMO_BUTTONS« führen die Objekte der Radiobuttons als Elemente, damit das GUI die für einen Song in der Datenbank gefundenen Bewertungen gleich voreinstellt.
Die Klasse »Rateplay::DBI« ab Zeile 27 erbt von »Class::DBI« und definiert die objektorientierte Abstraktion auf die SQLite-Datenbank. Falls diese noch nicht existiert (was bei SQLite daran zu sehen ist, dass es die entsprechende Datei nicht gibt), legt der SQL-Code ab Zeile 37 die Datenbankdatei samt benötigter »rated_songs«-Tabelle mit den Spalten »path« (Pfad zur MP3-Datei), »energize« (für den Energize-Level) und »schmoop« (Schmoop-Level) an. Zeile 43 führt die Arbeit aus.
Das in Zeile 30 hereingezogene »Class:: DBI::AbstractSearch« erlaubt später über die Funktionalität von »Class::DBI« hinausgehende And-/Or-Abfragen mit »Rateplay::Song->search_where()«. Die OO-Abstraktion auf die Tabelle legt die Klasse »Rateplay::Song« ab Zeile 47 an. Damit ist der Rest des Skripts frei von SQL-Statements.
MP3-Player mit POE ansteuern
Das Hauptprogramm steht im Paket »main« ab Zeile 56. Es definiert die POE-Session, die das GUI und den Player betreibt. Das mit dem Parameter »package _states« referenzierte Array legt eine Liste von später im Skript definierten Funktionen fest, die von gleichnamigen POE-Events aufgerufen werden. Ruft das Hauptprogramm beispielsweise die »getpos()«-Methode des Players auf, antwortet dieser mit der Position im aktuell gespielten Song, indem er der POE-Hauptsession einen »getpos«-Event schickt. Dank der obigen »package_states«-Deklaration weiß die Session nun, dass sie in diesem Fall die ab Zeile 79 definierte Funktion »getpos()« anspringen muss. Den Ablauf der kompletten Session zeigt Abbildung 2.

Abbildung 2: Mit POE springt das Programm zwischen verschiedenen Zuständen hin und her. Die »PoCo«-Prozesse laufen parallel. Doppelpfeile signalisieren Zustände, die der POE-Kernel kurz anspringt und dann parallel weiterlaufen lässt.
Ähnlich verhält es sich mit »getinfocurr«: Gemäß der Dokumentation von »PoCo::Player::Musicus« löst der Player diesen Event aus, wenn das Skript die »getinfocurr()«-Methode des Player-Objekts aufruft. Die Callback-Funktion erhält als Parameter den Interpreten, Titel und einige weitere MP3-Informationen des gerade abgespielten Stücks. Die Zeilen 92 und 93 frischen die Interpret- und Titelanzeige im GUI auf.
Den »song«-Event löst das Skript selbst aus. Immer wenn der Player ein neues Stück spielen soll, schickt Rateplay – wie in Zeile 307 – einen »song«-Event an die in Zeile 143 getaufte Session »main«, also an die POE-Hauptsession. Diese ruft daraufhin die ab Zeile 97 definierte gleichnamige »song()«-Funktion auf, die den Pfad der MP3-Datei als erstes Argument aufschnappt, den Player stoppt und ihn dann die neue MP3-Datei abspielen lässt.
Der »scan_mp3s«-Event wird in Zeile 75 kurz nach dem Systemstart ausgelöst und lässt das Skript in die ab der Zeile 107 definierte Funktion »scan_mp3s« springen. Diese holt mittels »retrieve_all()« alle bewerteten Songs aus der Datenbank und legt sie als Schlüssel im globalen Hash »%RATED« ab. Dann startet sie einen Kindprozess in einer »PoCo::Child«-Session, der das externe »find«-Kommando aufruft und MP3-Dateien auf der Festplatte aufstöbert. Wenn Find eine Datei gefunden hat, schreibt es den Pfad immer nach Stdout.
Die Session springt dann aufgrund der Event-Definition in Zeile 113 (und der »package_states«-Definition in Zeile 64) in die »mp3_stdout()«-Funktion, die ab Zeile 328 definiert ist. Sie hängt den Dateinamen an das globale »@MP3S«-Array an, falls der Anwender das Stück noch nicht bewertet hat. Zeile 336 frischt die Statusanzeige über die ablaufende Suche auf. Wie in[2] nachzulesen ist, arbeitet POE mit ungewöhnlichen Parameter-Konstanten. »ARG0« ist zum Beispiel der Index, an dem der Parameter in »@_« steht, der dem Event übergeben wurde. Der von »PoCo::Child« gesetzte erste Parameter ist eine Referenz auf einen Hash, der unter dem Schlüssel »out« die gerade aufgeschnappte Stdout-Zeile des Kindprozesses parat hält (Zeilen 332 und 334). Leider feuert »PoCo::Player::Musicus« keinen Event ab, wenn ein gespieltes Stück beendet ist.
Also muss Rateplay den Player in kurzen Abständen anstoßen und die aktuelle Liedposition mit »getpos()« abfragen. Kommt ein negativer Wert zurück, dreht Musicus gerade Däumchen und lechzt nach neuen Songs. Die Funktion implementiert ab Zeile 69 dieses periodische Pollen. Sie ist dem »poll_player«-Event zugewiesen und schickt eine »getpos()«-Abfrage an Musicus. Als Ergebnis dieser Abfrage geht ein »getpos«-Event an die »main«-Session. Danach sorgt Zeile 71 dafür, dass der Kernel nach einer Sekunde wieder einen »poll_player«-Event auslöst. Die ab Zeile 79 definierte Funktion »getpos()« frischt die Our-Variable »$POS« auf, die einen Integer-Wert speichert; er entspricht der aktuellen Position im Song.
War der vorherige Wert größer null und kommt nun ein negativer Wert daher, ist dies ein Indiz dafür, dass der Player gerade einen Song beendet hat und Zeile 83 die »next_in_playlist()«-Funktion aufrufen soll. Diese extrahiert das erste Element des globalen Array »@PLAYLIST«, hängt es hinten wieder an und reicht es in Zeile 307 dem Player zum Abspielen weiter. Falls der erste Parameter von »next_in_playlist()« gesetzt ist, geht die nächste Fahrt rückwärts und der vorige Song wird gespielt.
Ist das Resultat aufgrund kompensierenden Vor- und Rückwärtsfahrens wieder derselbe Song, fährt Zeile 302 nochmals eins weiter. Für jeden neu gespielten Song ruft »song()« die ab Zeile 311 definierte Funktion »update_rating()« auf. Sie sieht mittels der »search()«-Methode in der Datenbank nach, ob der Song schon bewertet ist, und besetzt die Radiobuttons mit den entsprechenden Werten für Energize und Schmoop, falls sie ein Ergebnis findet. Falls nicht, stellt sie die kleinstmöglichen Werte ein. So sieht der Benutzer für jeden Song auf einer Playlist das Rating und korrigiert es gegebenenfalls. Dazu genügt es, die gewünschten Werte einzustellen und auf »Rate« zu klicken.
Geschmackvolle Auswahl
Die ab Zeile 272 definierte Funktion »select_songs()« kümmert sich um die Auswahl von Songs für eine Playlist gemäß den im GUI eingestellten Checkbutton-Werten für Energize und Schmoop. Die Arrays »@PLAY_ENERG« und »@PLAY _SCHMO« führen jeweils fünf Elemente. Ist der zugeordnete Checkbutton am oberen Ende des GUI aktiviert, enthält das entsprechende Element den Wert 1, andernfalls 0. Steht in »@PLAY_ENERG« zum Beispiel »(0,0,1,1,0)«, sind der dritte und der vierte Checkbutton in der Energize-Leiste aktiviert, alle anderen dagegen nicht.
Zeile 274 extrahiert daraus die Liste der gewünschten Energize-Werte und legt sie in »@energ« ab. Der »search_where()«-Aufruf in Zeile 281 fügt noch einen ungültigen Null-Wert hinzu, um zu verhindern, dass es zu einem Fehler kommt, falls »@energ« leer ist. Denn »search_where()« reagiert auf leere Arrays allergisch. Beide Kriterien für Energize und Schmoop verknüpft »search_where()« mit einem logischen UND, also äquivalent zu »WHERE a AND b« in SQL. Die Elementwerte der übergebenen Arrays hingegen werden als ODER ausgewertet. So sortiert folgender Code alle Songs in den gegebenen Geschmacksgrenzen:
Rateplay::Song->search_where({
energize => [2, 3, 0],
schmoop => [1, 0]});
Die entsprechende SQL-Abfrage sieht folgendermaßen aus:
SELECT * from rated_songs
WHERE energize = 2 OR
energize = 3 OR
energize = 0
AND schmoop = 1 OR
schmoop = 0
Das vor den »map()«-Befehl gesetzte »sort { rand < 0.5 }« in Zeile 279 wirbelt die Ergebnisliste durcheinander, bevor sie an den Player geht – schließlich will der Benutzer Abwechslung und nicht immer dieselbe Reihenfolge.
Die Funktion »process_rating()« ab Zeile 261 sucht mit »find_or_create()« einen Eintrag unter dem angegebenen MP3-Pfad in der Datenbank. Sie gibt als Ergebnis das gefundene Objekt zurück. Falls sie keins findet, erzeugt »find_ or_create()« einfach einen neuen Eintrag. Die »energize()«- und »schmoop()«-Methodenaufrufe setzen die entsprechenden Felder des Datensatzes und die »update()«-Methode schreibt alles in die Datenbank zurück.
Äußerlichkeiten
Die ab Zeile 138 definierte Funktion »my_gtk_init()« baut die GTK-Oberfläche auf. Alle GUI-Objekte landen unter beschreibenden Namen im globalen Hash »%GUI«, damit sie schön gruppiert und einfach global abrufbar sind. Denn manche Funktion muss in bestimmten Situationen schnell einige der grafischen Elemente auffrischen. Wie in[2] kommen auch hier zwei verschiedene GUI-Container zum Einsatz, nämlich »Gtk:: VBox« und »Gtk::Table«, beide mit verschiedenen Pack-Verfahren (»pack_ start()« und »attach_defaults()«).
Die Funktion »add_buttons()« ab Zeile 230 wird für beide Checkbox-Buttonreihen an der oberen Hälfte des GUI aufgerufen. Jedes Mal schiebt das Hauptprogramm eine andere Funktion hinein, die aufgerufen wird, falls der Benutzer den Button anklickt. Ab Zeile 193 definiert Rateplay, welche Aktionen auf Maus-Events folgen. Als Reaktion auf ein »destroy«-Signal (das eintrifft, falls der Benutzer das Applikationsfenster schließt), reißt »Gtk->exit(0)« das GUI ab.
Der Button »Play Rated« (»$btns[0]«) löst »select_songs()« aus und stößt mit »next_in_playlist()« die Playliste an. »Play Next« und »Play Previous« fahren vor und zurück und »Random Rate« (»$btns[3]«) wirbelt die im globalen Array »@MP3S« gespeicherten unbewerteten MP3s mittels der »shuffle«-Funktion aus »Algorithm::Numerical::Shuffle« durcheinander, um sie dann der Reihe nach anzubieten.
Die Callback-Funktion des »Rate«-Buttons schließlich greift auf die in der Funktion »getinfocurr()« gesetzte globale Variable »$TAG« zu, die das MP3-Tag des gerade abgespielten Songs enthält, und ruft »process_rating()« auf, um einen Datenbankeintrag für den Song entsprechend den eingestellten Radiobuttons vorzunehmen.
Installation
Damit Rateplay mit dem MP3-Player zusammenspielt, muss Xmms auf dem Rechner installiert sein. Danach lädt der Anwender die Musicus-Quellen von[3] herunter, entpackt sie und tippt »make« ein. Das Binary »musicus« kopiert er dann nach »/usr/bin/«.
Die Perl-Module »POE«, »PoCo::Player:: Musicus« und »Gtk« finden sich im CPAN. Der Artikel unter[2] gibt einige Hinweise zur Installation. Rateplay benötigt auch die Module »DBI«, »DBD:: SQLite«, »Class::DBI« »Class::DBI::AbstractSearch« und »Algorithm::Numerical::Shuffle«. Die CPAN-Shell löst alle eventuell auftretenden Abhängigkeiten automatisch auf.
Die Programmierer von Musicus und »POE::Component::Player::Musicus« arbeiten eifrig an Verbesserungen ihrer Programme. Falls die aktuellen Versionen nicht zusammenarbeiten wollen, stehen unter[6] zwei Tarbälle bereit, die funktionieren. (mwe)
|
Listing 1: |
|---|
001 #!/usr/bin/perl
002 ###########################################
003 # rateplay - Rate MP3s and play them
004 # Mike Schilli, 2004 (m@perlmeister.com)
005 ###########################################
006 use strict; use warnings;
007
008 our $DB_NAME = "/data/rp.dat";
009 our $SONG_DIR = "/ms1/SONGS/pods";
010 our $FIND = "/usr/bin/find";
011
012 use Gtk; use POE;
013 use Class::DBI;
014 use POE::Component::Player::Musicus;
015 use Algorithm::Numerical::Shuffle
016 qw(shuffle);
017 my (%GUI, %RATED, $TAG, $SONG, @PLAYLIST,
018 @MP3S);
019 my @PLAY_ENERG = (0, 0, 0, 0, 0);
020 my @PLAY_SCHMO = (0, 0, 0, 0, 0);
021 my $RATE_ENERG = 0;
022 my $RATE_SCHMO = 0;
023 my @RATE_ENERG_BUTTONS = ();
024 my @RATE_SCHMO_BUTTONS = ();
025
026 ###########################################
027 package Rateplay::DBI;
028 ###########################################
029 use base q(Class::DBI);
030 use Class::DBI::AbstractSearch;
031
032 __PACKAGE__->set_db('Main',
033 "dbi:SQLite:$main::DB_NAME", 'root', '');
034
035 if(! -e "$main::DB_NAME") {
036 __PACKAGE__->set_sql(create => q{
037 CREATE TABLE rated_songs (
038 path VARCHAR(256)
039 PRIMARY KEY NOT NULL,
040 energize INT, schmoop INT
041 )
042 });
043 __PACKAGE__->sql_create()->execute();
044 }
045
046 ###########################################
047 package Rateplay::Song;
048 ###########################################
049 use base q(Rateplay::DBI);
050
051 __PACKAGE__->table('rated_songs');
052 __PACKAGE__->columns(
053 All => qw(path energize schmoop));
054
055 ###########################################
056 package main;
057 ###########################################
058
059 my $PLAYER =
060 POE::Component::Player::Musicus->new();
061
062 POE::Session->create(
063 package_states => [ "main" => [
064 qw(getpos getinfocurr mp3_stdout
065 song scan_mp3s)]],
066
067 inline_states => {
068 _start => &my_gtk_init,
069 poll_player => sub {
070 $PLAYER->getpos();
071 $poe_kernel->delay('poll_player', 1);
072 }});
073
074 $poe_kernel->post("main", "poll_player");
075 $poe_kernel->post("main", "scan_mp3s");
076 $poe_kernel->run();
077
078 ###########################################
079 sub getpos {
080 ###########################################
081 our $POS;
082
083 next_in_playlist() if defined $POS and
084 $POS > 0 and $_[ARG0] < 0;
085 $POS = $_[ARG0];
086 }
087
088 ###########################################
089 sub getinfocurr {
090 ###########################################
091 $TAG = $_[ARG0];
092 $GUI{artist}->set($TAG->{artist});
093 $GUI{title}->set($TAG->{title});
094 }
095
096 ###########################################
097 sub song {
098 ###########################################
099 $SONG = $_[ARG0];
100 $PLAYER->stop();
101 $PLAYER->play($SONG);
102 $PLAYER->getinfocurr();
103 update_rating($SONG);
104 }
105
106 ###########################################
107 sub scan_mp3s {
108 ###########################################
109 %RATED = map { $_->path() => 1 }
110 Rateplay::Song->retrieve_all();
111
112 my $comp = POE::Component::Child->new(
113 events => { 'stdout' => 'mp3_stdout' },
114 );
115
116 $comp->run($FIND, $SONG_DIR);
117 }
[...]
120 sub add_label {
[...]
137 ###########################################
138 sub my_gtk_init {
139 ###########################################
140 my @btns = ("Play Rated", "Play Next",
141 "Play Previous", "Random Rate");
142
143 $poe_kernel->alias_set('main');
144
145 $GUI{mw} = Gtk::Window->new();
146 $GUI{mw}->set_default_size(150,200);
147
148 $GUI{vb} = Gtk::VBox->new(0, 0);
149
150 $GUI{$_}= Gtk::Button->new($_) for @btns;
151
152 my $tbl = Gtk::Table->new(2, 6);
153 $GUI{vb}->pack_start($tbl, 1, 1, 0);
154
155 add_label($tbl, 'Energize', 0, 1, 0, 1);
156 add_buttons($tbl,
157 sub { $PLAY_ENERG[$_[1]] ^= 1 }, 0);
158 add_label($tbl, 'Schmoop', 0, 1, 1, 2);
159 add_buttons($tbl,
160 sub { $PLAY_SCHMO[$_[1]] ^= 1}, 1);
161
162 # Status line on top of buttons
163 $GUI{status} = add_label($GUI{vb}, "");
164
165 # Pack buttons
166 $GUI{vb}->pack_start($GUI{$_}, 0, 0, 0)
167 for @btns;
168
169 for(qw(artist title)) {E
170 $GUI{$_} = add_label($GUI{vb}, "");
171 }
172
173 $GUI{rate_table} = Gtk::Table->new(2, 6);
174 $GUI{vb}->pack_start($GUI{rate_table},
175 0, 0, 0);
176
177 add_label($GUI{rate_table},
178 'Energize', 0, 1, 0, 1);
179 attach_radio_buttons($GUI{rate_table},
180 sub { $RATE_ENERG = $_[1]+1;
181 }, 0, @RATE_ENERG_BUTTONS);
182 add_label($GUI{rate_table},
183 'Schmoop', 0, 1, 1, 2);
184 attach_radio_buttons($GUI{rate_table},
185 sub { $RATE_SCHMO = $_[1]+1;
186 }, 1, @RATE_SCHMO_BUTTONS);
187
188 my $rate = Gtk::Button->new('Rate');
189 $GUI{vb}->pack_start($rate, 0, 0, 0);
190 $GUI{mw}->add($GUI{vb});
191
192 # Destroying window
193 $GUI{mw}->signal_connect('destroy',
194 sub {Gtk->exit(0)});
195
196 # Pressing Play Rated button
197 $GUI{$btns[0]}->signal_connect('clicked',
198 sub { @PLAYLIST = select_songs();
199 $GUI{status}->set("Playlist has " .
200 scalar @PLAYLIST . " songs.");
201 next_in_playlist();
202 });
203
204 # Pressing Play Next button
205 $GUI{$btns[1]}->signal_connect('clicked',
206 sub { next_in_playlist() });
207
208 # Pressing Play Previous button
209 $GUI{$btns[2]}->signal_connect('clicked',
210 sub { next_in_playlist(1) });
211
212 # Pressing Random Rate Button
213 $GUI{$btns[3]}->signal_connect('clicked',
214 sub { @PLAYLIST = shuffle @MP3S;
215 $GUI{status}->set("Random Rating " .
216 scalar @PLAYLIST . " songs.");
217 next_in_playlist();
218 });
219 # Pressing Rate button
220 $rate->signal_connect('clicked',
221 sub { return unless defined $TAG;
222 process_rating();
223 next_in_playlist();
224 } );
225
226 $GUI{mw}->show_all();
227 }
228
229 ###########################################
230 sub add_buttons {
231 ###########################################
232 my($table, $sub, $row) = @_;
233
234 for (0..4) {
235 my $b = Gtk::CheckButton->new($_+1);
236 $b->signal_connect(clicked=> $sub, $_);
237 $table->attach_defaults($b, 1+$_, 2+$_,
238 0+$row, 1+$row);
239 }
240 }
[...]
243 sub attach_radio_buttons {
[...]
260 ###########################################
261 sub process_rating {
262 ###########################################
263 my $rec = Rateplay::Song->find_or_create(
264 { path => $SONG });
265
266 $rec->energize($RATE_ENERG);
267 $rec->schmoop($RATE_SCHMO);
268 $rec->update();
269 }
270
271 ###########################################
272 sub select_songs {
273 ###########################################
274 my @energ = grep { $PLAY_ENERG[$_-1] }
275 (1..@PLAY_ENERG);
276 my @schmo = grep { $PLAY_SCHMO[$_-1] }
277 (1..@PLAY_SCHMO);
278
279 return sort { rand > 0.5 }
280 map { $_->path() }
281 Rateplay::Song->search_where({
282 energize => [@energ, 0],
283 schmoop => [@schmo, 0]},
284 );
285 }
286
287 ###########################################
288 sub next_in_playlist {
289 ###########################################
290 my($backward) = @_;
291
292 return unless scalar @PLAYLIST;
293 my $path;
294
295 { if($backward) {
296 $path = pop @PLAYLIST;
297 unshift @PLAYLIST, $path;
298 } else {
299 $path = shift @PLAYLIST;
300 push @PLAYLIST, $path;
301 }
302 redo if defined $SONG and
303 $SONG eq $path and @PLAYLIST > 1;
304 }
305
306 $PLAYER->stop();
307 $poe_kernel->post('main', 'song', $path);
308 }
309
310 ###########################################
311 sub update_rating {
312 ###########################################
313 my ($path) = @_;
314
315 if(my ($song) = Rateplay::Song->search(
316 path => $path)) {
317 my $e = $song->energize();
318 my $s = $song->schmoop();
319 $RATE_SCHMO_BUTTONS[$s-1]->activate();
320 $RATE_ENERG_BUTTONS[$e-1]->activate();
321 } else {
322 $RATE_SCHMO_BUTTONS[0]->activate();
323 $RATE_ENERG_BUTTONS[0]->activate();
324 }
325 }
326
327 ###########################################
328 sub mp3_stdout {
329 ###########################################
330 my ( $self, $args ) = @_[ ARG0 .. $#_ ];
331
332 return if exists $RATED{$args->{out}};
333
334 push @MP3S, $args->{out};
335
336 $GUI{status}->set(scalar @MP3S .
337 " songs ready for rating.");
338 }
|
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://ftp.linux-magazin.de/pub/listings/magazin/2004/07/Perl] [2] Michael Schilli, “Kurs-Makler”: Linux- Magazin 04/04, S. 116 [3] Musicus-Homepage: [http://muth.org/Robert/Musicus] [4] Michael Schilli, “Musik aus dem Keller”: Linux-Magazin, 03/03, S. 102 [5] SQLite: [http://sqlite.org] [6] Musicus und sein POE-Pendant: [http://perlmeister.com/musicus] |
|
Der Autor |
|---|
|
Michael Schilli arbeitet als Software-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er hat “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen und seine Homepage heißt [http://perlmeister.com]. |
Copyright © 2002 Linux New Media AG






