#!/usr/bin/perl # jukebox.pl - GTK2 jukebox wrapper for ogg123 # Copyright (C) 2002-2003 Toby A Inkster # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA use threads; use threads::shared; use Thread::Queue; use Glib; use Gtk2 '-init'; use Gtk2::SimpleList; use constant TRUE => 1; use constant FALSE => 0; # path $path = $ENV{'JUKEBOX'} || '/usr/share/jukebox'; # queue for songs (and textual representation of it!) my @queue : shared = (); my $tQueue : shared = ''; # list of all songs my @allSongs : shared = &getSongList; # current song my $nowSong : shared = ''; my $nowSongDetails : shared = ''; my $nowSongPid : shared = -1; my $pidLock : shared = 0; # InstaQuit my $instaQuit : shared = 0; $guiThread = threads->create(\&drawGui); # main loop while ($nowSong ne '*') { $nowSong = &qPop; if ($instaQuit == 0) { if (!defined $nowSong) { $nowSong = &randomSong; } if ($nowSong ne '*') { $nowSongDetails = &getDetails($nowSong); &playSong($nowSong); } } else { exit; } } exit; ################### ## Player::Queue ################### sub qPush { $song = shift @_; push @queue, $song; $tQueue = ppQueue(@queue); } sub qPop { $song = shift @queue; $tQueue = ppQueue(@queue); return $song; } sub ppQueue { $r = ''; while ($s = shift) { $r .= '.' . $s . "\n" unless ($s eq '*'); $r .= "and exit.\n" if ($s eq '*'); } chomp $r; return $r; } ################### ## Player::Play ################### sub playSong { $song = shift @_; $attempts = 0; $success = 0; while ( ($attempts < 5) && ($success == 0) ) { sleep 1; $r = system("ogg123 -d esd -q '" . $path . $song . "'"); # $r will be 0 if the song was played through successfully, # 15 if skipped, 256 if there was an ESD problem. if (($r == 0)||($r == 15)) { $success++; } else { $attempts++; } } if ($success == 0) { die "Couldn't play $song\n"; } } sub killSong { # not great, but better'an nowt. system("killall ogg123"); } sub getDetails { $song = shift @_; @l = split(/\n/, `vorbiscomment '$path$song'`); $r = ''; %details = (); foreach $l (@l) { ($k,$v) = split(/=/, $l); $k =~ tr/[A-Z]/[a-z]/; $details{$k} = $v; } if ($details{'artist'}) { $r .= 'Artist: ' . $details{'artist'} . "\n"; } if ($details{'title'}) { $r .= 'Title: ' . $details{'title'} . "\n"; } if ($details{'album'}) { $r .= 'Album: ' . $details{'album'} . "\n"; } if ($details{'date'}) { $r .= 'Date: ' . $details{'date'} . "\n"; } $r .= 'File: .' . $song; } ################### ## Player::Songlist ################### sub randomSong { $r = int(rand($#allSongs + 1)); return $allSongs[$r]; } sub getSongList { # this bit should really be rewritten to use native perl @f = split(/\n/, `find $path | grep ogg | sort`); $l = length($path); @r = (); foreach $f (@f) { $s = substr($f,$l); @r = (@r, $s); } return @r; } ################### ## GUI::Draw ################### sub drawGui { # create main window $window = Gtk2::Window->new; $window->set_title('Jukebox'); $window->signal_connect(destroy => \&eventExit); $window->set_border_width(3); # list of files $flist = Gtk2::SimpleList->new('File' => 'text'); foreach $s (@allSongs) { push @{$flist->{data}}, [ $s ]; } $sw1 = Gtk2::ScrolledWindow->new(undef, undef); $sw1->set_shadow_type ('etched-in'); $sw1->set_policy ('automatic', 'automatic'); $sw1->add($flist); # status field $statusfield = Gtk2::Label->new; Glib::Idle->add (sub { $statusfield->set_text($nowSongDetails); $queuefield->set_text($tQueue); return 1; }); # queue field $queuefield = Gtk2::Label->new('q'); # boxes and frames for arranging things $vbox = Gtk2::VBox->new; $vbox->set_border_width(3); $frame = Gtk2::Frame->new('Controls'); $frame->set_border_width(3); $frame->add($vbox); $hbox = Gtk2::HBox->new; $hbox->set_border_width(3); $hbox->pack_start($sw1, TRUE, TRUE, 0); $hbox->pack_start($frame, FALSE, FALSE, 0); $hr = Gtk2::HSeparator->new; $outerbox = Gtk2::VBox->new; $outerbox->pack_start($hbox, TRUE, TRUE, 0); $outerbox->pack_start($statusfield, FALSE, FALSE, 0); $outerbox->pack_start($hr, FALSE, FALSE, 0); $outerbox->pack_start($queuefield, FALSE, FALSE, 0); $window->add($outerbox); # buttons for controls $button{'add'} = Gtk2::Button->new('En_queue'); $vbox->pack_start($button{'add'}, FALSE, FALSE, 0); $button{'add'}->signal_connect(clicked => \&eventBtnAdd); $button{'skip'} = Gtk2::Button->new('_Skip'); $vbox->pack_start($button{'skip'}, FALSE, FALSE, 0); $button{'skip'}->signal_connect(clicked => \&eventBtnSkip); $button{'end'} = Gtk2::Button->new('_Delayed Exit'); $vbox->pack_start($button{'end'}, FALSE, FALSE, 0); $button{'end'}->signal_connect(clicked => \&eventBtnEnd); $button{'quit'} = Gtk2::Button->new('E_xit'); $button{'quit'}->signal_connect(clicked => \&eventExit); $vbox->pack_end($button{'quit'}, FALSE, FALSE, 0); # show window and enter GTK+2 loop $window->set_default_size( 640, 480 ); $window->show_all; Gtk2->main; $instaQuit = 1; } ################### ## GUI::EventHandlers ################### sub eventBtnAdd { @indices = $flist->get_selected_indices(); foreach $i (@indices) { &qPush($allSongs[$i]); } } sub eventBtnEnd { &qPush('*'); } sub eventBtnSkip { &killSong; } sub eventExit { $instaQuit = 1; &killSong; Gtk2->main_quit; }