#!/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;
}