#!/usr/bin/perl ########################################### # rateplay - Rate MP3s and play them # Mike Schilli, 2004 (m@perlmeister.com) ########################################### use strict; use warnings; our $DB_NAME = "/data/rp.dat"; our $SONG_DIR = "/ms1/SONGS/pods"; our $FIND = "/usr/bin/find"; use Gtk; use POE; use Class::DBI; use POE::Component::Player::Musicus; use Algorithm::Numerical::Shuffle qw(shuffle); my (%GUI, %RATED, $TAG, $SONG, @PLAYLIST, @MP3S); my @PLAY_ENERG = (0, 0, 0, 0, 0); my @PLAY_SCHMO = (0, 0, 0, 0, 0); my $RATE_ENERG = 0; my $RATE_SCHMO = 0; my @RATE_ENERG_BUTTONS = (); my @RATE_SCHMO_BUTTONS = (); ########################################### package Rateplay::DBI; ########################################### use base q(Class::DBI); use Class::DBI::AbstractSearch; __PACKAGE__->set_db('Main', "dbi:SQLite:$main::DB_NAME", 'root', ''); if(! -e "$main::DB_NAME") { __PACKAGE__->set_sql(create => q{ CREATE TABLE rated_songs ( path VARCHAR(256) PRIMARY KEY NOT NULL, energize INT, schmoop INT ) }); __PACKAGE__->sql_create()->execute(); } ########################################### package Rateplay::Song; ########################################### use base q(Rateplay::DBI); __PACKAGE__->table('rated_songs'); __PACKAGE__->columns( All => qw(path energize schmoop)); ########################################### package main; ########################################### my $PLAYER = POE::Component::Player::Musicus->new(); POE::Session->create( package_states => [ "main" => [ qw(getpos getinfocurr mp3_stdout song scan_mp3s)]], inline_states => { _start => \&my_gtk_init, poll_player => sub { $PLAYER->getpos(); $poe_kernel->delay('poll_player', 1); }}); $poe_kernel->post("main", "poll_player"); $poe_kernel->post("main", "scan_mp3s"); $poe_kernel->run(); ########################################### sub getpos { ########################################### our $POS; next_in_playlist() if defined $POS and $POS > 0 and $_[ARG0] < 0; $POS = $_[ARG0]; } ########################################### sub getinfocurr { ########################################### $TAG = $_[ARG0]; $GUI{artist}->set($TAG->{artist}); $GUI{title}->set($TAG->{title}); } ########################################### sub song { ########################################### $SONG = $_[ARG0]; $PLAYER->stop(); $PLAYER->play($SONG); $PLAYER->getinfocurr(); update_rating($SONG); } ########################################### sub scan_mp3s { ########################################### %RATED = map { $_->path() => 1 } Rateplay::Song->retrieve_all(); my $comp = POE::Component::Child->new( events => { 'stdout' => 'mp3_stdout' }, ); $comp->run($FIND, $SONG_DIR); } ########################################### sub add_label { ########################################### my($parent, $text, @coords) = @_; my $lbl = Gtk::Label->new(); $lbl->set_alignment(0.5, 0.5); $lbl->set($text); if(ref $parent eq "Gtk::Table") { $parent->attach_defaults($lbl, @coords); } else { $parent->pack_start($lbl, 0, 0, 0); } return $lbl; } ########################################### sub my_gtk_init { ########################################### my @btns = ("Play Rated", "Play Next", "Play Previous", "Random Rate"); $poe_kernel->alias_set('main'); $GUI{mw} = Gtk::Window->new(); $GUI{mw}->set_default_size(150,200); $GUI{vb} = Gtk::VBox->new(0, 0); $GUI{$_}= Gtk::Button->new($_) for @btns; my $tbl = Gtk::Table->new(2, 6); $GUI{vb}->pack_start($tbl, 1, 1, 0); add_label($tbl, 'Energize', 0, 1, 0, 1); add_buttons($tbl, sub { $PLAY_ENERG[$_[1]] ^= 1 }, 0); add_label($tbl, 'Schmoop', 0, 1, 1, 2); add_buttons($tbl, sub { $PLAY_SCHMO[$_[1]] ^= 1}, 1); # Status line on top of buttons $GUI{status} = add_label($GUI{vb}, ""); # Pack buttons $GUI{vb}->pack_start($GUI{$_}, 0, 0, 0) for @btns; for(qw(artist title)) { $GUI{$_} = add_label($GUI{vb}, ""); } $GUI{rate_table} = Gtk::Table->new(2, 6); $GUI{vb}->pack_start($GUI{rate_table}, 0, 0, 0); add_label($GUI{rate_table}, 'Energize', 0, 1, 0, 1); attach_radio_buttons($GUI{rate_table}, sub { $RATE_ENERG = $_[1]+1; }, 0, \@RATE_ENERG_BUTTONS); add_label($GUI{rate_table}, 'Schmoop', 0, 1, 1, 2); attach_radio_buttons($GUI{rate_table}, sub { $RATE_SCHMO = $_[1]+1; }, 1, \@RATE_SCHMO_BUTTONS); my $rate = Gtk::Button->new('Rate'); $GUI{vb}->pack_start($rate, 0, 0, 0); $GUI{mw}->add($GUI{vb}); # Destroying window $GUI{mw}->signal_connect('destroy', sub {Gtk->exit(0)}); # Pressing Play Rated button $GUI{$btns[0]}->signal_connect('clicked', sub { @PLAYLIST = select_songs(); $GUI{status}->set("Playlist has " . scalar @PLAYLIST . " songs."); next_in_playlist(); }); # Pressing Play Next button $GUI{$btns[1]}->signal_connect('clicked', sub { next_in_playlist() }); # Pressing Play Previous button $GUI{$btns[2]}->signal_connect('clicked', sub { next_in_playlist(1) }); # Pressing Random Rate Button $GUI{$btns[3]}->signal_connect('clicked', sub { @PLAYLIST = shuffle @MP3S; $GUI{status}->set("Random Rating " . scalar @PLAYLIST . " songs."); next_in_playlist(); }); # Pressing Rate button $rate->signal_connect('clicked', sub { return unless defined $TAG; process_rating(); next_in_playlist(); } ); $GUI{mw}->show_all(); } ########################################### sub add_buttons { ########################################### my($table, $sub, $row) = @_; for (0..4) { my $b = Gtk::CheckButton->new($_+1); $b->signal_connect(clicked=> $sub, $_); $table->attach_defaults($b, 1+$_, 2+$_, 0+$row, 1+$row); } } ########################################### sub attach_radio_buttons { ########################################### my($table, $sub, $row, $buttons) = @_; my $group; for (0..4) { my $btn = Gtk::RadioButton->new($_+1, defined $group ? $group : ()); $group = $btn; $btn->signal_connect(clicked => $sub, $_); push @$buttons, $btn; $table->attach_defaults($btn, 1+$_, 2+$_, 0+$row, 1+$row); } } ########################################### sub process_rating { ########################################### my $rec = Rateplay::Song->find_or_create( { path => $SONG }); $rec->energize($RATE_ENERG); $rec->schmoop($RATE_SCHMO); $rec->update(); } ########################################### sub select_songs { ########################################### my @energ = grep { $PLAY_ENERG[$_-1] } (1..@PLAY_ENERG); my @schmo = grep { $PLAY_SCHMO[$_-1] } (1..@PLAY_SCHMO); return sort { rand > 0.5 } map { $_->path() } Rateplay::Song->search_where({ energize => [@energ, 0], schmoop => [@schmo, 0]}, ); } ########################################### sub next_in_playlist { ########################################### my($backward) = @_; return unless scalar @PLAYLIST; my $path; { if($backward) { $path = pop @PLAYLIST; unshift @PLAYLIST, $path; } else { $path = shift @PLAYLIST; push @PLAYLIST, $path; } redo if defined $SONG and $SONG eq $path and @PLAYLIST > 1; } $PLAYER->stop(); $poe_kernel->post('main', 'song', $path); } ########################################### sub update_rating { ########################################### my ($path) = @_; if(my ($song) = Rateplay::Song->search( path => $path)) { my $e = $song->energize(); my $s = $song->schmoop(); $RATE_SCHMO_BUTTONS[$s-1]->activate(); $RATE_ENERG_BUTTONS[$e-1]->activate(); } else { $RATE_SCHMO_BUTTONS[0]->activate(); $RATE_ENERG_BUTTONS[0]->activate(); } } ########################################### sub mp3_stdout { ########################################### my ( $self, $args ) = @_[ ARG0 .. $#_ ]; return if exists $RATED{$args->{out}}; push @MP3S, $args->{out}; $GUI{status}->set(scalar @MP3S . " songs ready for rating."); }