#!/usr/bin/perl
# jukebox.pl - GTK2 jukebox wrapper for ogg123
# Copyright (C) 2002-2004 Toby A Inkster
$ver = '2.09a';
# 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
# Stuff that you may like to change.
# path to ogg files!
$path = $ENV{'JUKEBOX'} || '/usr/share/jukebox';
# Don't change below here unless you know what you're doing
# Load modules. These should all be on CPAN.
# threading
use threads;
use threads::shared;
use Thread::Queue;
# gui
use Glib;
use Gtk2 '-init';
use Gtk2::SimpleList;
use constant TRUE => 1;
use constant FALSE => 0;
# audio
use Ogg::Vorbis::Header;
use Ogg::Vorbis::Decoder;
use Audio::Ao qw(:all);
# POSIX
use POSIX;
# 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 $nowSongDie : shared = FALSE;
my $nowSongDetails : shared = '';
my $nowSongPause : shared = FALSE;
# InstaQuit
my $instaQuit : shared = FALSE;
$guiThread = threads->create(\&drawGui);
# main loop
while ($nowSong ne '*') {
$nowSong = &qPop;
if ($instaQuit == FALSE) {
if (!defined $nowSong) {
$nowSong = &randomSong;
}
if ($nowSong ne '*') {
$nowSongDetails = &getDetails($nowSong);
&playSong($nowSong);
}
} else {
exit;
}
}
exit;
###################
## Player::Queue
###################
sub qPush {
my $song = shift @_;
push @queue, $song;
$tQueue = ppQueue(@queue);
}
sub qPop {
my $song = shift @queue;
$tQueue = ppQueue(@queue);
return $song;
}
sub ppQueue {
my $r = '';
while (my $s = shift) {
if ($s eq '*') {
$r .= "[exit]\n"
} else {
$r .= '.' . $s . "\n" ;
}
}
chomp $r;
return $r;
}
###################
## Player::Play
###################
sub playSong {
my $song = shift @_;
my $header = Ogg::Vorbis::Header->new("$path$song");
my $decoder = Ogg::Vorbis::Decoder->open("$path$song");
die "Bad ogg\n" unless $header && $decoder;
initialize_ao;
my $device = &open_live(default_driver_id, 16, $header->info('rate'), $header->info('channels'), is_big_endian, {});
while ( ($nowSongDie == FALSE) && (($i = $decoder->read(\$buffer)) > 0) ) {
while ($nowSongPause == TRUE) {
sleep 1;
}
&play($device, $buffer, $i);
}
$nowSongDie = FALSE
&close_ao($device);
&shutdown_ao;
}
sub killSong {
$nowSongPause = FALSE;
$nowSongDie = TRUE;
}
sub getDetails {
my $song = shift @_;
my $r = '';
my %details = ();
my $ogg = Ogg::Vorbis::Header->new("$path$song");
foreach my $com ($ogg->comment_tags) {
$com =~ tr/[A-Z]/[a-z]/;
@t = $ogg->comment($com);
$details{$com} .= $t[0];
}
while (my ($k, $v) = each %{$ogg->info}) {
$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";
}
my $min = POSIX::floor($details{'length'} / 60);
my $sec = $details{'length'} % 60;
$sec = "0$sec" unless ($sec > 9);
my $bitrate = POSIX::floor($details{'bitrate_nominal'} / 1024);
$r .= "File: .$song\n";
$r .= 'Length: ' . $min . ':' . $sec . ' (at ' . $bitrate . 'kbps)';
return $r;
}
###################
## Player::Songlist
###################
sub randomSong {
my $r = int(rand($#allSongs + 1));
return $allSongs[$r];
}
sub getSongList {
# this bit should really be rewritten to use native perl
my @f = split(/\n/, `find $path | grep ogg | sort`);
my $l = length($path);
my @r = ();
foreach my $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 $ver");
$window->signal_connect(destroy => \&eventExit);
$window->set_border_width(3);
$window->set_icon_from_file("$path/icon.png");
# list of files
$flist = Gtk2::SimpleList->new('File' => 'text');
$flist->get_selection->set_mode ('multiple');
$flist->get_selection->unselect_all;
$flist->signal_connect (row_activated => \&eventBtnAdd);
foreach my $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;
$statusfield->set_alignment(0,0);
Glib::Timeout->add (500, sub {
$statusfield->set_text($nowSongDetails);
$queuefield->set_text($tQueue);
return 1;
});
# queue field
$queuefield = Gtk2::Label->new('q');
$queuefield->set_alignment(0,0);
# 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, TRUE, 0);
$outerbox->pack_start($hr, FALSE, FALSE, 0);
$outerbox->pack_start($queuefield, FALSE, TRUE, 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{'pause'} = Gtk2::ToggleButton->new('_Pause');
$button{'pause'}->set_active(FALSE);
$vbox->pack_start($button{'pause'}, FALSE, FALSE, 0);
$button{'pause'}->signal_connect(clicked => \&eventBtnPause);
$button{'skip'} = Gtk2::Button->new('_Skip');
$vbox->pack_start($button{'skip'}, FALSE, FALSE, 0);
$button{'skip'}->signal_connect(clicked => \&eventBtnSkip);
$button{'quit'} = Gtk2::Button->new('E_xit');
$button{'quit'}->signal_connect(clicked => \&eventExit);
$vbox->pack_end($button{'quit'}, FALSE, FALSE, 0);
$button{'end'} = Gtk2::Button->new('_Delayed Exit');
$vbox->pack_end($button{'end'}, FALSE, FALSE, 0);
$button{'end'}->signal_connect(clicked => \&eventBtnEnd);
# show window and enter GTK+2 loop
$window->set_default_size( 640, 480 );
$window->show_all;
Gtk2->main;
$instaQuit = TRUE;
}
###################
## GUI::EventHandlers
###################
sub eventBtnAdd {
my @indices = $flist->get_selected_indices();
foreach $i (@indices) {
&qPush($allSongs[$i]);
}
}
sub eventBtnEnd {
&qPush('*');
}
sub eventBtnSkip {
$button{'pause'}->set_active(FALSE);
&killSong;
}
sub eventBtnPause {
$nowSongPause = $button{'pause'}->get_active;
}
sub eventExit {
$instaQuit = TRUE;
&killSong;
Gtk2->main_quit;
}