summaryrefslogtreecommitdiff
path: root/scripts/gdgentools.pm
diff options
context:
space:
mode:
authorlvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b>2004-05-28 15:30:48 +0000
committerlvw <lvw@e10066b5-e1e2-0310-b819-94efdf66514b>2004-05-28 15:30:48 +0000
commit2370a13b7f6512147550e5eec8a773e69d49119b (patch)
treedcf92a706d71b2c21cebef1969298c761f74de2c /scripts/gdgentools.pm
parent616adfc77dc1d08f3bfcd79991a78c6350e4e2f6 (diff)
downloadvdr-plugin-muggle-2370a13b7f6512147550e5eec8a773e69d49119b.tar.gz
vdr-plugin-muggle-2370a13b7f6512147550e5eec8a773e69d49119b.tar.bz2
Merged and added import scripts
git-svn-id: https://vdr-muggle.svn.sourceforge.net/svnroot/vdr-muggle/trunk/muggle-plugin@99 e10066b5-e1e2-0310-b819-94efdf66514b
Diffstat (limited to 'scripts/gdgentools.pm')
-rwxr-xr-xscripts/gdgentools.pm1606
1 files changed, 1606 insertions, 0 deletions
diff --git a/scripts/gdgentools.pm b/scripts/gdgentools.pm
new file mode 100755
index 0000000..3522302
--- /dev/null
+++ b/scripts/gdgentools.pm
@@ -0,0 +1,1606 @@
+package gdgentools;
+
+##################################################
+#
+# GiantDisc mp3 Jukebox
+#
+# © 2000, Rolf Brugger
+#
+##################################################
+
+
+
+### General tool routines
+
+use lib '/home/andi/muggle/import';
+use gdparams;
+use IO::Socket;
+
+
+### Constants
+my $pl_list = 0; # play list
+my $rp_list = 1; # rip list
+my $co_list = 2; # compression list
+my $cr_list = 3; # cd recording list
+
+
+
+
+
+#use lib '/usr/local/bin';
+#BEGIN{;}
+#END{;}
+
+
+############################################################
+### Shuffle routine
+sub fisher_yates_shuffle {# generate a random permutation of @array in place
+ my $array = shift;
+ my $i;
+ for ($i = @$array; --$i; ) {
+ my $j = int rand ($i+1);
+ next if $i == $j;
+ @$array[$i,$j] = @$array[$j,$i];
+ }
+}
+# USAGE:
+# fisher_yates_shuffle( \@array ); # permutes @array in placesub query_random_artists{
+
+
+
+
+
+############################################################
+### barix exstreamer routines
+
+sub exstreamer_command{
+ # sends a command string to the exstreamer at 'playerhost', without
+ # waiting for a result that is sent back by the exstreamer
+
+ my ($playerhost, $command) = @_;
+ my $port = 12302; # default exstreamer tcp command port
+ my $sock = new IO::Socket::INET( PeerAddr => $playerhost,
+ PeerPort => $port,
+ Proto => 'tcp');
+ die "Error: could not open socket $opt_h:$port. $!\n" unless $sock;
+
+ ### send the command
+ print $sock $command."\n";
+ close($sock);
+}
+
+
+sub exstreamer_command_res{
+ # sends a command string to the exstreamer at 'playerhost'.
+ # It returns the first line that is sent back by the exstreamer (last \n chopped off)
+
+ my ($playerhost, $command) = @_;
+ my $port = 12302; # default exstreamer tcp command port
+ my $sock = new IO::Socket::INET( PeerAddr => $playerhost,
+ PeerPort => $port,
+ Proto => 'tcp');
+ die "Error: could not open socket $opt_h:$port. $!\n" unless $sock;
+
+ ### send a command
+ print $sock $command."\n";
+ my $res = <$sock>;
+ close($sock);
+ chop $res;
+ return $res;
+}
+
+
+############################################################
+### Playlist routines
+
+
+# creates new playstate record. If one with the same playerid/audiochannel
+# exists, it is overwritten.
+sub pll_new_playstate{
+ my ($dbh, $playerid, $audiochannel, $playertype, $snddevice, $playerapp, $playerparams, $ptlogger) = @_;
+ $dbh->do("REPLACE INTO playerstate SET "
+ ."playerid=$playerid, "
+ ."audiochannel=$audiochannel, "
+ ."playertype=$playertype, "
+ ."snddevice='$snddevice', "
+ ."playerapp='$playerapp', "
+ ."playerparams='$playerparams', "
+ ."ptlogger='$ptlogger', "
+ ."currtracknb=0, "
+ ."state='st', " # stopped
+ ."framesplayed=0, "
+ ."framestotal=0 "
+ );
+}
+
+# deletes playstate record
+sub pll_del_playstate{
+ my ($dbh, $playerid, $audiochannel) = @_; #parameters: database handle, player id, sound out it
+ $dbh->do("REPLACE FROM playerstate "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel ");
+}
+
+
+# Returns th specified player-type
+sub pll_get_playertype{
+ my ($dbh, $playerid, $audiochannel) = @_; #parameters: database handle, player id, sound out it
+
+ my $sth = $dbh->prepare(
+ "SELECT playertype,ipaddr,snddevice FROM player,playerstate "
+ ."WHERE player.id=playerstate.playerid "
+ ." AND player.id=$playerid "
+ ." AND playerstate.audiochannel=$audiochannel");
+
+ my $nbrec = $sth->execute;
+ #print("$nbrec playstates found (should be exactly 1)\n");
+
+ my ($playertype,$playerhost,$snddevice);
+
+ if($row = $sth->fetchrow_hashref){
+ $playertype = $row->{playertype};
+ $playerhost = $row->{ipaddr};
+ $snddevice = $row->{snddevice};
+ }
+ else{
+ ### This case should not happen! make the best out of it
+ print ("ERROR: can't get playertype for player $playerid channel $audiochannel\n");
+ print (" no player/playerstate record found\n");
+ }
+ $sth->finish;
+
+ return ($playertype,$playerhost,$snddevice);
+}
+
+
+# Returns the current playstate
+sub pll_get_playstate{
+ my ($dbh, $playerid, $audiochannel) = @_; #parameters: database handle, player id, sound out it
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM playerstate "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel" );
+
+ my $nbrec = $sth->execute;
+ #print("$nbrec playstates found (should be exactly 1)\n");
+
+ my $currtracknb = 0;
+ my $state = "st";
+ my $shufflestat = "";
+
+ if($row = $sth->fetchrow_hashref){
+ $currtracknb = $row->{currtracknb};
+ $state = $row->{state};
+ $framesplayed= $row->{framesplayed};
+ $shufflestat = $row->{shufflestat};
+ }
+ else{
+ ### This case should not happen! make the best out of it
+ #pll_new_playstate($dbh, $playerid, $audiochannel, "", "", "");
+ print ("ERROR: can't get playstate for player $playerid channel $audiochannel\n");
+ print (" no playerstate record found\n");
+ }
+ $sth->finish;
+
+ return ($currtracknb, $state, $framesplayed, $shufflestat);
+}
+
+
+# Returns
+sub playerdefinition{
+ my ($dbh, $playerid, $audiochannel) = @_; #parameters: database handle, player id, sound out it
+
+ my $sth = $dbh->prepare(
+ "SELECT playertype, ipaddr, snddevice FROM player,playerstate "
+ ."WHERE player.id=$playerid AND playerstate.playerid=$playerid AND audiochannel=$audiochannel");
+
+ my $nbrec = $sth->execute;
+ #print("$nbrec playstates found (should be exactly 1)\n");
+
+ my ($playertype, $playerhost, $sounddevice);
+ my $playertype = 0;
+ my $playerhost = "localhost";
+ my $sounddevice = "/dev/dsp";
+
+ if($row = $sth->fetchrow_hashref){
+ $playertype = $row->{playertype};
+ $playerhost = $row->{ipaddr};
+ $sounddevice = $row->{snddevice};
+ }
+ else{
+ ### This case should not happen!
+ print ("ERROR: can't get player definition for player $playerid channel $audiochannel\n");
+ print (" no playerstate record found\n");
+ }
+ $sth->finish;
+
+ return ($playertype, $playerhost, $sounddevice);
+}
+
+
+# Returns the process id of the player (that was previously stored in the DB)
+sub pll_get_player_processid{
+ my ($dbh, $playerid, $audiochannel) = @_; #parameters: database handle, player id, sound out it
+ my $sth = $dbh->prepare(
+ "SELECT processid FROM playerstate "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel" );
+ my $nbrec = $sth->execute;
+ my $playerpid = -1;
+ if($row = $sth->fetchrow_hashref){
+ $playerpid = $row->{processid};
+ }
+ else{
+ ### This case should not happen! make the best out of it
+ print ("ERROR: can't get playstate for player $playerid channel $audiochannel\n no playerstate record found\n");
+ }
+ $sth->finish;
+ return ($playerpid);
+}
+
+
+
+# Writes the playstate to the playstate record
+# Parameters:
+# currtracknb
+# state (one of: pl, st, in, ff, Ff, rw, Rw) [in=pause(interrupted)]
+sub pll_write_playstate{
+ my ($dbh, $playerid, $audiochannel, $currtracknb, $state) = @_;
+ my $retval = $dbh->do(
+ "UPDATE playerstate "
+ ."SET currtracknb=$currtracknb, state='$state' "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+}
+
+
+# Writes the players process id to the playstate record
+# The programs 'gdplayd.pl' and 'gdplaytmsim.pl' write their process id into
+# the DB. This is necessary to efficiently stop playing or rewinding.
+sub pll_write_player_processid{
+ my ($dbh, $playerid, $audiochannel, $playerpid) = @_;
+ my $retval = $dbh->do(
+ "UPDATE playerstate "
+ ."SET processid=$playerpid "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+}
+
+#sub pll_clear_player_processid{
+# my ($dbh, $playerid, $audiochannel, $playerpid) = @_;
+# if ($gdparms::multiclients){
+##print "clearing pid \n";
+# my $retval = $dbh->do(
+# "UPDATE playerstate "
+# ."SET processid=0 "
+# ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+# }
+#}
+
+
+### Shuffle Play Parameters
+sub pll_write_shuffleparameters{
+ my ($dbh, $playerid, $audiochannel, $parameterstring, $statisticsstring) = @_;
+ print("writing shuffleparamstring: '$parameterstring'\n");
+ my $retval = $dbh->do(
+ "UPDATE playerstate "
+ ."SET shufflepar =".$dbh->quote($parameterstring).", "
+ ." shufflestat=".$dbh->quote($statisticsstring)." "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+}
+
+
+
+#######################################################################
+### Stop playing
+#
+# Stopping to play means killing the specifig playing-deamon 'gdplayd.pl'
+# and all the sub-processes it has spawned.
+#
+# As long as only one instance of a GiantDisc server is running on a
+# machine, we can just blindly kill all potentially spawned processes by
+# name (see routine 'kill_all_play_processes').
+#
+# If we have more than one server instance on the same machine, this blindly
+# killing method doesn't work anymore. Stopping to play on one instance would
+# terminate the play processes of all other server instances. It is therefore
+# necessary to specifically kill the involved processes. I have tried many
+# (really a lot of) approaches during 2 years - nothing really worked well.
+# There were 2 main problems:
+# - killing the processes was far too slow
+# - multiple instances of gdplayd.pl and it sub-processes appeared leading
+# to locked soundcards etc. ( This especially happened, when playing was
+# quickly stopped and restarted, or after fast sequences of 'play next'
+# commands.
+#
+# The currently adopted however method seems to work fine, it is robust and
+# efficient enough. It is explained in detail below, see routines plmsg_...
+#
+
+
+### tested and not well working: Killall and Killfam!
+### Comment: Proc::Killall is terribly slow (much slower than system("killall..."))
+### using Proc::Killfam is unstable because of overlapping playing
+### commands, that inevitably lead to multiple instances of gdplayd.pl
+### (mainly because pid of gdplayd.pl is known too late?)
+### Finally - Proc::Killfam is too slow too!
+
+
+sub stop_play_processes{
+
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my $new_kill_player = 1;
+# if ($gdparms::multiclients){
+ if ($new_kill_player){
+ my $playerpid = pll_get_player_processid($dbh, $playerid, $audiochannel);
+
+ if ($playerpid >0){
+ ### get all child processes of $playerpid
+ my $chprocs = `pstree $playerpid -p |tr -d '\012'`; # get process id's, on one text line
+ my @chpids = split /\(/, $chprocs;
+ shift @chpids; # pop off first element
+ foreach $elem (@chpids){
+ $elem =~ s/\).*//;
+ }
+
+ if (scalar(@chpids)>0){
+ #print " specifically killing ".join (",",@chpids)."\n";
+ system "kill ".join (" ",@chpids);
+ }
+ }
+ }
+ else{
+ kill_all_play_processes();
+ }
+}
+
+
+sub kill_all_play_processes{
+ #print "killing blindly\n";
+ system("killall -q gdplayd.pl; "
+ ."killall -q mpg123; "
+ ."killall -q ogg123; "
+ ."killall -q flac; "
+ ."killall -q rawplay; "
+ ."killall -q gdplaytmsim.pl; "
+ ."killall -q gdstream.pl");
+
+ #killall and killfam are terribly slow!
+}
+
+
+#######################################################################
+### IPC Message routines to make sure, that a stop operation really kills
+# all player processes.
+# Method:
+# 1) the server starts the player 'gdplayd.pl' in background
+# 2) right after starting gdplayd.pl, the server waits for a message of
+# gdplayd.pl
+# 3) in the init phase of gdplayd.pl, it writes it's own process-id to the
+# database and sends then a message to the message queue
+# 4) the server receives the message and can continue to process requests
+# from the palm
+# 5) when the server should now stop playing, it is 100% sure, that the
+# correct process-id o the playing process is in the db.
+# Killing it (and it's subprocesses) stops playing.
+
+use IPC::SysV qw(IPC_CREAT S_IRWXU ftok);
+use IPC::Msg;
+
+# Die folgenden Konstanten kennzeichnen die Message-Queue im System.
+# RENDEZVOUS muss der Name einer _existierenden_ Datei sein!
+# Nur die unteren 8 Bits von RVID sind wichtig und muessen !=0 sein!
+# ftok(RENDEZVOUS, RVID) erzeugt einen immer identischen Schluessel,
+# so lange die Datei RENDEZVOUS nicht neu angelegt wurde.
+
+use constant RENDEZVOUS => "/etc";
+#use constant RVID => 121;
+
+sub rendevous_id{
+ my ($playerid, $audiochannel) = @_;
+#print "p=$playerid, chn=$audiochannel -> rdvid=".($playerid*16 + $audiochannel + 1)."\n";
+ if ($playerid>15 || $audiochannel>15){
+ print "\n WARNING:\n ";
+ print "too many palyers/audiochannels\n";
+ print " p=$playerid, chn=$audiochannel -> rdvid=".($playerid*16 + $audiochannel + 1)."\n\n";
+ }
+ return $playerid*16 + $audiochannel + 1;
+}
+sub plmsg_newqueue{
+ ### creates messae queue RENDEVOUS, if it doesn't already exist
+ my ($playerid, $audiochannel) = @_;
+ my $rdvid = rendevous_id($playerid, $audiochannel);
+ use vars qw($msg);
+ $msg = new IPC::Msg(ftok(RENDEZVOUS, $rdvid), S_IRWXU | IPC_CREAT);
+
+}
+
+sub plmsg_send{
+ ### appends a message to the queue
+ my ($playerid, $audiochannel) = @_;
+ my $rdvid = rendevous_id($playerid, $audiochannel);
+ my $msg = new IPC::Msg(ftok(RENDEZVOUS, $rdvid), 0);
+ my ($prio, $text)=(1,"player started");# = @MESSAGES[$i,$i+1];
+ $msg->snd($prio, $text, 0);
+}
+
+sub plmsg_waitrcv{
+ my ($playerid, $audiochannel) = @_;
+ ### pulls one message from the message queue, waits until one message is there
+ my $rdvid = rendevous_id($playerid, $audiochannel);
+ my $msg = new IPC::Msg(ftok(RENDEZVOUS, $rdvid), 0);
+ my $buflen = 256;
+ $prio = $msg->rcv($buf, $buflen, 0, 0);
+# print "Found: ($buf, $prio)\n";
+}
+
+
+sub pl_start_playd_and_wait{
+# starts playing-deamon and waits until it has written it's pid in the db
+ my ($dbhost, $playerid, $audiochannel) = @_;
+#print "--> start gdplayd.pl\n";
+ system("gdplayd.pl $dbhost $playerid $audiochannel & ");
+#print "--> wait for gdplayd.pl to be started\n";
+ plmsg_waitrcv($playerid, $audiochannel);
+#print "--> message received\n";
+}
+
+
+#######################################################################
+#######################################################################
+#
+# Basic Play Controls (also used as API)
+
+
+sub pl_play{
+# starts playing
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ if(tracklist_get_item($dbh, $playerid, 0, $trackind) < 1){$trackind=1;} # playstate error
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel, $trackind, "pl");
+ pl_start_playd_and_wait($gdparms::dbhost, $playerid, $audiochannel);
+# system("gdplayd.pl $gdparms::dbhost $playerid $audiochannel & ");
+}
+
+sub pl_play_at{
+# starts playing at specified position (seconds)
+ my ($dbh, $playerid, $audiochannel, $songpos_sec) = @_;
+# my ($songpos_sec) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ if(tracklist_get_item($dbh, $playerid, 0, $trackind) < 1){$trackind=1;} # playstate error
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ my $startframe = $songpos_sec * frames_per_second();
+ pll_write_playtime_only($dbh, $playerid, $audiochannel, $startframe);
+ pll_write_playstate($dbh, $playerid, $audiochannel, $trackind, "pl");
+ pl_start_playd_and_wait($gdparms::dbhost, $playerid, $audiochannel);
+# system("gdplayd.pl $gdparms::dbhost $playerid $audiochannel & ");
+}
+
+sub pl_stop{
+# stops playing and reset playtime-state
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my($played, $total) = pll_get_playtime($dbh, $playerid, $audiochannel);
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel, $trackind, "st");
+ pll_write_playtime($dbh, $playerid, $audiochannel, 0, $total);
+}
+
+sub pl_pause{
+# stops playing and preserve playtime-state
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my($played, $total) = pll_get_playtime($dbh, $playerid, $audiochannel);
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel,
+ $trackind, "in"); # state: interrupted
+}
+
+sub pl_rw{
+# similar as pause
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my ($played, $total) = pll_get_playtime($dbh, $playerid, $audiochannel);
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel,
+ $trackind, "rw"); # state: rw
+
+ ### continuously write current playtime to db (every second)
+ system("gdplaytmsim.pl $gdparms::dbhost $playerid $audiochannel & ");
+}
+
+sub pl_ff{
+# similar as pause
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my ($played, $total) = pll_get_playtime($dbh, $playerid, $audiochannel);
+ stop_play_processes($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel,
+ $trackind, "ff"); # state: ff
+
+ ### continuously write current playtime to db (every second)
+ system("gdplaytmsim.pl $gdparms::dbhost $playerid $audiochannel & ");
+}
+
+sub pl_goto{
+ ### makes a new track the current track
+ # sets the current playtime-posititon to zero
+ # the rest of the playstate is preserved
+
+ use integer;
+ my ($dbh, $playerid, $audiochannel, $newind) = @_;
+# my ($newind) = @_; # the new index (must be valid)
+
+ stop_play_processes($dbh, $playerid, $audiochannel);
+
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ pll_write_playstate($dbh, $playerid, $audiochannel, $newind, $state);
+ pll_write_playtime($dbh, $playerid, $audiochannel, 0, 0);
+ if($state eq "pl"){ # restart player
+ pl_start_playd_and_wait($gdparms::dbhost, $playerid, $audiochannel);
+# system("gdplayd.pl $gdparms::dbhost $playerid $audiochannel & ");
+ }
+ else{ # new 'current' track: load it's total length
+ my $trackid = tracklist_get_item($dbh, $playerid, 0, $newind);
+ if (length($trackid)>0) {
+ my $sth = $dbh->prepare("SELECT * FROM tracks WHERE id = $trackid");
+ my $cnt = $sth->execute;
+ if ($cnt > 0){
+ my $row = $sth->fetchrow_hashref;
+ pll_write_playtime($dbh, $playerid, $audiochannel, 0, $row->{length}*frames_per_second());
+ }
+ else{
+ pll_write_playtime($dbh, $playerid, $audiochannel, 0, 0);
+ }
+ $sth->finish;
+ }
+ else{
+ pll_write_playtime($dbh, $playerid, $audiochannel, 0, 0);
+ }
+ }
+}
+
+sub pl_next{
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my $listlen = tracklist_get_nb_items($dbh, $playerid, $pl_list);
+ if ($trackind < $listlen) {$trackind++;}
+ pl_goto($dbh, $playerid, $audiochannel, $trackind);
+}
+
+sub pl_prev{
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my $frames5sec = 5*frames_per_second(); # nb frames in 5 sec
+ my ($trackind, $state, $frame, $shufflestat) = pll_get_playstate($dbh, $playerid, $audiochannel);
+ my ($played, $total) = pll_get_playtime($dbh, $playerid, $audiochannel);
+ if ($trackind>0 && $played<$frames5sec){$trackind--;}
+ pl_goto($dbh, $playerid, $audiochannel, $trackind);
+}
+
+#######################################################################
+#######################################################################
+
+
+
+
+
+#######################################################################
+#
+# Playtime routines:
+# The current position is always saved in the field 'framesplayed'
+# If the player app can't continuously write this field, another realtime
+# app has to write the current playtime (and ff, rew position)
+
+# mp3 constants
+my $samplesPerFrame = 1152;
+my $samplesPerSecond = 44100;
+
+sub frames_per_second{
+ use integer;
+ return $samplesPerSecond/$samplesPerFrame; # = 38.281
+}
+sub samples_per_frame{
+ return $samplesPerFrame;
+}
+sub samples_per_second{
+ return $samplesPerSecond;
+}
+
+
+# Writes the playtime to the playtime record.
+# Parameters: played, total (units: frames)
+sub pll_write_playtime{
+ my ($dbh, $playerid, $audiochannel, $played, $total) = @_;
+ my $retval = $dbh->do(
+ "UPDATE playerstate "
+ ."SET framesplayed=$played, framestotal=$total "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+}
+
+sub pll_write_playtime_only{ # like pll_write_playtime without changing 'framestotal'
+ my ($dbh, $playerid, $audiochannel, $played) = @_;
+ my $retval = $dbh->do(
+ "UPDATE playerstate "
+ ."SET framesplayed=$played "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel");
+}
+
+
+# Returns the current playtime (frames played and total frames)
+sub pll_get_playtime{
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my $played = 0;
+ my $total = 0;
+
+ use integer;
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM playerstate "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel" );
+
+ my $nbrec = $sth->execute;
+ #print("$nbrec playtime found (should be exactly 1)\n");
+
+ if($row = $sth->fetchrow_hashref){
+ $total = $row->{framestotal};
+ $played = $row->{framesplayed};
+ }
+ $sth->finish;
+
+ return ($played, $total);
+}
+
+
+sub seconds_to_hm{
+ my ($seconds) = @_;
+ my ($hours, $minutes);
+ use integer; # switch to int math
+ $hours = $seconds / 3600;
+ $minutes = ($seconds % 3600)/60;
+ return sprintf("%ih%02im", $hours, $minutes);
+}
+
+sub seconds_to_sm{
+ my ($seconds) = @_;
+ my ($minutes, $sec);
+ use integer; # switch to int math
+ $minutes = $seconds / 60;
+ $sec = $seconds % 60;
+ return sprintf("%i:%02i", $minutes, $sec);
+}
+
+
+### is the string a mp3-file or a mp3-stream?
+sub is_mp3stream{
+ my ($mp3filename) = @_;
+ return ($mp3filename =~ /^http:\/\/.*/); # matches "http://" at the beginning?
+}
+
+
+#######################################################################
+
+# Returns the player parameter ($snddevice, $playerapp, $playerparams, $ptlogger, $shufflepar)
+sub pll_get_playparams{
+ my ($dbh, $playerid, $audiochannel) = @_;
+ my ($snddevice, $playerapp, $playerparams, $ptlogger);
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM playerstate "
+ ."WHERE playerid=$playerid AND audiochannel=$audiochannel" );
+ my $nbrec = $sth->execute;
+
+ if($row = $sth->fetchrow_hashref){
+ $snddevice = $row->{snddevice};
+ $playerapp = $row->{playerapp};
+ $playerparams = $row->{playerparams};
+ $ptlogger = $row->{ptlogger};
+ $shufflepar = $row->{shufflepar};
+ }
+ $sth->finish;
+
+ return ($snddevice, $playerapp, $playerparams, $ptlogger, $shufflepar);
+}
+
+
+############################################################
+# Returns the main player parameters
+# ($ipaddr, $uichannel, $logtarget, $cdripper, $mp3encoder, $cdromdev, $cdrwdev)
+sub pll_get_mainparams{
+ my ($dbh, $playerid) = @_;
+ my ($ipaddr, $uichannel, $logtarget, $cdripper, $mp3encoder, $cdromdev, $cdrwdev);
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM player "
+ ."WHERE id=$playerid" );
+ my $nbrec = $sth->execute;
+
+ if($row = $sth->fetchrow_hashref){
+ $ipaddr = $row->{ipaddr};
+ $uichannel = $row->{uichannel};
+ $logtarget = $row->{logtarget};
+ $cdripper = $row->{cdripper};
+ $mp3encoder= $row->{mp3encoder};
+ $cdromdev = $row->{cdromdev};
+ $cdrwdev = $row->{cdrwdev};
+ }
+ $sth->finish;
+
+ return ($ipaddr, $uichannel, $logtarget, $cdripper, $mp3encoder, $cdromdev, $cdrwdev);
+}
+
+
+############################################################
+### General tracklist functions (table tracklistitem)
+#
+# 'tracknb' always starts with 0 and is contiguous
+# ex: 0,1,2,3 is legal, 0,1,2,4,5 is illegal
+#
+
+sub tracklist_get_nb_items{
+ # returns the of tracks in the specified tracklist
+ my ($dbh, $playerid, $listtype) = @_;
+ my $listlen = 0;
+ my $sth = $dbh->prepare(
+ "SELECT COUNT(*) FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype" );
+ $sth->execute;
+ my @row;
+ if(@row = $sth->fetchrow_array){
+ $listlen = $row[0];
+ }
+ $sth->finish;
+ return $listlen;
+}
+
+sub tracklist_append_list{
+ # appends a list of trackid's to the specified tracklist.
+ # Parameters: dbh, playerid, listtype, trackids...
+ my ($dbh) = shift(@_);
+ my ($playerid) = shift(@_);
+ my ($listtype) = shift(@_);
+
+ my $curritem = tracklist_get_nb_items($dbh, $playerid, $listtype);
+
+ while($trackid = shift(@_)){
+ $dbh->do("INSERT INTO tracklistitem SET "
+ ."playerid=$playerid, "
+ ."listtype=$listtype, "
+ ."tracknb=$curritem, "
+ ."trackid=$trackid ");
+ $curritem++;
+ }
+}
+
+
+# moves the specified list chunk down by one
+sub tracklist_move_chunk_up{
+ my ($dbh, $playerid, $listtype, $first, $last) = @_;
+ # "shift" specified items up (to higher index) by 1
+ # have to do increment item by item because order is important
+ my $sth = $dbh->prepare(
+ "SELECT * FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb>=$first AND tracknb<=$last "
+ ."ORDER BY tracknb DESC" ); # order: highest index first!
+ $sth->execute;
+ my $row;
+ while($row = $sth->fetchrow_hashref){
+ $dbh->do(
+ "UPDATE tracklistitem "
+ ."SET tracknb=tracknb+1 "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=".$row->{tracknb} );
+ }
+ $sth->finish;
+}
+
+
+# removes the specified list item from the list (item index starts with 0)
+sub tracklist_del_item{
+ my ($dbh, $playerid, $listtype, $trackindex) = @_;
+ $dbh->do(
+ "DELETE FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=$trackindex" );
+
+### should write a routine 'tracklist_move_chunk_down' (like tracklist_move_chunk_up)
+ # "shift" following items down by 1
+ # have to do decrement item by item because order is important
+ my $sth = $dbh->prepare(
+ "SELECT * FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb>$trackindex "
+ ."ORDER BY tracknb" );
+ $sth->execute;
+ my $row;
+ while($row = $sth->fetchrow_hashref){
+ $dbh->do(
+ "UPDATE tracklistitem "
+ ."SET tracknb=tracknb-1 "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=".$row->{tracknb} );
+ }
+ $sth->finish;
+}
+
+# removes the all list items from 0 to 'trackindex'
+sub tracklist_del_upto_item{
+
+### VERY INEFFICIENT IMPLEMENTATION!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ my ($dbh, $playerid, $listtype, $trackindex) = @_;
+ print("deleting up to $trackindex\n");
+ my $counter=0;
+ while ($counter < $trackindex){
+ tracklist_del_item($dbh, $playerid, $listtype, 0);
+ $counter++;
+ }
+}
+
+
+# reorders the specified list item in the list (item index starts with 0)
+# -> 'destpos' must be lower than 'srcpos'!
+sub tracklist_reorder_item{
+ my ($dbh, $playerid, $listtype, $srcpos, $destpos) = @_;
+
+ ### "move" src-item to a save location
+ my $savepos = -1000; # a bit too hacky?
+ $dbh->do(
+ "UPDATE tracklistitem "
+ ."SET tracknb=$savepos "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=$srcpos" );
+
+ # "shift" following items up by 1
+ tracklist_move_chunk_up($dbh, $playerid, $listtype, $destpos, ($srcpos)-1);
+
+ ### "move" src-item from save location to destination
+ $dbh->do(
+ "UPDATE tracklistitem "
+ ."SET tracknb=$destpos "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=$savepos" );
+}
+
+
+
+# empties the specified tracklist
+sub tracklist_delete{
+ my ($dbh, $playerid, $listtype) = @_;
+ $dbh->do(
+ "DELETE FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype" );
+}
+
+
+# gets the trackid of the specified list item (item index starts with 0)
+# If an error occurs, 0 is returned.
+sub tracklist_get_item{
+ my ($dbh, $playerid, $listtype, $trackindex) = @_;
+ my $trackid = 0;
+ my $sth = $dbh->prepare(
+ "SELECT * FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype AND tracknb=$trackindex" );
+ my $nbrec = $sth->execute;
+ if($row = $sth->fetchrow_hashref){
+ $trackid = $row->{trackid};
+ }
+ $sth->finish;
+ return $trackid;
+}
+
+
+# Returns the current playlist (list of track ID's)
+sub tracklist_get_all{
+ my ($dbh, $playerid, $listtype) = @_;
+ my @playlist=();
+
+ my $sth = $dbh->prepare(
+ "SELECT * FROM tracklistitem "
+ ."WHERE playerid=$playerid AND listtype=$listtype "
+ ."ORDER BY tracknb" );
+ my $nbrec = $sth->execute;
+ while($row = $sth->fetchrow_hashref){
+ $trackid = $row->{trackid};
+ push @playlist, $row->{trackid};
+ }
+ $sth->finish;
+
+ return @playlist;
+}
+
+
+
+############################################################
+### id3tag functions
+
+### returns the value of a id3 tag or other mp3 parameters for the specified file
+### Possible tagcodes are
+
+### NEW version:
+
+# %a Artist [string]
+# %b Number of corrupt audio frames [integer]
+# %c Comment [string]
+# %C Copyright flag [string]
+# %e Emphasis [string]
+# %E CRC Error protection [string]
+# %f Filename without the path [string]
+# %F Filename with the path [string]
+# %g Musical genre [string]
+# %G Musical genre [integer]
+# %l Album name [string]
+# %L MPEG Layer [string]
+# %m Playing time: minutes only [integer]
+# %n Track [integer]
+# %O Original material flag [string]
+# %o Stereo/mono mode [string]
+# %p Padding [string]
+# %Q Sampling frequency in Hz [integer]
+# %q Sampling frequency in KHz [integer]
+# %r Bit Rate in KB/s (type and meaning
+# affected by -r option)
+# %s Playing time: seconds only [integer]
+# (usually used in conjunction with # %m)
+# %S Total playing time in seconds [integer]
+# %t Track Title [string]
+# %u Number of good audio frames [integer]
+# %v MPEG Version [float]
+# %y Year [string]
+# %% A single percent sign
+
+sub get_mp3info{
+ my ($tagcode, $filename) = @_;
+ my $base = gdparams::gdbase();
+ my $res = `mp3info -p "$tagcode" "$filename"`;
+
+ ### Error cases
+ if ($tagcode eq "%S" && length($res)==0){
+ my $res2 = `mpg123 -v -t -n 0 "$filename" 2> $base/tmp/gdinfo.tmp;grep Frame $base/tmp/gdinfo.tmp`;
+ $res2 =~ m/.*\[(.+):(.+).(.+)\].*/;
+ my $min = $1;
+ my $sec = $2;
+ $res = $min*60 + $sec;
+ print("playlength fixed to $res\n");
+ }
+ if($tagcode eq "%r" && ($res eq "Variable"))
+ { #check for Variable Bitrate
+# $res="Var".`mp3info -p %r -r m "$filename"`; ### don't predeed wit 'wav' - it messes up bitrate calculations of streamer
+ $res=`mp3info -p %r -r m "$filename"`;
+ return $res;
+ }
+ if ($tagcode eq "%r" && ($res <= 0 )){
+ $res = "128";
+ }
+
+ return $res;
+}
+
+
+############################################################
+### audio metadata with ogg support.
+#
+# standard ogg tags according to
+# http://www.xiph.org/ogg/vorbis/doc/v-comment.html
+# -> ARTIST TITLE ALBUM TRACKNUMBER YEAR GENRE COMMENT
+# and
+
+### typical output of ogginfo
+#>ogginfo file.ogg
+#filename=file.ogg
+#
+#serial=6039
+#header_integrity=pass
+#ALBUM=ob die Engel auch Beine haben
+#TITLE=Die Schampullamaschine
+#ARTIST=Zentriert ins Antlitz
+#DATE=2002
+#TRACKNUMBER=7
+#GENRE=Industrial
+#ORGANIZATION=-
+#COMMENT=ZIA Ogg Vorbis 1.0 Final
+#vendor=Xiph.Org libVorbis I 20020717
+#version=0
+#channels=2
+#rate=44100
+#bitrate_upper=none
+#bitrate_nominal=128000
+#bitrate_lower=none
+#stream_integrity=pass
+#bitrate_average=126915
+#length=50.248005
+#playtime=0:50
+#stream_truncated=false
+#
+#total_length=50.248005
+#total_playtime=0:50
+
+sub oggfile_title{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^title=`;
+ chop $line;
+ return substr($line, 6);
+}
+sub oggfile_artist{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^artist=`;
+ chop $line;
+ return substr($line, 7);
+}
+sub oggfile_album{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^album=`;
+ chop $line;
+ return substr($line, 6);
+}
+sub oggfile_year{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^date=`;
+ chop $line;
+ $line = substr($line, 5);
+ if ($line =~ /\D*(\d*).*/){
+ return $1;
+ }
+ else {
+ return 0;
+ }
+}
+sub oggfile_lengthsec{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^length=`;
+ chop $line;
+ my $lengthsec = substr($line, 7);
+ return int($lengthsec);
+}
+sub oggfile_bitrate{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^bitrate_nominal=`;
+ chop $line;
+ my $bitrate = substr($line, 16);
+ return (int(($bitrate/4000)+0.5))*4; # round to modulo 4
+}
+sub oggfile_tracknumber{
+ my($audiofile) = @_;
+ $line = `ogginfo "$audiofile" |grep --ignore-case ^tracknumber=`;
+ chop $line;
+ $line = substr($line, 12);
+ return int($line);
+}
+sub oggfile_genre{
+ return "";
+}
+
+###~CU~ #FLAC_BEGIN
+
+############################################################
+### FLAC metadata functions
+### Autor: Christian Uebber
+###
+### Uses metaflac (see http://flac.sourceforge.net/)
+### for extracting VORBIS_COMMENT metadata.
+###
+### Future versions may also support extracting
+### cue-sheet information.
+###
+
+sub flacfile_title{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=title "$audiofile"`;
+ chop $line;
+ return substr($line, 6);
+}
+
+sub flacfile_artist{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=artist "$audiofile"`;
+ chop $line;
+ return substr($line, 7);
+}
+sub flacfile_album{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=album "$audiofile"`;
+ chop $line;
+ return substr($line, 6);
+}
+
+sub flacfile_year{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=date "$audiofile"`;
+ chop $line;
+ $line = substr($line, 5);
+ if ($line =~ /\D*(\d*).*/){
+ return $1;
+ }
+ else {
+ return 0;
+ }
+}
+
+sub flacfile_lengthsec{
+ my($audiofile) = @_;
+ $line = `metaflac --show-total-samples "$audiofile"`;
+ chop $line;
+ return int($line/44100); ## Please verify (theoretically correct)
+}
+
+sub flacfile_bitrate{
+ my($audiofile) = @_;
+ $line = `metaflac --show-sample-rate "$audiofile"`;
+ chop $line;
+ return (int($line/1000)); # respect maximum field length
+# return ($line/1000); # respect maximum field length
+# return (int($line)/100); # respect maximum field length
+}
+
+### Alternative:
+#sub flacfile_type{
+# my($audiofile) = @_;
+# $line = `metaflac --show-sample-rate "$audiofile"`;
+# chop $line;
+# if ($line="44100"){
+# return "cda";
+# }
+# else {
+# return "";
+# }
+#}
+
+sub flacfile_tracknumber{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=tracknumber "$audiofile"`;
+ chop $line;
+ $line = substr($line, 12);
+ return int($line);
+}
+sub flacfile_genre{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=genre "$audiofile"`;
+ chop $line;
+ return substr($line, 6);
+}
+
+sub flacfile_tracknumber{
+ my($audiofile) = @_;
+ $line = `metaflac --show-vc-field=tracknumber "$audiofile"`;
+ chop $line;
+ $line = substr($line, 12);
+ return int($line);
+}
+
+###~CU~ #FLAC_END
+
+############################################################
+
+sub audiofile_title{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ my $title = "";
+ if ($ftype eq "mp3"){
+ $title = get_mp3info("%t", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ $title = oggfile_title($audiofile);
+ }
+ elsif ($ftype eq "flac"){ ###~CU~
+ $title = flacfile_title($audiofile);
+ }
+
+ if (length($title) > 0){
+ return $title;
+ }
+ else {
+ use File::Basename;
+ return basename($audiofile);
+ }
+}
+
+
+sub audiofile_artist{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ my $artist = "";
+ if ($ftype eq "mp3"){
+ $artist = get_mp3info("%a", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ $artist = oggfile_artist($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ $artist = flacfile_artist($audiofile); ###~CU~
+ }
+
+ if (length($artist) > 0){
+ return $artist;
+ }
+ else {
+ use File::Basename;
+ return basename($audiofile);
+ }
+}
+
+
+sub audiofile_album{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ if ($ftype eq "mp3"){
+ return get_mp3info("%l", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ return oggfile_album($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ return flacfile_album($audiofile); ###~CU~
+ }
+ else {
+ return "Album name";
+ }
+}
+
+
+sub audiofile_year{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ if ($ftype eq "mp3"){
+ return get_mp3info("%y", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ return oggfile_year($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ return flacfile_year($audiofile); ###~CU~
+ }
+ else {
+ return 1990;
+ }
+}
+
+
+sub audiofile_lengthsec{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ if ($ftype eq "mp3"){
+ return get_mp3info("%S", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ return oggfile_lengthsec($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ return flacfile_lengthsec($audiofile); ###~CU~
+ }
+ else {
+ return 0;
+ }
+}
+
+
+sub audiofile_bitrate{
+ my($audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ if ($ftype eq "mp3"){
+ return get_mp3info("%r", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ return oggfile_bitrate($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ return flacfile_bitrate($audiofile); # ###~CU~ flac: return sampling rate / please check alternative
+ }
+ else {
+ return 128;
+ }
+}
+
+
+sub audiofile_tracknumber{
+ my($audiofile, $default_tracknb) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ my $tracknb=0;
+ if ($ftype eq "mp3"){
+ $tracknb = get_mp3info("%n", $audiofile);
+ }
+ elsif ($ftype eq "ogg"){
+ $tracknb = oggfile_tracknumber($audiofile);
+ }
+ elsif ($ftype eq "flac"){
+ $tracknb = flacfile_tracknumber($audiofile);
+ }
+
+ if($tracknb > 0){
+ return $tracknb;
+ }
+ else {
+ return $default_tracknb;
+ }
+}
+
+
+sub audiofile_genre{
+ ### extracts id3 gerne code or genre string (depends on filetype)
+ ### returns GD-genre code
+ my($dbh, $audiofile) = @_; # must be full filename with path!
+ my $ftype = audio_filetype($audiofile);
+ if ($ftype eq "mp3"){
+ my $id3genre = get_mp3info("%G", $audiofile);
+ return genre_id3togd($dbh, $id3genre);
+ }
+ elsif ($ftype eq "ogg"){
+ my $genrestring = oggfile_genre($audiofile);
+ return genre_stringtogd($dbh, $genrestring);
+ }
+ elsif ($ftype eq "flac"){
+ my $genrestring = flacfile_genre($audiofile);
+ return genre_stringtogd($dbh, $genrestring);
+ }
+ else {
+ return "";
+ }
+}
+
+
+############################################################
+
+sub audio_filetype{
+ ### the filetype is derived form the filename extension
+ my($audiofile) = @_;
+
+ if ($audiofile =~ /[Mm][Pp]3$/){
+ return "mp3";
+ }
+ if ($audiofile =~ /ogg$/){
+ return "ogg";
+ }
+ if ($audiofile =~ /flac$/){
+ return "flac";
+ }
+}
+
+
+
+############################################################
+
+sub get_bitrate_str{
+ # returns format/bitrate in kBit/s (ex. "mp3 128", "ogg 112")
+ # takes as argument the audio file name with its full path.
+ my ($audiofile) = @_;
+ my $bitrate = audiofile_bitrate($audiofile);
+ my $ftype = audio_filetype($audiofile);
+ return "$ftype $bitrate";
+}
+
+sub bitrate_str_to_format_param{
+ # gets a bitrate string like "mp3 128" and returns array ("mp3", "128")
+ my ($bitratestr) = @_;
+ my ($audiofmt, $audioparam) = split ' ', $bitratestr; # split "mp3 128"
+ if (length($audiofmt)<2){ # something went wrong
+ $audiofmt = "mp3"; # set reasonable default values
+ $audioparam = 128;
+ }
+ return ($audiofmt, $audioparam);
+}
+
+
+sub get_full_audiofile_path{
+ # returns the full path of an audio file in a 00, 01, 02, ... directory
+ # or an empty string if the file doesn't exist
+ my ($audiofile) = @_;
+ my $base = gdparams::gdbase();
+ my $fname = `ls $base/[0-9][0-9]/$audiofile`; # get full path
+ chop($fname);
+ return $fname;
+}
+
+############################################################
+### Returns the first music directory with enough space
+ # to save ripped files. If no large enough directory is found
+ # an empty string is returned.
+ # Parameter: minimum free space in Mbytes
+
+sub get_ripdirectory{
+ my ($minfreeMbytes) = @_;
+ my $base = gdparams::gdbase();
+
+ ### Get mp3 directories
+ my @mdir = gdparams::mp3dirs();
+
+ ### Get an mp3 directory with enough space left (1GB)
+ my $i=0;
+ my @dfres;
+ my $mbfree;
+ my $ripdir="";
+
+ while($i < @mdir){
+ if (-d "$base/$mdir[$i]"){
+ @dfres = split / +/, `df -m $base/$mdir[$i]|tail -1`;
+ $mbfree = $dfres[3];
+ #print "$base/$mdir[$i] has $mbfree MB free \n";
+ if($mbfree > $minfreeMbytes){
+ $ripdir = $mdir[$i];
+ last; # break
+ }
+ }
+ else{print "$base/$mdir[$i] is not a directory or does not exist\n";}
+ $i++;
+ }
+ #print("Rip directory: $ripdir \n");
+ return $ripdir;
+}
+
+
+############################################################
+### import booklet/cover images
+sub import_cover_images{
+ # imports the jpeg images in a directory and associates them to an album
+ # the images are imported in lexical order.
+ # Naming scheme: trxx(cd-id)-(num).jpg, where num is an automatically
+ # incremented counter. The file imgxx(cd-id)-00.jpg is the front cover,
+ # the other are the following pages in a booklet.
+
+ # Parameters: 1) dbh,
+ # 2) full directory path,
+ # 3) cd-id (like 0x10ac77e0, xx00001496)
+ # 4) test? (if set to 1, only show what would be done)
+
+ my ($dbh, $fullpath, $cdid, $test) = @_;
+
+ my ($sourcefile, $targetfile);
+ my $base = gdparams::gdbase();
+ print "import images at $fullpath for id $cdid\n";
+
+ if (length($cdid)<10){
+ print "Error (import_cover_images): illegal format of cdid '$cdid'\n";
+ return;
+ }
+
+ opendir INBOX, "$fullpath";
+ my @imgfilelist = sort (grep /\.jpg$/, readdir INBOX);
+ closedir INBOX;
+
+ #print "imgs: ".join (":",@imgfilelist)."\n";
+ if (scalar(@imgfilelist) > 0){
+
+ ### Get a directory with enough space left (1GB)
+ my $ripdir=get_ripdirectory($gdparams::minfreehdspace);
+
+ if($ripdir ne ""){### put image-file in music directory
+ my $imgnum = 0;
+
+ foreach $sourcefile (@imgfilelist){
+ $targetfile = sprintf ("$base/$ripdir/img%s-%02ld.jpg", $cdid, $imgnum);
+ if ($test){
+ print "test: move '$fullpath/$sourcefile' to '$targetfile'\n";
+ }
+ else{
+ print "move '$fullpath/$sourcefile' to '$targetfile'\n";
+ system "mv \"$fullpath/$sourcefile\" \"$targetfile\"";
+ if ($imgnum == 0){
+
+ my $sqlcmd = ("UPDATE album SET coverimg='img$cdid-00.jpg' WHERE cddbid = SUBSTRING('$cdid',3)");
+ #print"\$dbh->do($sqlcmd);\n"
+ $dbh->do($sqlcmd);
+ }
+ }
+ $imgnum += 1;
+ }# end foreach
+ }
+ else{
+ print("Not enough space left on disc \n");
+ }
+ }
+ else{
+ print "no jpeg images found in $fullpath \n";
+ }
+}
+
+
+############################################################
+### common tool routines
+
+### returns highest number of imported (=not recorded from audio-CD, =no
+### CDDB-ID associated) mp3 tracks.
+### Parameter: database handle.
+sub last_imported_tracknb{
+ my ($dbh) = @_;
+
+ my $mp3num;
+ my $trkseth = $dbh->prepare('select mp3file from tracks '. # get last record
+ 'where mp3file like "trxx________%" order by mp3file desc limit 1');
+# 'where mp3file like "trxx%" order by mp3file desc limit 1');
+ my $nbmp3files = $trkseth->execute;
+ if ($nbmp3files > 0){
+ $tracks = $trkseth->fetchrow_hashref;
+ $tracks->{mp3file} =~ m/trxx([0-9]*)\.\w/; # extract number
+ $mp3num = $1;
+ }
+ else{
+ $mp3num = 0;
+ }
+ $trkseth->finish;
+ return $mp3num;
+}
+
+
+### Translates an id3 genre (numeric) to a GiantDisc genre code (string)
+### Parameters: database handle, id3-genre.
+### Returns: GiantDisc genre code (or empty string, if no match found)
+sub genre_id3togd{
+ my ($dbh, $id3genre)= @_;
+
+ my $gdcode = "";
+ if(length($id3genre)>0 && $id3genre >= 0){
+ my $genseth = $dbh->prepare('select * from genre where id3genre="'. $id3genre . '" ');
+ my $nbgenres = $genseth->execute;
+ if ($nbgenres > 0){
+ my $genres = $genseth->fetchrow_hashref;
+ $gdcode = $genres->{id};
+ }
+ $genseth->finish;
+ }
+ #print("Translating id3:\"$id3genre\" to gdgenre \"$gdcode\" \n\n");
+ return $gdcode;
+}
+
+### Translates a genre string to a GiantDisc genre code (string)
+### Parameters: database handle, genre string.
+### Returns: GiantDisc genre code (or empty string, if no match found)
+sub genre_stringtogd{
+ my ($dbh, $genrestring)= @_;
+
+ my $gdcode = "";
+ if(length($genrestring)>0){
+ my $genseth = $dbh->prepare( # get best=shortest match
+ "SELECT id,length(genre) AS len FROM genre "
+ ."WHERE genre like \"%".$genrestring."%\" ORDER BY len");
+# 'select id, from genre where genre="'. $genrestring . '" ');
+ my $nbgenres = $genseth->execute;
+ if ($nbgenres > 0){
+ my $genres = $genseth->fetchrow_hashref;
+ $gdcode = $genres->{id};
+ }
+ $genseth->finish;
+ }
+ #print("Translating genrestring:\"$genrestring\" to gdgenre \"$gdcode\" \n\n");
+ return $gdcode;
+}
+
+
+
+###############################################################################
+
+sub iso2ascii{
+ # converts all non-ascii characters in the passed string as good
+ # as possible to ascii characters, and returns the result
+
+ my ($str) = @_;
+
+ $str =~ tr/\xc0-\xc6/A/;
+ $str =~ tr/\xc7/C/;
+ $str =~ tr/\xc8-\xcb/E/;
+ $str =~ tr/\xcc-\xcf/I/;
+ $str =~ tr/\xd0\xd1/DN/;
+ $str =~ tr/\xd2-\xd8/O/;
+ $str =~ tr/\xd9-\xdc/U/;
+ $str =~ tr/\xdd\xde\xdf/YTs/;
+
+ $str =~ tr/\xe0-\xe6/a/;
+ $str =~ tr/\xe7/c/;
+ $str =~ tr/\xe8-\xeb/e/;
+ $str =~ tr/\xec-\xef/i/;
+ $str =~ tr/\xf0\xf1/dn/;
+ $str =~ tr/\xf2-\xf8/o/;
+ $str =~ tr/\xf9-\xfc/u/;
+ $str =~ tr/\xfd\xfe\xff/yts/;
+
+ $str =~ tr/\xa0-\xff/_/;
+
+ return $str;
+}
+
+sub ascii2filename{
+ # converts all ascii characters in the passed string as good
+ # as possible to ascii characters that are allowed in filenames,
+ #and returns the result
+
+ my ($str) = @_;
+
+ $str =~ tr/\//-/; # translate / to -
+ $str =~ tr/\x2f/_/;
+ $str =~ s/"/''/g;
+
+ return $str;
+}
+
+###############################################################################
+
+
+1;
+#