#!/usr/bin/env perl ######################################################################## # # LICENSE # # Copyright (C) 2005 Felix Suwald # # # 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. # # ######################################################################## # # CODE # # ripit.pl - Rips CD Audio and encodes files in the following steps: # 1) Query CDDB data for album/artist/track info # 2) Rip the audio files from the CD # 3) Encode the wav files # 4) ID3 tag the encoded files # 5) Optional: Create a playlist (M3U) file. # 6) Optional: Prepares and sends a CDDB submission. # # Version 3.6.0 - June 16th 2007 - Felix Suwald, thanks for input: # C. Blank # G. Edwards # G. Ross # Version 3.5.0 - June 6th 2006 - Felix Suwald, credits to # E. Riesebieter (deb-package) # C. Walter (normalize option) # S. Warten (general support & loop) # S. Warten (signal handling) # Version 3.4 - December 3rd 2005 - Felix Suwald, credits to # M. Bright (infolog file) # M. Kaesbauer (lcdproc) and # C. Walter (config file). # Version 2.5 - November 13th 2004 - Felix Suwald # Version 2.2 - October 18th 2003 - Mads Martin Joergensen # Version 1.0 - February 16th 1999 - Simon Quinn # # ######################################################################## # # User configurable variables: # Keep these values and save your own settings # in the config file with option --save! # my $cddev = "/dev/cdrom";# Path of audio CD device. my $outputdir = ""; # Where the sound should go to. my $ripopt = ""; # Options for audio CD ripper. my $ripper = 1; # 0 - dagrab, 1 - cdparanoia, # 2 - cdda2wav, 3 - tosha, 4 - cdd. my @coder = (0); # 0 - Lame, 1 - Oggenc, 2 - Flac, # 3 - Faac, comma separated list. my $bitrate = 128; # Bitrate for lame, if --vbrmode used, # bitrate is equal to the -b option. my $maxrate = 0; # Bitrate for lame using --vbrmode, # maxrate is equal to the -B option. my @quality = (5,3,5,100);# Quality for lame in vbr mode (0-9), # best quality = 0, quality for oggenc # (1-10) best = 10; or compression # level for Flac (0-8), lowest = 0. my $qualame = 5; # my $qualoggenc= 3; # my $quaflac = 5; # my $quafaac = 100; # my $lameopt = ""; # my $oggencopt = ""; # my $flacopt = ""; # my $faacopt = ""; # my $lcd = 0; # Use lcdproc (1 yes, 0 no). my $chars = "off"; # Exclude special chars in file names. my $verbose = 3; # Normal output: 3, no output: 0, minimal # output: 1, normal without encoder msgs: 2, # normal: 3, verbose: 4, extremly verbose: 5 my $commentag = ""; # Comment ID3 tag. my $genre = ""; # Genre of Audio CD for ID3 tag. my $year = ""; # Year of Audio CD for ID3 tag. my $utftag = 1; # Keep Lame-tags in utf or decode them to # ISO8895-1 (1 yes, 0 no). my $eject = 0; # Eject the CD when done (1 yes, 0 no). my $ejectcmd = "eject"; # Command to use for eject my $ejectopt = "{cddev}"; # Options to above my $halt = 0; # Shutdown machine when finished # (1 yes, 0 no). my $nice = 0; # Set nice for encoding process. my $nicerip = 0; # Set nice for ripping process. my $savenew = 0; # Saved passed parameters to new config # file (1 yes, 0 no). my $savepara = 0; # Save parameters passed in config file # (1 yes, 0 no). my $config = 1; # Use config file to read paramaters # (1 yes, 0 no). my $submission= 1; # Send CDDB submission # (1 yes, 0 no). my $parano = 1; # Use paranoia mode in cdparanoia # (1 yes, 0 no). my $playlist = 1; # Do create the m3u playlist file # (1 yes, 0 no, 2 no full path in filename). my $resume = 0; # Resume a previously started session # (1 yes, 0 no). my $infolog = ""; # InfoLog # (filename) my $interaction= 1; # If 0 do not ask anything, take the 1st # CDDB entry found or use default names! # (1 yes, 0 no). my $underscore= 0; # Use _ instead of spaces in filenames # (1 yes, 0 no). my $lowercase = 0; # Lowercase filenames # (1 yes, 0 no). my $archive = 0; # Save CDDB files in ~/.cddb dir # (1 yes, 0 no). my $mirror = "freedb"; # The host (a freedb mirror) that # shall be used instead of freedb. my $transfer = "cddb"; # Transfer mode, cddb or http, will set # default port to 8880 or 80 (for http). my $vbrmode = ""; # Variable bitrate, only used with lame, # (new or old), see lame-manpage. my $proto = 6; # CDDB protocol level for CDDB query. my $proxy = ""; # Set proxy. my $CDDB_HOST = "freedb.org"; # Set cddb host my $mailad = ""; # Users return e-mail address. my @core = 1; # Number of encoding processes for each box my @sshlist = (); # List of remote machines. my $scp = 0; # Use scp to access files (1 yes, 0 no). my $local = 1; # Encode on locale machine (1 yes, 0 no). my $wav = 0; # Keep the wav files (1 yes, 0 no). my $encode = 1; # Encode the wav files (1 yes, 0 no). my $rip = 1; # Rip the CD files (1 yes, 0 no). my $cdtoc = 0; # Create a cd.toc for CDRDAO (1 yes, 0 no). my $loop = 0; # Restart ripit as soon as a new CD is # inserted. This option implies that the CD # is ejected (option --eject)! my $ghost = 0; # Check and extract ghost songs from all # tracks (1 yes, 0 no). my $prepend = 2; # Extend ghost songs by 2 sedonds at the # beginning. my $extend = 2; # Extend ghost songs by 2 sedonds at the # end. my $dpermission= "0777"; # Directory permissions. my $fpermission= "0644"; # Audio and text file permissions. my $md5sum = 0; # Generate MD5 sums for evry sound file # not deleted (1 yes, 0 no). my $multi = 0; # Not yet official. Do NOT use! # # # # Directory and track template variables: # Contains the format the track names will be written as. # The '" and "' are important and must surround the template. # Example variables to use are: $tracknum, $album, $artist, $genre, # $trackname or $year. # E.g. example setting of $tracktemplate produces a trackname of # "07 The Game - Swandive" . # $tracktemplate = '"$tracknum $trackname - $artist"'; # my $dirtemplate = '"$artist - $album"'; my $tracktemplate = '"$tracknum $trackname"'; # # # lcdproc settings: # my $lcdhost = "localhost"; my $lcdport = "13666"; my $lcdline1 = " [initializing] "; my $lcdline2 = " ripit-lcd-module "; my $lcdline3 = " 2005 "; my $lcdoline1 = ""; my $lcdoline2 = ""; my $lcdoline3 = ""; my $lcdproc; my $lcdtrackno = 0; # # # # normalize settings: # my $normalize = 0; # normalize CD, needs 'normalize' in $path my $normopt = "-bv"; # options for normalize my $subdir = "Unsorted"; # # ######################################################################## # # No User configurable parameters below here. # ######################################################################## # use strict; use Fcntl; use Getopt::Long qw(:config no_ignore_case); use IO::Socket; #use warnings; #use Color::Output; #Color::Output::Init; # # Initialize paths. # my $homedir = "$ENV{HOME}"; my $workdir = "$ENV{PWD}"; my $usename = "$ENV{USER}"; my $hostnam = "$ENV{HOSTNAME}"; my $charset = "$ENV{G_FILENAME_ENCODING}"; if($charset =~ /UTF-8/) { $charset = "UTF-8"; } elsif($charset =~ /ISO-8859-15/) { $charset = "ISO-8859-15"; } else { $charset = "ISO-8859-1"; } #print ($_,$ENV{$_},"\n") foreach (keys %ENV); # # Initialise global variables. # my $categ = ""; # CDDB category my $cddbid = 0; # Needed in several subroutines my $lameflag = 0; # Flag to check if lame is used. my $trackselection = ""; # Passed from command line my @tracksel = (); # Array of all track numbers, including # those not explicitly stated. my @seltrack = (); # Array of all track numbers, including # those not explicitly stated and ghost # songs found by ripper needed by encoder. my @framelist = (); # Needed in several subroutines my @secondlist = (); # Needed in several subroutines my @tracklist = (); # Needed in several subroutines my @tracktags = (); # Needed in several subroutines my %cd = (); # Hash of all CD-data. my $cddbsubmission = 2; # If zero then data for CDDB submission is # incorrect, if 1: submission OK, if 2: CDDB # entry not changed (edited) my $wpreset = ""; # Preset written into config file. my $wcoder = ""; # Use a comma separated string to write the # coder-array into the config file. my $wcore = ""; # Use a comma separated string to write the # core-array into the config file. my $wsshlist = ""; # As above for the list of remote machines. my $sshflag = 0; # Ssh encoding OK if sshflag == 1. my %sshlist = (); # Hash of remote machines. my $version = "3.6.0"; my $hiddenflag = 0; my $logpath = ""; # Used with not *to-use* option --multi, # is one level below sound-dir. my $logfile = ""; # Used with not *to-use* option --multi, # is the "device-log"-file in `pwd`. my $help = 0; # Print help and exit if 1. my $printver = 0; # Print version and exit if 1. my @delname = (); # List of tracknames being processed, i.e. # ready for deletion. my @skip = (); # List of merged tracks. # # # Initialize subroutines without (). # sub check_bitrate; sub check_cddev; sub check_chars; sub check_lame; sub check_vbrmode; sub choose_genre; sub extract_comm; sub get_rev; sub init_mod; sub init_var; sub lame_preset; sub main_sub; sub skip_tracks; # # # Define the variables which catch the command line input. # The p stands for passed (from command line). my ( $parchive, $pbitrate, $pmaxrate, $PCDDB_HOST, $pcddev, $pcdtoc, @pcoder, $pcommentag, $pconfig, $pdirtemplate, $ptracktemplate, $peject, $pencode, $pfaacopt, $pflacopt, $plameopt, $poggencopt, $pgenre, $phalt, $pinfolog, $pinteraction, $plcdhost, $plcdport, $plcd, $plocal, $ploop, $plowercase, $pmirror, $pmailad, $pmulti, $pnice, $pnormalize, $pnormopt, $poutputdir, $pparano, $pplaylist, $ppreset, $pproto, $pproxy, @pquality, $pripopt, $prip, $pripper, $psavenew, $psavepara, $pscp, @psshlist, $psubdir, $psubmission, $ptransfer, $punderscore, $putftag, $pvbrmode, $pverbose, $pwav, $pyear, $presume, $pmerge, $pghost, $pprepend, $pextend, $pejectopt, $pejectcmd, $pdpermission, $pfpermission, $pmd5sum, $pnicerip, @pcore, ); # # Get the parameters from the command line. # available: A E F IjJkK O Q X Y # already used: a bBcCdDe f gGhi lLmMnNo pPq rRsStTuUvVwWx y zZ # GetOptions( "archive|a!" => \$parchive, "bitrate|b=s" => \$pbitrate, "maxrate|B=i" => \$pmaxrate, "chars|W:s" => \$chars, "cddbserver|C=s" => \$PCDDB_HOST, "cdtoc=i" => \$pcdtoc, "config!" => \$pconfig, "coder|c=s" => \@pcoder, "comment=s" => \$pcommentag, "core=s" => \@pcore, "device|d=s" => \$pcddev, "dirtemplate|D=s" => \$pdirtemplate, "eject|e!" => \$peject, "ejectcmd=s" => \$pejectcmd, "ejectopt=s" => \$pejectopt, "encode!" => \$pencode, "extend=i" => \$pextend, "faacopt=s" => \$pfaacopt, "flacopt=s" => \$pflacopt, "lameopt=s" => \$plameopt, "oggencopt=s" => \$poggencopt, "genre|g=s" => \$pgenre, "ghost|G!" => \$pghost, "halt" => \$phalt, "help|h" => \$help, "infolog=s" => \$pinfolog, "interaction|i!" => \$pinteraction, "lcd!" => \$plcd, "lcdhost=s" => \$plcdhost, "lcdport=s" => \$plcdport, "lowercase|l!" => \$plowercase, "local!" => \$plocal, "loop!" => \$ploop, "md5sum!" => \$pmd5sum, "merge=s" => \$pmerge, "mirror|m=s" => \$pmirror, "mail|M=s" => \$pmailad, "multi" => \$pmulti, "nice|n=s" => \$pnice, "nicerip=s" => \$pnicerip, "normalize|N!" => \$pnormalize, "normopt|z=s" => \$pnormopt, "subdir=s" => \$psubdir, "outputdir|o=s" => \$poutputdir, "dpermission=s" => \$pdpermission, "fpermission=s" => \$pfpermission, "playlist|p:s" => \$pplaylist, "prepend=i" => \$pprepend, "preset|S=s" => \$ppreset, "proxy|P=s" => \$pproxy, "protocol|L=i" => \$pproto, "quality|q=s" => \@pquality, "resume|R" => \$presume, "rip!" => \$prip, "ripper|r=s" => \$pripper, "ripopt=s" => \$pripopt, "savenew" => \$psavenew, "save" => \$psavepara, "scp" => \$pscp, "sshlist=s" => \@psshlist, "submission|s!" => \$psubmission, "tracktemplate|T=s" => \$ptracktemplate, "transfer|t=s" => \$ptransfer, "underscore|u!" => \$punderscore, "utftag|U!" => \$putftag, "vbrmode|v=s" => \$pvbrmode, "verbose|x=i" => \$pverbose, "version|V" => \$printver, "year|y=i" => \$pyear, "wav|w!" => \$pwav, "disable-paranoia|Z" => \$pparano, ) or exit print_usage(); # # Evaluate the command line parameters if passed. We need to do it this # way, because passed options have to be saved (in case user wants to # save them in the config file) before config file is read to prevent # overriding passed options with options from config file. The passed # options shall be stronger than the config file options! # Problems arrise with options that can be zero. Because a usual if-test # can not distinguish between zero or undef, use the defined-test! # # First for the normal options, e. g. options which are never zero. # # Check for array @coder will be done in the subroutine! $faacopt = $pfaacopt if($pfaacopt); $flacopt = $pflacopt if($pflacopt); $lameopt = $plameopt if($plameopt); $oggencopt = $poggencopt if($poggencopt); $oggencopt = " " unless($oggencopt); # Oops, only to prevent warnings... $CDDB_HOST = $PCDDB_HOST if($PCDDB_HOST); $cddev = $pcddev if($pcddev); # Get the default "no-argument" values. $chars = ":*#?\$!" if($chars eq ""); # On the other hand, clean the initialization if operator # wants to save the the default "no-argument" values. if(defined $psavenew) { $chars = "" if($chars eq "off" && $psavenew == 1); } $commentag = $pcommentag if($pcommentag); $dirtemplate = $pdirtemplate if($pdirtemplate); $tracktemplate = $ptracktemplate if($ptracktemplate); $halt = $phalt if($phalt); $infolog = $pinfolog if($pinfolog); $lcdhost = $plcdhost if($plcdhost); $lcdport = $plcdport if($plcdport); $mailad = $pmailad if($pmailad); $mirror = $pmirror if($pmirror); $normopt = $pnormopt if($pnormopt); $outputdir = $poutputdir if($poutputdir); my $preset = $ppreset if($ppreset); $ripopt = $pripopt if($pripopt); $dpermission = $pdpermission if($pdpermission); $fpermission = $pfpermission if($pfpermission); $proto = $pproto if($pproto); $proxy = $pproxy if($pproxy); # Check for variable $psshlist will be done in the subroutine! # Check for variable $pcore will be done in the subroutine! $transfer = $ptransfer if($ptransfer); $vbrmode = $pvbrmode if($pvbrmode); $year = $pyear if($pyear); # # Options which might be zero. $bitrate = $pbitrate if($pbitrate); $cdtoc = $pcdtoc if defined $pcdtoc; $extend = $pextend if defined $pextend; $genre = $pgenre if defined $pgenre; $md5sum = $pmd5sum if defined $pmd5sum; $maxrate = $pmaxrate if defined $pmaxrate; $nice = $pnice if defined $pnice; $nicerip = $pnicerip if defined $pnicerip; $parano = 0 if defined $pparano; $playlist = $pplaylist if defined $pplaylist; $playlist = 1 if($playlist eq ""); $prepend = $pprepend if defined $pprepend; $resume = $presume if defined $presume; $ripper = $pripper if defined $pripper; $savepara = $psavepara if defined $psavepara; $savenew = $psavenew if defined $psavenew; $scp = $pscp if defined $pscp; $verbose = $pverbose if defined $pverbose; # # And for the negatable options. $archive = $parchive if defined $parchive; $config = $pconfig if defined $pconfig; $encode = $pencode if defined $pencode; $eject = $peject if defined $peject; $ejectcmd = $pejectcmd if defined $pejectcmd; $ejectopt = $pejectopt if defined $pejectopt; $ghost = $pghost if defined $pghost; $interaction = $pinteraction if defined $pinteraction; $lcd = $plcd if defined $plcd; $local = $plocal if defined $plocal; $loop = $ploop if defined $ploop; $lowercase = $plowercase if defined $plowercase; $multi = $pmulti if defined $pmulti; $normalize = $pnormalize if defined $pnormalize; $rip = $prip if defined $prip; $submission = $psubmission if defined $psubmission; $underscore = $punderscore if defined $punderscore; $utftag = $putftag if defined $putftag; $wav = $pwav if defined $pwav; # # # To have the version printed first of all other (warning-) messages, # find out if verbosity is set off or not, either by command line or # by config file. # my $ripdir = $homedir."/.ripit/config"; if(-r "$ripdir" && $config == 1) { open(CONF, "$ripdir") || print "Can not read config file!\n"; my @conflines = ; close CONF; my @verbose = grep(s/^verbose=//, @conflines); $verbose = $verbose[0] unless defined $pverbose; chomp $verbose; } # print "\n\n\nRipIT version $version.\n" if($verbose >= 1); log_info("RipIT version $version infolog-file.\n"); # # # Then, before doing anything, check if the necessary modules are # installed properly. # init_mod; # # # Do some checks before writing a new config file (if wanted): # # First check if arguments of option merge are OK. my @dummy = skip_tracks if($pmerge); # # Then the options that will be written to the config file. if($help ne 1 && $printver ne 1) { check_coder(); # Check encoder array. check_quality(); # Check if quality is valid. check_proto(); # Check if protocol level is valid. check_sshlist(); # Check list of remote machines. check_preset() if($preset); # Check preset settings. # # To be able to save a new config file we have to write it before # reading the parameters not yet passed from the config file. # if($savenew == 1) { save_config(); print "Saved a new config file!\n\n" if($verbose >= 3); } # # Read the config file. # read_config() if($config == 1); check_lame; # The xxx enables reading this value, but should be switched off # before writing! $chars = "" if($chars eq "xxx" || $chars eq "off"); # # Security check for new options: give them default value if empty. # This can happen, if the config file is not yet up-to date. # This will go away again in version 3.8. This is also done to prevent # warnings. # $maxrate = 0 if($maxrate eq "" && !defined $pmaxrate); $normalize = 0 unless($normalize); $cdtoc = 0 unless($cdtoc); $ghost = 0 unless($ghost); $extend = 2 unless($extend); $prepend = 2 unless($prepend); $md5sum = 0 unless($md5sum); $nicerip = 0 unless($nicerip); $ejectopt = "{cddev}" unless($ejectopt); $ejectcmd = "eject" unless($ejectcmd); $bitrate = "off" unless($bitrate); $dpermission = "0777" unless($dpermission); $fpermission = "0644" unless($fpermission); @core = 1 unless($core[0]); # # Save the config file. # save_config() if($savepara == 1); print "Updated the config file!\n\n" if($verbose >= 3 && $savepara == 1); check_coder(); # Check it again for lame cbr vs vbr. check_sshlist(); # Check it again to create the hash. } # # ######################################################################## # # MAIN ROUTINE # ######################################################################## # if($printver) { print "\n"; exit 2; } if($help == 1) { print_help(); exit 3; } if(!$pcddev) { # Check CD dev if none defined. check_cddev; } else { # Close the tray. my $closeopt = $cddev if($ejectopt eq '{cddev}'); $closeopt = "-t " . $closeopt if($ejectcmd =~ /^eject$/); $closeopt = $closeopt . " close" if($ejectcmd =~ /cdcontrol/); log_system("$ejectcmd $closeopt") if($multi == 0); } if($chars) { check_chars; } if($lcd == 1) { # lcdproc init_lcd(); } if($outputdir eq "") { $outputdir = $homedir; chomp $outputdir; } if($outputdir =~ /^\.\//){ # Change relative paths to full ones. $outputdir =~ s/^\./$workdir/; } if($outputdir =~ /^~\//){ # If one wrote ~ in the config file. $outputdir =~ s/^~/$homedir/; } $poutputdir = $outputdir; # Keep a virgin copy of that var. if(length($year) > 0 && length($year) != 4 ) { print STDERR "Warning: year is not Y2K compliant - $year\n" if($verbose >= 1); } if($halt == 1 && $verbose >= 2) { print "Will halt machine when finished.\n"; } if($eject == 1 && $verbose >= 2) { print "Will eject CD when finished.\n"; $ejectcmd = "eject -v" if($ejectcmd =~ /eject/ && $verbose >=4 ); } if($cdtoc >= 1 && $verbose >= 2) { print "Will create a cd.toc file.\n"; } if($ghost == 1) { print "Will analyze tracks for ghost songs.\n"if($verbose >= 2); } if($playlist >= 1 && $verbose >= 2) { print "Will create a playlist file.\n"; } if($resume == 1 && $verbose >= 2) { print "Will try to resume previous session.\n"; } if($utftag == 0 && $verbose >= 2 && "@coder" =~ /0/) { print "Will change encoding of Lame-tags to ISO8859-1.\n"; } if($wav == 1 && $verbose >= 2) { print "Will keep the wav files.\n"; } if($normalize == 1 && $verbose >= 2) { print "Will normalize the CD.\n"; } if($md5sum == 1 && $verbose >= 2) { print "Will generate MD5SUMs of sound files.\n"; } if($pdpermission && $verbose >= 2) { $dpermission = sprintf("%04d", $dpermission); print "Will directory permission to $dpermission.\n"; } if($pfpermission && $verbose >= 2) { $fpermission = sprintf("%04d", $fpermission); print "Will set file permission to $fpermission.\n"; } if($loop) { print "Will loop and eject CD.\n" if($verbose >= 2); $eject = 1; while(1) { main_sub; init_var; } } else { main_sub; } ######################################################################## # # Main subroutine. # sub main_sub{ if(@ARGV) { $trackselection = $ARGV[0]; } if($bitrate ne "off" && $lameflag == 1) { check_bitrate; } if($vbrmode ne "" && $lameflag == 1) { check_vbrmode; } if($preset) { lame_preset; } unless( cd_present() ) { print "\nPlease insert an audio CD!\n"; while ( not cd_present() ) { check_cddev; sleep(6); } } get_cdinfo(); create_seltrack($trackselection); create_dirs(); if($normalize == 1) { &rip_cd(); &norm_cd(); &enc_cd(); } else { &rip_cd(); } if($eject == 1) { my $ejectopt = $cddev if($ejectopt eq '{cddev}'); $ejectopt = $ejectopt . " eject" if($ejectcmd =~ /cdcontrol/); log_system("$ejectcmd $ejectopt"); } if($verbose >= 1 && $encode == 1) { print "\nWaiting for encoder to finish...\n\n"; } if($sshflag == 1) { del_wav(); } else { wait; } if($playlist >= 1 && $encode == 1) { create_m3u(); } my ($riptime,$enctime,$encend,$ghostnote,$splitnote) = cal_times(); del_erlog(); if(-r "$outputdir/error.log") { if($verbose >= 1) { print "\nCD may NOT be complete! ", "Check the error.log in $outputdir!\n"; } elsif($verbose >= 3){ print "\nRipping needed $riptime min and encoding needed ", "$enctime min.\n\n"; } } else { if($verbose >= 1) { print "\nAll complete!\n"; if($ghost == 1){ print "No ghost songs found!\n" if($ghostnote =~ /0/); print "Ghost song(s) found!\n" if($ghostnote =~ /1/); print "No tracks trimmed!\n" if($splitnote =~ /0/); print "Track(s) trimmed!\n" if($splitnote =~ /1/); } print "Ripping needed $riptime min and "; print "encoding needed $enctime min.\n\n"; } } log_info("\nRipping needed $riptime minutes."); log_info("Encoding needed $enctime minutes."); if($lcd == 1) { # lcdproc $lcdline1 = " "; $lcdline2 = " RipIT finished "; $lcdline3 = " "; ulcd(); close($lcdproc) || die "close: $!"; } if($halt == 1 && $verbose >= 1) { print "\nShutdown PC...\n"; log_system("shutdown -h now"); } if($multi == 1) { open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!"; print SRXY "\nEncoding ended: $encend"; print SRXY "\nRipping needed: $riptime min."; print SRXY "\nEncoding needed: $enctime min."; close(SRXY); my $cddevno = $cddev; $cddevno =~ s/\/dev\///; open(SUCC,">>$logpath/success.log") or print "Can not append to file \"$logpath/succes.log\"!"; print SUCC "$cd{artist};$cd{title};$genre;$categ;$cddbid;"; print SUCC "$cddevno;$hostnam;$riptime;$enctime\n"; close(SUCC); $cddev =~ s/\/dev\//device /; $cddev = $cddev . " " unless($cddev =~ /\d\d$/); print "Encoding done in $cddev with: $cd{artist} - $cd{title}.\n"; print "Ghost song(s) found!\n" if($ghostnote =~ /1/ && $ghost == 1); print "Track(s) trimmed!\n" if($splitnote =~ /1/ && $ghost == 1); # cprint("\x037Encoding done in $cddev with: $cd{artist} - $cd{title}.\x030"); # cprint("\x033\nGhost song(s) found!\x030") if($ghostnote =~ /1/ && $ghost == 1); # cprint("\x033\nTrack(s) trimmed!\x030") if($splitnote =~ /1/ && $ghost == 1); } log_info("*" x 72, "\n"); print "\n"; # rename("$infolog","$outputdir/info.log") if($infolog && $loop); exit unless($loop); } ######################################################################## # # SUBROUTINES # ######################################################################## ######################################################################## # # Read the album, artist, discID and track titles from the get_CDDB() # generated TOC file. # sub get_cdinfo { CDDB_get->import( qw( get_cddb get_discids ) ); my ($artist, $album, %config, $revision); my ($CDDB_INPUT, $CDDB_MODE, $CDDB_PORT); my @comment = (); if($pgenre) { $genre = $pgenre; } else { $genre = ""; } if($pyear) { $year = $pyear; } else { $year = ""; } # Get ID and number of tracks of CD. my $cd = get_discids($cddev); my ($id, $tracks, $toc) = ($cd->[0], $cd->[1], $cd->[2]); $cddbid = sprintf("%08x", $id); my $usearch = "x"; my @categs = (); if($archive == 1 && $multi == 0) { print "\nChecking for a local DB entry, please wait...\n\n" if($verbose >= 1); log_system("mkdir -m 0777 -p $homedir/.cddb") or die "Can not create directory $homedir/.cddb: $!\n"; opendir(CDDB, "$homedir/.cddb/") or print "Can not read in $homedir/.cddb: $!"; @categs = grep {/\w/i} readdir(CDDB); close CDDB; my @cddbid = (); foreach (@categs) { if(-d "$homedir/.cddb/$_") { opendir(CATEG, "$homedir/.cddb/$_") or print "Can not read in $homedir/.cddb: $!"; my @entries = grep {/$cddbid/} readdir(CATEG); close CATEG; push @cddbid, $_ if($entries[0]); } elsif(-f "$homedir/.cddb/$_" && -s "$homedir/.cddb/$_") { push @cddbid, $_ if($_ =~ /$cddbid/); } } my $count = 1; my @dirflag = (); if($cddbid[0]) { print "Found local entry $cddbid in $homedir/.cddb !\n" if($interaction == 1); print "This CD could be:\n\n" if($interaction == 1); foreach (@cddbid) { my $openflag = "no"; if(-s "$homedir/.cddb/$_/$cddbid") { open(LOG, "$homedir/.cddb/$_/$cddbid"); $openflag = "ok"; $dirflag[$count-1] = 1; } elsif(-s "$homedir/.cddb/$cddbid") { open(LOG, "$homedir/.cddb/$cddbid"); $_ = "no category found!"; $openflag = "ok"; $dirflag[$count-1] = 0; } if($openflag eq "ok") { my @loglines = ; close(LOG); # Here we should test if @loglines is a good entry! # If there are empty files, we get warnings! my @artist = grep(s/^DTITLE=//, @loglines); $artist = clean_all($artist[0]); chomp $artist; my @genre = grep(s/^DGENRE=//, @loglines); my $agenre = $genre[0]; $agenre =~ s/[\015]//g; chomp $agenre; print "$count: $artist (genre: $agenre, category: $_)\n" if($interaction == 1); $count++; $agenre = ""; } } print "\n0: Use online freeDB instead!\n" if($interaction == 1); if($interaction == 0) { $usearch = 1; } else { while($usearch !~ /\d/ || $usearch >= $count) { print "\nChoose: (1) "; $usearch = ; chomp $usearch; $usearch = 1 if($usearch eq ""); } } } else { $usearch = 0; } if($usearch != 0) { if($dirflag[$usearch-1] == 1) { read_entry("$homedir/.cddb/$cddbid[$usearch-1]/$cddbid", $cddbid[$usearch-1],$tracks); } elsif($dirflag[$usearch-1] == 0) { read_entry("$homedir/.cddb/$cddbid", $cddbid[$usearch-1],$tracks); } } } else { $usearch = 0; } if($usearch == 0) { print "\nChecking for a DB entry \@ $mirror.$CDDB_HOST...\n" if($verbose >= 1); #Configure CDDB_get parameters $config{CDDB_HOST} = $mirror . "." . $CDDB_HOST; while($transfer !~ /^cddb$|^http$/) { print "Transfer mode not valid!\n"; print "Enter cddb or http : "; $transfer = ; chomp $transfer; } if($transfer eq "cddb") { $CDDB_PORT = 8880; $CDDB_MODE = "cddb"; } elsif($transfer eq "http") { $CDDB_PORT = 80; $CDDB_MODE = "http"; } $config{CDDB_MODE} = $CDDB_MODE; $config{CDDB_PORT} = $CDDB_PORT; $config{CD_DEVICE} = $cddev; $config{HTTP_PROXY}= $proxy if($proxy); if($interaction == 0) { $CDDB_INPUT = 0; } else { $CDDB_INPUT = 1; } $config{input} = $CDDB_INPUT; $config{PROTO_VERSION} = $proto; # Change to whatever, but be aware to enter exactly 4 words! # E.g. username hostname clientname version my $hid = "RipIT www.suwald.com/ripit/ripit.html RipIT $version"; my @hid = split(/ /, $hid); if($hid[4]) { print "There are more than 4 words in the \"HELLO_ID\"!\n"; print "The handshake with the freeDB-server will fail!\n\n"; } $config{HELLO_ID} = $hid; eval {%cd = get_cddb(\%config);}; if($@) { print "No connection to internet? The error message is:\n"; $@ =~ s/at\s\//at\n\//; print "$@\$! is <$!> and \$? is <$?>\n" if($verbose >= 1); $submission = 0; } } # Write CDDB entry to ~/.cddb/category if there is not already # an entry present (then $usearch != 0). if($archive == 1 && $usearch == 0 && defined $cd{title}) { $categ = $cd{cat}; chomp $categ; log_system("mkdir -m 0777 -p $homedir/.cddb/$categ/") or die "Can not create directory $homedir/.cddb/$categ: $!\n"; open(TOC, "> $homedir/.cddb/$categ/$cddbid") or print "Can not write to $homedir/.cddb/$categ/$cddbid: $!"; foreach (@{$cd{raw}}) { print TOC $_; } close TOC; } if($multi == 1) { my @devnameblock = split(/\//, $cddev); $logfile = $workdir . "/" . $devnameblock[$#devnameblock]; read_entry($logfile); # This has nothing to do here, but has to be # done somewhere, because the bash is not able doing it. open(LOG, "$workdir/settings.log") or print "Can not open settings.log!\n"; my @settinglines = ; close(LOG); my @commentagno=grep(s/^comment=//g, @settinglines); my $commentagno=$commentagno[0]; $commentag="Rip2media" if($commentagno == 1); $commentag="Respect ©!" if($commentagno == 2); } if(defined $cd{title}) { $album = clean_all($cd{title}); $artist = clean_all($cd{artist}); $categ = $cd{cat}; # Set the year if it wasn't passed on command line. unless($year) { $year = $cd{year} if($cd{year}); $year =~ s/[\015]//g; } # Set the genre if it wasn't passed on command line. if(!defined $pgenre && defined $cd{genre}) { $genre = $cd{genre}; $genre =~ s/[\015]//g; } @comment = extract_comm; $revision = get_rev; } else { # # We don't want tracks to be defined globally. Hack it into the # # cd-hash! Note that it won't exist afterwards! # %cd = (track => $tracks); if($submission == 0) { print "\nNo CDDB info choosen or found for this CD\n" if($verbose >= 1); } # Set submission OK, will be set to zero if default # names are used. $cddbsubmission = 1; # Don't ask for default settings, use them! ... if($interaction == 0) { create_deftrack(1); } # ... or ask whether 1) default or 2) manual entries # shall be used or entered. else { create_deftrack(2); } $album = $cd{title}; $artist = $cd{artist}; $revision = $cd{revision}; } my $genreno = ""; if($genre eq "" && $interaction == 1) { print "\nPlease enter a valid CDDB genre (or none): "; $genre = ; chomp $genre; $cd{genre} = $genre; } if($genre) { $genre =~ s/[\015]//g; ($genre,$genreno) = check_genre($genre); } # What if someone wants underscore and/or lowercase in filenames # and has genre in the dir or tracktemplate? So we have to # lowercase genre and underscore it if it is a 2 word expression. if(($dirtemplate =~ /\$genre/ or $tracktemplate =~ /\$genre/) && $genre ne "") { my $tempgenre = $genre; $tempgenre = lower_case($tempgenre) if($lowercase == 1); if($genre =~ /\w\s\w/ && $underscore == 1) { $tempgenre =~ s/ /_/g; # Do it the hard way: assume we have the definitive # genre, so replace the string "$genre" with the # value of $tempgenre! $dirtemplate =~ s/\$genre/$tempgenre/g; $tracktemplate =~ s/\$genre/$tempgenre/g; } } if($verbose >= 1) { print "\n-----------------"; print "\nCDDB and tag Info"; print "\n-----------------\n"; print "Artist: $artist\n"; print "Album: $album\n"; print "Category: $categ\n" if($verbose >= 2); if($genre) { print "ID3-Genre: $genre ($genreno)\n" if($lameflag >= 0); print "Genre-tag: $genre\n" if($lameflag == -1); if(lc($genre) ne lc($cd{'genre'})) { print "CDDB-Genre: $cd{genre}\n"; } } else{ print "ID3-Genre: none\n"; } print "Year: $year\n"; print "Revision: $revision\n" if($verbose >= 2); # It happens, that the ID from CDDB is NOT # identical to the ID calculated from the # frames of your CD... if($cddbid ne $cd{id} && defined $cd{id} ) { print "CDDB id: $cd{id}\n"; } print "CD id: $cddbid\n"; if(@comment && $verbose >= 2) { foreach (@comment) { print "Comment: $_\n" if($_); } } print "\n"; } log_info("\nArtist: $artist"); log_info("Album: $album"); log_info("ID3-Genre: $genre ($genreno)") if($genre); log_info("ID3-Genre: none") unless($genre); log_info("Category: $categ"); log_info("CD id: $cddbid\n"); # Read out pregap before calculating track lengths. my $frames = $toc->[0]->{'frames'}; push @framelist, "$frames"; if ($frames > 400) { my $second = int($frames/75); my $frame = $frames - $second * 75; my $minute = int($second/60); $second = $second - $minute * 60; printf("%s %02d:%02d %s\n", "There might be a hidden track", $minute, $second, "long,\nbecause offset of track 01 has ", $frames, " frames intstead of typically 150 (2s).\n") if($verbose >= 1); my $riptrackname = "Hidden Track"; $riptrackname = lower_case($riptrackname) if($lowercase == 1); $riptrackname =~ s/ /_/g if($underscore == 1); printf("%s: [%02d:%02d.%02d] %s\n", "00", $minute, $second, $frame, $riptrackname) if($verbose >= 1); $second = int($frames/75); $hiddenflag = 1 if($trackselection eq "" || $trackselection =~ /^0/ || $trackselection =~ /\D0/); # We can't add this track to seltrack and framelist, because this # would break (re-) submission of CDDB. # Note: seltrack is not yet defined... But we start to fill the # @secondlist array (yet empty) with track lengths in seconds. # TODO: push the pregap seconds to the list in any case, then we # don't need to differentiate between the case hiddenfalg==1 or # hiddenflag == 0 while choosing tracknames. push @secondlist, "$second" if($hiddenflag == 1); } my $n = 1; # Print track infos. foreach (@{$cd{track}}) { $_ = clean_all($_); push @tracktags, $_; # Get frames and total time. my $frames = $toc->[$n]->{'frames'}; push @framelist, "$frames"; $frames = $frames - $framelist[$n-1]; my $second = int($frames/75); push @secondlist, "$second"; my $frame = $frames - $second * 75; my $minute = int($second/60); $second = $second - $minute * 60; $_ = clean_chars($_) if($chars); printf("%02d: [%02d:%02d.%02d] %s\n", $n, $minute, $second, $frame, $_) if($verbose >= 2); $_ = clean_name($_); $_ = lower_case($_) if($lowercase == 1); $_ =~ s/ /_/g if($underscore == 1); push @tracklist, $_; $n++; } print "\n\n" if($verbose >= 1); # Some more error checking. if($artist eq "") { die "ERROR: No Artist Found!\n"; } # lcdproc if($lcd == 1){ $lcdline1 = $artist . "-" . $album; $lcdline2 = "R00|00.0%|----------"; $lcdline3 = "E00|00.0%|----------"; ulcd(); } } ######################################################################## # # Create the track selection from the parameters passed on the command- # line, i. e. create an array with all track numbers including those not # explicitly stated at the command line. # sub create_seltrack { my($tempstr,$intrack); ($tempstr) = @_; if($_[0] eq "-") { die "Invalid track selection \"-\"!\n\n"; } if(($tempstr =~ /,/) || ($tempstr =~ /\-/)) { my @intrack = split(/,/ , $tempstr); # If last character is a , add an other item with a - if($tempstr =~ /,$/) { push @intrack, ($intrack[$#intrack]+1) . "-"; } foreach $intrack (@intrack) { if($intrack =~ /\-/) { my @outrack = split(/-/ , $intrack); # If last character is a -, add last track to $outrack if($#outrack == 0) { $outrack[1] = $#tracklist + 1; } for(my $i = $outrack[0]; $i <= $outrack[1]; $i++) { push @seltrack, $i; } } else { push @seltrack, $intrack; } } } elsif($tempstr eq '') { for(my $i = 1; $i <= ($#tracklist + 1); $i++) { $seltrack[$i - 1] = $i; } } elsif($tempstr =~ /^[0-9]*[0-9]$/) { $seltrack[0] = $tempstr; } else { die "Track selection invalid!\n"; } # Sort the tracks in order, perl is so cool :-) @seltrack = sort {$a <=> $b} @seltrack; # Check the validity of the track selection. foreach (@seltrack) { if($_ > ($#tracklist + 1)) { die "Track selection higher than number of tracks on CD.\n\n"; } elsif($_ == 0) { shift @seltrack; } } } ######################################################################## # # Ask if CDDB submission shall be done. Either because one might change # some settings a last time before writing to directories and files (if # there was not DB entry and operator entered all by hand) or because # DB entry has some typos! # Then create the directory where the sound files shall go. # sub create_dirs { # Directory created will be: /outputdir/$dirtemplate my $index = 2; unless($cddbsubmission == 0 || $interaction == 0) { while($index !~ /^[0-1]$/) { print "\nDo you want to edit or submit the CDDB entry?"; print "\nTo confirm each question type Enter!\n\n"; print "1: Yes, and I know about the naming-rules of "; print "freedb.org!\n\n"; print "0: No\n\nChoose [0-1]: (0) "; $index = ; chomp $index; if($index eq "") { $index = 0; } print "\n"; } if($index == 1) { my $revision = get_rev(); if($revision) { print "\nPlease change some settings!"; print "\nYou may confirm CDDB settings with Enter!\n"; create_deftrack(0); } } elsif($index != 0) { print "You should choose 0 or 1!\n"; } } if($index == 1) { pre_subm(); } my $album = clean_all($cd{title}); my $artist = clean_all($cd{artist}); $album = clean_name($album); $artist = clean_name($artist); $album = clean_chars($album) if($chars); $artist = clean_chars($artist) if($chars); $album =~ s/ /_/g if($underscore == 1); $artist =~ s/ /_/g if($underscore == 1); # Check and create the full path "outputdir" where the files # will go. First Check the dirtemplate and use the actual year # as default if $year is in the template and none is given! if(($dirtemplate =~ /\$year/ || $tracktemplate =~ /\$year/) && $year eq "") { $year = sprintf("%04d", sub {$_[5]+1900}->(localtime)); } if(($dirtemplate =~ /\$genre/ || $tracktemplate =~ /\$genre/) && $genre eq "") { $genre = "Other"; chomp $genre; } my $dir; if(!eval("\$dir = $dirtemplate")) { die "Directory Template incorrect, caused eval to fail: $!"; } $dir = lower_case($dir) if($lowercase == 1); $dir = clean_chars($dir) if($chars); $dir =~ s/ /_/g if($underscore == 1); $outputdir = $outputdir . "/" . $dir; # Delete ending . in directory name if no special characters # wanted! $outputdir =~ s/[.]+$// if($chars); # Check if the outputdir alread exists, if it does, try # "outputdir i" with i an integer until it works, unless we resume. my $cdexistflag = 0; my $i = 1; my $noutputdir = $outputdir; while(defined(opendir(TESTDIR, $noutputdir)) && $rip == 1 && $resume == 0) { my $sfx = " " . $i if($underscore == 0); $sfx = "_" . $i if($underscore == 1); $sfx = clean_chars($sfx) if($chars); $noutputdir = $outputdir . $sfx; $i++; $cdexistflag = 1; } if($multi == 1) { my @logpath = split(/\//, $noutputdir); my $aadir = pop(@logpath); if($cdexistflag == 1){ open(SRXY,"$logfile") or print "Can not open \"$logfile\"!\n"; my @srxylines = ; close(SRXY); grep(s/^album:\s(.*)$/album: $1 $i/, @srxylines) if($underscore == 0); grep(s/^album:\s(.*)$/album: $1_$i/, @srxylines) if($underscore == 1); open(SRXY,">$logfile") or print "Can not write to file \"$logfile\"!\n"; print SRXY @srxylines; close(SRXY); } open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!\n"; print SRXY "\nArtist - Album:$aadir"; close(SRXY); pop(@logpath); $logpath = join('/', @logpath); } $outputdir = $noutputdir . "/"; if(!opendir(TESTDIR, $outputdir)) { # Explicitly log outputdir creation. log_info("new-mediadir: $outputdir"); log_system("mkdir -m $dpermission -p \"$outputdir\"") or die "Can not create directory $outputdir: $!\n"; } else { closedir(TESTDIR); } } ######################################################################## # # Create the full-path track file name from the tracktemplate variable. # sub get_trackname { my($trnum,$trname,$riptrname); ($trnum,$trname) = @_; my $album = clean_all($cd{title}); my $artist = clean_all($cd{artist}); $album = clean_name($album); $artist = clean_name($artist); $album = clean_chars($album) if($chars); $artist = clean_chars($artist) if($chars); $album =~ s/ /_/g if($underscore == 1); $artist =~ s/ /_/g if($underscore == 1); # Create the full file name from the track template, unless # the disk is unknown. if(defined $cd{title}) { # We do not need to lowercase the tracktemplate, because # all variables in are already lowercased! $tracktemplate =~ s/ /\\_/g if($underscore == 1); # We have to update tracknum and trackname because they # are evalueted by the tracktemplate! my $tracknum = sprintf("%02d", $trnum); my $trackname = $trname; if(!eval("\$riptrname = \$outputdir.$tracktemplate")) { die "Track Template incorrect, caused eval to fail: $!"; } } else { $trname = lower_case($trname) if($lowercase == 1); $trname =~ s/ /_/g if($underscore == 1); $riptrname = $outputdir . $trname; } return $riptrname; } ######################################################################## # # Rip the CD. # sub rip_cd { my($shortname, @shortname, $ripcom, $riptrackname); my $startenc = 0; my $failflag = 0; my $resumerip = $resume; # Cleaning. my $albumtag = clean_all($cd{title}); my $artistag = clean_all($cd{artist}); my $album = $albumtag; $album = clean_name($album); my $artist = $artistag; $artist = clean_name($artist); $album = clean_chars($album) if($chars); $artist = clean_chars($artist) if($chars); $album =~ s/ /_/g if($underscore == 1); $artist =~ s/ /_/g if($underscore == 1); # Define an array with intervalls and the tracks to be skipped. my @merge = (); my @skip = (); if($pmerge){ @skip = skip_tracks; @merge = split(/,/, $pmerge); # If merge is used, we need to calculate the true track length for # the playlist file. And it would be nice, if the filename # reflects "missing" tracks. Define a string to concatenate the # track names. my $concat = " + "; $concat =~ s/ /_/g if($underscore == 1); $concat = clean_chars($concat) if($chars); foreach(@merge){ my @bea = split(/-|\+/, $_); my $beg = $bea[0] - 1; while($bea[0] < $bea[1]) { $secondlist[$beg] += $secondlist[$bea[0]]; $tracklist[$beg] = $tracklist[$beg] . $concat . $tracklist[$bea[0]]; $tracktags[$beg] = $tracktags[$beg] . " + " . $tracktags[$bea[0]]; $bea[0]++; } } } # Display info which tracks are going to be ripped. Because of option # merge we have to work hard to make it look nice: @tracksel = @seltrack; # Use a copy of @seltrack to work with. my @printracks; # A new array in nice print format. my $trackcn; my $prevtcn = -1; foreach $trackcn (@tracksel) { next if($trackcn <= $prevtcn); my $trackno; # Check if next tracknumber is in the skip array of tracks being # merged. If so, add a hyphen. if($skip[0] && ($trackcn + 1) =~ /$skip[0]/){ $trackno = $trackcn . "-"; shift(@skip); $trackcn++; # Is the next tracknumber the last of the intervall of merged # tracks? If not, continue to increase the tracknumber. while($skip[0] && ($trackcn + 1) =~ /$skip[0]/) { $trackcn++; shift(@skip); } $trackno = $trackno . $trackcn; $prevtcn = $trackcn; } else { $trackno = $trackcn; } push(@printracks, $trackno); } if($#seltrack == 0 && $hiddenflag == 0) { print "Track @printracks will be ripped!\n\n" if($verbose > 0); } elsif(!@seltrack && $hiddenflag == 1) { print "Track 0 will be ripped!\n\n" if($verbose > 0); } elsif($hiddenflag == 1) { print "Tracks 0 @printracks will be ripped!\n\n" if($verbose > 0); } else { print "Tracks @printracks will be ripped!\n\n" if($verbose > 0); } # Get the time when ripping started, and save it in the error.log. my $ripstart = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime)); my $date = sprintf("%04d-%02d-%02d", sub {$_[5]+1900, $_[4]+1, $_[3]}->(localtime)); open(ERO,">$outputdir/error.log") or print "Can not append to file \"$outputdir/error.log\"!\n"; print ERO "Ripping started: $ripstart\n"; close(ERO); if($multi == 1) { open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!\n"; print SRXY "\nRipping started: $ripstart"; close(SRXY); } # Write a toc (cue) file. if($cdtoc == 1){ my $cdtocartis = oct_char($artistag); my $cdtocalbum = oct_char($albumtag); open(CDTOC ,">$outputdir/cd.toc") or print "Can not append to file \"$outputdir/cd.toc\"!\n"; print CDTOC "CD_DA\n//Ripit $version cd.toc file generated ", "$date at $ripstart.", "\n//Use command >cdrdao scanbus< to detect device.", "\n//Assume the device found is: ATA:1,0,0, then, ", "use e. g. command", "\n//--> cdrdao write --device ATA:1,0,0 ", "--speed 4 cd.toc <-- to burn the CD.", "\n//Note: Not all CD (DVD) burners are able to burn", " CD-text! Test your device!"; print CDTOC "\n\n//CD Text:\nCD_TEXT{LANGUAGE_MAP {0 : EN}\n\t"; print CDTOC "LANGUAGE 0 {\n\t\tTITLE \"$cdtocalbum\"\n\t\t"; print CDTOC "PERFORMER \"$cdtocartis\"\n"; ## print CDTOC "\t\tGENRE \"$genreno\"\n" if($genreno); print CDTOC "\t\tDISC_ID \"$cddbid\"\n\t}\n}\n"; close(CDTOC); } # Start to rip the hidden track if there's one: First check if # cdparanoia is available. if($ripper != 1) { unless(log_system("cdparanoia -V")) { print "Cdparanoia not installed? Can't rip the hidden track "; print "without cdparanoia!\n" if($hiddenflag == 1); $hiddenflag = 0; } } # Check if the hidden track has been done in a previous session. my $checknextflag = 0; if($resumerip){ $riptrackname = "Hidden Track"; $riptrackname = lower_case($riptrackname) if($lowercase == 1); $riptrackname =~ s/ /_/g if($underscore == 1); $riptrackname = get_trackname(0, $riptrackname); if(-r "$riptrackname.rip") { unlink("$riptrackname.rip"); print "Found $riptrackname.rip.\n" if($verbose >= 1); } elsif(-r "$riptrackname.wav") { $checknextflag = 1; print "Found $riptrackname.wav.\n" if($verbose >= 1); } else{ my $sufix; foreach $sufix ('flac','m4a','mp3','ogg') { if(-r "$riptrackname.$sufix") { $checknextflag = 1; print "Found compressed file $riptrackname.$sufix.\n"; } } } if($checknextflag == 1){ $riptrackname = "Hidden Track"; unshift @tracktags, $riptrackname; unshift @seltrack, 0; unshift @tracklist, $riptrackname; } } # Process a possible hidden (first) track. if($hiddenflag == 1 && $checknextflag == 0) { $riptrackname = "Hidden Track"; unshift @tracktags, $riptrackname; my $cdtocname = $riptrackname; $riptrackname = lower_case($riptrackname) if($lowercase == 1); $riptrackname =~ s/ /_/g if($underscore == 1); unshift @seltrack, 0; unshift @tracklist, $riptrackname; # Change riptrackname to the full path & template name. $riptrackname = get_trackname(0, $tracklist[0]); @shortname = split(/\// , $riptrackname); $shortname = $shortname[$#shortname]; # What if the operator wants to merge a hidden track with the 1st # and so on tracks? Calculate the number of the first track not to # be merged with the hidden track. my $endtrackno = 1; if($pmerge) { my @bea = split(/-|\+/, $merge[0]); if($bea[0] && $bea[0] == 0) { $endtrackno = shift(@merge); $endtrackno =~ s/^0.//; $endtrackno++; } } # Assemble the command for cdparanoia to rip the hidden track. my $saveripopt = $ripopt; $ripopt = $ripopt . " -Z" if($parano == 0); $ripopt = $ripopt . " -q" if($verbose <= 1); $ripcom = "cdparanoia $ripopt -d $cddev [00:00]$endtrackno \\ \"$riptrackname.rip\""; print "\nRipping \"$shortname\"...\n" if($verbose >= 1 && $rip == 1); unless(log_system("$ripcom")) { # If no success, shift the hidden track stuff out of arrays. $hiddenflag = 0; shift(@secondlist); shift(@seltrack); shift(@tracklist); shift(@tracktags); } # Write to the toc (cue) file. if($cdtoc == 1 && $hiddenflag == 1){ open(CDTOC ,">>$outputdir/cd.toc") or print "Can not append to file \"$outputdir/cd.toc\"!\n"; print CDTOC "\n//Track 0:\nTRACK AUDIO\nTWO_CHANNEL_AUDIO\n"; print CDTOC "CD_TEXT {LANGUAGE 0 {\n\t\tTITLE \"$cdtocname\""; print CDTOC "\n\t\tPERFORMER \"$artistag\"\n\t}\n}\n"; print CDTOC "FILE \"$shortname.wav\" 0\n"; close(CDTOC); } # Check the hidden track for gaps. We do not care about option # merge... should we? Yes, we should. If option merge has been # choosen for this track, splitting is not allowed, while # extracting one chunk of sound may be desired. if($ghost == 1 && $hiddenflag == 1){ split_chunks(0, $riptrackname, $shortname); } if($hiddenflag == 1) { rename("$riptrackname.rip", "$riptrackname.wav"); } $ripopt = $saveripopt; } # End prepartion of ripping process. # # # Start ripping each track. Note that we have to skip a possible # hidden track. To prevent reripping ghost songs pushed into the # @seltrack array, make a copy which will not be altered. @tracksel = @seltrack; # Define some counters: # Because cdtoc is written in different subroutines, define a counter # for each track written into the toc file. This way, ghost songs are # sorted in the toc file, while they aren't in the @seltrack array. my $cdtocn = 0; # Encoder messages are printed into a file which will be read by the # ripper to prevent splitting ripper-messages. Lines already printed # will not be printed again. my $encline = 0; $trackcn = 0; foreach (@tracksel) { next if($_ == 0); # Skip hidden track. $trackcn++; $riptrackname = get_trackname($_, $tracklist[$_ - 1]); $riptrackname = get_trackname($_, $tracklist[$_]) if($hiddenflag == 1); my $riptrackno = $_; @shortname = split(/\//, $riptrackname); $shortname = $shortname[$#shortname]; # If we use option merge, skip a previously merged track: my $skipflag = 0; if($pmerge) { @skip = skip_tracks; foreach my $skip (@skip) { $skipflag = 1 if($_ == $skip); } } print "\nSkip track $_, it has been merged into previous one.\n" if($verbose >=1 && $skipflag == 1); next if($skipflag == 1); # Write the toc entry only if wav present. if($cdtoc == 1){ $cdtocn++; my $cdtoctitle = $tracktags[$_ - 1]; $cdtoctitle = $tracktags[$_] if($hiddenflag == 1); $cdtoctitle = oct_char($cdtoctitle); my $cdtocartis = oct_char($artistag); open(CDTOC, ">>$outputdir/cd.toc") or print "Can not append to file \"$outputdir/cd.toc\"!\n"; print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n"; print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t"; print CDTOC "TITLE \"$cdtoctitle\"\n\t\t"; print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n"; print CDTOC "FILE \"$shortname.wav\" 0\n"; close(CDTOC); } # Remember: $riptrackno is the track number passed to the encoder. # If we want to merge, we substitute it with the interval, with a # hyphen for cdparanoia and a plus sign for cdda2wav. my $saveriptrackno = $riptrackno; if($pmerge && $merge[0]) { my @bea = split(/-|\+/, $merge[0]); if($bea[0] && $riptrackno == $bea[0]) { $riptrackno = shift(@merge); $riptrackno =~ s/-/\+/ if($ripper == 2); $riptrackno =~ s/\+/-/ if($ripper == 1); # TODO: check for dagrab and sox... } } # lcdproc if($lcd == 1){ my $_lcdtracks = scalar @tracksel; $lcdtrackno++; my $lcdperc; if($_lcdtracks eq $lcdtrackno) { $lcdperc = "*100"; } else { $lcdperc = sprintf("%04.1f", $lcdtrackno/$_lcdtracks*100); } $lcdline2 =~ s/\|\d\d.\d/\|$lcdperc/; my $lcdtracknoF = sprintf("%02d", $lcdtrackno); $lcdline2 =~ s/\r\d\d/\r$lcdtracknoF/; substr($lcdline2,10,10) = substr($shortname,3,13); ulcd(); } # There is a problem with too long file names, encountered, e. g. # with some classical CDs. Cdparanoia cuts the length of the file # name, cdda2wav too... but how should RipIT know? Therefore use # a shorter track name if total length (including the full path) # > 230 characters. if(length($riptrackname) > 230) { $riptrackname = get_trackname($_, $_ . "short"); } # Check for tracks already done with option resume. $checknextflag = 0; if($resumerip){ if ($normalize == 0) { # Start the encoder in the background, but only once. # We do it already here, because: # i) if all wavs are done, the encoding porcess at the end # of this subroutine will not be started at all! # ii) why should we wait for an wav, if others are already # here and encoding could continue right away? if($startenc == 0 && $encode == 1) { $startenc = 1; open(ENCLOG,">$outputdir/enc.log"); close ENCLOG; unless(fork) { enc_cd(); } } } if(-r "$riptrackname.rip") { unlink("$riptrackname.rip"); print "Found $riptrackname.rip.\n" if($verbose >= 1); } elsif(-r "$riptrackname\_rip.wav" && $ripper == 2) { unlink("$riptrackname\_rip.wav"); print "Found $riptrackname\_rip.wav.\n" if($verbose >= 1); } elsif(-r "$riptrackname.wav") { $checknextflag = 1; print "Found $riptrackname.wav.\n" if($verbose >= 1); } elsif($wav == 0){ my $sufix; foreach $sufix ('flac','m4a','mp3','ogg') { if(-r "$riptrackname.$sufix") { $checknextflag = 1; print "Found compressed file $riptrackname.$sufix.\n" if($verbose >= 1); } } } # Cdda2wav is somehow unpleasent. It dies not quick enough with # ^+c. I. e. even if a track has not been ripped to the end, # there will be a *.wav. So we have to check for the encoded # files and assume, that for not encoded files present, there # is no fully ripped file. This is not so problematic, because # cdda2wav is quite fast, ripping that track again doesn't # cost a lot of time. if($ripper == 2 && $checknextflag == 1) { my $sufix; foreach $sufix ('flac','m4a','mp3','ogg') { if(-r "$riptrackname.$sufix") { $checknextflag = 1; } else { $checknextflag = 0; } last if($checknextflag == 1); } } } # Skip that track, i.e. restart the foreach-loop of tracks if a # wav file or other (mp3, ogg, ma4, flac) was found. next if($checknextflag == 1); # Don't resume anymore, if we came until here. $resumerip = 0; # Now do the job of ripping: print "\nRipping \"$shortname\"...\n" if($verbose >= 1 && $rip == 1); # Choose the cdaudio ripper to use. # # TODO: Check behaviour of all rippers on data tracks. # Choose to use print instead of die if ripper stops itself! # Dagrab fails @ data-track, so don't die and create an error.log, # cdparanoia fails @ data-track, so don't die and create an # error.log. # cdda2wav prints errors @ data-track, therefore die! if($ripper == 0 && $rip == 1) { if($trackcn == 1) { $ripopt = $ripopt . " -r 3" if($parano == 0); $ripopt = $ripopt . " -v" if($verbose >= 2); } $ripcom = "(dagrab $ripopt -d $cddev \\ -f \"$riptrackname.rip\" \\ $riptrackno 3>&1 1>&2 2>&3 \\ | tee -a \"$outputdir/error.log\") 3>&1 1>&2 2>&3 "; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { print "Dagrab detected some read errors on ", "$tracklist[$_ - 1]\n\n"; # Create error message in CD-directory for encoder: don't # wait. open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Dagrab detected some read errors at $riptrackno"; print ERO " on CD $artist - $album, do not worry!\n"; close(ERO); } print "\n"; } elsif($ripper == 1 && $rip == 1) { if($trackcn == 1) { $ripopt = $ripopt . " -Z" if($parano == 0); $ripopt = $ripopt . " -q" if($verbose <= 1); } if($multi == 0) { $ripcom = "cdparanoia $ripopt -d $cddev $riptrackno \\ \"$riptrackname.rip\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { print "cdparanoia failed on track ", $_, " $tracklist[$_ - 1]\n\n" if($hiddenflag == 0); print "cdparanoia failed on track ", $_, " $tracklist[$_]\n\n" if($hiddenflag == 1); # Create error message in CD-directory for encoder: # don't wait. open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Track $riptrackno on CD $artist - $album "; print ERO "failed!\n"; close(ERO); $failflag = 1; } } elsif($multi == 1) { $ripcom = "cdparanoia $ripopt -d $cddev $riptrackno \\ \"$riptrackname.rip\" 2>> \\ \"$logfile.$riptrackno.txt\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { # Apend error message to file srXY for rip2m to start # checktrack. open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!"; print SRXY "\ncdparanoia failed on $tracklist[$_ - 1] " if($hiddenflag == 0); print SRXY "\ncdparanoia failed on $tracklist[$_] " if($hiddenflag == 1); print SRXY "in device $logfile"; close(SRXY); # Create error message in CD-directory for encoder: # don't wait. open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Track $riptrackno on CD $artist - $album "; print ERO "failed!\n"; close(ERO); # Kill failed CD only if it is not the last track. Last # track may be data/video track. # I.e. print error message to file srXY.Z.txt, checktrack # will grep for string # "cdparanoia failed" and kill the CD immediately! if($riptrackno != $tracksel[$#tracksel]) { open(SRTF,">>$logfile.$riptrackno.txt") or print "Can not append to file ", "\"$logfile.$riptrackno.txt\"!"; print SRTF "cdparanoia failed on $tracklist[$_ - 1]" if($hiddenflag == 0); print SRTF "cdparanoia failed on $tracklist[$_ - 1]" if($hiddenflag == 1); print SRTF "\nin device $logfile, error !"; close(SRTF); # Create on the fly error message in log-directory. my $devnam = $cddev; $devnam =~ s/.*dev.//; open(ERO,">>$logpath/failed.log") or print "Can not append to file ", "\"$logpath/failed.log\"!"; print ERO "$artist;$album;$genre;$categ;$cddbid;"; print ERO "$devnam;$hostnam; Cdparanoia failure!\n"; close(ERO); # Now wait to be terminated by checktrack. sleep 360; exit; } } } } elsif($ripper == 2 && $rip == 1) { if($trackcn == 1) { $ripopt = $ripopt . " -q" if($verbose <= 1); } if($multi == 0) { $ripcom = "cdda2wav -D $cddev -H $ripopt -t $riptrackno \\ \"$riptrackname\_rip\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { print "cdda2wav failed on <$tracklist[$_ - 1]>.\n" if($hiddenflag == 0); print "cdda2wav failed on <$tracklist[$_]>.\n" if($hiddenflag == 1); open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Track $riptrackno on CD $artist - $album "; print ERO "failed!\n"; close(ERO); $failflag = 1; } } elsif($multi == 1) { $ripcom = "cdda2wav -D $cddev -H $ripopt -t $riptrackno \\ \"$riptrackname\_rip\" \\ 2>> \"$logfile.$riptrackno.txt\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { # Apend error message to file srXY for rip2m to start # checktrack. open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!"; print SRXY "\ncdda2wav failed on $tracklist[$_ - 1] in " if($hiddenflag == 0); print SRXY "\ncdda2wav failed on $tracklist[$_] in " if($hiddenflag == 1); print SRXY "device $logfile"; close(SRXY); # Create error message in CD-directory for encoder: # don't wait. open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Track $riptrackno on CD $artist - $album "; print ERO "failed!\n"; close(ERO); # Kill failed CD only if it is not the last track. # Last track may be data/video track. # I.e. print error message to file srXY.Z.txt, checktrack # will grep for string # "cdparanoia failed" and kill the CD immediately! if($riptrackno != $tracksel[$#tracksel]) { open(SRTF,">>$logfile.$riptrackno.txt") or print "Can not append to file ", "\"$logfile.$riptrackno.txt\"!"; print SRTF "cdda2wav failed on $tracklist[$_ - 1]\n" if($hiddenflag == 0); print SRTF "cdda2wav failed on $tracklist[$_]\n" if($hiddenflag == 1); print SRTF "in device $logfile, error !"; close(SRTF); # Create on the fly error message in log-directory. my $devnam = $cddev; $devnam =~ s/.*dev.//; open(ERO,">>$logpath/failed.log") or print "Can not append to file ", "\"$logpath/failed.log\"!"; print ERO "$artist;$album;$genre;$categ;$cddbid;"; print ERO "$devnam;$hostnam; Cdda2wav failure!\n"; close(ERO); # Now wait to be terminated by checktrack. sleep 360; exit; } } } } elsif($ripper == 3 && $rip == 1) { $ripcom = "tosha -d $cddev -f wav -t $riptrackno \\ -o \"$riptrackname.rip\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { die "tosha failed on $tracklist[$_ - 1]"; } } elsif($ripper == 4 && $rip == 1) { my $cdd_dev = $cddev; $cdd_dev =~ s/^\/dev\/r//; $cdd_dev =~ s/c$//; $ripcom = "cdd -t $riptrackno -q -f $cdd_dev - 2> /dev/null \\ | sox -t cdr -x - \"$riptrackname.rip\""; $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0); unless(log_system("$ripcom")) { die "cdd failed on $tracklist[$_ - 1]"; } } elsif($rip == 1) { print "No CD Ripper defined"; } # Rename rip file to a wav for encoder so that it will be picked # up by the encoder background process. # # Cdda2wav output is not easy to handle. Everything beyond a last # period . has been erased. Example: riptrackname is something # like "never ending...", then we assign cdda2wav in the above # section to rip a file called: "never ending..._rip", but # cdda2wav misbehaves and the file is called "never ending...". # Therefore we rename the ripped file to the standard name # riptrackname.rip first (if cdda2wav was used). if($ripper == 2) { if($riptrackname =~ /\./) { # But split is too clever! If a trackname ends with "bla..." # all points get lost, so we've to add a word at the end! my $cddatrackname = $riptrackname . "end"; my @riptrackname = split(/\./, $cddatrackname); delete($riptrackname[$#riptrackname]); $cddatrackname = join('.',@riptrackname); rename("$cddatrackname.wav", "$riptrackname.rip"); } else { rename("$riptrackname\_rip.wav", "$riptrackname.rip"); } } if(-r "$riptrackname.rip") { $cdtocn = split_chunks($saveriptrackno, $riptrackname, $shortname, $cdtocn) if($ghost == 1 && $failflag == 0); } rename("$riptrackname.rip", "$riptrackname.wav"); md5_sum("$riptrackname.wav") if($md5sum == 1 && $normalize == 0 && $wav == 1 && $failflag == 0); chmod oct($fpermission), "$riptrackname.wav" if($fpermission); chmod oct($fpermission), "$outputdir/cd.toc" if($fpermission); unlink("$logfile.$riptrackno.txt") if($multi == 1); $failflag = 0; if ($normalize == 0) { # Start the encoder in the background, but only once. if($startenc == 0 && $encode == 1) { my $encstart = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime)); # my $encstart = `date \'+%R\'`; chomp $encstart; if($multi == 1) { open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!"; print SRXY "\nEncoding started: $encstart"; close(SRXY); } $startenc = 1; open(ENCLOG,">$outputdir/enc.log"); close ENCLOG; unless(fork) { enc_cd(); } } } # Print encoder messages saved in enc.log not to spoil the # ripper output. if($encode == 1) { open(ENCLOG, "< $outputdir/enc.log"); my @loglines = ; close ENCLOG; my $lincn = 0; my @outlines = (); foreach (@loglines) { if($verbose >= 3) { push(@outlines, $_) if($lincn >= $encline && $_ !~ /^\n/); } elsif($verbose == 1 || $verbose == 2) { print $_ if($lincn >= $encline && $_ =~ /complete\./); } $lincn++; } # Compact output. $encline = $lincn; if($outlines[0] && $verbose >= 2) { push(@outlines, "*" x 47, "\n") if($verbose >= 3); unshift(@outlines, "*"x15, " Encoder reports ", "*"x15, "\n") if($verbose >= 3); print @outlines; } } } unlink("$outputdir/enc.log") if(-r "$outputdir/enc.log"); # Hack to tell the child process that we are waiting for it to # finish. my $ripend = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime)); open(ERR, ">>$outputdir/error.log") or print "Can not append to file error.log!\n"; print ERR "The audio CD ripper reports: all done!\n"; print ERR "Ripping ended: $ripend\n"; close(ERR); if($multi == 1) { open(SRXY,">>$logfile") or print "Can not append to file \"$logfile\"!"; print SRXY "\nRipping complete: $ripend"; close(SRXY); } } ######################################################################## # # Normalize the wav. # Using normalize will disable parallel ripping & encoding. # sub norm_cd { print "Normalizing the wav-files...\n" if($verbose >= 1); my($norm,$normtrackname); # Generate filelist. foreach (@seltrack) { my $riptrackname = &get_trackname($_, $tracklist[$_ - 1]); $riptrackname = get_trackname($_, $tracklist[$_]) if($hiddenflag == 1); # normalize is picky about certain characters - get them escaped! $riptrackname = esc_char($riptrackname); $normtrackname = "$normtrackname $riptrackname.wav"; } $norm = "normalize $normopt -- $normtrackname"; if(! system("$norm")) { print "\nNormalizing complete.\n" if($verbose >= 1); } else { die "\nNormalizing failed.\n" if($verbose >= 1); } } ######################################################################## # # Encode the wav. # This runs as a separate process from the main program which # allows it to continuously encode as the ripping is being done. # The encoder will also wait for the ripped wav in-case the encoder # is faster than the CDROM. In fact it will be waited 3 times the length # of the track to be encoded. # sub enc_cd { my ($enc, $riptrackno, $riptrackname, $sufix); my ($albumlametag, $artislametag, $commentlametag, $tracklametag); my ($ripcomplete, $trackcn, $totalencs) = (0, 0, 0); my $lastskip = $tracksel[0]; my $resumenc = $resume; my @md5tracks = (); # List of tracks to be md5-checked. # Cleaning. my $albumtag = clean_all($cd{title}); my $artistag = clean_all($cd{artist}); my $album = $albumtag; my $artist = $artistag; $album = clean_name($album); $artist = clean_name($artist); $album = clean_chars($album) if($chars); $artist = clean_chars($artist) if($chars); $album =~ s/ /_/g if($underscore == 1); $artist =~ s/ /_/g if($underscore == 1); # Create special variables for Lame-tags because of UTF8 problem. if($utftag == 0) { $artislametag = back_encoding($artistag); $albumlametag = back_encoding($albumtag); $commentlametag = back_encoding($commentag); } else{ $artislametag = $artistag; $albumlametag = $albumtag; $commentlametag = $commentag; } # Write a playlist file. my $playfile; if($playlist >= 1) { $playfile = "$artist" . " - " . "$album" . ".m3u"; $playfile =~ s/ /_/g if($underscore == 1); open(PLIST, ">$outputdir$playfile"); print PLIST "#EXTM3U\n"; } my $ghostflag = 0; my $ghostcn = 0; # Start encoding each track. foreach (@tracksel) { # A lot of hacking for ghost songs. Remember, array @tracksel is # the original one, without ghost songs as long as we did not get # to the end. Once all tracks are donne, this array will be # updated if ghost songs were found. # Now: if only one track in the middle of the album has been # selected, problems occur if this track has ghostsongs. Why? # Because the updated array @tracksel will be e.g. 4 4 4 4 if the # track 4 has 3 ghost songs. But the track- and tag list arrays # have all tracknames in, so after the track number 4 will come # track number 5, but no track "04 name of track 5" exists, and # encoder fails! Once all tracks are done, one should set the # $ghostcn to the total number of tracks of the CD. $ghostflag = 2 if($ghostflag == 1 && $riptrackno >= $_); $ghostcn = $#{$cd{track}} + 1 if($ghostflag == 0); $riptrackno = $_; $trackcn++; $riptrackname = get_trackname($_, $tracklist[$_ - 1]); $riptrackname = get_trackname($_, $tracklist[$_]) if($hiddenflag == 1); if($ghostflag >= 1){ $ghostcn++; $riptrackname = get_trackname($_, $tracklist[$ghostcn - 1]); $riptrackname = get_trackname($_, $tracklist[$ghostcn]) if($hiddenflag == 1); } # If we want to merge, skip a previously merged track: my $skipflag = 0; if($pmerge) { @skip = skip_tracks; foreach my $skip (@skip) { $skipflag = 1 if($_ == $skip); } } next if($skipflag == 1); $lastskip = $_; # Cosmetics for nice output. my @shortname = split(/\// , $riptrackname); my $shortname = $shortname[$#shortname]; # lcdproc if($lcd == 1){ my $_lcdtracks = scalar @tracksel; my $_lcdenctrack = $trackcn; my $lcdperc; if($_lcdtracks eq $_lcdenctrack) { $lcdperc = "*100"; } else { $lcdperc = sprintf("%04.1f",$_lcdenctrack/$_lcdtracks*100); } $lcdline3=~ s/\|\d\d.\d/\|$lcdperc/; my $_lcdenctrackF = sprintf("%02d",$_lcdenctrack); $lcdline3=~ s/\E\d\d/\E$_lcdenctrackF/; substr($lcdline3,10,10) = substr($shortname,3,13); ulcd(); } # Adjust encoding of tracktag for Lame. my $tracktag = $tracktags[$_ - 1]; $tracktag = $tracktags[$_] if($hiddenflag == 1); if($ghostflag >= 1){ $tracktag = $tracktags[$ghostcn - 1]; $tracktag = $tracktags[$ghostcn] if($hiddenflag == 1); } if($utftag == 0) { $tracklametag = back_encoding($tracktag); } else{ $tracklametag = $tracktag; } # If the file name was too long for ripper, look for special name. my $wavname = $riptrackname; if(length($riptrackname) > 230) { $wavname = get_trackname($_,$_."short"); } # Check for tracks already done. my $checknextflag = 1; if($resumenc){ my @sufix = ('mp3', 'ogg', 'flac', 'm4a'); foreach my $c (@coder) { if(! -r "$riptrackname.$sufix[$c]") { $checknextflag = 0; } else{ print "Found $riptrackname.$sufix[$c].\n" if($verbose >= 1); } last if($checknextflag == 0); } if($checknextflag == 1 && $playlist >= 1){ print PLIST "#EXTINF:$secondlist[$_ - 1],$tracktag\n" if($hiddenflag == 0); print PLIST "#EXTINF:$secondlist[$_],$tracktag\n" if($hiddenflag == 1); print PLIST "$riptrackname.suffix\n" if($playlist == 1); print PLIST "$shortname.suffix\n" if($playlist == 2); print PLIST "Add Ghost Song $_ Here.\n" if($ghost == 1); } } # Skip that track, i. e. restart the foreach-loop of tracks if a # compressed file (mp3, ogg, ma4, flac) was found. next if($resumenc && $checknextflag == 1); # Don't resume anymore, if we came until here. $resumenc = 0; # Keep looping until the wav file appears, i.e. wait for # ripper timeout. Timeout is 3 times the length of track # to rip/encode. Then leave that one and finish the job! my $slength = $secondlist[$_ - 1]; my $mlength = (int($slength / 60) + 1) * 3; my $tlength = (int($slength / 10) + 6) * 3; # We don't need this for ghost songs, as they are done only when # the (original) last track was successfully ripped. my $dataflag = 0; my $xtime = 0; while(! -r "$wavname.wav" && $ghostflag == 0) { $xtime++; last if($xtime > $tlength); # Condition 1: Too long waiting for the track! if($xtime >= $tlength) { if($multi != 1) { print "Encoder waited $mlength minutes for file\n"; print "$shortname.wav to appear, now giving up!\n"; print "with $artist - $album in device $cddev\n"; log_info("Encoder waited $mlength minutes for file"); log_info("$shortname.wav to appear, now giving up!"); log_info("with $artist - $album in device $cddev"); } else { $xtime = 0; print "Encoder waited $mlength minutes for file\n"; print "$shortname.wav to appear\n"; print "with $artist - $album in device $cddev.\n"; print "Don't worry, I continue the job!\n\n"; } } sleep 10; # Condition 2: Check the error log! # If at this moment the ripper did not start with # the riptrackname.rip, assume it was a data track! # If cdparanoia failed on a data track, there will # be an entry in the error.log. # If dagrab gave error messages, but the wav file # was created, we won't get to this point, so don't # worry. if(-r "$outputdir/error.log") { open(ERR, "$outputdir/error.log") or print "error.log disappeared!\n"; my @errlines = ; close ERR; my @errtrack = grep(/^Track $riptrackno /, @errlines); my $errtrack = "@errtrack"; @errtrack = split(/ /, $errtrack); $errtrack = $errtrack[1]; if($errtrack) { $xtime = $tlength + 1; $dataflag = 1; if($verbose >= 2) { print "\nDid not detect track $errtrack"; print " ($shortname.rip), assume ripper failure!\n"; } print "I will finish the job! Check the error.log!\n" if($verbose >= 2 && $sshflag == 0); } } } # This is an other hack to update the track-arrays modifed by the # ripper if ghost songs were found. Is there another way to # communicate with the parent process? # This loop was supposed to be at the end of this sub-routine, # but we need it here in case of data tracks. The encoder would # stop here after a data track and fail to encode previously found # ghost songs because @tracksel has not yet been updated. if($ghost == 1 && $_ == $tracksel[$#tracksel] && -r "$outputdir/ghost.log") { open(GHOST, "<$outputdir/ghost.log") or print "Can not read file ghost.log!\n"; my @errlines = ; close(GHOST); my @selines = grep(s/^Array seltrack: //, @errlines); @tracksel = split(/ /, $selines[$#selines]); chomp($_) foreach(@tracksel); my @seclines = grep(s/^Array secondlist: //, @errlines); @secondlist = split(/ /, $seclines[$#seclines]); chomp($_) foreach(@secondlist); @tracklist = grep(s/^Array tracklist: //, @errlines); chomp($_) foreach(@tracklist); @tracktags = grep(s/^Array tracktags: //, @errlines); chomp($_) foreach(@tracktags); unlink("$outputdir/ghost.log"); $ghost = 0; $ghostflag = 1; $resumenc = $resume; # Continue to resume ghost songs. } # Jump to the next track if wav wasn't found. Note that the # $tlength does not exist for additional ghost songs, so don't # test this condition when encoding ghost songs, furthermore we # assume that ghost songs are present as soon as one was found. next if($ghostflag == 0 && $xtime >= $tlength || $dataflag == 1); if(length($riptrackname) > 230) { rename("$wavname.wav","$riptrackname.wav"); } my $delwav = 0; my $starts = sprintf("%3d", sub {$_[1]*60+$_[0]}->(localtime)); # my $starts = `date \'+%s\'`; # chomp $starts; if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "\nEncoding \"$shortname\"...\n" if($verbose >= 3); close ENCLOG; } else { print "\nEncoding \"$shortname\"...\n" if($verbose >= 3); } my $failflag = 0; # Set the encoder(s) we are going to use. for(my $c=0; $c<=$#coder; $c++) { # Get the command for the encoder to use! if($coder[$c] == 0) { if($trackcn == 1) { if($preset) { $lameopt = $lameopt . " --preset $preset"; } else { $lameopt = $lameopt . " --vbr-$vbrmode" if($vbrmode); $lameopt = $lameopt . " -b $bitrate" if($bitrate ne "off"); $lameopt = $lameopt . " -B $maxrate" if($maxrate != 0); $lameopt = $lameopt . " -V $qualame" if($qualame ne "off" && $vbrmode); $lameopt = $lameopt . " -q $qualame" if($qualame ne "off" && !$vbrmode); } } $enc = "lame $lameopt -S --tt \"$tracklametag\" \\ --ta \"$artislametag\" --tl \"$albumlametag\" \\ --ty \"$year\" --tg \"$genre\" --tn $riptrackno \\ --tc \"$commentlametag\" --add-id3v2 \\ \"$riptrackname.wav\" \\ \"$riptrackname.mp3_enc\""; if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "\nLame $lameopt encoding track $trackcn" . " of " . ($#tracksel + 1) . " " if($verbose >= 3); print ENCLOG "\@ quality $qualame\n" if($verbose >= 3 && !$preset); print ENCLOG "\n" if($verbose >= 3 && $preset); close ENCLOG; } else { print "\nLame $lameopt encoding track $trackcn" . " of " . ($#tracksel + 1) . " " if($verbose >= 3); print "\@ quality $qualame\n" if($verbose >= 3 && !$preset); print "\n" if($verbose >= 3 && $preset); } log_info("new-mediafile: ${riptrackname}.mp3"); $sufix = "mp3"; } elsif($coder[$c] == 1) { if($trackcn == 1) { $oggencopt = $oggencopt . " -q $qualoggenc" if($qualoggenc ne "off"); $oggencopt = $oggencopt . " -M $maxrate" if($maxrate != 0); } $enc = "oggenc $oggencopt -Q -t \"$tracktag\" \\ -a \"$artistag\" -l \"$albumtag\" \\ -d \"$year\" -G \"$genre\" \\ -N $riptrackno -c \"DESCRIPTION=$commentag\" \\ -o \"$riptrackname.ogg_enc\" \\ \"$riptrackname.wav\""; if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "\nOggenc $oggencopt encoding track" . " $trackcn of " . ($#tracksel + 1) . " " . "\@ quality $qualoggenc\n" if($verbose >= 3); close ENCLOG; } else { print "\nOggenc $oggencopt encoding track $trackcn of " . ($#tracksel + 1) . " \@ quality $qualoggenc\n" if($verbose >= 3); } log_info("new-mediafile: ${riptrackname}.ogg"); $sufix = "ogg"; } elsif($coder[$c] == 2) { if($trackcn == 1) { $flacopt = $flacopt . " -$quaflac" if($quaflac ne "off"); } $enc = "flac $flacopt -s --tag=TITLE=\"$tracktag\" \\ --tag=ARTIST=\"$artistag\" --tag=ALBUM=\"$albumtag\" \\ --tag=DATE=\"$year\" --tag=TRACKNUMBER=\"$riptrackno\" \\ --tag=GENRE=\"$genre\" --tag=CATEGORY=\"$categ\" \\ --tag=DESCRIPTION=\"$commentag\" --tag=CDID=\"$cddbid\" \\ -o \"$riptrackname.flac_enc\" \\ \"$riptrackname.wav\""; if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "\nFlac $flacopt encoding track $trackcn" . " of " . ($#tracksel + 1) . " " . " \@ compression $quaflac\n" if($verbose >= 3); close ENCLOG; } else { print "\nFlac $flacopt encoding track $trackcn of " . ($#tracksel + 1) . " \@ compression $quaflac\n" if($verbose >= 3); } log_info("new-mediafile: ${riptrackname}.flac"); $sufix = "flac"; } elsif($coder[$c] == 3) { if($trackcn == 1) { $faacopt = $faacopt . " -q $quafaac" if($quafaac ne "off"); } $enc = "faac $faacopt -w --title \"$tracktag\" \\ --artist \"$artist\" --album \"$album\" \\ --year \"$year\" --genre \"$genre\" --track $riptrackno \\ --comment \"$commentag\" \\ -o \"$riptrackname.m4a_enc\" \\ \"$riptrackname.wav\" "; if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "\nFaac $faacopt encoding track $trackcn " . " of " . ($#tracksel + 1) . " " . " \@ quality $quafaac\n" if($verbose >= 3); close ENCLOG; } else { print "\nFaac $faacopt encoding track $trackcn of " . ($#tracksel + 1) . " \@ quality $quafaac\n" if($verbose >= 3); } log_info("new-mediafile: ${riptrackname}.m4a"); $sufix = "m4a"; } $sufix = "mp3" if($sufix eq ""); # Set "last encoding of track" - flag. $delwav = 1 if($wav == 0 && $c == $#coder); # Set nice if wished. $enc="nice -n $nice ".$enc if($nice != 0); # Make the output look nice, don't mess the messages! my $ripmsg = "The audio CD ripper reports: all done!"; if($ripcomplete == 0 ) { if(-r "$outputdir/error.log") { open(ERR, "$outputdir/error.log") or print "Can not open file error.log!\n"; my @errlines = ; close ERR; my @ripcomplete = grep(/^$ripmsg/, @errlines); $ripcomplete = 1 if(@ripcomplete); } } # Finally, do the job of encoding. if($sshflag == 1) { enc_ssh($delwav,$enc,$riptrackname,$shortname,$sufix); if($md5sum == 1) { push(@md5tracks, "$riptrackname.$sufix"); my @waitracks; foreach my $donetrack (@md5tracks) { if( -r "$donetrack") { md5_sum("$donetrack"); chmod oct($fpermission), "${outputdir}MD5SUM_$sufix" if($fpermission); } else { push(@waitracks, "$donetrack"); } } @md5tracks = @waitracks; } } else { if(log_system("$enc > /dev/null 2> /dev/null")) { if($ripcomplete == 0) { if(-r "$outputdir/error.log") { open(ERR, "$outputdir/error.log") or print "Can open file error.log!\n"; my @errlines = ; close ERR; my @ripcomplete = grep(/^$ripmsg/, @errlines); $ripcomplete = 1 if(@ripcomplete); } } rename("$riptrackname.$sufix\_enc","$riptrackname.$sufix"); chmod oct($fpermission), "$riptrackname.$sufix" if($fpermission); md5_sum("$riptrackname.$sufix") if($md5sum == 1); chmod oct($fpermission), "${outputdir}MD5SUM_$sufix" if($fpermission && $md5sum == 1); if(-r "$outputdir/enc.log" && $ripcomplete == 0) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "Encoding of \"$shortname.$sufix\" " . "complete.\n" if($verbose >= 1); close ENCLOG; } else { print "Encoding of \"$shortname.$sufix\" " . "complete.\n" if($verbose >= 1); } } else { print "Encoder failed on $tracklist[$_ - 1] in $cddev.", "\nError message says: $?\n"; $failflag = 1; if($multi == 1) { # Print error message to file srXY.Z.txt, checktrack # will grep for string "encoder failed" and kill the # CD immediately! open(SRTF,">>$logfile.$riptrackno.txt") or print "Can not append to file ", "\"$logfile.$riptrackno.txt\"!"; print SRTF "\nencoder failed on $tracklist[$_ - 1] "; print SRTF "in device $cddev, error $? !"; close(SRTF); # Create on the fly error message in log-directory. my $devnam = $cddev; $devnam =~ s/.*dev.//; open(ERO,">>$logpath/failed.log") or print "Can not append to file ", "\"$logpath/failed.log\"!"; print ERO "$artist;$album;$genre;$categ;$cddbid;"; print ERO "$devnam;$hostnam; Encoder failure!\n"; close(ERO); # Wait to be terminated by checktrack. sleep 360; } } sleep 1; } } # Calculate time in seconds when encoding ended and total time # Encoder needed. my $endsec = sprintf("%3d", sub {$_[1]*60+$_[0]}->(localtime)); $endsec += 60 while($endsec <= $starts); $totalencs = $totalencs + $endsec - $starts; # Delete the wav if not wanted. unlink("$riptrackname.wav") if($delwav == 1 && $sshflag == 0); # Write the playlist file. This is somehow tricky, if ghost songs # may appear. To ensure the files in the right order, introduce # placeholders for possible ghost songs. # The problem is, that the secondlist with the true track lengths # will only be updated, when the last track has been encoded (the # last track except ghost songs). But we need the true length # right now. So, if $ghost == 1, check for the ghost.log file at # any track. if($failflag == 0 && $playlist >= 1) { # Ghost songs follow after the last track, but $ghostflag was # set to 1 just before last track is encoded. Therefore set # $ghostflag to 2 after the last track has been done and # inserted in the playlist file as a regular file (below), # and insert sound files as ghost songs only when $ghostflag is # 2. If only the last song has been splitted into chunks and # the counter increased, continue to insert as regular file. if($ghostflag == 2) { print PLIST "GS$_:#EXTINF:$secondlist[$ghostcn - 1],", "$tracktag\n" if($hiddenflag == 0); print PLIST "GS$_:#EXTINF:$secondlist[$ghostcn],$tracktag\n" if($hiddenflag == 1); print PLIST "GS$_:$riptrackname.suffix\n" if($playlist == 1); print PLIST "GS$_:$shortname.suffix\n" if($playlist == 2); } else { if($ghost == 1 && -r "$outputdir/ghost.log") { open(GHOST, "<$outputdir/ghost.log") or print "Can not read file ghost.log!\n"; my @errlines = ; close(GHOST); my @seclines = grep(s/^Array secondlist: //, @errlines); @secondlist = split(/ /, $seclines[$#seclines]); chomp($_) foreach(@secondlist); } print PLIST "#EXTINF:$secondlist[$_ - 1],$tracktag\n" if($hiddenflag == 0); print PLIST "#EXTINF:$secondlist[$_],$tracktag\n" if($hiddenflag == 1); print PLIST "$riptrackname.suffix\n" if($playlist == 1); print PLIST "$shortname.suffix\n" if($playlist == 2); print PLIST "Add Ghost Song $_ Here.\n" if($ghost == 1 || $ghostflag == 1); } } } # Tell the mother process the encoding time. open(ERR, ">>$outputdir/error.log") or print "Can not append to file error.log!\n"; print ERR "Encoding needed $totalencs seconds!\n"; print ERR "md5: $_\n" foreach(@md5tracks); close(ERR); close(PLIST); exit ; } ######################################################################## # # Finish the M3U file used by players such as Amarok, Noatun, X11Amp... # sub create_m3u { my $playfile; my @mp3s = (); my $sufix = ""; my $album = clean_all($cd{title}); my $artist = clean_all($cd{artist}); $album = clean_name($album); $artist = clean_name($artist); $album = clean_chars($album) if($chars); $artist = clean_chars($artist) if($chars); $playfile = "$artist" . " - " . "$album" . ".m3u"; $playfile =~ s/ /_/g if($underscore == 1); open(PLIST, "<$outputdir$playfile") or print "Can not open file $outputdir$playfile!\n"; my @playlines = ; close(PLIST); my @ghosts = grep(/^GS\d+:/, @playlines); my @playlist = (); foreach (@playlines) { next if($_ =~ /^GS\d+:/ || $_ =~ /^$/); $_ =~ s/^Add Ghost Song (\d+) Here.$/$1/; chomp $_; if($_ =~ /^\d+$/) { foreach my $ghostsong (@ghosts) { if($ghostsong =~ s/^GS$_\://) { # Why not as a 1-liner? $ghostsong =~ s/^GS$_\://; chomp $ghostsong; push @playlist, $ghostsong; } } } else { push @playlist, $_; } } my $nplayfile; foreach my $c (@coder) { my @mp3s = @playlist; if($c == 1) { $_ =~ s/\.suffix$/.ogg/i foreach (@mp3s); $sufix = "ogg"; } elsif($c == 2) { $_ =~ s/\.suffix/.flac/i foreach (@mp3s); $sufix = "flac"; } elsif($c == 3) { $_ =~ s/\.suffix/.m4a/i foreach (@mp3s); $sufix = "m4a"; } else { $_ =~ s/\.suffix/.mp3/i foreach (@mp3s); $sufix = "mp3"; } if($#coder != 0){ $nplayfile = $playfile; $nplayfile =~ s/\.m3u/ - $sufix\.m3u/ if($underscore == 0); $nplayfile =~ s/\.m3u/_-_$sufix\.m3u/ if($underscore == 1); unlink("$outputdir$playfile"); open(PLST, ">$outputdir$nplayfile"); } else { $nplayfile = $playfile; open(PLST, ">$outputdir$nplayfile"); } print PLST "$_\n" foreach(@mp3s); close(PLST); chmod oct($fpermission), "$outputdir$nplayfile" if($fpermission); } } ######################################################################## # # Create a default or manual track list. # sub create_deftrack { # Choose if you want to use default names or enter them manually. # Do not ask if we come form CDDB submission, i.e. index == 0, # or if $interaction == 0, then $index == 1. my ($i, $j, $index) = (0,1,@_); my ($album, $artist); my $tracks = substr($cddbid, 6); $tracks = hex($tracks); $album = clean_all($cd{title}) if(defined $cd{title}); $artist = clean_all($cd{artist}) if(defined $cd{artist}); # Preselect answer if no interaction whished. $index = 1 if($interaction == 0); while($index !~ /^[0-1]$/ ) { print "\nThis CD shall be labeled with:\n\n"; print "1: Default Album, Artist and Tracknames\n\n"; print "0: Manual input\n\nChoose [0-1]: (0) "; $index = ; chomp $index; $index = 0 unless($index); print "\n"; } # Create default tracklist and cd-hash. # NOTE: here we define an additional key: revision, which does not # exist if %cd is filled by CDDB_get. When this key exists, we know # that it is a new entry. if($index == 1) { $artist = "Unknown Artist"; $album = "Unknown Album"; %cd = ( artist => $artist, title => $album, cat => $categ, genre => $genre, id => $cddbid, revision => 0, year => $year, ); while($i < $tracks) { $j = $i + 1; $j = "0" . $j if($j < 10); $cd{track}[$i] = "Track " . "$j"; ++$i; } $cddbsubmission = 0; } # Create manual tracklist. elsif($index == 0) { # In case of CDDB resubmission if(defined $cd{artist}) { print "\n Artist ($artist): "; } # In case of manual CDDB entry. else { print "\n Artist : "; } $artist = ; chomp $artist; # If CDDB entry confirmed, take it. if(defined $cd{artist} && $artist eq "") { $artist = $cd{artist}; } # If CDDB entry CHANGED, submission OK. elsif(defined $cd{artist} && $artist ne "") { $cddbsubmission = 1; $cd{artist} = $artist; } if($artist eq "") { $artist = "Unknown Artist"; $cddbsubmission = 0; } if(defined $cd{title}) { print "\n Album ($album): "; } else { print "\n Album : "; } $album = ; chomp $album; while($year !~ /^\d{4}$/) { if(defined $cd{year}) { print "\n Year ($year): "; } else { print "\n year : "; } $year = ; chomp $year; last if($year eq ""); } # If CDDB entry confirmed, take it. if(defined $cd{title} && $album eq "") { $album = $cd{title}; } # If CDDB entry CHANGED, submission OK. elsif(defined $cd{title} && $album ne "") { $cddbsubmission = 1; $cd{title} = $album; } if($album eq "") { $album = "Unknown Album"; $cddbsubmission = 0; } %cd = ( artist => $artist, title => $album, cat => $categ, genre => $genre, id => $cddbid, revision => 0, year => $year, ) unless(defined $cd{title}); print "\n"; $i = 1; while($i <= $tracks) { if(defined $cd{track}[$i-1]) { printf(" Track %02d (%s): ", $i, $tracktags[$i-1]); } else { printf(" Track %02d: ", $i); } my $tracktag = ; chomp $tracktag; $tracktag = clean_all($tracktag); my $track = $tracktag; $track = clean_name($track); $track = clean_chars($track) if($chars); $track = lower_case($_) if($lowercase == 1); $track =~ s/ /_/g if($underscore == 1); # If CDDB entry confirmed, take and replace it in tracklist. if(defined $cd{track}[$i-1] && $track ne "") { splice @tracklist, $i-1, 1, $track; splice @tracktags, $i-1, 1, $tracktag; $cddbsubmission = 1; } elsif(!$cd{track}[$i-1] && $track eq "") { $track = "Track " . sprintf("%02d", $i); $cddbsubmission = 0; } # Fill the "empty" array @{$cd{track}}. push @{$cd{track}}, "$track"; $i++; } print "\n"; } else { # I don't like die, but I don't like if-loops without else. # This should not happen because of previous while-loop! die "You should choose 0 or 1!\n\n"; } } ######################################################################## # # Read the CD and generate a TOC with DiscID, track frames and total # length. Then prepare CDDB-submission with entries from @tracklist. # sub pre_subm { my($check,$i,$ans,$genreno,$line,$oldcat,$subject) = (0,0); my $tracks = $#framelist; my $totals = int($framelist[$#framelist] / 75); my $album = clean_all($cd{title}); my $artist = clean_all($cd{artist}); my $revision = get_rev(); if($revision) { # TODO: if submission fails, set revision back. $revision++; } elsif(defined $cd{revsision}) { $revision = $cd{revision}; } else { $revision = 0; } # Check for CDDB ID vs CD ID problems. if($cddbid ne $cd{id} && defined $cd{id}) { print "\nObsolet warning: CDID ($cddbid) is not identical to "; print "CDDB entry ($cd{id})!"; print "\nYou might get a collision error. Try anyway!\n"; $revision = 0; } # Questioning to change CDDB entries and ask to fill missing fields. if(defined $cd{year} && $year ne "") { $year = get_answ("year",$year); } if(!$year) { while($year !~ /^\d{4}$| / || !$year ) { print "\nPlease enter the year (or none): "; $year = ; chomp $year; $cd{year} = $year; last if(!$year); } } if($cd{year}) { $cddbsubmission = 1 unless($year eq $cd{year}); } else { $cddbsubmission = 1; } # Ask if CDDB category shall be changed and check if done; # $categ will be an empty string if user wants to change it. $oldcat = $categ; if($cd{cat} && $categ) { $categ = get_answ("CDDB category",$categ); } my @categ = (); my @categories = ( "blues", "classical", "country", "data", "folk", "jazz", "misc", "newage", "reggae", "rock", "soundtrack" ); if(!$categ && $submission != 0) { print "I check for available categories, please wait."; print "\n\nAvailable categories:\n"; foreach $_ (@categories) { my $templines = ""; my $source = "http://www.freedb.org/freedb/" . $_ . "/" . $cddbid; $templines = LWP::Simple::get($source); # Question: what is wrong that I need to put a \n the print # command to force perl to print right away, and not to print # the whole bunch only when the foreach-loop is done??? if($templines) { push @categ, $_; } else { print " $_\n" } } if($categ[10]) { print "\nAll 11 categories are used, bad luck!"; print "\nSave the file locally with --archive!\n"; print "\nUse one of the following categories:"; print "\nblues, classical, country, data, folk"; print "\njazz, misc, newage, reggae, rock, soundtrack\n"; $cddbsubmission = 0; } # Check if the $categ variable is correct. while($categ !~ /^blues$|^classical$|^country$|^data$|^folk$| |^jazz$|^newage$|^reggae$|^rock$|^soundtrack$| |^misc$/ ) { print "\nPlease choose one of the available CDDB categories: " if($categ[10]); print "\nPlease choose one of the categories: " unless($categ[10]); $categ = ; chomp $categ; } $cddbsubmission = 1 unless($categ eq $cd{cat}); } # If one changes catecory for a new submission, set Revision to 0. if($oldcat ne $categ && defined $cd{cat}){ $revision = 0; } # Remind the user if genre is not ID3v2 compliant even if Lame is # not used! Reason: There should be no garbage genres in the DB. # If Lame is used, genre has already been checked! if($lameflag != 1 && defined $genre) { ($genre,$genreno) = check_genre($genre); $cddbsubmission = 1 unless($genre eq $cd{'genre'}); } # Do not to ask if genre had been passed from command line. unless($pgenre) { $genre = get_answ("genre",$genre); } unless($genre) { print "\nPlease enter a valid CDDB genre (or none): "; $genre = ; chomp $genre; $cd{genre} = $genre; # Allow to submit no genre! Else check it! if($genre) { $genre =~ s/[\015]//g; ($genre,$genreno) = check_genre($genre); } } $cddbsubmission = 1 unless($genre eq $cd{'genre'}); my $dtitle = $artist . " / " . $album; substr($dtitle, 230, 0, "\nDTITLE=") if(length($dtitle) > 250); substr($dtitle, 460, 0, "\nDTITLE=") if(length($dtitle) > 500); # Start writing the CDDB submission. open(TOC, ">$homedir/cddb.toc") or die "Can not write to cddb.toc $!\n"; print TOC "# xmcd CD database generated by RipIT\n"; print TOC "#\n"; print TOC "# Track frame offsets:\n"; $i = 0; foreach (@framelist) { print TOC "# $_\n" if($i < $#framelist); $i++; } print TOC "#\n"; print TOC "# Disc length: $totals seconds\n"; print TOC "#\n"; print TOC "# Revision: $revision\n"; my $time = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime)); my $date = sprintf("%04d-%02d-%02d", sub {$_[5]+1900, $_[4]+1, $_[3]}->(localtime)); $date = $date . " at " . $time; print TOC "# Submitted via: RipIT $version "; print TOC "www.suwald.com/ripit/ripit.html on $date\n"; print TOC "#\n"; print TOC "DISCID=$cddbid\n"; print TOC "DTITLE=$dtitle\n"; print TOC "DYEAR=$year\n"; if(defined $genre){ print TOC "DGENRE=$genre\n"; } elsif($genre eq "" && defined $categ) { print TOC "DGENRE=$categ\n"; } $i = 0; foreach (@tracktags) { substr($_, 230, 0, "\nTTITLE$i=") if(length($_) > 250); substr($_, 460, 0, "\nTTITLE$i=") if(length($_) > 500); print TOC "TTITLE$i=$_\n"; ++$i; } my @comment = extract_comm; my $commentest = "@comment"; if($commentest) { $ans = "x"; $check = 0; print "Confirm (Enter), delete or edit each comment line "; print "(c/d/e)!\n"; foreach (@comment) { while($ans !~ /^c|^d|^e/) { print "$_ (c/d/e): "; $ans = ; chomp $ans; if($ans eq "") { $ans = "c"; } } if($ans =~ /^c/ || $ans eq "") { print TOC "EXTD=$_\\n\n"; $check = 1; } elsif($ans =~ /^e/) { print "Enter a different line: \n"; my $ans = ; chomp $ans; substr($ans, 230, 0, "\nEXTD=") if(length($ans) > 250); substr($ans, 460, 0, "\nEXTD=") if(length($ans) > 500); print TOC "EXTD=$ans\\n\n"; $cddbsubmission = 1; $check = 1; } else { # Don't print the line. $cddbsubmission = 1; } $ans = "x"; } $line = "a"; while(defined $line) { print "Do you want to add a line? (Enter for none or type!): "; $line = ; chomp $line; $cddbsubmission = 1 if($line ne ""); last if(!$line); substr($line, 230, 0, "\nEXTD=") if(length($line) > 250); substr($line, 460, 0, "\nEXTD=") if(length($line) > 500); print TOC "EXTD=$line\\n\n"; $check = 1; } # If all lines have been deleted, add an empty EXTD line! if($check == 0){ print TOC "EXTD=\n"; } } # If there are no comments, ask to add some. elsif(!$comment[0]) { $line = "a"; my $linecn = 0; while(defined $line) { print "Please enter a comment line (or none): "; $line = ; chomp $line; $cddbsubmission = 1 if($line ne ""); substr($line, 230, 0, "\nEXTD=") if(length($line) > 250); substr($line, 460, 0, "\nEXTD=") if(length($line) > 500); print TOC "EXTD=$line\n" if($linecn == 0); print TOC "EXTD=\\n$line\n" if($linecn != 0); $linecn++; # This line has to be written, so break the # while loop here and not before, as above. last if(!$line); } } else { print TOC "EXTD=\n"; } # Extract the track comment lines EXTT. my @trackcom = grep(/^EXTT\d+=/, @{$cd{raw}}); @trackcom = grep(s/^EXTT\d+=//, @trackcom); foreach (@trackcom) { chomp $_; $_ =~ s/[\015]//g; } $ans = get_answ('Track comment','existing ones'); if($ans eq "") { $i = 0; while($i < $tracks) { my $track; if($trackcom[$i]) { printf(" Track comment %02d (%s):", $i+1, $trackcom[$i]); } else { printf(" Track comment %02d: ", $i+1); } $track = ; chomp $track; substr($track, 230, 0, "\nEXTT$i=") if(length($track) > 250); substr($track, 460, 0, "\nEXTT$i=") if(length($track) > 500); # If CDDB entry confirmed, take and replace it in tracklist. if(defined $trackcom[$i] && $track eq "") { print TOC "EXTT$i=$trackcom[$i]\n"; } elsif(defined $trackcom[$i] && $track ne "") { print TOC "EXTT$i=$track\n"; $cddbsubmission = 1; } elsif($track ne "") { print TOC "EXTT$i=$track\n"; $cddbsubmission = 1; } else { print TOC "EXTT$i=\n"; } $i++; } } elsif(@trackcom) { $i = 0; foreach (@tracklist) { print TOC "EXTT$i=$trackcom[$i]\n"; ++$i; } } else { $i = 0; foreach (@tracklist) { print TOC "EXTT$i=\n"; ++$i; } } # Extract the playorder line. my @playorder = grep(/^PLAYORDER=/, @{$cd{raw}}); @playorder = grep(s/^PLAYORDER=//, @playorder); if(@playorder) { my $playorder = $playorder[0]; chomp $playorder; print TOC "PLAYORDER=$playorder\n"; } else { print TOC "PLAYORDER=\n"; } close(TOC); # Copy the *edited* CDDB file if variable set to the ~/.cddb/ # directory. if($archive == 1 && $cddbsubmission != 2) { log_system("mkdir -m 0777 -p \"$homedir/.cddb/$categ\"") or print "Can not create directory \"$homedir/.cddb/$categ\": $!\n"; log_system( "cp \"$homedir/cddb.toc\" \"$homedir/.cddb/$categ/$cddbid\"" ) or print "Can not copy cddb.toc to directory ", "\"$homedir/.cddb/$categ/$cddbid\": $!"; print "Saved file $cddbid in \"$homedir/.cddb/$categ/\""; } print "\n"; # If no connection to the internet do not submit. if($submission == 0) { $cddbsubmission = 0; } if($cddbsubmission == 1) { my $ans = "x"; while($ans !~ /^y$|^n$/) { print "Do you really want to submit your data? [y/n] (y) "; $ans = ; chomp $ans; if($ans eq ""){ $ans = "y"; } } if($ans =~ /^y/){ $cddbsubmission = 1; } else{ $cddbsubmission = 0; } } if($cddbsubmission == 1) { while($mailad !~ /.@.+[.]./) { print "\nReady for submission, enter a valid return "; print "e-mail address: "; $mailad = ; chomp $mailad; } open TOC, "cat \"$homedir/cddb.toc\" |" or die "Can not open file $homedir/cddb.toc $!\n"; my @lines = ; close TOC; $subject = "cddb " . $categ . " " . $cddbid; open MAIL, "|/usr/sbin/sendmail -t -r $mailad" or print "/usr/sbin/sendmail not installed? $!"; # Generate the mail-header and add the toc-lines. print MAIL "From: $mailad\n"; print MAIL "To: freedb-submit\@freedb.org\n"; # print MAIL "To: test-submit\@freedb.org\n"; print MAIL "Subject: $subject\n"; print MAIL "MIME-Version: 1.0\n"; print MAIL "Content-Type: text/plain; charset=$charset\n"; print MAIL $_ foreach (@lines); close MAIL; print "Mail exit status not zero: $?" if($?); print "CDDB entry submitted.\n\n" unless($?); unlink("$homedir/cddb.toc"); } elsif($cddbsubmission == 2) { print "\n CDDB entry created and saved in \$HOME, but not send, "; print "because no changes"; print "\n were made! Please edit and send it manually to "; print "freedb-submit\@freedb.org"; print "\n with subject: cddb $categ $cddbid\n\n"; sleep (4); } else { print "\n CDDB entry saved in your home directory, but not send,"; print "\n please edit it and send it manually to:"; print "\n freedb-submit\@freedb.org with subject:"; print "\n cddb $categ $cddbid\n\n"; } } ######################################################################## # # Check if genre is correct. # sub check_genre { my $genre = $_[0]; my $genreno = ""; my $genrenoflag = 1; $genre = " " if ($genre eq ""); # If Lame is not used, don't die if ID3v2-tag is not compliant. if($lameflag == 0) { unless(log_system("lame --genre-list | grep -i \" $genre\$\" > /dev/null ")) { print "Genre $genre is not ID3v2 compliant!\n" if($verbose >= 1); print "I continue anyway!\n\n" if($verbose >= 1); $genreno = "not ID3v2 compliant!\n"; chomp $genreno; } return ($genre,$genreno); } # If Lame is not installed, don't loop for ever. if($lameflag == -1) { $genreno = "Unknown.\n"; chomp $genreno; return ($genre,$genreno); } # Check if (similar) genre exists. Enter a new one with interaction, # or take the default one. while(!log_system("lame --genre-list | grep -i \"$genre\" > /dev/null ")) { print "Genre $genre is not ID3v2 compliant!\n" if($verbose >= 1); if($interaction == 1) { print "Use \"lame --genre-list\" to get a list!\n"; print "\nPlease enter a valid CDDB genre (or none): "; $genre = ; chomp $genre; $cd{genre} = $genre; } else { print "Genre \"Other\" will be used instead!\n" if($verbose >= 1); $genre = "12 Other"; } } if($genre eq "") { return; } elsif($genre =~ /^\d+$/) { $genre = `lame --genre-list | grep -i \' $genre \'`; chomp $genre; } else { # First we want to be sure that the genre from the DB, which might # be "wrong", e.g. wave (instead of Darkwave or New Wave) or synth # instead of Synthpop, will be correct. Put the DB genre to ogenre # and get a new right-spelled genre... Note, we might get several # possibilites, e.g. genre is Pop, then we get a bunch of # "pop-like" genres! # There will be a linebreak, if multiple possibilities found. my $ogenre = $genre; $genre = `lame --genre-list | grep -i \'$genre\'`; chomp $genre; # Second we want THE original genre, if it precisly exists. my $testgenre = `lame --genre-list | grep -i \'\^... $ogenre\$\'`; chomp $testgenre; $genre = $testgenre if($testgenre); # If we still have several genres: # Either let the operator choose, or if no interaction, take # default genre: "12 Other". if($genre =~ /\n/ && $interaction == 1) { print "More than one genre possibility found!\n"; my @list = split(/\n/,$genre); my ($i,$j) = (0,1); while($i > $#list+1 || $i == 0) { # TODO: Here we should add the possibility to choose none! # Or perhaps to go back and choose something completely # different. foreach (@list) { printf(" %2d: $_ \n",$j); $j++; } $j--; print "\nChoose [1-$j]: "; $i = ; chomp $i; $j = 1; } $genre = $list[$i-1]; chomp $genre; } # OK, no interaction! Take the first or default genre! elsif($genre =~ /\n/ && $interaction != 1 && $lameflag == 1) { $genre = "12 Other" if($genre eq ""); $genre =~ s/\n.*//; } # OK, the genre is not Lame compliant, and we do not care about, # because Lame is not used. Set the genre-number-flag to 0 to # prevent genre-number-extracting at the end of the subroutine. elsif($lameflag != 1) { $genre = $ogenre; $genrenoflag = 0; } chomp $genre; } # Extract genre-number. if($genre ne "" && $genrenoflag == 1){ $genre =~ s/^\s*//; my @genre = split(/ /, $genre); $genreno = shift(@genre); $genre = "@genre"; } return ($genre,$genreno); } ######################################################################## # # Check mirrors. Seems to be obsolet, therefore not used anymore. # sub check_host { while($mirror !~ /^freedb$|^at$|^au$|^ca$|^es$|^fi$|^fr$|^jp$|^jp2$| |^ru$|^uk$|^uk2$|^us$/){ print "host mirror ($mirror) not valid!\nenter freedb, ", "at, au, ca, es, fi, fr, jp, jp2, ru, uk, uk2 or us: "; $mirror = ; chomp $mirror; } } ######################################################################## # # Reply to question # sub get_answ { my $ans = "x"; while($ans !~ /^y|^n/) { print "Do you want to enter a different ".$_[0]." than ".$_[1]; print "? [y/n], (n): "; $ans = ; chomp $ans; if($ans eq ""){ $ans = "n"; } } if($ans =~ /^y/){ return ""; } return $_[1]; } ######################################################################## # # Check quality passed from command line for oggenc, flac and lame only # if vbr wanted (then it's encoder no 3). # sub check_quality { my $corrflag = 0; if($qualame ne "off") { while($qualame > 9) { print "\nThe quality $qualame is not valid for Lame!\n"; print "Please enter a different quality (0 = best), [0-9]: "; $qualame = ; chomp $qualame; $corrflag = 1; } } if($qualoggenc ne "off") { while($qualoggenc > 10 || $qualoggenc == 0) { print "\nThe quality $qualoggenc is not valid for Oggenc!\n"; print "Please enter a different quality (10 = best), [1-10]: "; $qualoggenc = ; chomp $qualoggenc; $corrflag = 1; } } if($quaflac ne "off") { while($quaflac > 8) { print "\nThe compression level $quaflac is not valid"; print " for Flac!\n"; print "Please enter a different compression level "; print "(0 = lowest), [0-8]: "; $quaflac = ; chomp $quaflac; $corrflag = 1; } } if($quafaac ne "off") { while($quafaac > 500 || $quafaac < 10) { print "\nThe quality $quafaac is not valid for Faac!\n"; print "Please enter a different quality (500 = max), "; print "[10-500]: "; $quafaac = ; chomp $quafaac; $corrflag = 1; } } # Save the corrected values to the pquality array! Do it in # the same order the encoders had been passed! If no encoders # were passed, test if there are encoders in the config file... if($corrflag == 1) { @pquality = split(/,/, join(',', @pquality)); for(my $index = 0; $index <= $#pquality; $index++) { if($coder[$index] =~ /^\d/) { $pquality[$index] = $qualame if($coder[$index] == 0); $pquality[$index] = $qualoggenc if($coder[$index] == 1); $pquality[$index] = $quaflac if($coder[$index] == 2); $pquality[$index] = $quafaac if($coder[$index] == 3); } else { $pquality[$index] = $qualame if($index == 0); $pquality[$index] = $qualoggenc if($index == 1); $pquality[$index] = $quaflac if($index == 2); $pquality[$index] = $quafaac if($index == 3); } } my $pquality = join(',',@pquality); @pquality = (); $pquality[0] = $pquality; } } ######################################################################## # # Check bitrate for Lame only if vbr is wanted. # sub check_vbrmode { while($vbrmode ne "new" && $vbrmode ne "old") { print "\nFor vbr using Lame choose *new* or *old*! (new): "; $vbrmode = ; chomp $vbrmode; $vbrmode = "new" if ($vbrmode eq ""); } } ######################################################################## # # Check preset for Lame only. # sub lame_preset { if($vbrmode eq "new") { $preset = "fast " . $preset; } } ######################################################################## # # Check if there is an other than $cddev which has a CD if no --device # option was given. # sub check_cddev { # Try to get a list of possible CD devices. open(DEV, "/etc/fstab"); my @dev = ; close DEV; @dev = grep(/^\s*\/dev/, @dev); @dev = grep(!/^\s*\/dev\/[f|h]d/, @dev); @dev = grep(!/sd/, @dev); my @devlist = (); foreach (@dev) { my @line = split(/\s/, $_); chomp $line[0]; push(@devlist, $line[0]); } # First check some default addresses. if(open(CD, "$cddev")) { $cddev = $cddev; close CD; } elsif(open(CD, "/dev/cdrecorder")) { $cddev = "/dev/cdrecorder"; close CD; } elsif(open(CD, "/dev/dvd")) { $cddev = "/dev/dvd"; close CD; } else { foreach (@devlist) { if(open(CD, "$_")) { $cddev = $_; chomp $cddev; close CD; } } } # On a notebook, the tray could not be closed automatically! # Print error message and retry detection. if($cddev eq "") { print "Is there a CD and the tray of the device closed?\n"; print "I pause 12 seconds.\n"; sleep(12); foreach (@devlist) { if(open(CD, "$_")) { $cddev = $_; chomp $cddev; close CD; } } } if($cddev eq ""){ print "Could not detect CD device! The default /dev/cdrom "; print "device will be used.\n"; $cddev = "/dev/cdrom"; } } ######################################################################## # # Check bitrate if bitrate is not zero. # sub check_bitrate { while($bitrate !~ /^32$|^40$|^48$|^56$|^64$|^80$|^96$|^112$|^128$| |^160$|^192$|^224$|^256$|^320$|^off$/) { print "\nBitrate should be one of the following numbers or "; print "\"off\"! Please Enter"; print "\n32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, "; print "256 or 320: (128) \n"; $bitrate = ; chomp $bitrate; if($bitrate eq "") { $bitrate = 128; } } } ######################################################################## # # Check protocol level for CDDB query. # sub check_proto { while($proto > 6) { print "Protocol level for CDDB query should be less-equal 6!\n"; print "Enter an other value for protocol level (6): "; $proto = ; chomp $proto; $proto = 6 if($proto eq ""); } } ######################################################################## # # Check, clean and sort the coder array, and then, according to, the # qualities. # sub check_coder { my $lameindex; # Create encoder array if passed or read from config file. if(@pcoder) { @coder = split(/,/, join(',',@pcoder)); } else { @coder = split /,/, join(',', @coder); } # Now check if an encoder was passed several times. my @scoder = sort {$a <=> $b} @coder; my $index = 0; while($index < $#scoder) { if($scoder[$index] == $scoder[$index+1]) { # TODO delete entry if encoder appears more than once. # splice @coder,$index,1; die "Found encoder $scoder[$index] twice, confused!\n"; } $index++; } # Now check, that there is no entry >= 4, find the index # of lame if present and calculate the number of encoders # that need a quality entry. my $nofcoders=0; for(my $index = 0; $index <= $#coder; $index++) { if($coder[$index] >= 4) { print "Encoder number $coder[$index] does not yet exist, "; print "please enter\n"; die "0 for Lame, 1 for Oggenc, 2 for Flac and 3 for Faac!\n\n"; } $nofcoders++; if($coder[$index] == 0 || $coder[$index] >= 4) { $lameindex = $index; $nofcoders = $nofcoders - 1; $lameflag = 1; } } # Use comma separated string to write the encoder array to the # config file! $wcoder = join(',', @coder); $nofcoders = $nofcoders - 1; # Order the qualities (if passed) in the same way as coders! # Note, the array of coders is NOT sorted! # First, check if there is a config file with an probably unusual # order of encoders. In this way, the check-quality subroutine # will ask the correct questions and not mess up the encoders! my $openflag="no"; if(!@pcoder && -r "$homedir/.ripit/config") { open(CONF, "$homedir/.ripit/config"); my @conflines = ; close CONF; @pcoder = grep(s/^coder=//, @conflines) unless(@pcoder); chomp @pcoder; if($pcoder[0] =~ /^\d/) { @coder = split(/,/, join(',',@pcoder)); } } # Second, check if quality was passed. if(@pquality) { @quality = split(/,/, join(',', @pquality)); # If lame was passed, but no quality for lame (because # user wants cbr), then add the default quality for lame # at the right place of array @quality to ensure that the # check_quality() subroutine works! NOTE: $nofcoder is # the number of non-lame encoders! if($#quality == $nofcoders && $lameflag == 1) { splice @quality, $lameindex, 0, 5; } for(my $index = 0; $index <= $#quality; $index++) { if($coder[$index] =~ /^\d/) { $qualame = $quality[$index] if($coder[$index] == 0); $qualoggenc = $quality[$index] if($coder[$index] == 1); $quaflac = $quality[$index] if($coder[$index] == 2); $quafaac = $quality[$index] if($coder[$index] == 3); } else { $qualame = $quality[$index] if($index == 0); $qualoggenc = $quality[$index] if($index == 1); $quaflac = $quality[$index] if($index == 2); $quafaac = $quality[$index] if($index == 3); } } } } ######################################################################## # # Over or re-write the config file (depends on option savenew or save). # sub save_config { log_system("mkdir -m 0777 -p $homedir/.ripit") or die "Can not create directory $homedir/.ripit/config: $!\n"; my $ripdir = $homedir . "/.ripit"; rename("$ripdir/config","$ripdir/config.old") if(-r "$ripdir/config"); open(CONF, "> $ripdir/config") or die "Can not write to $homedir/.ripit/config: $!\n"; print CONF " ##### # # RipIT $version configuration file. # # For further information on ripit # configuration / parameters and examples see # the manpage or type ripit --help # or the README provided with ripit. ##### # # Ripping device & path. # # cddevice: define ripping device # if other than /dev/cdrom # Default: /dev/cdrom cddevice=$cddev # output: path for audio files # Default: not set output=$outputdir # directory permissions: Permissions for directories # Default: 0777 dpermission=$dpermission # file permissions: Permissions for sound and log files # Default: 0644 fpermission=$fpermission ##### # # Ripping options. # # ripper: select CD ripper # 0 - dagrab # 1 - cdparanoia # 2 - cdda2wav # 3 - tosha # 4 - cdd # Default: cdparanoia ripper=$ripper # ripopt: user definable options for the CD ripper # Default: not set ripopt=$ripopt # paranoia: turn \"paranoia\" on or off for dagrab # and cdparanoia # Possible values: 0 - off, 1 - on # Default: on paranoia=$parano # ghost: analyze the wavs for possible gaps and # split the wav into chunks of sound # Possible values: 0 - off, 1 - on # Default: off ghost=$ghost # prepend: enlarge the the chunk of sound by a number of # seconds at the beginning (if possible). # Possible values: any positive number # Default: 2 prepend=$prepend # extend: enlarge the the chunk of sound by a number of # seconds at the end (if possible). # Possible values: any positive number # Default: 2 extend=$extend ##### # # Encoding options # # encode: encode the wavs # Possible values: 0 - off, 1 - on # Default: on encode=$encode # coder: Select encoders for audio files # 0 - Lame (mp3) # 1 - Oggenc (ogg) # 2 - Flac (flac) # 3 - Faac (m4a) # Multiple encoders can be selected by giving # a comma-separated list # Example: coder=0,1,2 encodes CD to mp3, ogg and flac files # Default: Lame coder=$wcoder ### # # lame (mp3) encoder options # # qualame: Sets audio quality for lame encoder # in vbr (variable bitrate) mode # Possible values: 0...9, off # 0: higest quality # 9: lowest quality # Can be set to \"off\" if preset is used # Default: 5 qualame=$qualame # lameopt: Additional options for lame encoder # Default: not set lameopt=$lameopt # vbrmode: Enable varibale bitrate for lame encoder # Values: \"old\" or \"new\" # Default: not set vbrmode=$vbrmode # bitrate: Sets bitrate for lame encoder # Possible values: 32...320, off # Should be set to \"off\" if vbr is used # Default: 128 bitrate=$bitrate # maxrate: Sets maximum bitrate for lame (when using vbr) # and oggenc # Possible values: 0 - off, 32...320 # Default: 0 maxrate=$maxrate # preset: Use lame presets # To set the \"fast\" switch, use --vbrmode new. # Possible values: medium, standard, extreme, insane # # medium: 160kbps # standard: 192kbps # extreme: 256kbps # insane: 320kbps # # Default: not set preset=$wpreset ### # # oggenc (ogg) encoder options # # qualoggenc: Sets audio quality for oggenc # Possible values: 1..10, off # 1: lowest quality # 10: highest quality # Can be set to \"off\" # Default: 3 qualoggenc=$qualoggenc # oggencopt: Additional options for oggenc # Default: not set oggencopt=$oggencopt ### # # flac (lossless) encoder options # # quaflac: Sets audio quality for flac encoder # Possible values: 0...8, off # 0: highest quality # 8: lowest quality # Can be set to \"off\" # Default: 5 quaflac=$quaflac # flacopt: Additional options for flac encoder # Default: not set flacopt=$flacopt ### # # faac (m4a) encoder options # # quafaac: Sets audio quality for faac encoder # Possible values: 10...500, off # 500: highest quality # 10: lowest quality # Can be set to \"off\" # Default: 100 quafaac=$quafaac # faacopt: Additional options for faac encoder # Default: not set faacopt=$faacopt ##### # # Trackname and directory template # # dirtemplate: Template for directory structure # The template can be created using the following # variables (tags) # \$album # \$artist # \$genre # \$trackname # \$tracknum # \$year # Example: \"\$artist - \$year\" # The double quotes (\") are mandatory! # Default: \"\$artist - \$album\" dirtemplate=$dirtemplate # tracktemplate: Template for track names # \"tracktemplate\" is used similarly to \"dirtemplate\" # Default: \"\$tracknum \$trackname\" tracktemplate=$tracktemplate # infolog: Log certain operations to file # (e.g. system calls, creation of dirs/files) # Possible values: filename (full path, no ~ here!) # Default: not set infolog=$infolog # lowercase: Convert filenames to lowercase # Possible values: 0 - off, 1 - on # Default: off lowercase=$lowercase # underscore: Replace blanks in filenames with undersocres # Possible values: 0 - off, 1 - on # Default: off underscore=$underscore # chars: Exclude special characters and (ending!) periods # in file names. If no argument passed to option, then # following characters will be purged: :*#?\$\! and # (ending) periods deleted. Else only the passed ones # will be erased, and (ending) periods. No need to escape # the special characters here, if one enters them manually. # Possible values: none, any (?) # Default: not set chars=$chars # playlist: Create m3u playlist with or without the full path # in the filename. # Possible values: 0 - off, 1 - on with full path # 2 - on with no path (filename only) # Default: on playlist=$playlist ##### # # Audio file tagging # # year-tag: State a year (mp3, m4a) or a date (ogg, flac) tag. # Possible values: integer # Default: not set year=$year # comment-tag: State a comment (mp3, m4a) or a # description (ogg, flac) tag. # Possible values: any string # Default: not set comment=$commentag # utftag: Use Lame-tags in UTF-8 or convert them # (but not the filenames) from Unicode to ISO8859-1. # Use when your mp3-audio player doesn't support Unicode tags. # Recommended with Lame. # Possible values: 0 - off, 1 - on # Default: on utftag=$utftag ##### # # CDDB options # # CDDBHOST: Specifies the CDDB server # Note: Full name of the server used is \$mirror.\$CDDBHOST # E.g., default server is freedb.freedb.org # Default: freedb.org CDDBHOST=$CDDB_HOST # mirror: Selects freedb mirror # Possible values: \"freedb\" or any freedb mirrors # See www.freedb.org for mirror list # Note: Full name of the server used is \$mirror.\$CDDBHOST # E.g., default server is freedb.freedb.org # Default: freedb mirror=$mirror # trasfer: Set transfer mode for cddb queries # Possible values: cddb, http # Default: cddb transfer=$transfer # proto: Set CDDP protocol level # Possible values: 5,6 # Protocol level 6 supports Unicode (UTF-8) # Default: 6 proto=$proto # proxy: Address of http-proxy, if needed # Default: not set proxy=$proxy # mailad: Mail address for cddb submissions # Valid user email address for submitting cddb entries # Default: not set mailad=$mailad # archive: Read and save cddb data on local machine # Possible values: 0 - off, 1 - on # Default: off archive=$archive # submission: Submit new or edited cddb entries to # freeCDDB # Possible values: 0 - off, 1 - on # Default: on submission=$submission # interaction: Turns on or off user interaction in cddb dialog # Possible values: 0 - off, 1 - on # Default: on interaction=$interaction ##### # # LCD options # # lcd: Use lcdproc to display status on LCD # Possible values: 0 - off, 1 - on # Default: off lcd=$lcd # lcdhost: Specify the lcdproc host # Default: localhost lcdhost=$lcdhost # lcdport: Specify port number for $lcdhost # Default: 13666 lcdport=$lcdport ##### # # Distributed ripping options # # sshlist: Comma separated list of remote machines # that ripit shall use for encoding # The output path must be the same for all machines. # Specify the login (login\@machine) only if not the # same for the remote machine. Else just state the # machine names. # Default: not set sshlist=$wsshlist # scp: Copy files to encode to the remote machine # Use if the fs can not be accessed on the remote machines # Possible values: 0 - off, 1 - on # Default: off scp=$scp # local: Turn off encoding on local machine, # e.g. use only remote machines # Possible values: 0 - off, 1 - on # Example: local=0 (off) turns off encoding on the # local machine # Default: on local=$local ##### # # Misc. options # # verbosity: Run silent (do not output comments, status etc.) (0), with # minimal (1), normal without encoder msgs (2), normal (3), verbose (4) # or extremly verbose (5) # Possible values: 0...5 # Default: 3 - normal verbose=$verbose # eject: Eject cd after finishing encoding # Possible values: 0 - off, 1 - on # Default: off eject=$eject # ejectcmd: Command used to eject and close CD tray # Possible values: string # Example: /usr/sbin/cdcontrol for FreeBSD # Default: eject ejectcmd=$ejectcmd # ejectopt: Options to command used to eject or close CD # Possible values: string # Note: Don't use options -t / close or eject, # RipIT knows when to eject or load the tray # Default: the cddevice ejectopt=$ejectopt # loop: Continue as soon a new CD is inserted, # implies that the CD is ejected when done! # Possible values: 0 - off, 1 - on # Default: off loop=$loop # halt: Powers off machine after finishing encoding # Possible values: 0 - off, 1 - on # Default: off halt=$halt # nice: Sets \"nice\" value for the encoding process # Possible values: 0..19 for normal users, # -20..19 for user \"root\" # Default: 0 nice=$nice # nicerip: Sets \"nice\" value for the ripping process # Possible values: 0..19 for normal users, # -20..19 for user \"root\" # Default: 0 nicerip=$nicerip # core: Comma separated list of numbers giving maximum # of allowed encoder processes to run at the same time # (on each machine when using sshlist). # Possible values: comma separated integers # Default: 1 core=$wcore # md5sum: Create file with md5sums for each type of sound files # Possible values: 0 - off, 1 - on # Default: off md5sum=$md5sum # wav: Don't delete wave-files after encoding # Possible values: 0 - off, 1 - on # Default: off wav=$wav # normalize: Normalizes the wave-files to a given dB-value # (Default: -12dB) # See http://normalize.nongnu.org for details # Possible values: 0 - off, 1 - on # Default: off normalize=$normalize # normopt: Options to pass to normalize # Possible values: -a -nndB : Normalize to -nn dB, default is -12dB, # Value range: All values <= 0dB # Example : normalize -a -20dB *.wav # -b : Batch mode - loudness differences # between individual tracks of a CD are # maintained # -m : Mix mode - all track are normalized to # the same loudness # -v : Verbose operation # -q : Quiet operation # For further options see normalize documentation. # Default: -bv normopt=$normopt # cdtoc: Create a toc (cue) file to burn the wavs with # cd-text using cdrdao or cdrecord (in dao mode) # Possible values: 0 - off, 1 - on # Default: off cdtoc=$cdtoc \n"; close CONF; } ######################################################################## # # Read the config file, take the parameters only if NOT yet defined! # sub read_config { my $ripdir = $homedir."/.ripit/config"; if(-r "$ripdir" ) { open(CONF, "$ripdir") || print "Can not read config file!\n"; my @conflines = ; close CONF; my @confver = grep(s/^# RipIT //, @conflines); @confver = split(/ /, $confver[0]) if($confver[0] =~ /^\d/); my $confver = $confver[0] if($confver[0] =~ /^\d/); $confver = 0 unless($confver); chomp $confver; if($version ne $confver && $savepara == 0) { $verbose = 3 if($verbose <= 1); print "\nPlease update your config-file with option --save"; print "\nto ensure correct settings! I pause 3 seconds!\n\n"; grep(s/^chars=[01]\s*$/chars=/, @conflines); sleep(3); } elsif($version ne $confver) { grep(s/^chars=[01]\s*$/chars=/, @conflines); } my @archive = grep(s/^archive=//, @conflines); $archive = $archive[0] unless defined $parchive; chomp $archive; my @bitrate = grep(s/^bitrate=//, @conflines); $bitrate = $bitrate[0] unless($pbitrate); chomp $bitrate; my @maxrate = grep(s/^maxrate=//, @conflines); $maxrate = $maxrate[0] unless($pmaxrate); chomp $maxrate; my @cddev = grep(s/^cddevice=//, @conflines); $cddev = $cddev[0] unless($pcddev); chomp $cddev; my @cdtoc = grep(s/^cdtoc=//, @conflines); $cdtoc = $cdtoc[0] unless($pcdtoc); chomp $cdtoc; my @chars = grep(s/^chars=//, @conflines) if($chars eq "xxx"); $chars = $chars[0] if($chars[0]); chomp $chars; my @commentag = grep(s/^comment=//, @conflines); $commentag = $commentag[0] unless($pcommentag); chomp $commentag; my @CDDB_HOST = grep(s/^CDDBHOST=//, @conflines); $CDDB_HOST = $CDDB_HOST[0] unless($PCDDB_HOST); chomp $CDDB_HOST; @pcoder = grep(s/^coder=//, @conflines) unless(@pcoder); # NOTE: all coders are in array entry $pcoder[0]! # NOTE: we have to fill the w_RITE_coder variable! $wcoder = $pcoder[0] if(@pcoder); chomp $wcoder; my @dirtemplate = grep(s/^dirtemplate=//, @conflines); $dirtemplate = $dirtemplate[0] unless($pdirtemplate); chomp $dirtemplate; my @dpermission = grep(s/^dpermission=//, @conflines); $dpermission = $dpermission[0] unless($pdpermission); chomp $dpermission if($dpermission); my @eject = grep(s/^eject=//, @conflines); $eject = $eject[0] unless defined $peject; chomp $eject; my @ejectcmd = grep(s/^ejectcmd=//, @conflines); $ejectcmd = $ejectcmd[0] unless defined $pejectcmd; chomp $ejectcmd if($ejectcmd); my @ejectopt = grep(s/^ejectopt=//, @conflines); $ejectopt = $ejectopt[0] unless defined $pejectopt; chomp $ejectopt if($ejectopt); my @encode = grep(s/^encode=//, @conflines); $encode = $encode[0] unless defined $pencode; chomp $encode; my @extend = grep(s/^extend=//, @conflines); $extend = $extend[0] unless defined $pextend; chomp $extend if($extend); my @fpermission = grep(s/^fpermission=//, @conflines); $fpermission = $fpermission[0] unless($pfpermission); chomp $fpermission if($fpermission); my @ghost = grep(s/^ghost=//, @conflines); $ghost = $ghost[0] unless defined $pghost; chomp $ghost if($ghost); my @halt = grep(s/^halt=//, @conflines); $halt = $halt[0] unless($phalt); chomp $halt; my @infolog = grep(s/^infolog=//, @conflines); $infolog = $infolog[0] unless($pinfolog); chomp $infolog; my @interaction = grep(s/^interaction=//, @conflines); $interaction = $interaction[0] unless defined $pinteraction; chomp $interaction; my @lcd = grep(s/^lcd=//, @conflines); $lcd = $lcd[0] unless defined $plcd; chomp $lcd; my @lcdhost = grep(s/^lcdhost=//, @conflines); $lcdhost = $lcdhost[0] unless($plcdhost); chomp $lcdhost; my @lcdport = grep(s/^lcdport=//, @conflines); $lcdport = $lcdport[0] unless($plcdport); chomp $lcdport; my @local = grep(s/^local=//, @conflines); $local = $local[0] unless defined $plocal; chomp $local; my @loop = grep(s/^loop=//, @conflines); $loop = $loop[0] unless defined $ploop; chomp $loop; my @lowercase = grep(s/^lowercase=//, @conflines); $lowercase = $lowercase[0] unless defined $plowercase; chomp $lowercase; my @mailad = grep(s/^mailad=//, @conflines); $mailad = $mailad[0] unless($pmailad); chomp $mailad; my @mirror = grep(s/^mirror=//, @conflines); $mirror = $mirror[0] unless($pmirror); chomp $mirror; my @normalize = grep(s/^normalize=//, @conflines); $normalize = $normalize[0] unless defined $pnormalize; chomp $normalize; my @normopt = grep(s/^normopt=//, @conflines); $normopt = $normopt[0] unless($pnormopt); chomp $normopt; my @nice = grep(s/^nice=//, @conflines); $nice = $nice[0] unless defined $pnice; chomp $nice; my @nicerip = grep(s/^nicerip=//, @conflines); $nicerip = $nicerip[0] unless defined $pnicerip; chomp $nicerip if($nicerip); my @outputdir = grep(s/^output=//, @conflines); $outputdir = $outputdir[0] unless($poutputdir); chomp $outputdir; my @parano = grep(s/^paranoia=//, @conflines); $parano = $parano[0] unless defined $pparano; chomp $parano; my @playlist = grep(s/^playlist=//, @conflines); $playlist = $playlist[0] unless defined $pplaylist; chomp $playlist; my @prepend = grep(s/^prepend=//, @conflines); $prepend = $prepend[0] unless defined $pprepend; chomp $prepend if($prepend); my @preset = grep(s/^preset=//, @conflines); $preset = $preset[0] unless($ppreset); # NOTE: we have to fill the w_RITE_preset variable! $wpreset = $preset[0] unless($ppreset); chomp $preset; chomp $wpreset; my @proto = grep(s/^proto=//, @conflines); $proto = $proto[0] unless($pproto); chomp $proto; my @proxy = grep(s/^proxy=//, @conflines); $proxy = $proxy[0] unless($pproxy); chomp $proxy; my @quafaac = grep(s/^quafaac=//, @conflines) unless(@pquality); $quafaac = $quafaac[0] unless(@pquality); chomp $quafaac; my @quaflac = grep(s/^quaflac=//, @conflines) unless(@pquality); $quaflac = $quaflac[0] unless(@pquality); chomp $quaflac; my @qualame = grep(s/^qualame=//, @conflines) unless(@pquality); $qualame = $qualame[0] unless(@pquality); chomp $qualame; # This search-string is stared to prevent warnings when used with # older config files. Should be changed to ^qualoggenc= in 3.8.0. # Introduced like this in 3.6.0. my @qualoggenc = grep(s/^qualogg.*=//, @conflines) unless(@pquality); $qualoggenc = $qualoggenc[0] unless(@pquality); chomp $qualoggenc; my @faacopt = grep(s/^faacopt=//, @conflines); $faacopt = $faacopt[0] unless($pfaacopt); chomp $faacopt; my @flacopt = grep(s/^flacopt=//, @conflines); $flacopt = $flacopt[0] unless($pflacopt); chomp $flacopt; my @lameopt = grep(s/^lameopt=//, @conflines); $lameopt = $lameopt[0] unless($plameopt); chomp $lameopt; # This search-string is stared to prevent warnings when used with # older config files. Should be changed to ^oggencopt= in 3.8.0. # Introduced like this in 3.6.0. my @oggencopt = grep(s/^ogg.*opt=//, @conflines); $oggencopt = $oggencopt[0] unless($poggencopt); chomp $oggencopt; my @ripper = grep(s/^ripper=//, @conflines); $ripper = $ripper[0] unless defined $pripper; chomp $ripper; my @ripopt = grep(s/^ripopt=//, @conflines); $ripopt = $ripopt[0] unless defined $pripopt; chomp $ripopt; my @clist = grep(s/^core=//, @conflines) unless($pcore[0]); chomp @clist; # NOTE: all core numbers are in array entry $clist[0]! @core = split(/,/, join(',',@clist)); my @rlist = grep(s/^sshlist=//, @conflines) unless($psshlist[0]); chomp @rlist; # NOTE: all machine names are in array entry $rlist[0]! @sshlist = split(/,/, join(',',@rlist)); my @scp = grep(s/^scp=//, @conflines); $scp = $scp[0] unless defined $pscp; chomp $scp; my @submission = grep(s/^submission=//, @conflines); $submission = $submission[0] unless defined $psubmission; chomp $submission; my @transfer = grep(s/^transfer=//, @conflines); $transfer = $transfer[0] unless($ptransfer); chomp $transfer; my @tracktemplate = grep(s/^tracktemplate=//, @conflines); $tracktemplate = $tracktemplate[0] unless($ptracktemplate); chomp $tracktemplate; my @underscore = grep(s/^underscore=//, @conflines); $underscore = $underscore[0] unless defined $punderscore; chomp $underscore; my @utftag = grep(s/^utftag=//, @conflines); $utftag = $utftag[0] unless defined $putftag; chomp $utftag; my @vbrmode = grep(s/^vbrmode=//, @conflines); $vbrmode = $vbrmode[0] unless($pvbrmode); chomp $vbrmode; my @year = grep(s/^year=//, @conflines); $year = $year[0] unless($pyear); chomp $year; my @wav = grep(s/^wav=//, @conflines); $wav = $wav[0] unless defined $pwav; chomp $wav; } else { print "\nNo config file found! Use option --save to create one.\n" if($verbose >= 2); } } ######################################################################## # # Change encoding of tags back to iso-8859-1. # sub back_encoding { my $temp_file = "/tmp/utf8-$$\.txt"; open(TMP, ">$temp_file") or print "$temp_file $!"; print TMP $_[0]; close TMP; my $decoded = `/usr/bin/iconv -f UTF-8 -t ISO-8859-1 $temp_file`; unlink("/tmp/utf8-$$\.txt"); return $decoded; } ######################################################################## # # Check the preset options. # sub check_preset { if($preset !~ /^\d/) { while($preset !~ /^insane$|^extreme$|^standard$|^medium$/) { print "\nPreset should be one of the following words! Please"; print " Enter \ninsane (320\@CBR), extreme (256), standard"; print " (192) or medium (160), (standard): "; $preset = ; chomp $preset; if($preset eq "") { $preset = "standard"; } } } else { while($preset !~ /^32$|^40$|^48$|^56$|^64$|^80$|^96$|^112$|^128$| |^160$|^192$|^224$|^256$|^320$/) { print "\nPreset should be one of the following numbers!", " Please Enter \n32, 40, 48, 56, 64, 80, 96, 112, 128,", " 160, 192, 224, 256 or 320, (128):\n"; $preset = ; chomp $preset; if($preset eq "") { $preset = 128; } } } $preset = "medium" if($preset =~ /\d+/ && $preset == 160); $preset = "standard" if($preset =~ /\d+/ && $preset == 192); $preset = "extreme" if($preset =~ /\d+/ && $preset == 256); $preset = "insane" if($preset =~ /\d+/ && $preset == 320); $wpreset = $preset; } ######################################################################## # # Check sshlist of remote machines and create a hash. # sub check_sshlist { if(@psshlist) { @sshlist = split(/,/, join(',', @psshlist)); } if(@pcore) { @core = split(/,/, join(',', @pcore)); } $wcore = join(',', @core); if(@sshlist || $core[0] > 1) { $sshflag = 1; $wsshlist = join(',', @sshlist); # Create a hash with all machines and the number of encoding # processes each machine is able to handle. $sshlist{'local'} = $core[0] if($local == 1); my $corecn = 1; foreach (@sshlist) { $core[$corecn] = 1 unless($core[$corecn]); $sshlist{$_} = $core[$corecn]; $corecn++; } } else { $sshflag = 0; } } ######################################################################## # # Dispatcher for encoding on remote machines. If there are no .lock # files, a ssh command will be passed, else the dispatcher waits until # an already passed ssh command terminates and removes the lock file. # The dispatcher checks all machines all 6 seconds until a machine is # available. If option --scp is used, the dispatcher will not start an # other job while copying. In this situation, it looks like nothing # would happen, but it's only during scp. # sub enc_ssh { my $machine; my @codwav = (); my $delwav = $_[0]; my $enccom = $_[1]; my $ripnam = $_[2]; my $shortname = $_[3]; my $sufix = $_[4]; my $old_outputdir = $outputdir; my $old_ripnam = $ripnam; my $esc_name; my $esc_dir; my $corecn; $sshflag = 2; while ($sshflag == 2) { # Start on the local machine first. $corecn = 1; for($corecn = 1; $corecn <= $core[0]; $corecn++) { if(! -r "$outputdir/local.lock_$corecn") { if($local == 1) { $sshflag = 1; $machine = "local"; push @codwav, "$shortname"; } } last if($sshflag == 1); } last if($sshflag == 1); $corecn = 1; foreach $_ (keys %sshlist) { $machine = $_; # Why this? for($corecn = 1; $corecn <= $sshlist{$_}; $corecn++) { if(! -r "$outputdir/$machine.lock_$corecn") { $sshflag = 1; } # Prepare array @codwav with all tracknames in, which are # still in progress, i. e. either being ripped or encoded. else { open(LOCK, "$outputdir/$machine.lock_$corecn"); my @locklines = ; close LOCK; my $locklines = $locklines[0] if($locklines[0]); chomp $locklines; # Push trackname into array only if not yet present. my @presence = grep(/$locklines/, @codwav); my $presence = $presence[0]; push @codwav, "$locklines" if(!$presence); } last if($sshflag == 1); } last if($sshflag == 1); } last if($sshflag == 1); sleep 3; } # Untested patch by joni-@gmx.de # He reported problems while deleting the local lock files on a nfs. # Could not reproduce problems. #$esc_name = esc_char($shortname.".".$sufix); #$esc_dir = esc_char($outputdir$machine.".lock"); #log_system("ssh $machine 'echo \"$esc_name\" > $esc_dir'"); if(-r "$outputdir/enc.log" && $verbose >= 3) { open(ENCLOG, ">>$outputdir/enc.log"); print ENCLOG "...on machine $machine.\n" if($#core > 0); print ENCLOG "Executing scp command to $machine.\n" if($scp && $machine !~ /^local$/); close ENCLOG; } elsif($verbose >= 3) { print "...on machine $machine.\n" if($scp && $machine !~ /^local$/); } open(LOCKF, ">$outputdir$machine.lock_$corecn"); print LOCKF "$shortname.$sufix\n"; close (LOCKF); # We need more quotes for the commands (faac,flac,lame,ogg) # passed to the remote machine. NOTE: But now pay attention # to single quotes in tags. Quote them outside of single quotes! # TODO: Please tell me how to quote leading periods, thanks!!! if($machine !~ /^local$/) { $enccom =~ s/'/'\\''/g; $enccom = "'".$enccom."'"; $enccom = "ssh $machine " . $enccom; if($scp) { # CREATE the directory: # Please tell me why one has to quote the double quotes # with a backslash when using ssh! $outputdir = esc_char($outputdir); log_info("new-outputdir: $outputdir on $machine created."); log_system("ssh $machine mkdir -p \\\"$outputdir\\\""); # COPY the File: # Please tell me why one should NOT quote a file name # with blanks when using scp! $ripnam = esc_char($ripnam); log_system("scp $ripnam.wav \\ $machine:\"$ripnam.wav\" \\ > /dev/null 2> /dev/null"); } } $enccom = $enccom . " > /dev/null"; # Because Lame comes with the "Can't get "TERM" environment string" # error message, I decided to switch off all error output. This is # not good, if ssh errors appear, then RipIT may hang with a message # "Checking for lock files". If this happens, switch to vervosity 4 # and look what's going on. $enccom = $enccom . " 2> /dev/null" if($verbose <= 3); if($machine !~ /^local$/ && $scp) { $enccom = $enccom . " && \\ scp $machine:\"$ripnam.$sufix\_enc\" \\ $ripnam.$sufix > /dev/null 2> /dev/null && \\ ssh $machine rm \"$ripnam.*\" "; } $enccom = $enccom . " && \\ mv \"$ripnam.$sufix\_enc\" \"$ripnam.$sufix\"" if($machine eq "local" || ($machine !~ /^local$/ && !$scp)); $enccom = $enccom . " && \\ rm \"$old_outputdir$machine.lock_$corecn\" &"; print "\nWill execute command on machine $machine and try to encode", "\n$ripnam.$sufix\_enc.\n" if($verbose >= 4); log_system("$enccom"); sleep 3; # Don't mess up with possible error-msgs from remote hosts. $outputdir = $old_outputdir; $ripnam = $old_ripnam; # Delete the wav only if all encodings of this track are done! # When the (last) encoding of a track started, its name is pushed # into the array @delname. Then the first (oldest) entry of the same # array (@delname) will be compared to the @codwav array. If this # entry is still present in the codewav-array, nothing happens, else # the wav file will be deleted and the trackname shifted out of the # @delname. if($delwav == 1) { push @delname, "$shortname"; my $delflag = 0; while ($delflag == 0) { my $delname = $delname[0]; my @delwav = grep(/$delname/, @codwav); if(!$delwav[0] && $#delname > 1) { unlink("$outputdir/$delname.wav"); log_info("File $outputdir$delname.wav deleted."); shift(@delname); # Prevent endless loop if array is empty. $delflag = 1 if(!$delwav[0]); } else { $delflag = 1; } } } } ######################################################################## # # Delete wavs if sshlist was used. TODO: Improve code for following # situation: if no .lock files are found, but the encoder did not yet # finish, don't delete the wavs. Do it only after 3*4 seconds timeout # with no .lock file. # sub del_wav { my $waitflag = 1; sleep 3; print "\nChecking for remaining lock files.\n" if($verbose >= 2); while ($waitflag <= 3) { sleep 3; opendir(DIR, "$outputdir"); my @locks = readdir(DIR); closedir(DIR); @locks = grep { /\.lock_\d+$/ } @locks; $waitflag++ if(! @locks); $waitflag = 0 if(@locks); } if($wav == 0) { print "Deleting the wavs...\n" if($verbose >= 1); opendir(DIR, "$outputdir"); my @wavs = readdir(DIR); closedir(DIR); @wavs = grep { /\.wav$/ } @wavs; foreach (@wavs) { unlink("$outputdir$_"); log_info("File $outputdir$_ deleted."); } } if($scp) { my $old_outputdir = $outputdir; $outputdir = esc_char($outputdir); foreach $_ (keys %sshlist) { my $machine = $_; log_system("ssh $machine rmdir \\\"$outputdir\\\" > /dev/null 2> /dev/null") if($machine !~ /^local$/); } $outputdir = $old_outputdir; } } ######################################################################## # # LCDproc subroutines, all credits to Max Kaesbauer. For comments and # questions contact max [dot] kaesbauer [at] gmail [dot] com. # # print sub plcd { my ($data) = @_; print $lcdproc $data."\n"; my $res = <$lcdproc>; } # update sub ulcd { if($lcdoline1 ne $lcdline1) { $lcdoline1 = $lcdline1; plcd("widget_set ripitlcd line1 1 2 {$lcdline1}"); } if($lcdoline2 ne $lcdline2) { $lcdoline2 = $lcdline2; plcd("widget_set ripitlcd line2 1 3 {$lcdline2}"); } if($lcdoline3 ne $lcdline3) { $lcdoline3 = $lcdline3; plcd("widget_set ripitlcd line3 1 4 {$lcdline3}"); } } # init sub init_lcd { $lcdproc = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $lcdhost, PeerPort => $lcdport, ) || die "Can not connect to LCDproc port\n"; $lcdproc->autoflush(1); sleep 1; print $lcdproc "hello\n"; my @lcd_specs = split(/ /,<$lcdproc>); my %screen; $screen{wid} = $lcd_specs[7]; $screen{hgt} = $lcd_specs[9]; $screen{cellwid} = $lcd_specs[11]; $screen{cellhgt} = $lcd_specs[13]; $screen{pixwid} = $screen{wid}*$screen{cellwid}; $screen{pixhgt} = $screen{hgt}*$screen{cellhgt}; fcntl($lcdproc, F_SETFL, O_NONBLOCK); plcd("client_set name {ripit.pl}"); plcd("screen_add ripitlcd"); plcd("screen_set ripitlcd name {ripitlcd}"); plcd("widget_add ripitlcd title title"); plcd("widget_set ripitlcd title {RipIT $version}"); plcd("widget_add ripitlcd line1 string"); plcd("widget_add ripitlcd line2 string"); plcd("widget_add ripitlcd line3 string"); } ######################################################################## # # Read the CDDB on the local machine. # sub read_entry { my ($album, $artist, $trackno); my $logfile = $_[0]; open(LOG, "<$logfile") || print "Can't open $logfile\n"; my @cddblines = ; close(LOG); %cd = (); # Note that long lines may be splited into several lines # all starting with the same keyword, e.g. DTITLE. if($multi == 0) { $cd{raw} = \@cddblines; my @artist = grep(s/^DTITLE=//g, @cddblines); $artist = join(' / ',@artist); $artist =~ s/[\015]//g; $artist =~ s/\n\s\/\s//g; chomp $artist; # Artist is just the first part before first occurrence of # the slash (/), album gets all the rest! my @disctitle = split(/\s\/\s/, $artist); $artist = shift(@disctitle); $album = join(' / ',@disctitle); chomp $artist; chomp $album; $categ = $_[1]; my @genre = grep(s/^DGENRE=//, @cddblines); $genre = $genre[0] if($genre eq ""); $genre =~ s/[\015]//g; chomp $genre; my @year = grep(s/^DYEAR=//, @cddblines); $year = $year[0] if($year eq ""); $year =~ s/[\015]//g; chomp $year; $trackno = $_[2]; } else { my @artist = grep(s/^artist:\s//i, @cddblines); $artist = $artist[0]; chomp $artist; my @album = grep(s/^album:\s//i, @cddblines); $album = $album[0]; chomp $album; my @category = grep(s/^category:\s//i, @cddblines); $categ = $category[0]; chomp $categ; my @genre = grep(s/^genre:\s//i, @cddblines); $genre = $genre[0]; chomp $genre; my @year = grep(s/^year:\s//i, @cddblines); $year = $year[0]; chomp $year; my @cddbid = grep(s/^cddbid:\s//i, @cddblines); $cddbid = $cddbid[0]; chomp $cddbid; my @trackno = grep(s/^trackno:\s//i, @cddblines); $trackno = $trackno[0]; chomp $trackno; } $cd{artist} = $artist; $cd{title} = $album; $cd{cat} = $categ; $cd{genre} = $genre; $cd{id} = $cddbid; $cd{year} = $year; my $i=1; my $j=0; while($i <= $trackno){ my @track = (); @track = grep(s/^TTITLE$j=//, @cddblines) if($multi == 0); @track = grep(s/^track\s$i:\s//i, @cddblines) if($multi == 1); my $track = join(' / ',@track); $track =~ s/[\015]//g; $track =~ s/\n\s\/\s//g; chomp $track; $cd{track}[$j]=$track; $i++; $j++; } } ######################################################################## # # Delete error.log if there is no Track comment in! # sub del_erlog { if(-r "$outputdir/error.log") { open(ERR, "$outputdir/error.log") or print "error.log disappeared!\n"; my @errlines = ; close ERR; my @md5tracks = grep(s/^md5: //, @errlines) if($md5sum == 1); if(@md5tracks){ md5_sum("$_") foreach (@md5tracks); } my @ulink = grep(/^Track /, @errlines); if(!@ulink && $multi == 0) { unlink("$outputdir/error.log"); } elsif($fpermission) { chmod oct($fpermission), "$outputdir/error.log"; } } } ######################################################################## # # Escape special characters when using scp. # sub esc_char { $_[0] =~ s/\(/\\\(/g; $_[0] =~ s/\)/\\\)/g; $_[0] =~ s/\[/\\\[/g; $_[0] =~ s/\]/\\\]/g; $_[0] =~ s/\&/\\\&/g; $_[0] =~ s/\!/\\\!/g; $_[0] =~ s/\?/\\\?/g; $_[0] =~ s/\'/\\\'/g; $_[0] =~ s/ /\\ /g; return $_[0]; } ######################################################################## # # Calculate how much time ripping and encoding needed. # sub cal_times{ my $encend = `date \'+%R\'`; chomp $encend; # Read times from the file $outputdir/error.log. open(ERR, "$outputdir/error.log") or print "error.log disappeared!\n"; my @errlines = ; close ERR; my @enctime = grep(s/^Encoding needed //, @errlines); my @ripstart = grep(s/^Ripping started: //, @errlines); my @ripend = grep(s/^Ripping ended: //, @errlines); my @ghostnote = grep(s/^Ghostflag = //, @errlines); my @splitnote = grep(s/^Splitflag = //, @errlines); $ghostnote[0] = 0 unless($ghostnote[0]); $splitnote[0] = 0 unless($splitnote[0]); @ripstart = split(/:/, $ripstart[0]); @ripend = split(/:/, $ripend[0]); my $riptime = ($ripend[0] * 60 + $ripend[1]) - ($ripstart[0] * 60 + $ripstart[1]); my $enctime = "@enctime"; chomp $enctime; if($encode == 1) { @enctime = split(/ /, $enctime); $enctime = int($enctime[0]/60); } else { $enctime = 0; } return ($riptime,$enctime,$encend,$ghostnote[0],$splitnote[0]); } ######################################################################## # # Thanks to mjb: log info to file. # sub log_info { if(!defined($infolog)){ return; } elsif($infolog eq ""){ return; } open(SYSLOG, ">> $infolog") || die "Can't open info log file\n"; print SYSLOG "@_\n"; close(SYSLOG); } ######################################################################## # # Thanks to mjb and Stefan Wartens improvements: # used throughout in place of system(). # sub log_system { my $P_command=shift; if($verbose >= 4){ print "system: $P_command\n\n"; } log_info("system: $P_command"); system($P_command); # system() returns several pieces of information about the launched # subprocess squeezed into a 16-bit integer: # # 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # | | | | | | | | | | | | | | | | | [ $? ] # +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ # \_____________________/ \/ \__________________/ # exit code core signal number # # To get the exit code, use ($? >> 8) # To get the signal number, use ($? & 127) # To get the dumped core flag, use ($? & 128) # Subprocess has been executed successfully. return 1 if $? == 0; # Subprocess was killed by SIGINT (CTRL-C). Exit RipIT. die "\n\nRipit caught a SIGINT.\n" if(( $? & 127) == 2); # Subprocess could not be executed or failed. return 0; } ######################################################################## # # Special characters in cd.toc file won't be written correctly by # cdrdao, so change them to octal! Please let me know how to do it in # a better way! # sub oct_char{ $_[0] =~ s/À/\\300/g; $_[0] =~ s/Á/\\301/g; $_[0] =~ s/Â/\\302/g; $_[0] =~ s/Ã/\\303/g; $_[0] =~ s/Ä/\\304/g; $_[0] =~ s/Å/\\305/g; $_[0] =~ s/Æ/\\306/g; $_[0] =~ s/Ç/\\307/g; $_[0] =~ s/È/\\310/g; $_[0] =~ s/É/\\311/g; $_[0] =~ s/Ê/\\312/g; $_[0] =~ s/Ë/\\313/g; $_[0] =~ s/Ì/\\314/g; $_[0] =~ s/Í/\\315/g; $_[0] =~ s/Î/\\316/g; $_[0] =~ s/Ï/\\317/g; $_[0] =~ s/Ð/\\320/g; $_[0] =~ s/Ñ/\\321/g; $_[0] =~ s/Ò/\\322/g; $_[0] =~ s/Ó/\\323/g; $_[0] =~ s/Ô/\\324/g; $_[0] =~ s/Õ/\\325/g; $_[0] =~ s/Ö/\\326/g; $_[0] =~ s/×/\\327/g; $_[0] =~ s/Ø/\\330/g; $_[0] =~ s/Ù/\\331/g; $_[0] =~ s/Ú/\\332/g; $_[0] =~ s/Û/\\333/g; $_[0] =~ s/Ü/\\334/g; $_[0] =~ s/Ý/\\335/g; $_[0] =~ s/Þ/\\336/g; $_[0] =~ s/ß/\\337/g; $_[0] =~ s/à/\\340/g; $_[0] =~ s/á/\\341/g; $_[0] =~ s/â/\\342/g; $_[0] =~ s/ã/\\343/g; $_[0] =~ s/ä/\\344/g; $_[0] =~ s/å/\\345/g; $_[0] =~ s/æ/\\346/g; $_[0] =~ s/ç/\\347/g; $_[0] =~ s/è/\\350/g; $_[0] =~ s/é/\\351/g; $_[0] =~ s/ê/\\352/g; $_[0] =~ s/ë/\\353/g; $_[0] =~ s/ì/\\354/g; $_[0] =~ s/í/\\355/g; $_[0] =~ s/î/\\356/g; $_[0] =~ s/ï/\\357/g; $_[0] =~ s/ð/\\360/g; $_[0] =~ s/ñ/\\361/g; $_[0] =~ s/ò/\\362/g; $_[0] =~ s/ó/\\363/g; $_[0] =~ s/ô/\\364/g; $_[0] =~ s/õ/\\365/g; $_[0] =~ s/ö/\\366/g; $_[0] =~ s/÷/\\367/g; $_[0] =~ s/ø/\\370/g; $_[0] =~ s/ù/\\371/g; $_[0] =~ s/ú/\\372/g; $_[0] =~ s/û/\\373/g; $_[0] =~ s/ü/\\374/g; $_[0] =~ s/ý/\\375/g; $_[0] =~ s/þ/\\376/g; return $_[0]; } ######################################################################## # # Check if there is a CD in the CD device. # sub cd_present { # require "ioctl.ph"; sysopen(CD, $cddev, O_RDONLY | O_NONBLOCK) or die "Can not open device $cddev: $!\n"; my $os = `uname -s`; my $CDROMREADTOCHDR=0x5305; # Linux if($os eq "SunOS") { $CDROMREADTOCHDR=0x49b; } elsif($os =~ /BSD/i) { $CDROMREADTOCHDR=0x40046304; } my $tochdr = ""; my $err = ioctl(CD, $CDROMREADTOCHDR, $tochdr); close(CD); return $err; } ######################################################################## # # A hack to reinitialize global variables before starting a new loop. # sub init_var { $categ = ""; $cddbid = 0; @framelist = (); @secondlist = (); @tracklist = (); @tracktags = (); @seltrack = (); @tracksel = (); %cd = (); $cddbsubmission = 2; $hiddenflag = 0; $outputdir = $poutputdir; } ######################################################################## # # Get the revision number of the CDDB entry. # sub get_rev { my @revision = grep(/^\#\sRevision:\s/, @{$cd{raw}}); @revision = grep(s/^\#\sRevision:\s//, @revision); my $revision = $revision[0]; chomp $revision if($revision); return $revision; } ######################################################################## # # Lowercase. # sub lower_case{ $_[0] = lc($_[0]); $_[0] =~ tr/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ]/[àáâãäåæçèéêëìíîï]/; $_[0] =~ tr/[ÐÑÒÓÔÕÖØÙÚÛÜÝÞ]/[ðñòóôõöøùúûüýþ]/; return $_[0]; } ######################################################################## # # Strip dodgey chars I. This will be done for file names and tags. # # TODO: Do we really have to erase all of them? Maybe we should keep # some for the tags... # sub clean_all{ $_[0] =~ s/[;|><"\015]//g; $_[0] =~ s/\`/\'/g; $_[0] =~ s/´/\'/g; $_[0] =~ s/\s+/ /g; return $_[0]; } ######################################################################## # # Strip dodgey chars II. This will only be done for file names. # sub clean_name{ $_[0] =~ s/[*]//g; $_[0] =~ s/\// - /g; $_[0] =~ s/\s+/ /g; return $_[0]; } ######################################################################## # # Strip dodgey chars III. This will optionally be done for file names. # Remember the default chars to be erased are: :*#?$! . # sub clean_chars{ $_[0] =~ s/$chars//g; return $_[0]; } ######################################################################## # # Put all chars in brackets and escape some. # sub check_chars { $chars =~ s/-/\\-/; $chars =~ s/]/\\]/; $chars =~ s/\s/\\s/; $chars = "[" . $chars . "]"; } ######################################################################## # # Extract the CDDB comment lines starting with EXTD=. # NOTE: Each EXTD line my have \n's, but two EXTD lines do NOT # mean that there's a linebreak in between! So, what we have to do # is, put all comment lines into a string and split the string # according to the explicitly \n's (i.e. use \\n).and add a \n at the # end of each line! # sub extract_comm { my @comment = grep(/^EXTD=/, @{$cd{raw}}); @comment = grep(s/^EXTD=//, @comment); my $line = "@comment"; $line =~ s/[\015]//g; @comment = split(/\\n/, $line); foreach (@comment) { chomp $_; $_ =~ s/^\s+//g; } return (@comment); } ######################################################################## # # Display a help page and exit! # sub print_help { print < 1) { print "\nStrange interval in argument of option merge ($_)!", "\nIs there a comma missing?\n\n"; exit; } # Track number larger than number of tracks on CD? if($#tracklist > 0) { if($bea[0] > $#tracklist + 1 || $bea[1] > $#tracklist + 1) { print "\nStrange interval in argument of option merge ($_)!", "\nHigher track number than tracks on CD?\n\n"; exit; } } while($i <= $bea[$#bea]){ push(@skip, $i); $i++; } } return(@skip); } ######################################################################## # # Split the wav into chunks of sound and rename them. # # Analyze the wav for chunks and gaps. Fill an array @times with two # blank separated numbers in each entry. These two numbers are the # time in seconds of the starting point of sound and the duration of # that chunk. This is important because this number will be used to seek # to that point of sound from the beginning of the file, not form the # end point of the previous cycle. For each chunk we start to seek from # zero; this is not a large time loss, seeking is fast. # # There were weeks of testing to manage Audio::FindChunks-0.03, gave up! # The behaviour seems inconsistent. For example: ripping all tracks of # the CD: Lamb - What Sound gave *no* gaps. When ripping only the last # track, gaps were immediately detected. # First, changing the sec_per_chunk value gave better results, but # suddenly no gaps at all were found. The threshold stayed at zero. # So then I introduced a loop where the sec_per_chunk increases from # 0.1 - 0.8 in steps of 0.1, and in the same time, the threshold from # 0.1 in steps of 0.2 only if the resulting threshold is less than 100. # You say that this is ugly? No, it is extremly ugly. And all this # because there might be a caching problem in Audio::FindChunks-0.03? # Then, testing on a 64bit machine was a drawback, no gaps at all. # # So I gave up this sophisticated and "fully documented" PM, and coded a # few lines to solve the problem. This code might not be usefull to # split manually recorded vinyl, but the results for ripped CDs are # much more precise than with the PM. Of course, I can test only on a # limited range of CDs, and I have no classical or Death-Metal to test. # But for the following CDs (and hundreds of CDs with no gaps --> # thousands of tracks and not one was erroneously splitted) this # snipplet works. (See below for explanation!) # # # Testreport (CDs with ghost songs): # OK: 2raumwohnung: in wirklich # OK: Archive: Londonium # OK: Archive: Take My Head # OK: Aromabar: 1 (has 2 ghost songs!) # OK: Autour de Lucie: L'échappée belle # OK: Cibelle: Cibelle # OK: Dining Rooms: Experiments In Ambient Soul # OK: Distain!: [li:quíd] # OK: Helena: Née dans la nature # OK: Jay-Jay Johanson: Antenna # OK: Laika: Sound Of The Satellites # OK: Lamb: Debut # OK: Lamb: Fear of Fours # OK: Lamb: What Sound # OK: Lamb: What Sound Limited Edition # OK: Lunik: Weather # OK: Massive Attack: 100th Window # OK: Moloko: Do You Like My Tight Sweater? # OK: Olive: Trickle # OK: Qntal: III # OK: Samia Farah: Samia Farah # OK: Stereo Total: Musique Automatique # # sub split_chunks { my $ghostflag = 0; my ($tcn, $trn, $shrt, $cdtocn) = @_; my @times = (); $trn = $trn . ".rip"; # Read the header of the wav file. open(IN, "< $trn") or print "Can't open $trn: $!"; binmode(IN); my $H = {}; $H->{header_size} = 44; my $wavheader; print "Can not read full WAV header!\n" if($H->{header_size} != read(IN, $wavheader, $H->{header_size})); # Unpack the wave header and fill all values into a hashref. ($H->{RIFF_header}, $H->{file_size_8}, $H->{WAV_header}, $H->{FMT_header}, $H->{WAV_chunk_size}, $H->{WAV_type}, $H->{channels}, $H->{sample_rate}, $H->{byte_per_sec}, $H->{block_align}, $H->{bit_per_sample}, $H->{data_header}, $H->{data_size}) = unpack("a4Va4a4VvvVVvva4V", $wavheader); $H->{sample_size} = ($H->{channels} * $H->{bit_per_sample})>>3; if($verbose >= 4) { print "\nThe wav header has following entries:\n"; print "$_ \t -> $H->{$_} \n" foreach (keys %$H); print "\n"; } # How do I analyze the chunks? I calculate a threshold value called # $thresh of the actual chunk by summing up its binary values - perl is # so cool! Then this value is used to calculate a kind of mean value # with the previous one --> $deltathre, but only at every 5th value, to # cancel short fluctuations. If the actual $thresh value lies # within a small range compared to the deltathre, a weight (counter) # will be increased and deltathre will be weighten to cancel (not so) # short peak changes (not only) at the end of a track (gap). # Silence is defined as (not so) significant drop of the $thresh value # compared to the $deltathre one. Use an upper cut-off value $maxthresh # (70% of maximum value) to allow deltathre to grow (quickly) in the # beginning but prevent to baile out immediately. During the track, a # weight will help to prevent the same. # my $bindata; my $bytecn = 0; my $silencecn = 0; my $chunkcn = 0; my $chunksize = 0.1; # Chunk size in seconds. my $chunkbyte = $H->{byte_per_sec} * $chunksize; my $chunklen = 0; my $startsnd = 0; my $soundflag = 0; my $deltathre = $H->{byte_per_sec} * $chunksize; my $weight = 1; my $maxthresh = $deltathre * 8 * 0.7; while(read(IN, $bindata, $chunkbyte)){ $chunkcn ++; my $thresh += unpack( '%32b*', $bindata ); $weight++ if($thresh > 0.9 * $deltathre && $thresh < 1.1 * $deltathre); if($thresh > 0.8 * $deltathre && $thresh < $maxthresh && $chunkcn =~ /[05]$/) { $deltathre = ($deltathre * $weight + $thresh) / (1 + $weight); } # if(unpack("h*", $bindata) !~ /[1-9abcdef]/){ if($thresh < 0.8 * $deltathre){ $silencecn += $chunkbyte; if ($silencecn == $H->{byte_per_sec} * 6) { $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec}; $chunklen -= $startsnd; if ($chunklen < 4) { $chunklen = 0; } else { push (@times, "$startsnd $chunklen"); } $soundflag = 1; } } else { $silencecn = 0; if ($soundflag == 1) { $startsnd = $bytecn / $H->{byte_per_sec}; $soundflag = 0; } } $bytecn += $chunkbyte; } $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec}; $chunklen -= $startsnd; push (@times, "$startsnd $chunklen") unless($startsnd == 0); push (@times, "$startsnd $chunklen") unless(@times); my $tracklen = $secondlist[$tcn - 1]; $tracklen = $secondlist[$tcn] if($hiddenflag == 1); if($verbose >= 2) { print "\nRipIT found following chunks for track\n"; print "$shrt (${tracklen}s long):\nstart duration (in seconds)\n"; foreach(@times){ my @interval = split(/ /, $_); printf("%3d %9.1f\n", $interval[0], $interval[1]); } } # Split wav into chunks. $chunkcn = 0; my @truetracklen = (); foreach (@times){ # Remember: each entry of @times has the form: "start duration" # where start is the beginning of sound in seconds, and duration # the time in seconds. my @interval = split(/ /, $_); if($interval[0] >= $prepend) { $interval[0] -= $prepend; $interval[1] += $prepend; } $interval[1] += $extend; # Don't split if interval is larger than tracklength from cdtoc. # Shorten the tracklength from cdtoc by a threshold of 10s. # Reasonable? my $tracklen = $secondlist[$tcn - 1]; $tracklen = $secondlist[$tcn] if($hiddenflag == 1); if($interval[0] + $interval[1] > $tracklen){ $interval[1] = $tracklen - $interval[0]; } $tracklen = $tracklen - 10; $interval[0] = int($interval[0]); $interval[1] = int($interval[1] + 0.5); if($verbose >= 4) { print "Using this values:\n"; printf("%4d %4d\n", $interval[0], $interval[1]); } log_info("Using this values:"); log_info("@interval"); if($tracklen <= $interval[1] || $interval[1] <= 2) { print "Track $tcn not splitted.\n\n" if($verbose >= 1); log_info("Track $tcn not splitted.\n"); return($cdtocn); } open(ERO,">>$outputdir/error.log") or print "Can not append to file ", "\"$outputdir/error.log\"!"; print ERO "Splitflag = 1\n" unless($times[1]); print ERO "Ghostflag = 1\n" if($times[1]); close(ERO); # Use array @truetracklen to save new track lengths. They will be # spliced into array @secondlist to allow the ripper process to # write correct playlist files and will be printed to ghost.log # for encoder process, see below. push(@truetracklen, $interval[1]); # From now on count in bytes instead of seconds: $interval[0] = $interval[0] * $H->{byte_per_sec} + 44; $interval[1] = $interval[1] * $H->{byte_per_sec}; # Seek from beginning of file to start of sound of chunk. print "I seek to: $interval[0] B, starting from 0 B.\n" if($verbose >= 4); seek(IN, $interval[0], 0) or print "\nCould not seek in file IN: $!\n"; # Prepare the filename for output. print "Splitting \"$shrt\" into " . ($#times + 1) . " chunks.\n" if($verbose >= 2 && $chunkcn == 0); my $outr = "Ghost Song $chunkcn"; $outr = get_trackname($tcn, $outr) . ".rip"; open(OUT, "> $outr"); binmode(OUT); # Edit header according to size of the chunk. $H->{data_size} = $interval[1]; $H->{file_size_8} = $H->{data_size} + 36; substr($wavheader, 4, 4) = pack("V", $H->{file_size_8}); substr($wavheader, 40, 4) = pack("V", $H->{data_size}); print(OUT $wavheader); if($verbose >= 5) { print "\nThe new wav header of\n$outr"; print "\nwill have following entries:\n"; print "$_ \t -> $H->{$_} \n" foreach (keys %$H); print "\n"; } # I don't know if it is good to read so many bytes a time, but it # is faster than reading byte by byte. $interval[1] += $interval[0]; while(read(IN, $bindata, $chunkbyte) && $interval[0] <= $interval[1]){ $interval[0] += $chunkbyte; print(OUT $bindata); } close(OUT); $chunkcn++; } close(IN); print "Track $tcn successfully splitted.\n\n" if($verbose >= 1); ###### # Do not read this, it is brainstorming only, not the way it is # implemented. # The idea would be to splice the new file into all arrays. But this # would break the merge option! Therefore we assume, that ghost songs # will rather appear on last tracks than somewhere else. We push the # filename to the arrays @tracklist, @tracktag and @seltrack. But # this will confuse the ripper, once all tracks are ripped. The key # to distinguish between a regular (last) track and ghost songs will # be, that @framelist will not have an entry. NOTE that @secondlist # needs also to be updated because playlist needs the extact values. # Another difference is that the track numbers pushed to @seltrack # will not increase, e. g. @seltrack = (0,1,2,3) will then look like # (0,1,2,3,0,3) if track 0 and 3 both have a ghost song (not very # common). So: if the ripper encounters a number less-equal than the # previous one (from @seltrack), then it must be a ghost song, # therefore quit ripping. If the encoder encounters a number less- # equal than the previous one, then an internal counter increases to # get the right file names and tags from the array, but the filename- # counter is not changed. This did not work as wanted. ###### # Here we go for renaming the chunks. # The ripper gets a copy of the initial @seltrack array, called # @tracksel. Ghost songs will be added in @seltrack, but not in array # @tracksel. This means: the ripper will behave as usual, and not # care about additional songs. # Rename the chunks called "Ghost Song $chunkcn" to the appropriate # file name. # If there is only one chunk, this chunk gets the true trackname. # If there are more than one chunk, the first chunk gets the true # track name; this might be wrong, but who knows? # Another problem is with data tracks. Then the track-counter will # not increase for ghost songs, as we expect for ghost songs that # appear in the last track, sad. (See below!) $chunkcn = 0; my $outr = "Ghost Song $chunkcn"; $outr = get_trackname($tcn, $outr) . ".rip"; rename("$outr", "$trn"); # If there are two chunks, i. e. only one ghost song, don't use a # counter in the filename. The suffix can now be wav instead of rip. $trn = $tracklist[$tcn - 1]; $trn = $tracklist[$tcn] if($hiddenflag == 1); $chunkcn++; shift(@times); $secondlist[$tcn - 1] = $truetracklen[0]; $secondlist[$tcn] = $truetracklen[0] if($hiddenflag == 1); shift(@truetracklen); $outr = "Ghost Song $chunkcn"; my $trt = "$trn - Ghost Song"; $trn = $trt; $trn = clean_name("$trn"); $trn = lower_case($trn) if($lowercase == 1); $trn =~ s/ /_/g if($underscore == 1); # Here we assume, that the trackname template is # "track-counter track-name". If not, then there should be something # to worry, tracks might be overridden, although this is very # unlikely: TODO: check if outputname already exist. # So, the trackname of a new ghost song shall have the same leading # tracknumber to identify its origin, except if it comes from # the last track, then the leading number may increase! Define a new # ghost counter $gcn. my $gcn = $tcn; $ghostflag = 1 if($tcn == $#framelist); $ghostflag = 1 if($hiddenflag == 1 && $tcn == $#framelist - 1); $gcn++ if($ghostflag == 1); if($#times == 0) { $outr = get_trackname($tcn, $outr); push(@secondlist, $truetracklen[0]); push(@seltrack, $gcn); push(@tracklist, $trn); push(@tracktags, "$trt"); $trn = get_trackname($gcn, $trn); rename("$outr.rip", "$trn.wav"); if($cdtoc == 1){ $cdtocn++; my $artistag = clean_all($cd{artist}); my @shortname = split(/\//, $trn); my $shortname = $shortname[$#shortname]; my $cdtocartis = oct_char($artistag); open(CDTOC, ">>$outputdir/cd.toc") or print "Can not append to file \"$outputdir/cd.toc\"!\n"; print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n"; print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t"; print CDTOC "TITLE \"Ghost Song\"\n\t\t"; print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n"; print CDTOC "FILE \"$shortname.wav\" 0\n"; close(CDTOC); } shift(@times); shift(@truetracklen); } # If there was only one ghost song, array @times is empty now. We're # done. # Do the same procedure, if there are more than 2 chunks, i. e. more # than 1 ghost song. Ghost songs now need a internal counter if they # are not form the last track. foreach (@times){ $trn = $tracklist[$tcn - 1]; $trn = $tracklist[$tcn] if($hiddenflag == 1); my $artistag = clean_all($cd{artist}); # Remember: $outr is the ripper's output track name of the wav. $outr = "Ghost Song $chunkcn"; # The name for the tags will be with originating track name as # prefix. my $trt = $trn . " - Ghost Song $chunkcn"; # The actual track name will be slightly different. $trn = $trt; $trn = clean_name($trn); $trn = lower_case($trn) if($lowercase == 1); $trn =~ s/ /_/g if($underscore == 1); push(@secondlist, $truetracklen[0]); push(@seltrack, $gcn); push(@tracklist, $trn); push(@tracktags, "$trt"); $outr = get_trackname($tcn, $outr); $trn = get_trackname($gcn, $trn); rename("$outr.rip", "$trn.wav"); if($cdtoc == 1){ $cdtocn++; my @shortname = split(/\//, $trn); my $shortname = $shortname[$#shortname]; my $cdtocartis = oct_char($artistag); open(CDTOC, ">>$outputdir/cd.toc") or print "Can not append to file \"$outputdir/cd.toc\"!\n"; print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n"; print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t"; print CDTOC "TITLE \"Ghost Song\"\n\t\t"; print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n"; print CDTOC "FILE \"$shortname.wav\" 0\n"; close(CDTOC); } $gcn++ if($ghostflag == 1); shift(@truetracklen); $chunkcn++; } print "\n\n" if($verbose >= 2); log_info("\n"); # Is there another way to communicate with the encoder process (child # precess) than writing log files? open(GHOST, ">$outputdir/ghost.log") or print "Can not append to file ghost.log!\n"; print GHOST "Array seltrack: @seltrack\n"; print GHOST "Array secondlist: @secondlist\n"; print GHOST "Array tracklist: $_\n" foreach(@tracklist); print GHOST "Array tracktags: $_\n" foreach(@tracktags); close(GHOST); return($cdtocn); } ######################################################################## # # Check if the necessary modules are available. # sub init_mod { print "\n" if($verbose >= 1); eval { require CDDB_get }; if($@) { print "\nPerl module CDDB_get not found. Needed for", "\nchecking the CD-ID and retrieving the CDDB", "\nentry from freeDB.org!", "\nPlease install CDDB_get from your closest", "\nCPAN mirror before trying again.", "\nInstall by hand or e.g. type as root:", "\nperl -MCPAN -e 'install CDDB_get'\n\n"; exit 0; } $@ = (); eval { require LWP::Simple }; if($@) { print "\nPerl module LWP::Simple not found. Needed for", "\nchecking free categories before submitting CDDB", "\nentries to freeDB.org!", "\nPlease install LWP::Simple and dependendencies ", "\nfrom your closest CPAN mirror or submission will fail.", "\nInstall by hand or e.g. type as root:", "\nperl -MCPAN -e 'install LWP::Simple'\n\n"; sleep 2; } $@ = (); eval { require Digest::MD5 } if($md5sum == 1); if($@) { print "\nPlease install Digest::MD5 and dependendencies ", "\nfrom your closest CPAN mirror before trying again with", "\noption --md5sum. Install by hand or e.g. type as root:", "\nperl -MCPAN -e 'install Digest::MD5'\n\n"; exit 0; } print "\n\n" if($verbose >= 1); } ######################################################################## # # Check if lame is installed. # sub check_lame { unless(log_system("lame --version > /dev/null 2> /dev/null")) { if(!@pcoder && "@coder" =~ /0/ || "@pcoder" =~ /0/) { print "\nLame not found (needed to encode mp3)!", "\nUse oggenc instead (to generate ogg)?\n"; my $ans = "x"; while($ans !~ /^y$|^n$/) { print "Do you want to try oggenc? [y/n] (y) "; $ans = ; chomp $ans; if($ans eq ""){ $ans = "y"; } } if($ans eq "y") { my $coders = "@coder"; my $pcoders = "@pcoder"; if($coders !~ /1/) { $coders =~ s/0/1/g; } else { $coders =~ s/0//g; } if($pcoders !~ /1/) { $pcoders =~ s/0/1/g; } else { $pcoders =~ s/0//g; } $lameflag = -1; @coder = split(/ /, $coders); @pcoder = split(/ /, $pcoders); } else { print "\n", "Install lame or choose another encoder with option", "\n", "-c 1 for oggenc, -c 2 for flag or -c 3 for faac.", "\n\n", "Type ripit --help or check the manpage for info.", "\n\n"; exit; } } else { $lameflag = -1; } } } ######################################################################## # # Create MD5sum file of sound files. # sub md5_sum { my $filename = shift; my @shortname = split(/\// , $filename); my $shortname = $shortname[$#shortname]; my $sufix = $shortname; $sufix =~ s/^.*\.//; chomp($sufix); my $md5file = $shortname[$#shortname - 1] . " - " . $sufix . ".md5"; $md5file =~ s/ /_/g if($underscore == 1); open(SND,"<$filename") or print "Can not open $filename: $!"; binmode SND; print "\nCalculating MD5-sum for $shortname..." if($verbose >= 4); my $md5 = Digest::MD5->new->addfile(*SND)->hexdigest; close SND; print "\nThe MD5-sum for $shortname is: $md5.\n" if($verbose >= 4); open(MD5SUM,">>$outputdir$md5file") or print "Can not append to file \"$outputdir$md5file\"!"; print MD5SUM "$md5 *$shortname\n"; close MD5SUM; }