1#!/usr/local/bin/perl 2use strict; 3use warnings; 4use Getopt::Long qw(:config no_ignore_case bundling); 5use File::Path; 6use Pod::Usage; 7use Cwd ('abs_path'); 8 9our $VERSION = 1.027_000; 10 11=pod 12 13=head1 NAME 14 15mp3cd - Burns normalized audio CDs from lists of MP3s/WAVs/Oggs/FLACs 16 17=head1 SYNOPSIS 18 19mp3cd [OPTIONS] [playlist|files...] 20 21 -s, --stage STAGE Start at a certain stage of processing: 22 clean Start fresh (default, requires playlist) 23 build Does not clean (requires playlist) 24 decode Turns MP3s/Oggs/FLACs into WAVs 25 correct Fix up any WAV formats 26 norm Normalizes WAV volumes 27 toc Builds a Table of Contents from WAVs 28 toc_ok Checks TOC validity 29 cdr_ok Checks for a CDR 30 burn Burns from the TOC 31 -q Quits after one stage of processing 32 -t, --tempdir DIR Set working dir (default "/tmp/mp3cd-$USER") 33 -d, --device PATH Look for CDR at "PATH" (default "/dev/cdrecorder") 34 -r, --driver TYPE Use CDR driver TYPE (default up to cdrdao) 35 -n, --simulate Don't actually burn a disc but do everything else. 36 -E, --no-eject Don't eject drive after the burn. 37 -L, --no-log Don't redirect output to "tool-output.txt" 38 -T, --no-cd-text Don't attempt to write CD-TEXT tags to the audio CD 39 -c, --cdrdao ARGS Pass the option string ARGS to cdrdao. 40 -S, --skip STAGES Skip the comma-separated list of stages in STAGES. 41 -V, --version Report which version of the script this is. 42 -v, --verbose Shows commands as they are executed. 43 -h, --usage Shows brief usage summary. 44 --help Shows detailed help summary. 45 --longhelp Shows complete help. 46 47=head1 OPTIONS 48 49=over 8 50 51=item B<-s STAGE>, B<--stage STAGE> 52 53Starts processing at a given stage. This is used in 54case you had to stop processing, or a file was missing, or things 55generally blew up. It is especially useful if a burn fails because then 56you don't have to start totally over and re-WAV the files. If you just 57want to perform a single step, use B<--quit> to abort after the stage 58you request with B<--stage>. Also see B<--skip>. 59 60=over 8 61 62=item B<clean> 63 64This is the default starting stage. The temp directory is cleared out. 65A playlist is required, since we expect to move to the B<build> stage 66next, which requires it. 67 68=item B<build> 69 70This stage examines the playlist from the command line, and tries to 71create a list of symlinks from the given playlist. So far, C<mp3cd> 72can understand ".m3u" files, XMLPlaylist files, and lists of files. 73 74=item B<decode> 75 76All the files are converted into WAVs. So far, C<mp3cd> knows how to 77decode MP3, Ogg, and FLAC files. (WAVs will be left as they are during 78this stage.) 79 80=item B<correct> 81 82The WAV files are corrected to have the correct bitrate and number of 83channels, as required for an audio CD. 84 85=item B<norm> 86 87The WAV files' volumes are normalized so any large differences in volume 88between records will be less noticeable. 89 90=item B<toc> 91 92Generates a Table of Contents for the audio CD. 93 94=item B<toc_ok> 95 96Validates the TOC, just in case something went really wrong with 97the WAV files. 98 99=item B<cdr_ok> 100 101Verifies that there is a CDR ready for burning. 102 103=item B<burn> 104 105Actually performs the burn of all the WAV files to the waiting CDR. 106 107=back 108 109=item B<-q>, B<--quit> 110 111Aborts after one stage of processing. See B<--stage>. 112 113=item B<-t DIR>, B<--tempdir DIR> 114 115Use a working directory other than "/tmp/mp3cd-B<username>". This is 116where all the file processing occurs. You will generally need at least 117650M free here (or more depending on the recording length of your destination 118CD). 119 120=item B<-d PATH>, B<--device PATH> 121 122Use a device path other than "/dev/cdrecorder". 123 124=item B<-r TYPE>, B<--driver TYPE> 125 126Use a CDRDAO driver other than what cdrdao automatically detects. Note that 127some drivers may not support CD-TEXT mode. In this case, try "generic-mmc-raw". 128 129=item B<-c ARGS>, B<--cdrdao ARGS> 130 131Pass the given option string of ARGS to cdrdao during each command. 132 133=item B<-n>, B<--simulate> 134 135Do not actually write to the disc but simulate the process instead. 136 137=item B<-E>, B<--no-eject> 138 139Don't eject drive after the burn. 140 141=item B<-L>, B<--no-log> 142 143Don't redirect output to "tool-output.txt". All information will instead be 144redirected to the terminal via standard output (STDOUT). This will cause a 145lot of low-level detail to be displayed. 146 147=item B<-T>, B<--no-cd-text> 148 149Don't attempt to write CD-TEXT tags to the audio CD. Some devices and drivers 150do not support this mode. See B<--driver> for more details. 151 152=item B<-S STAGES>, B<--skip STAGES> 153 154While processing, skips the stages listed in the comma-separated list of 155stages given in STAGES. This would only be used if you really know what 156you're doing. For example, if the audio is already normalized and you 157didn't want to burn a CD, you could skip the normalizing and burning stages 158by giving "--skip norm,burn". See B<--stage> and B<--quit>. 159 160=item B<-V>, B<--version> 161 162Report which version of mp3cd this is. 163 164=item B<-v>, B<--verbose> 165 166Shows commands as they are executed. 167 168=item B<-h>, B<--usage> 169 170Show brief usage summary. 171 172=item B<--help> 173 174Show detailed help summary. 175 176=item B<--longhelp> 177 178Shows the full command line instructions. 179 180=back 181 182=head1 DESCRIPTION 183 184This script implements the suggested methods outlined in the 185Linux MP3 CD Burning mini-HOWTO: 186 L<http://tldp.org/HOWTO/MP3-CD-Burning/> 187 188This will burn a playlist (.m3u, XMLPlaylist or command line list) of 189MP3s, Oggs, FLACs, and/or WAVs to an audio CD. The ".m3u" format is really 190nothing more than a list of fully qualified filenames. The script handles 191making the WAVs sane by resampling if needed, and normalizing the volume 192across all tracks. 193 194If a failure happens, earlier stages can be skipped with the '-s' flag. 195The file "tool-output.txt" in the temp directory can be examined to see what 196went wrong during the stage. Some things are time-consuming (like decoding 197the audio into WAVs) and if the CD burn fails, it's much nicer not to have to 198start over from scratch. When doing this, you will not need the m3u file any 199more, since the files have already been built. See the list of stages using 200'-h'. 201 202=head1 PREREQUISITES 203 204Requires C<cdrdao>, and that /dev/cdrecorder is a valid symlink to the 205/dev/sg device that cdrdao will use. Use .cdrdao to edit driver 206options. (See "man cdrdao" for details.) 207 208Requires C<sox> to decode MP3 and check/correct WAV formats. 209 http://www.spies.com/Sox/ 210 211Requires C<normalize> to process the audio. 212 http://www.cs.columbia.edu/~cvaill/normalize/ 213 214Optionally requires C<oggdec> to decode Ogg to WAV files. 215 http://www.gnu.org/directory/audio/ogg/OggEnc.html/ 216 217Optionally requires C<flac> to decode flac to WAV files. 218 http://flac.sourceforge.net/ 219 220Optionally requires C<Config::Simple> Perl module if you want to use 221the .mp3cdrc file. 222 http://search.cpan.org/~sherzodr/Config-Simple/ 223 224=head1 FILES 225 226=over 8 227 228=item B<~/.mp3cdrc> 229 230Default options can be recorded in this file. The option names are the same 231as their command line long-name. Command line options will override these 232values. All options are run through perl's eval. For example: 233 tempdir: /scratch/mp3cd/$ENV{'USER'} 234 device: /dev/burner 235 236=back 237 238=head1 AUTHOR 239 240 Kees Cook <kees@outflux.net> 241 242 Contributors: 243 244 J. Katz (Ogg support) 245 Alex Rhomberg (XMLPlaylist support) 246 Kevin C. Krinke (filelist inspiration, and countless many patches) 247 James Greenhalgh (flac support) 248 249=head1 SEE ALSO 250 251perl(1), cdrdao(1), sox(1), oggdec(1), flac(1), sox(1), normalize(1). 252 253=head1 COPYRIGHT 254 255 Copyright (C) 2003-2011 Kees Cook 256 kees@outflux.net, http://outflux.net/ 257 258 This program is free software; you can redistribute it and/or 259 modify it under the terms of the GNU General Public License 260 as published by the Free Software Foundation; either version 2 261 of the License, or (at your option) any later version. 262 263 This program is distributed in the hope that it will be useful, 264 but WITHOUT ANY WARRANTY; without even the implied warranty of 265 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 266 GNU General Public License for more details. 267 268 You should have received a copy of the GNU General Public License 269 along with this program; if not, write to the Free Software 270 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 271 http://www.gnu.org/copyleft/gpl.html 272 273=cut 274 275# Change this to a location where you'll have at least a CD's worth of 276# disk space available. (For the WAVs) 277# Its contents will be deleted, so be careful. :) 278my $BURNDIR="/tmp/mp3cd-".getpwuid($<); 279 280# Filename to redirect sub-tool stdout/stderr 281my $LOG="tool-output.txt"; 282 283# Filename to write the TOC to 284my $CDTOC="cdda.toc"; 285 286# Filename to write tag info to 287my $TAGS="tag.data"; 288 289# List of audio files to burn (useful only for the "build" stage) 290my @FILES=(); 291 292my %stage_func = ( 293 "clean" => \&Do_Clean, 294 "build" => \&Do_Build, 295 "decode" => \&Do_Decode, 296 "correct" => \&Do_Correct, 297 "norm" => \&Do_Normalize, 298 "toc" => \&Do_TOC, 299 "toc_ok" => \&Do_TOC_Verify, 300 "cdr_ok" => \&Do_CDR_Check, 301 "burn" => \&Do_Burn, 302); 303my $UNKNOWN="unknown-format"; 304my %decoders = ( 305 "flac" =>{ 'require' => 'flac', 306 'args' => '--silent -d -F $input -o $output', 307 'normal' => '--silent', 308 'verbose' => '', 309 }, 310 "ogg" => { 'require' => 'oggdec', 311 'args' => '$input -o $output', 312 'normal' => '--quiet', 313 'verbose' => '', 314 }, 315 "mp3" => { 'require' => 'sox', 316 'args' => '$input $output', 317 'normal' => '', 318 'verbose' => '-v', 319 }, 320 "m4a" => { 'require' => 'faad', 321 'args' => '-o $output $input', 322 'normal' => '--quiet', 323 'verbose' => '', 324 }, 325 $UNKNOWN => { 'require' => 'mplayer', 326 'args' => '-hardframedrop -vc null -vo null -ao pcm:fast:file=$output $input', 327 'normal' => '-quiet', 328 'verbose' => '', 329 }, 330 # Dummy entry to recognize WAVs 331 "wav" => { 'require' => 'sox', 332 }, 333); 334 335my @stages; 336my %stages; 337my $count=0; 338my $stage; 339foreach $stage (qw(clean build decode correct norm toc toc_ok cdr_ok burn)) { 340 push(@stages,$stage); 341 $stages{$stage}=$count++; 342} 343 344 345our $opt_help=undef; 346our $opt_longhelp=undef; 347our $opt_usage=undef; 348our $opt_version=undef; 349our $opt_quit=undef; 350our $opt_stage="clean"; 351our $opt_tempdir=undef; 352our $opt_cdrdao=""; 353our $opt_device="/dev/cdrecorder"; 354our $opt_driver=undef; 355our $opt_simulate=undef; 356our $opt_no_eject=0; 357our $opt_no_log=0; 358our $opt_no_cd_text=0; 359our $opt_skip=""; 360our $opt_verbose=0; 361 362my @options=( 363 'help', 364 'longhelp', 365 'usage|h', 366 'version|V', 367 'verbose|v', 368 'stage|s=s', 369 'skip|S=s', 370 'quit|q', 371 'tempdir|t=s', 372 'device|d=s', 373 'driver|r=s', 374 'cdrdao|c=s', 375 'simulate|n', 376 'no-eject|E', 377 'no-log|L', 378 'no-cd-text|T', 379); 380 381# Look for RC defaults 382my %rc; 383my $rcfile="$ENV{'HOME'}/.mp3cdrc"; 384if (-r $rcfile) { 385 require Config::Simple; 386 Config::Simple->import_from($rcfile,\%rc); 387} 388foreach my $opt (@options) { 389 my ($name) = $opt =~ /^([^|]+)/; 390 $name=~s/-/_/g; 391 my $is_str = $opt =~ /=s$/ || 0; 392 393 if (defined($rc{$name})) { 394 eval "\$opt_$name = \"$rc{$name}\";"; 395 if (!$is_str) { 396 eval "\$opt_$name = \$opt_$name ? 1 : 0;"; 397 } 398 } 399} 400 401# Load command line options 402GetOptions(@options) or pod2usage( -exitval=>1, -verbose=>0 ); 403 404# Handle help/usage 405pod2usage( -exitval=>0, -verbose=>2 ) if ($opt_longhelp); 406pod2usage( -exitval=>0, -verbose=>1 ) if ($opt_help); 407pod2usage( -exitval=>0, -verbose=>0 ) if ($opt_usage); 408Version() if ($opt_version); 409 410# cdrdao needs to pick up device and driver from the command line 411$opt_cdrdao .= " --device $opt_device"; 412$opt_cdrdao .= " --driver $opt_driver" if (defined($opt_driver)); 413 414# Validate starting stage 415if (!defined($stages{$opt_stage})) { 416 pod2usage( -exitval=>1, -verbose=>0, 417 -msg=>"Unknown start stage '$opt_stage'!" ); 418} 419$stage=$opt_stage; 420 421# Check if we need (or do not need) a playlist/filelist 422if ($stage eq "clean" || 423 $stage eq "build") 424{ 425 if (!defined($ARGV[0])) { 426 pod2usage( -exitval=>1, -verbose=>0, 427 -msg=>"Playlist/File list is required!" ); 428 } 429} 430elsif (@ARGV) { 431 pod2usage( -exitval=>1, -verbose=>0, 432 -msg=> "Playlists/Files are ignored past stage 'build'!" ); 433} 434 435# Build a hash of the stages to skip 436my %skip_stage; 437foreach my $skip (split(/,/,$opt_skip)) { 438 if (!defined($stages{$skip})) { 439 pod2usage( -exitval=>1, -verbose=>0, 440 -msg=>"Unknown stage to skip '$skip'!" ); 441 } 442 $skip_stage{$skip}=1; 443} 444# Skip all the stages after the selected one, in case of "--quit" 445my $cancel_rest = 0; 446foreach my $last (@stages) { 447 if ($cancel_rest) { 448 $skip_stage{$last}=1; 449 } 450 if ($opt_quit && $last eq $stage) { 451 $cancel_rest = 1; 452 } 453} 454 455# Figure out our burning directory 456$BURNDIR=$opt_tempdir if (defined($opt_tempdir)); 457 458# check for directory 459if (!opendir(DIR, $BURNDIR)) { 460 eval { mkpath($BURNDIR) }; 461 if ($@) { 462 die "Can't create working directory '$BURNDIR': $@\n"; 463 } 464 opendir(DIR, $BURNDIR) || die "Can't open directory '$BURNDIR': $!\n"; 465} 466closedir DIR; 467 468# if no_log print all to stdout 469my $OUTPUT = ( $opt_no_log ) ? "" : ">>$LOG"; 470 471sub System 472{ 473 my $cmd = $_[0]; 474 print STDERR $cmd."\n" if $opt_verbose; 475 return system($cmd); 476} 477 478sub Backtick 479{ 480 my $cmd = $_[0]; 481 print STDERR $cmd."\n" if $opt_verbose; 482 # Cannot pipe to "tee" since it will mask exit codes 483 my $output = `$cmd 2>&1`; 484 my $rc = $?; 485 486 my $logfile; 487 open($logfile, ">>$LOG") or die "Cannot write to $LOG: $!\n"; 488 print $logfile $output; 489 close($logfile); 490 print $output if ($opt_no_log); 491 492 return $rc, $output; 493} 494 495# For-sure needed tools 496my %PREREQS = ( 497 'sox' => 'sox', 498 'cdrdao' => 'cdrdao', 499 'gst-launch' => 'gst-launch', 500); 501$PREREQS{'normalize'} = 'normalize,normalize-audio' 502 if (!defined($skip_stage{'norm'})); 503my %found; 504 505sub Lookup_tools 506{ 507 # check for required tools 508 foreach my $requirement (sort keys %PREREQS) { 509 foreach my $dir (split(/:/,$ENV{'PATH'})) { 510 foreach my $prog (split(/,/,$PREREQS{$requirement})) { 511 if (!defined($found{$requirement}) && -x "$dir/$prog") { 512 $found{$requirement}="$dir/$prog"; 513 last; 514 } 515 } 516 } 517 } 518 my $abort=undef; 519 foreach my $requirement (sort keys %PREREQS) { 520 if (!defined($found{$requirement})) { 521 my $tried = "Tried: ".$PREREQS{$requirement}; 522 $tried =~ s/,/, /g; 523 warn "Cannot find program to handle '$requirement'! $tried\n"; 524 $abort=1; 525 } 526 } 527 return $abort; 528} 529 530# Load file list, update needed tools 531Load_file_list(); 532pod2usage( -exitval => 1, -verbose => 0 ) if (Lookup_tools()); 533 534# check for CDR device 535my $skip_cdr = defined($skip_stage{'cdr_ok'}) && defined($skip_stage{'burn'}); 536if (!$skip_cdr && ! -w $opt_device) { 537 pod2usage( -exitval=>1, -verbose=>0, 538 -msg=> "Cannot write to '$opt_device'!" ); 539} 540 541# Run through all the stages we need to... 542for (; 543 defined($stage) && defined($stages{$stage}); 544 $stage=$stages[$stages{$stage}+1]) { 545 if (defined($skip_stage{$stage})) { 546 print "Skipping '$stage' stage...\n"; 547 next; 548 } 549 550 $stage_func{$stage}->(); 551} 552 553# end of line 554exit(0); 555 556 557### Functions 558 559sub require_extension($$) 560{ 561 my ($ext,$file) = @_; 562 my $lookup = $ext; 563 if (!defined($decoders{$lookup})) { 564 # Unknown audio file format 565 print STDERR "Not sure how to handle file type '$ext' ($file),\n"; 566 print STDERR "falling back to ".$decoders{$UNKNOWN}->{'require'}.".\n"; 567 $lookup = $UNKNOWN; 568 } 569 $PREREQS{"decoder:$lookup"}=$decoders{$lookup}->{'require'}; 570} 571 572sub Load_file_list 573{ 574 # Keep a count of how many files we've examined, and stop after, say, 575 # 1000, in case an m3u lists itself (which is REALLY unlikely, but would 576 # effectively put this code into a memory-eating endless loop). 577 my $toomany=1000; 578 while (my $file=shift @ARGV) { 579 $file =~ m/\.([^\.]+)$/i; 580 my $ext = lc($1 || ""); 581 if ($ext eq "m3u" || $ext eq "pls" || $ext eq "xspf" || $ext eq "") { 582 # Playlist 583 open(M3U,$file) || die "Cannot open '$file': $!\n"; 584 my @lines=<M3U>; 585 close(M3U); 586 587 my @files; 588 if (scalar(@lines) && $lines[0] =~ /<!DOCTYPE\s+XMLPlaylist>/i) { 589 # kaffeine playlists 590 require XML::Simple; 591 my $contents = XML::Simple::XMLin($file); 592 if (ref($contents->{entry}) eq 'ARRAY') { 593 @files = map {$_->{url}} @{$contents->{entry}}; 594 s/^file:// for @files; 595 } else { 596 @files = ($contents->{entry}->{url}); 597 } 598 } 599 else { 600 # regular list of files 601 foreach (@lines) { 602 chomp; 603 next if (/^#/); 604 push(@files,$_); 605 } 606 } 607 unshift(@ARGV,@files); 608 } 609 else { 610 require_extension($ext,$file); 611 push(@FILES,$file); 612 } 613 die ">1000 files in the list?! I must have started looping forever.\n" 614 if (--$toomany<0); 615 } 616 # Get absolute locations 617 @FILES = map { abs_path($_) } @FILES; 618} 619 620sub Do_Clean 621{ 622 print "Cleaning up...\n"; 623 624 # clear out burn dir 625 my @list = ("$BURNDIR/$CDTOC","$BURNDIR/$LOG", "$BURNDIR/$TAGS"); 626 foreach my $ext ("wav", sort keys %decoders) { 627 push(@list,"$BURNDIR/*.$ext"); 628 } 629 System("rm -f ".join(" ",@list)); 630} 631 632sub append_tag_info($$$) 633{ 634 my ($media, $title, $path) = @_; 635 my $artist = ""; 636 my ($rc, $output) = Backtick("gst-launch -t filesrc location=$media ! decodebin"); 637 die "Could not extract tags: $!\n" if ($rc != 0); 638 my $tags = 0; 639 # Parse gst-launch -t output 640 # FOUND TAG : found by element "qtdemux0". 641 # title: Just Dance 642 # artist: Lady GaGa & Colby O'Donis 643 644 foreach my $line (split("\n", $output)) { 645 if ($line =~ /^FOUND TAG/) { 646 $tags = 1; 647 next; 648 } 649 next if ($tags != 1); 650 if ($line =~ /^\S/) { 651 $tags = 0; 652 next; 653 } 654 my ($field, $value) = $line =~ /^\s*(\S*)\s*:\s*(.*)$/; 655 next if (!defined($field)); 656 $title=$value if ($field eq "title"); 657 $artist=$value if ($field eq "artist"); 658 } 659 my $tagfile; 660 open($tagfile,">>$TAGS") or die "Cannot write to $TAGS: $!\n"; 661 print $tagfile "$title\n"; 662 print $tagfile "$artist\n"; 663 if ($opt_verbose) { 664 print "\ttitle: $title\n"; 665 print "\tartist: $artist\n"; 666 } 667} 668 669sub Do_Build 670{ 671 # go there 672 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 673 674 # Clear the tag file, since we're regenerating it 675 System("rm -f $TAGS"); 676 677 my $error=undef; 678 my $count=0; 679 # make link for each file, and retain extension 680 foreach my $file (@FILES) 681 { 682 chomp($file); 683 next if ($file =~ /^#/); 684 my @parts=split(/\./,$file); 685 my $ext=lc(pop(@parts)); 686 $ext=~tr/A-Z/a-z/; 687 688 @parts=split(/\//,$file); 689 my $name=pop(@parts); 690 691 if (!defined($decoders{$ext}) && !defined($decoders{$UNKNOWN})) { 692 warn "Error: '$file': unknown extension '$ext'!\n"; 693 $error=1; 694 next; 695 } 696 697 if (!-f $file) 698 { 699 warn "Error: '$file': $!\n"; 700 $error=1; 701 next; 702 } 703 704 $count++; 705 my $track=sprintf("%02d",$count); 706 print "$track: [...]/$name\n"; 707 symlink($file,"$track.$ext") || die "symlink('$file','$count.$ext'): $!\n"; 708 append_tag_info("$track.$ext", $name, $file); 709 } 710 711 die "Stopping due to errors...\n" if (defined($error)); 712 713 # make sure we have some tracks 714 die("No tracks?!\n") unless ($count>0); 715} 716 717sub Do_Decode 718{ 719 # go there 720 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 721 722 # leave any WAVs in playlist alone 723 opendir(DIR, $BURNDIR) || die "Can't read directory '$BURNDIR': $!\n"; 724 my @need_decode = grep { /^\d+\.[^\.]+$/i && !/\.wav$/ && -f "$BURNDIR/$_" } readdir(DIR); 725 closedir DIR; 726 727 # Re-check extensions and tools in case we're restarting 728 foreach my $to_decode (sort {$a cmp $b} @need_decode) 729 { 730 my @parts=split(/\./,$to_decode); 731 my $name=shift(@parts); 732 my $ext=pop(@parts); 733 require_extension($ext, $to_decode); 734 } 735 die "Cannot locate needed decoders\n" if (Lookup_tools()); 736 737 # decode audio into WAV files 738 foreach my $to_decode (sort {$a cmp $b} @need_decode) 739 { 740 my @parts=split(/\./,$to_decode); 741 my $name=shift(@parts); 742 my $ext=pop(@parts); 743 my $file="${name}.wav"; 744 745 if (-f $file) 746 { 747 print "Skipping track $name: $file exists.\n"; 748 } 749 else 750 { 751 print "Creating WAV for track $name ...\n"; 752 my $lookup = $ext; 753 if (!defined($decoders{$lookup})) { 754 $lookup = $UNKNOWN; 755 } 756 my $decoder = $decoders{$lookup}; 757 if (!defined($decoder)) { 758 die("No decoder available for extension '$ext' - decoding failed!\n"); 759 } 760 my @cmd = ($found{"decoder:$lookup"}); 761 762 # chose verbosity level 763 if (!$opt_no_log) { 764 push(@cmd,$decoder->{'normal'}); 765 } 766 else { 767 push(@cmd,$decoder->{'verbose'}); 768 } 769 770 # set up arguments 771 my $input = $to_decode; 772 my $output = $file; 773 push(@cmd,eval "return \"$decoder->{'args'}\""); 774 775 # run decoder (don't need to worry about arg splits since we're 776 # operating against symlinked files with known names, etc) 777 my $cmd = join(" ",@cmd); 778 # redirect logging 779 $cmd="$cmd $OUTPUT 2>&1"; 780 781 System($cmd) == 0 782 or die("Decoding failed!\n"); 783 } 784 } 785} 786 787sub Do_Correct 788{ 789 # go there 790 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 791 792 # get list of wavs from directory 793 opendir(DIR, $BURNDIR) || die "Can't read directory '$BURNDIR': $!\n"; 794 my @wavs = grep { /^\d+\.wav$/i && -f "$BURNDIR/$_" } readdir(DIR); 795 closedir DIR; 796 797 # correct any wav file formats 798 foreach my $wav (sort {$a cmp $b} @wavs) 799 { 800 my @parts=split(/\./,$wav); 801 my $name=shift(@parts); 802 print "Checking WAV format for track $name ...\n"; 803 my $report=`sox -V $wav $wav.raw trim 0.1 1 2>&1`; 804 805 my ($channels, $frequency, $samples); 806 if ($report =~ /^Input File/m) { 807 # In version 13.0.0, the report format has changed 808 809 # Sample Size : 8-bit (1 byte) 810 # Channels : 1 811 # Sample Rate : 11025 812 $report =~ m/Sample (?:Size|Encoding)\s*:\s+(\d+)-bit/s 813 or die "sox did not report sample size:\n$report"; 814 $samples = $1; 815 $report =~ m/Channels\s+:\s+(\d+)/s 816 or die "sox did not report channel count:\n$report"; 817 $channels = $1; 818 $report =~ m/Sample Rate\s+:\s+(\d+)/s 819 or die "sox did not report sample frequency:\n$report"; 820 $frequency = $1; 821 } 822 else { 823 # sox: Reading Wave file: Microsoft PCM format, 2 channels, 824 # sox: 44100 samp/sec 176400 byte/sec, block align, 16 bits/samp, 825 # sox: 44886528 data bytes 826 $report =~ m|(\d+) channels?|s 827 or die "sox did not report channel count:\n$report"; 828 $channels = $1; 829 $report =~ m|(\d+) samp/sec|s 830 or die "sox did not report sample frequency:\n$report"; 831 $frequency = $1; 832 $report =~ m|(\d+) bits/samp|s 833 or die "sox did not report sample size:\n$report"; 834 $samples = $1; 835 } 836 837 unless ($channels == 2 && 838 $frequency == 44100 && 839 $samples == 16) 840 { 841 842 # only do a "resample" if frequency isn't correct 843 my $resample="resample"; 844 $resample="" if ($frequency == 44100); 845 print "Correcting WAV format for track $name ...\n"; 846 System("sox $wav -r 44100 -c 2 new-$wav $resample $OUTPUT 2>&1") == 0 847 or die("Correction failed!\n"); 848 unlink($wav) || die "unlink('$wav'): $!\n"; 849 rename("new-$wav",$wav) || die "rename('new-$wav','$wav'): $!\n"; 850 } 851 unlink("$wav.raw"); 852 } 853} 854 855sub Do_Normalize 856{ 857 # go there 858 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 859 860 # normalize the volumes 861 print "Normalizing volume levels...\n"; 862 System("$found{'normalize'} -m [0-9]*.wav") == 0 863 or die("Normalizing failed!\n"); 864 print "Normalizing finished.\n"; 865} 866 867sub encode_cd_text_data($) 868{ 869 my ($data) = @_; 870 my $encoded = ""; 871 # Handle backslash and quotes 872 $data =~ s/\\/\\\\/g; 873 $data =~ s/"/\\"/g; 874 # Using the binary data method seems to fail (missing trailing 0?) 875# if ($data =~ /"/) { 876# $encoded = "{ " . join(", ",map(ord, split(//,$data))) . " }"; 877# } 878# else { 879 $encoded = "\"" . $data . "\""; 880# } 881 return $encoded; 882} 883 884sub cd_text($$) 885{ 886 my ($title, $artist) = @_; 887 chomp($title); 888 chomp($artist); 889 890 my $text = "CD_TEXT {\n LANGUAGE 0 {\n"; 891 $text .= " TITLE " . encode_cd_text_data($title) . "\n"; 892 $text .= " PERFORMER " . encode_cd_text_data($artist) . "\n"; 893 $text .= " }\n}\n"; 894 895 return $text; 896} 897 898sub Do_TOC 899{ 900 # go there 901 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 902 903 print "Generating CDR Table of Contents...\n"; 904 905 # Get ready to read tags 906 my $tagfile; 907 open($tagfile,"<$TAGS") || die "Cannot read $TAGS: $!\n"; 908 909 # create a TOC for cdrdao 910 open(TOC,">$CDTOC") || die("Cannot write to '$CDTOC': $!\n"); 911 print TOC "CD_DA\n"; 912 if (!$opt_no_cd_text) { 913 # CDRDAO wants title/performer for the cd itself too, so leave them blank 914 print TOC <<EOM; 915CD_TEXT { 916 LANGUAGE_MAP { 917 0 : EN 918 } 919 LANGUAGE 0 { 920 TITLE "" 921 PERFORMER "" 922 } 923} 924EOM 925 } 926 print TOC "\n"; 927 928 # get list of wavs 929 opendir(DIR, $BURNDIR) || die "Can't read directory '$BURNDIR': $!\n"; 930 my @wavs = grep { /^\d+\.wav$/i && -f "$BURNDIR/$_" } readdir(DIR); 931 closedir DIR; 932 933 foreach my $wav (sort {$a cmp $b} @wavs) 934 { 935 die ("Yikes! What happened to '$wav'?!\n") unless (-f $wav); 936 print TOC "TRACK AUDIO\n"; 937 if (!$opt_no_cd_text) { 938 print TOC cd_text(scalar(<$tagfile>), scalar(<$tagfile>)); 939 } 940 # The trailing space was (is?) needed for some versions of cdrdao 941 print TOC "FILE \"$wav\" 0 \n\n"; 942 } 943 close TOC; 944 close $tagfile; 945} 946 947sub Do_TOC_Verify 948{ 949 # go there 950 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 951 952 print "Verifying generated Table of Contents...\n"; 953 System(cdrdao('read-test')." $CDTOC $OUTPUT 2>&1") == 0 954 or die "Failed to create CD Table of Contents?!\n"; 955} 956 957sub Do_CDR_Check 958{ 959 # go there 960 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 961 962 print "Checking for CDR...\n"; 963 my ($rc, $report) = Backtick(cdrdao('disk-info')); 964 die "CDR not loaded?!\n" if ($rc != 0); 965 print "\tCDR found.\n"; 966 967 if (!$opt_no_cd_text) { 968 my $options = undef; 969 my $driver_name = undef; 970 foreach my $line (split("\n",$report)) { 971 chomp($line); 972 if ($line =~ /^Using driver: (.*)\(options (0x[0-9a-fA-F]+)\)$/) { 973 $driver_name = $1; 974 $options = hex($2); 975 } 976 } 977 if (!defined($options)) { 978 die "Could not determine driver options!\n"; 979 } 980 elsif ($opt_verbose) { 981 printf("\tDriver name: %s\n", $driver_name); 982 printf("\tDriver options: 0x%04x\n", $options); 983 } 984 # 0x10 == OPT_MMC_CD_TEXT /usr/share/cdrdao/drivers 985 if (($driver_name =~ /raw writing/) || ($options & 0x10) == 0x10) { 986 print "\tCD-TEXT supported.\n"; 987 } 988 else { 989 print "ERROR: It seems that driver selected by cdrdao for $opt_device\n"; 990 print " does not support CD-TEXT writing. Either disable CD-TEXT via\n"; 991 print " '--no-cd-text' or select a different driver (e.g. try using\n"; 992 print " '--driver generic-mmc-raw').\n"; 993 exit(1); 994 } 995 } 996} 997 998sub Do_Burn 999{ 1000 # go there 1001 chdir($BURNDIR) || die "Cannot chdir('$BURNDIR'): $!\n"; 1002 1003 my $cmd = cdrdao('write'); 1004 $cmd.=" --eject" if (!$opt_no_eject); 1005 $cmd.=" -n $CDTOC"; 1006 System($cmd) == 0 1007 or die "BURN FAILED!\n"; 1008} 1009 1010sub Version 1011{ 1012 # Create human-readable version with un-human-readable code 1013 print "mp3cd version ". 1014 join(".",map{$_+0} (sprintf("%.6f",$VERSION) 1015 =~/^(\d+)\.?(\d{3})?(\d{3})?$/))."\n"; 1016 print <<'EOM'; 1017Copyright 2003-2011 Kees Cook <kees@outflux.net> 1018This program is free software; you may redistribute it under the terms of 1019the GNU General Public License. This program has absolutely no warranty. 1020EOM 1021 exit(0); 1022} 1023 1024# return a good cdrdao command string prefix 1025sub cdrdao { 1026 my $operation = $_[0] || 'simulate'; 1027 $operation = 'simulate' if ($opt_simulate && $operation eq 'write'); 1028 1029 return "cdrdao $operation $opt_cdrdao"; 1030} 1031 1032# /* vi:set ai ts=4 sw=4 expandtab: */ 1033