#!/usr/local/bin/perl ## $Id: mp3-cdr-fill,v 1.2 2003/04/05 05:44:39 forman Exp forman $ ## ## Author: Michael Forman ## URL: http://www.Michael-Forman.com ## Creation Date: Date: 2000/09/09 01:39:55 GMT ## Last Revision: $Date: 2003/04/05 05:44:39 $ ## Revision: $Revision: 1.2 $ ## ## Copyright (C) 1999, 2000 Michael Forman. All rights reserved. ## This program is free software; you can redistribute it ## and/or modify it under the same terms as Perl itself. ## Please see the Perl Artistic License. ## ## Category: MP3, CDR ## Rating: 4/5 ## ## ## ## ## # $| = 0; use Getopt::Long; use POSIX; #--------------------------------------------------------------------------- Getopt::Long::Configure("no_ignore_case"); GetOptions( #---------------------------- "h|help" => \$help, "f|flat" => \$flat, "r|riovolt" => \$rio, "t|test" => \$test, "v|verbose" => \$verb, #---------------------------- "P|playlists!"=> \$plists, "H|history!" => \$history, "a|automatic" => \$auto, #---------------------------- "s|speed=i" => \$speed, "c|cdrsize=i" => \$cdrs, "m|max=i" => \$max_size, #---------------------------- "D|drive=s" => \$drive, "d|dir=s" => \$mp3dir, "p|prune=s" => \$prune, "dev=s" => \$dev, #---------------------------- ); #--------------------------------------------------------------------------- # Notes # # language: hand select lang lessons # classical-40: hand select all or part of classical-40 # # sparse: randomly fill selected directories # set size limit on file usage # keep track of old files # # playlists: # sequential do all songs from start to end # random create random lists of 20 songs # artist create playlists based on artists # ##-------------------------------------------------------------------------- ## System-dependent constants. ## Change these to suit your system and cdrom # $TMPDIR = "/scratch/forman"; # temp directory $MNTDIR = "/scratch/forman/mnt"; # mount directory $CDR_SIZE = 700; # YOUR CDR size in (MB) $CDR_DEV = "1,0,0"; # YOUR CDR device $SPD_MAX = 4; # YOUR maximum record speed $SPD_RIO = 1; # recommended rec speed for RioVolt $DRIVE = "D:"; # YOUR drive if using DOSisms @MP3DIR = ( # mp3 directories "/vortex/mp3", ); @PRUNE = ( # omit directories from find cmd "/vortex/mp3/artists", "/vortex/mp3/books", "/vortex/mp3/gnutella", "/vortex/mp3/language", "/vortex/mp3/lost+found", ); ##-------------------------------------------------------------------------- ## Algorithm constants. ## DO NOT CHANGE. # $RC_FILE = "$ENV{HOME}/.mp3cdrfillrc"; # List of songs to prevent duplicate $MAX_SIZE = 10000000; # Maximum mp3 file upload size (B) $MIN_SIZE = 150000; # Minimum mp3 file upload size (B) $MIN_FREE = $MIN_SIZE; # Acceptable unused space on CD (B) $MAX_LOOP = 500; # Maximum loops to find a random song $MAX_REC = 4000; # Maximum length of recent song list $PL_SIZE = 20; # Number of songs in a rand play list ##-------------------------------------------------------------------------- ## Check command line parameters and modify global variables. # if($help) { exec("perldoc $0"); } if($test) { $cdrs = 100; } if($history eq ""){ $history= 1; } if($drive eq ""){ $drive = $DRIVE; } if($max_size ne ""){ $MAX_SIZE = $max_size; } if($font eq ""){ $font = $FONT; } if($plists eq ""){ $plists = 1; } if($speed eq ""){ if($rio){ $speed = $SPD_RIO; } else{ $speed = $SPD_MAX; } } if($dev eq ""){ $dev = $CDR_DEV; } if($cdrs eq ""){ $cdrs = $CDR_SIZE; } if($mp3dir eq ""){ @mp3dir = @MP3DIR; } else{ @mp3dir = split(' ', $mp3dir); } if($prune eq ""){ @prune = @PRUNE; } else{ @prune = split(' ', $prune); } #--------------------------------------------------------------------------- ## ## This is a modification to prevent the creation of a "flat" directory on ## a CD when several subdirectories in a larger mp3 directory are given. ## ## The following example is standard usage. The program will use the ## following directories each as master mp3 directory and store the files ## on the CD relative to those paths. Thus if there are no subdirectories ## under the given paths, no subdirectories will exist on the CD. ## (The CD will not contain the subdirs "80s", "90s", and "classical".) ## ## -d "/mp3/80s /mp3/90s /mp3/clasical" ## ## The following example will search the same file space but will ## include the subdirectories "80s", "90s", and "classical" on the CD. ## The first directory is the root directory. Everything that does not ## begin with a "/" is treated as a subdirectory off of the root path. ## ## -d "/mp3 80s 90s clasical" ## ## This example has two root directories. On the CD there will exist ## two subdirectories, "80s" and "90s" with the contents from ## "/mp3/80s and "/mp3/new/80s" merged into the CDs "80s" directory. ## It is the same for the "90s" directory. ## ## -d "/mp3 80s 90s /mp3/new 80s 90s" ## ## Supported paths to the "-d" flag: ## -d "/mp3" ## -d "/mp3/80s /mp3/90s" ## -d "/mp3 80s 90s" ## -d "/mp3 80s 90s /new unfiled test" # foreach $mp3dir (@mp3dir){ if($mp3dir =~ /^\//){ push(@mp3root, $mp3dir); } else{ push(@mp3new, "$mp3root[$#mp3root]/$mp3dir"); } } if($#mp3new == -1){ @mp3dir = @mp3root; } else{ @mp3dir = @mp3new; } ##-------------------------------------------------------------------------- ## Make directories. # if(!-e $TMPDIR){ mkdir($TMPDIR)||die("Cannot make $TMPDIR: $!\n"); } if(!-e $MNTDIR){ mkdir($MNTDIR)||die("Cannot make $MNTDIR: $!\n"); } foreach $mp3dir (@mp3dir){ if(!-e $mp3dir){ die("The mp3 directory does not exist: $mp3dir\n"); } } ##-------------------------------------------------------------------------- ## Initialize formatting. # Format(); select((select(STDOUT), $^= "CDH", $~= "CD", )[0]); ##-------------------------------------------------------------------------- ## State what's up. # if($verb){ print("\n"); print("Variables\n"); print(" mp3root:\n"); foreach $mp3root (@mp3root){ print(" $mp3root\n"); } print(" mp3dir:\n"); foreach $mp3dir (@mp3dir){ print(" $mp3dir\n"); } print(" mntdir:\n $MNTDIR\n"); print(" tmpdir:\n $TMPDIR\n"); } ##-------------------------------------------------------------------------- ## Open RC file # if($history){ if($verb){ print("\nReading RC file...\n"); } $n = read_rc(); if($verb){ print(" $n/$MAX_REC songs in $RC_FILE.\n"); } } ##-------------------------------------------------------------------------- ## Check CDR and filesystem # if($verb){ print("\n"); print("CDR information...\n"); } cdr_stat(); $ifree = $Ifree/1000000; if($verb){ printf(" Size: $ifree MB\n"); } ##-------------------------------------------------------------------------- ## Select the songs # if($verb){ print("\n"); print("Reading mp3 directory...\n"); } mp3_dir(); if($verb){ print (" Files: $File_Num files\n"); printf(" Size: %4.0f MB\n",$File_Siz); } ##-------------------------------------------------------------------------- ## Create a list of songs # if($verb){ print("\n"); print("Selecting songs randomly...\n"); } @ifile = select_songs($Ifree); ##-------------------------------------------------------------------------- ## Create the ISO image # $isofile = "$TMPDIR/ext2-cdr.iso"; if(!$rio){ if($verb){ print("\n"); print("Creating empty iso file...\n"); } system("dd if=/dev/zero of=$isofile bs=1024k count=$cdrs"); if($verb){ print("\n"); print("Creating ext2 fs on iso file...\n"); } system("/sbin/mke2fs -F -b 2048 $isofile"); if($verb){ print("\n"); print("Mounting ext2 iso file...\n"); } system("sudo mount -t ext2 -o resgid=100 -o loop=/dev/loop1 $isofile $MNTDIR"); } ##-------------------------------------------------------------------------- ## Copy the songs # #$Cnum = $Tnum; $Cnum = 1; $Cmem = $Tmem; if($verb){ print("\n"); print("Loading the songs...\n"); } if($#ifile >= 0){ upload_files("", @ifile); } ##-------------------------------------------------------------------------- ## Link Files # if(!$rio){ if($verb){ print("\n"); print("Linking files...\n"); } mkdir("$MNTDIR/all"); system("cd $MNTDIR; find . -name \\*mp3 -a -type f -exec ln -s .\\{} all \\;"); } ##-------------------------------------------------------------------------- ## Create playlists # if($plists){ write_playlists(); } ##-------------------------------------------------------------------------- ## Write CD # if($verb){ print("\n"); print("Status: \n"); if($rio){ $du = `du -s $MNTDIR`; print("$du"); }else{ $df = `df $MNTDIR`; $ls = `ls -l $isofile`; print("$df"); print("$ls"); } } if(!$auto){ print("\n"); print("Examine $MNTDIR for accuracy, then press to start the write."); $tmp = <>; } if($rio){ if($verb){ print("\n"); print("Creating iso9660 fs with RR on iso file...\n"); } system("/usr/bin/mkisofs -J -r -f -o $isofile $MNTDIR"); if(!-e "$isofile"){ print("\n"); print("There has been an error creating the iso file.\n"); print("This is almost certainly due to a problem with creating unique\n"); print("filenames for the Joliet extensions to the iso9660 filesystem.\n"); print("\n"); print("You will have to finish the process by hand.\n"); print("\n"); print("Run \"mkisofs -J -r -o $isofile $MNTDIR\" to find the problem.\n"); print("Keep trying until you fix the problem.\n"); print("When fixed, run \"cdrecord -v speed=$speed dev=$dev -data $isofile\".\n"); die("\n"); }else{ system("rm -rf $MNTDIR"); } }else{ if($verb){ print("\n"); print("Unmounting ext2 iso file...\n"); } system("sudo umount $MNTDIR"); } if($verb){ print("\n"); print("Burning CD...\n"); } if(!$test){ system("cdrecord -v speed=$speed dev=$dev -data $isofile"); } ##-------------------------------------------------------------------------- ## Write RC file # if($verb){ print("\n"); print("Writing RC file...\n"); } write_rc(); ##-------------------------------------------------------------------------- ## Clean up # system("rm -f $isofile"); ##-------------------------------------------------------------------------- ## Done # if($verb){ print("\n"); print("Done.\n"); print("\n"); } #=========================================================================== #=========================================================================== #=========================================================================== #=========================================================================== #--------------------------------------------------------------------------- sub write_playlists { ## ## write playlists # @dir = @CDRp; print("\n"); $lis = POSIX::ceil(($#dir+1)/$PL_SIZE); if($verb){ print("Creating $lis playlist"); if($lis > 1){ print("s"); } print("...\n"); } $lnm = 1; $snm = 0; $plt = "$MNTDIR/playlist-00.m3u"; open(APLT, ">$plt")||die("Cannot open $plt: $!\n"); @dir = arand(@dir); foreach $file (@dir){ ## ## Monitor playlist size and switch to a ## new file if required. # $snm++; if(($snm > $PL_SIZE)&&($lnm != $lis)){ $snm = 1; $lnm++; } $lsn = $lnm; $lsn =~ s/^(\d)$/0$1/; $plt = "$MNTDIR/playlist-$lsn.m3u"; open(PLT, ">>$plt")||die("Cannot open $plt: $!\n"); ## ## Write entries to an open playlist # $file =~ s/\/+/\//g; # // -> / $file =~ s/^\///; # /file -> file if($rio){ $file =~ s/\//\\/g; # Har! Backslashes! #print(PLT "$drive\\$file\n"); # Har! Drive letters! #print(APLT "$drive\\$file\n"); print(PLT "/cdrom/$file\n"); print(APLT "/cdrom/$file\n"); }else{ print(PLT "/cdrom/all/$file\n"); print(APLT "/cdrom/all/$file\n"); } close(PLT); } close(APLT); } #--------------------------------------------------------------------------- sub write_rc { ## ## Write rc file # my($i); open(RC, ">$RC_FILE")||die("Cannot open $RC_FILE: $!\n"); unshift(@Rec,@nRec); foreach $file (@Rec){ if($i++ > $MAX_REC){ close(RC); return; } print(RC "$file\n"); } close(RC); } #--------------------------------------------------------------------------- sub read_rc { ## ## Read rc file # my($n) = 0; if(! -e $RC_FILE){ `touch $RC_FILE`; } open(RC, "$RC_FILE")||die("Cannot open $RC_FILE: $!\n"); while(){ chomp; $Rec{$_} = 1; push(@Rec,$_); $n++; } close(RC); return($n); } #--------------------------------------------------------------------------- sub upload_files{ my(@file) = @_; ## ## Upload the files ## # my($file); my($flag); my($cper); my($i); my($si); $flag = shift(@file); foreach $file (@file){ $cper = 100*($Cmem/$Tmem); if($verb){ printf("File %3d/$Tnum, %4.1f%% remaining\n",$Cnum,$cper); } ## ## Create useful variables # #cdfd - CD File Dir $cdfd = $file; # /mp3/90s/file.mp3 foreach $mp3root (@mp3root){ $cdfd =~ s/\Q$mp3root\E//; # /90s/file.mp3 } $cdfd =~ s/\/[^\/]+$//; # /90s #tmfd - TeMp File Dir $tmfd = "$MNTDIR/$cdfd"; # /tmp/90s #cdln - CD Long file Name $cdln = $file; # /mp3/90s/file.mp3 $cdln =~ s/.*\///; # file.mp3 #cdsn - CD Short file Name $cdsn = $file; # /mp3/90s/long...file.mp3 $cdsn =~ s/.*\///; # long...file.mp3 $cdsn =~ s/(^.{60})(.*$)/$1.mp3/; # long...fil.mp3 if($rio){ ## The joliet filesystem can have problems with file names ## greater than 64 characters long. ## This code is an attempt to work around that. # if($cdsn{$cdsn}){ $i = 0; $cdsn =~ s/....mp3$/.00.mp3/i; # long....00.mp3 while($cdsn{$cdsn} ne ""){ $i++; $si = $i; $si =~ s/(^\d$)/0$1/; $cdsn =~s/\d\d.mp3$/$si.mp3/; # long....07.mp3 } } $cdsn{$cdsn} = 1; #cdfn - CD File Name $cdfn = $cdsn; }else{ #cdfn - CD File Name $cdfn = $cdln; } #cdfp - CD File Path $cdfp = "$cdfd/$cdfn"; # /90s/file.mp3 #tmfp - TeMp File Path $tmfp = "$MNTDIR/$cdfp"; # /tmp/90s/file.mp3 if($flat){ push(@CDRp,$cdfn); # (save for rio m3u files) if($rio){ `ln -s "$file" "$MNTDIR/$cdfn"`; } else{ `cp "$file" "$MNTDIR/$cdfn"`; } }else{ push(@CDRp,$cdfp); # (save for rio m3u files) `mkdir -p "$tmfd"`; if($rio){ `ln -s "$file" "$tmfp"`; } else{ `cp "$file" "$tmfp"`; } } $Cmem-= $File2Size{$file}; $Cnum++; } } #--------------------------------------------------------------------------- sub select_songs { my($free) = $_[0]; ## ## This function generates a list of songs ## for a given amount of memory. ## ## Globals: ## $Flist ## $Wrfile ## $Wsize ## $Tmem Total upload size ## $Tnum Total number of files ## $File2Size Converts filename to size # my($header) = 0; my($loop); my($rnd); my(@ifile); my($ifile); my($rfile); my($size); my(%Rnd); $loop = 0; $Flist = ""; srand; do{ @key = keys(%Rnd); # This returns if all the avaible if($#File == $#key){ return; } # songs have been found. do{ $rnd = int(rand($#File+1)); # Get a random song until: if($loop++ > $MAX_LOOP){ # b) we hit MAX_LOOP counts return(@ifile); } }while( ($Rnd{$rnd} == 1)|| # Rnd{$rnd}, 1 if rnum already chosen ($Fil{$rnd} == 1)|| # Fil{$rnd}, 1 if file has been chosen ($Rec{$sFile[$rnd]} == 1) # $Rec{$File[$rnd]}, not empty if in RC ); $Rnd{$rnd} = 1; $size = $File2Size{$File[$rnd]}; if(($size < $free)&& ($size < $MAX_SIZE)&& ($size > $MIN_SIZE)){ push(@ifile,$File[$rnd]); $free -= $size; $Tmem += $size; $Tnum++; $rfile = $File[$rnd]; $rfile =~ s/^.*\///; $Flist.= " $size\t$rfile\n"; $Wrfile = $rfile; $Wsize = $size; $Wrsize = $free; $Fil{$rnd} = 1; if($verb){ write(); } if(!$header){ $header = 1; $date = `date`; $LINE = "-"x40; chomp($date); push(@nRec,"#----- $date $LINE"); } push(@nRec,$rfile); }else{ # This loop will go and go until: $loop++; # a) the rio is totally full if($loop > $MAX_LOOP){ # b) we hit MAX_LOOP counts $free = -1; } } }while($free > $MIN_FREE); $ifile = join('" "',@ifile); $ifile = '"'.$ifile.'"'; return(@ifile); } #--------------------------------------------------------------------------- sub mp3_dir { ## ## Read in mp3 dir. ## Globals: ## %File2Size ## %Sile2Fize ## @File ## $File_Num ## $File_Siz # local($files); local(@files); local(@info); local($size); local($file); local($info); local($i); local($path); local($prune); foreach $path (@prune){ $prune.= " -path $path/\\* -o "; } $prune =~ s/-o\s*$//; $find = "find @mp3dir -follow ". "\\( ". " \\( -type f -o -type l \\) -a ". " \\( -name \\*mp3 -o -name \\*MP3 \\) -a ". " \\( ". " \! \\( $prune \\) ". " \\) ". "\\) ". "\\( -ls -a -print \\)"; #print("$find\n"); $files = `$find`; @files = split("\n",$files); for($i=0;$i<(($#files)+1);$i=$i+2){ $info = $files[$i]; $file = $files[$i+1]; $info =~ s/^\s*//; @info = split(/\s+/,$info); $size = $info[6]; $sfile = $file; $sfile =~ s/^.*\///; push(@sFile,$sfile); push(@File,$file); $File2Size{$file} = $size; while($Size2File{$size} ne ""){ $size+= 0.001; } $Size2File{$size} = $file; $File_Siz+= $size; #if($size < $MIN_SIZE){ $MIN_SIZE = $size; } # This is bad } $File_Num = $#File+1; $File_Siz/= 1024**2; $MAX_LOOP = $File_Num; # This is bad } #--------------------------------------------------------------------------- sub cdr_stat { ## ## Contact the rio player. ## ## Globals: ## $CDR_On ## $Ifree # local(@stat); local($stat); local($line); $Ifree = $cdrs * 1000000; if($Ifree > 0){ $CDR_On = 1; } if(!$CDR_On){ die(" CDR is not on or not connected.\n\n"); } if($Ifree == 0){ die(" CDR is reporting no free memory.\n"); } } #--------------------------------------------------------------------------- sub arand { my(@array) = @_; @array[-$i,$j] = @array[$j,-$i] while $j = rand(@array - $i), ++$i < @array; return(@array); } #--------------------------------------------------------------------------- sub Format { $= = 1000; # format lines per page. set high, only want one header format CDH = Remaining Size File Name ---------- -------- ------------------------------------------------------- . format CD = @>>>>>>>>> @>>>>>>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $Wrsize, $Wsize, $Wrfile . } #--------------------------------------------------------------------------- ## ## Use "perldoc mp3-cdr-fill" to read the man page below. # __END__ =head1 NAME B - Fills a temporary directory randomly with music and burns it to a CDR =head1 SYNOPSIS B S<[ B<-ahimvx> ]> S<[ B<-d> I ]> =head1 DESCRIPTION B fills a temporary directory randomly with mp3 files and burns it to a CDR. It will fill the directory with as much music as possible. =head1 OPTIONS B<-h --help> Prints this information. B<-D --drive> [I] If using the B<-r --rio> flag, this can be used to add a drive letter (shudder) to the m3u playlists. B<-d --dir> [I [I I<...>]] B<-d --dir> [I I [I I<...>]] Specifies the root mp3 directory to search for files. If unspecified, the default directory, which may be changed by editing this file, is used. The list of directories provided to this option can take two forms. The first form is a list of absolute paths to be used as root directories for the mp3 file search. The second form is a list that begins with an absolute root path followed by a list of subdirectories off that root path. The difference is that the second form will preserve the subdirectories on the CD filesystem. An example of the first usage is shown below. The program will use each directory in the list as a root mp3 directory and store the files on the CD relative to those paths. Thus if there are no subdirectories under the paths given, no subdirectories will exist on the CD. (The CD will B contain the subdirectories "I<80s>", "I<90s>", and "I".) -d "/mp3/80s /mp3/90s /mp3/clasical" The alternate usage consists of an absolute path to a root mp3 directory followed by several relative paths to subdirectories. Everything, which does not begin with a "I", is treated as a subdirectory off of the root mp3 directory. Note that the root mp3 directory is not searched for mp3 files, only the subdirectories are. The following example searches the same file space as the previous example but includes the subdirectories "I<80s>", "I<90s>", and "I" on the CD. -d "/mp3 80s 90s clasical" More than one root mp3 directory with subdirectories can be specified at a time. The example below has two root mp3 directories. On the CD there will exist two subdirectories, "I<80s>" and "I<90s>" with the contents from "I" and "I" merged into the CD's "I<80s>" directory. The same holds true for the "I<90s>" directory. -d "/mp3 80s 90s /mp3/new 80s 90s" B<-f --flat> Does not create a CD with subdirectory, but instead places all files including playlists in the root CD directory. B<-h --history> (B<--nohistory>) Checking and excluding selected files against a list of previously recorded mp3 files is enabled by default to minimize repitition on CDs. To force the program to ignore previously downloaded songs use the B<--nohistory> flag. B<-m --max> [I] This sets the largest maximum file size that an mp3 file can have and still be uploaded. The purpose of this is to prevent a couple of massive files from taking all the memory. To allow no limit on file size, set to zero. B<-P --playlists> (B<--noplaylists>) The creation of m3u playlists is enabled by default. To suppress the creation of playlists use the B<--noplaylists> flag. B<-r --riovolt> Creates an iso9660 filesystem with rockridge and jolliet extensions instead of an ext2 filesystem. An ext2 filesystem can only be read on a linux system. The iso9660 filesystem can be read in mp3 CD players (RioVolt) as well as most operating systems. NOTE: using the iso9660 filesystem is substantially less cool than using an ext2 filesystem and fills the playlists with DOSisms for compatability with those who do not know the glory of *nix. B<-v --verbose> Extra debugging information. B<-s --speed [speed]> Specify the recording speed of the CDR drive. At the time of this writing, RioVolt players have difficulty playing CDR disks written at speeds higher than "1", even though a computer can play the same disks without a problem. The default value is "1" if the "B<-r --riovolt>" flag is used, otherwise the default is "4". B<-t --test> Print files without copying and burning. =head1 EXAMPLE The example below will create an ext2 filesystem from files randomly selected from the "I" root directory. B B<-v> B<-d> I The example below will create an iso9660 filesystem with DOSisms from the directories "I", "I", and "I". The subdirectories "I<80s>", "I<90s>", and "I" will be preserved on the CD filesystem. B B<-v> B<-r> B<-d> I<"/mp3 80s 90s classical"> =head1 NOTES This program was converted from I and is filled with unecessary code. =head1 BUGS Poorly written loops that may or may not exit depending on the barometric pressure. A sad packing algorithm that has the potential of stepping through the I list of songs in order to to maximally fill the rio. Did I mention that my graduate advisor is Satan? =head1 SEE ALSO mp3-archos-fill, mp3-rio-fill =head1 AUTHOR AND COPYRIGHT Michael Forman http://www.Michael-Forman.com Copyright (C) 2000, 2001 Michael Forman. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Please see the Perl Artistic License. =head1 VERSION Current Revision: $Revision: 1.2 $ Last Modification: $Date: 2003/04/05 05:44:39 $