Code:
use File::Find::Rule;
use Parallel::ForkManager;
use MP4::Info;
*blooper*
*blooper* ConvertMusic.pl
*blooper* Author: Ian Grant <ian -at- iangrant -dot- me>
*blooper*
*blooper*
*blooper* Configuration options
*blooper* =====================
*blooper*
*blooper* Use forward slashes in place of backslashes for these paths
my $source_dir = 'D:/shares/Music/Ripped CDs';
my $dest_dir = 'D:/shares/Converted Music/Ripped CDs';
*blooper* Case of source extension is important. Don't include the leading period.
my $source_ext = 'flac';
my $dest_ext = 'm4a';
*blooper* Output codec and options.
*blooper* It's easiest to set the desired options in CD Ripper and then look up the CLI syntax in the registry:
*blooper* See the HKCU\Software\Illustrate\dBpoweramp\CDRipper\Profiles\(default) key (or whatever profile you configured)
*blooper* The 'CodecCLI_<codec>' and 'DSPEffects' strings contain the values needed. Leave any backslashes as they are.
my $dest_codec = 'm4a Nero (AAC)';
my $codec_options = ' -cli_encoder="C:\Program Files\Illustrate\dBpoweramp\encoder\m4a Nero (AAC)\neroAacEnc.exe" -cli_cmd="-q .45 -ignorelength -if - -of {qt}[outfile]{qt}" -selection="0,4,0" ';
my $dsp_effects = ' -dspeffect1="Volume Normalize= -mode={qt}rg{qt} -maxamp={qt}8{qt} -desiredb={qt}-0{qt} -adapt_wnd={qt}6000{qt} -fixed={qt}0{qt}" -dspeffect2="Bit Depth=-depth={qt}16{qt}" -dspeffect3="Delete Destination File on Error=" ';
*blooper* dBpoweramp options.
*blooper* The '-silent' flag is recommended to avoid overlapping progress output. Leave paths with backslashes.
my $dmc_options = ' -silent -error="D:\shares\Converted Music\Error Log.txt" ';
*blooper* Log file. Use forward slashes in the path, leave blank to disable.
my $logfile = 'D:/shares/Converted Music/Conversion Log.txt';
*blooper* Set this to the number of CPUs/cores in your system, or 0 to disable multi-processing.
my $number_of_cpus = 2;
*blooper* Location of dBpoweramp CoreConverter.exe (leave with backslashes here)
my $coreconverter_exe = 'C:\Program Files\Illustrate\dBpoweramp\CoreConverter.exe';
*blooper*
*blooper* There should be no need to edit anything below this line
*blooper* ========================================================
*blooper*
*blooper*
*blooper*
*blooper* Logging sub-routine
*blooper*
sub logmsg {
if ( $logfile ne "" ) { *blooper* log to file if the logfile is defined
open LOGFILE , ">>$logfile";
print LOGFILE "$_[0]\n";
}
print "$_[0]\n"; *blooper* also print to screen (unless running under wperl.exe)
}
*blooper*
*blooper* Find all source files
*blooper*
my @srcfiles = File::Find::Rule->file()
->name( '*.' . $source_ext )
->in( $source_dir );
*blooper*
*blooper* Output header info
*blooper*
logmsg "==================================";
logmsg "dMC recursive directory conversion";
logmsg "==================================";
@months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
@weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
my $starttime = time();
($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime($starttime);
$year = 1900 + $yearOffset;
$hour = sprintf("%02d", $hour);
$minute = sprintf("%02d", $minute);
$second = sprintf("%02d", $second);
$theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year";
logmsg "Starting conversion at $theTime";
my $src_backslashed = $source_dir;
my $dst_backslashed = $dest_dir;
$src_backslashed =~ s/\//\\/g;
$dst_backslashed =~ s/\//\\/g;
logmsg "Source directory: ".$src_backslashed;
logmsg "Target directory: ".$dst_backslashed;
logmsg "Converting all ".uc($source_ext)." files to ".$dest_codec;
logmsg "Codec CLI options: ".$codec_options;
logmsg "DSP effects: ".$dsp_effects;
logmsg "dBpoweramp options: ".$dmc_options;
logmsg "------------------";
*blooper*
*blooper* Set up fork manager for parallel processing and
*blooper* define number of processes to spawn for conversion
*blooper* (set to, e.g., number of CPUs. Use 0 to disable for testing)
*blooper*
my $pm = new Parallel::ForkManager($number_of_cpus);
*blooper* Callback for when a conversion child thread finishes
$pm->run_on_finish(
sub { my ($pid, $exit_code, $ident) = @_;
$dst = $ident;
$dst =~ s/$source_dir(.*)\.$source_ext/$dest_dir$1\.$dest_ext/i;
my $info = get_mp4info($dst); *blooper* if converting to MP4, this will allow the encoding speed vs real-time to be calculated
$total_audio_time += $info->{SECS};
$total_converted_files += 1;
$ident =~ s/$source_dir\/(.*)\.$source_ext/$1\.$dest_ext/;
$ident =~ s/\//\\/g;
*blooper*logmsg " ...Finished $ident";
}
);
*blooper* Callback for when a conversion child thread is initiated
$pm->run_on_start(
sub { my ($pid,$ident)=@_;
$ident =~ s/$source_dir\///;
$ident =~ s/\//\\/g;
logmsg "Converting $ident...";
}
);
*blooper*
*blooper* Process files
*blooper*
$total_audio_time = 0;
$total_converted_files = 0;
foreach my $srcfile (@srcfiles) {
my $destfile = $srcfile;
$destfile =~ s/$source_dir(.*)\.$source_ext/$dest_dir$1\.$dest_ext/i;
if (! -e $destfile) {
$pm->start($srcfile) and next; *blooper* do the fork
$srcfile =~ s/\//\\/g;
$destfile =~ s/\//\\/g;
system('"'.$coreconverter_exe.'"' .
' -infile="' . $srcfile . '" ' .
' -outfile="' . $destfile . '" ' .
$dmc_options .
$dsp_effects .
' -convert_to="' . $dest_codec . '" ' .
$codec_options);
$pm->finish; *blooper* do the exit in the child process
}
}
$pm->wait_all_children;
my $endtime = time();
($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime($endtime);
$year = 1900 + $yearOffset;
$hour = sprintf("%02d", $hour);
$minute = sprintf("%02d", $minute);
$second = sprintf("%02d", $second);
$theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year";
$seconds = $endtime - $starttime;
@parts = gmtime($seconds);
$duration = sprintf ("%02d:%02d:%02d", @parts[2,1,0]);
if ($seconds > 0 && $total_audio_time > 0) {
$realtime = sprintf("%.1f", $total_audio_time / $seconds);
} else {
$realtime = 0;
}
@parts = gmtime($total_audio_time);
if ($total_audio_time > 0) {
$playing_time = sprintf("%dd %dh %dm", @parts[7,2,1]);
} else {
$playing_time = 0;
}
logmsg "------------------";
logmsg "Finished converting at $theTime.";
logmsg "Converted $total_converted_files files (total playing time $playing_time).";
logmsg "Conversion completed in $duration at ${realtime}x real-time encoding speed.\n";
*blooper* Close the logfile (if it was open)
close LOGFILE;
P.S. Why are the hashes that mark comments in my script appearing as '*blooper*' ?