Wednesday, November 21, 2012

M.A.M.E CHD Convert v4 to v5

I think we can agree that converting the drive images from v4 to v5 is tedious.  Definitely something to be scripted.  Since it's easy to find a Windows' version of the chdman program (I'm using a version from .147u3), I decided to do the script in Windows with Perl.  If you don't have Perl for Windows, get it here.  Once Perl is installed, copy and save the script below.  The script will scan your mame/rom directory looking for sub-directories.  It will then change to each sub-dir looking for version 4 of the CHD.  If the CHD is version 4, it will check the v5 list.  If the CHD is version 4 and found in the v5 list, it will get converted to version 5.  The v5 list is based on 147u3.  I'll try to keep it up to date.  Hope this helps someone.

Things to note.  I've already ran a CRC check against each CHD and know they all verify successfully.  If you have a corrupt CHD and are not aware, this program will probably continue to run, the resulting CHD will not work (neither did the original CHD it tried to convert).  I didn't do a lot of error checking.  The qx process will capture all STDOUT.  The data is there if you wish to parse STDOUT looking for success or failures during the conversion process.  I've also commented out the deletion of the original file.  I should probably include moving the v4 file to a backup directory instead of leaving it in its original directory.

If you use the script, be sure to set the location of  your chdman.exe and the ROM directory within the script.  Not sure if anyone else has experienced chdman hangs but I've seen the application hang during the copy process.  I've read that the hang is due to a possible problem in the multi-processor code.  I haven't confirmed that but I did build a single vCPU guest in my virtual environment for this task.  No hangs yet.

#### Updated for .147u4 - 12/17/2012

#### Updated for .148 - 1/25/2012

#### Updated for .164 - 7/29/2015


<----  Begin script ---->

#!/usr/bin/perl -w
#
# Get info to determine version:
#    chdman info -i <input file name>
#    Will return "File Version: 4"
#
# Convert from version the latest version
#    chdman copy -i <input file name> -o <new file name>
#    remove the original file and rename the new file
#

our $CHDMAN="g:\\mame\\chdman.exe";
our $Temp_Name = "tempnew.chd";

our @CHD_V5 = qw(a51site4-2_01.chd
a51site4-2_0.chd
alien.chd
arctthnd.chd
uarctict.chd
area51t.chd
area51.chd
area51mx.chd
atronic.chd
gdl-0018.chd
bam2.chd
batlgear.chd
bg2_204j.chd
bg2_201j.chd
gdl-0023a.chd
beachhead2000_5-27-2003.chd
beachhead2002_5-27-2003.chd
beachhead2003desertwar_5-27-2003.chd
beachhead2000_9-16-2001.chd
bikiniko.chd
biofreak.chd
bldyr3b.chd
blitz.chd
blitz2k.chd
blitz99.chd
753jaa11.chd
853jaa11.chd
gcb07jca02.chd
gcb07jca01.chd
gcc01jca02.chd
gcc01jca01.chd
825jaa11.chd
847jaa11.chd
981jaa11.chd
a21jaa11.chd
b07jaa11.chd
993hdda01.chd
988jaa11.chd
858jaa11.chd
a05jaa11.chd
995jaa11.chd
c01jaa11.chd
c44jaa03.chd
985jaa01.chd
bntyhunt.chd
a45a02.chd
645c04.chd
rtimes.chd
telly.chd
calchase.chd
calspeed.chd
calspeda.chd
carnevil.chd
carnevi1.chd
cartfury.chd
chaosheat.chd
chaosheatj.chd
gds-0001.chd
gdl-0014a.chd
cliffhgr.chd
cobra.chd
922d02.chd
922b02.chd
comebaby.chd
gdx-0002b.chd
420uaa04.chd
csplayh1.chd
csplayh5.chd
csplayh7.chd
cubeqst.chd
885jab01.chd
ep_pharo.chd
810uba02.chd
szz_cf.chd
fateulc.chd
jam1-dvd0.chd
firefox.chd
flipmaze.chd
fuudol.chd
gamecst2.chd
gamecstl.chd
99bottles.chd
gammagic.chd
gauntdl.chd
gauntd24.chd
gauntleg.chd
gauntl12.chd
gdvsgd.chd
gobyrc.chd
rcdego.chd
941b02.chd
gdx-0013.chd
sed1dvd0.chd
vr_xp_system_6-11-2002.chd
globalvr_xp_system.chd
hydro.chd
hyperath.chd
hyperv2_pqi_6-12-02.chd
hyperv2_pqi_9-30-01.chd
hyprdriv.chd
gdl-0010.chd
gds-0039b.chd
gds-0027.chd
gds-0026b.chd
gds-0033.chd
gds-0032c.chd
jdreddb.chd
jdreddc.chd
jn010108.chd
cap-jjk-3.chd
cap-jjm-1.chd
b41c02.chd
junai.chd
junai2.chd
gdl-0040.chd
kdeadeye.chd
kn1-b.chd
kinst.chd
kinst2.chd
cdp-00146.chd
kollon.chd
kollonc.chd
landhigh.chd
mace.chd
macea.chd
mach3.chd
mahjngoh.chd
a40jab02.chd
maxforce.chd
c09c04.chd
c09d04.chd
gdx-0017f.chd
mjmania.chd
a29b02.chd
a29a02.chd
b33a02.chd
b47jxb02.chd
mwskinsa.chd
mwskins104.chd
mwskins.chd
nagano98.chd
720jaa01.chd
nbanfl.chd
nbashowt.chd
npy1cd0b.chd
nfsug1_1-disc2.chd
nfsug1_1-disc1.chd
gds-0023e.chd
nightrai.chd
offrthnd.chd
gdx-0007.chd
orbatak.chd
otenamhf.chd
otenamih.chd
otenki.chd
gdx-0004a.chd
gdx-0014a.chd
a00jac02.chd
a00uad02.chd
a00kac02.chd
a00uac02.chd
a00eaa02.chd
b11a02.chd
pbball96.chd
pp201.chd
831jhdda01.chd
gq986jaa01.chd
gq986jaa02.chd
a04jaa02.chd
a04jaa01.chd
gqa16jaa01.chd
gqa16jaa02.chd
b00jab01.chd
b00jaa02.chd
gqb30jaa01.chd
gqb30jaa02.chd
c00jab.chd
gea02jaa02.chd
gea02jaa01.chd
977kaa02.chd
a11jaa01.chd
a11jaa02.chd
977jaa02.chd
gc977jaa02.chd
gc977jaa01.chd
ppp2nd.chd
primrag2.chd
psattack.chd
gdl-0024.chd
psyvaria.chd
psyvarrv.chd
gds-0031.chd
pwrshovl.chd
gq460a08.chd
ge557a09.chd
quakeat.chd
pqiidediskonmodule.chd
676a04.chd
gdl-0032a.chd
raizpin.chd
raycris.chd
cap-wzd-3.chd
roadburn.chd
rotr.chd
rrv1-a.chd
gca18jaa.chd
savquest.chd
gdx-0018a.chd
scp1cd0.chd
gdl-0030a.chd
sf2049se.chd
sf2049te.chd
sf2049.chd
sfrush.chd
sfrushrk.chd
gds-0016.chd
shanghss.chd
shanghaito.chd
gdl-0021.chd
shikigam.chd
sianniv.chd
simpbowl.chd
sc21-dvd0b.chd
sc21-dvd0d.chd
sc31001-na-dvd0-b.chd
soutenry.chd
sf010101.chd
speeddrv.chd
spuzbobj.chd
spuzbobl.chd
a13b02.chd
a13c02.chd
gdl-0005.chd
hm-in2.chd
db1.chd
gv027j1.chd
tk10100-1-na-dvd0-a.chd
tk9100-1-na-dvd0-a.chd
tef1dvd0.chd
te51-dvd0.chd
tenthdeg.chd
thenanpa.chd
a41b02.chd
a41a02.chd
a41c02.chd
tst1dvd0.chd
755jaa01.chd
756jab01.chd
tokyocop.chd
gdl-0036a.chd
gdl-0026.chd
tkk2-a.chd
a30b02.chd
a30c02.chd
turrett.chd
gdl-0035.chd
usagi.chd
usvsthem.chd
vaportrx.chd
vaportrp.chd
vcircle.chd
gds-0036f.chd
voyager.chd
wmn1.chd
gdx-0016a.chd
warfa.chd
wargods_08-15-1996.chd
wargods_10-09-1996.chd
wargods_12-11-1995.chd
c22d02.chd
c22a02.chd
c22c02.chd
weddingr.chd
wg3dh.chd
c18jaa03.chd
c27jaa03.chd
xiistag.chd
b4xb02.chd
yuyuhaku.chd
zga1dvd0.chd
zdx1dvd0.chd
zokuoten.chd
zooo.chd
b44jaa01.chd
706jaa02.chd
887kba02.chd
887aaa02.chd
887jaa02.chd
887kaa02.chd
a22jaa02.chd
a34jaa02.chd
a27jaa02.chd
b19jaa02.chd
b20jaa02.chd
894jaa02.chd
a38jaa02.chd
845aaa02.chd
845jba02.chd
845jab02.chd
810eaa02.chd
gdt-0008c.chd
b17jaa02.chd
gdl-0001.chd
gdl-0006.chd
a12jaa01.chd
a12jaa02.chd
gdl-0034.chd
gdl-0039.chd
gdl-0039a.chd
gdl-0028c.chd
gds-0023c.chd
623jaa02.chd
802jab02.chd
gdl-0017.chd
gdl-0002.chd
gds-0004.chd
gds-0005.chd
gds-0019.chd
gdx-0003a.chd
gds-0024a.chd
gds-0036a.chd
gds-0036d.chd
gdt-0002.chd
gdt-0015.chd
gdt-0013e.chd
gds-0011.chd
gds-0010.chd
gdl-0020.chd
);

sub ctrl_c_handler {
 print "\nCtrl C pressed \n";
 exit 2;
}

sub CHD_Info
{
 my $ROM_Name = $_[0] or exit 1;
 my $CHDVer = 0;
 print $ROM_Name,"\n";
 my $output = qx{$CHDMAN info -i $ROM_Name};
 foreach (split(/\n/,$output)) {
 if ($_ =~ /^File/) {
   $CHDVer = $_;
   chomp $CHDVer;
  }
 }

 my $junk;
 ($junk, $CHDVer) = split(':', $CHDVer);

# $CHDVer = int($CHDVer);

 return int $CHDVer;
}

sub CHD_Copy
{
 my $ROM_Name = $_[0] or exit 1;
 my $stdout = qx {$CHDMAN copy -i $ROM_Name -o $Temp_Name};
 my $Old_Name = $ROM_Name . ".old";
 $stdout = qx {ren $ROM_Name $Old_Name};
 $stdout = qx {ren $Temp_Name $ROM_Name};
# $stdout = qx {del $Old_Name};
}

unless (-f $CHDMAN) {
    print "We need a working chdmgr program.  Check the variable CHDMAN in the script\n";
    exit 1;
}
print "Lets go...\n";

$SIG {"INT"} = \&ctrl_c_handler; # "INT" indicates "Interrupt" signal.

my $Src_Dir = "g:\\mame\\roms";
my $Version;
chdir 'g:\\mame\\roms';

my @Src_Chds = qx {dir /AD /b /on};
foreach my $Src_Chd (@Src_Chds) {
 chomp $Src_Chd;
 chdir $Src_Chd;
 print "Directory: ", $Src_Chd, "\n";
 if (-e $Temp_Name) {
  unlink $Temp_Name;
 }
 my @CHDS = qx {dir *.chd /b};
 foreach my $CHD (@CHDS) {
  chomp $CHD;
  if (grep {$_ eq $CHD} @CHD_V5) {
    $Version = CHD_Info $CHD;
    if ($Version == 4) {
     print "\n", $CHD, " is version: ", $Version, " - Now Converting to v5\n";
     CHD_Copy $CHD;
    }
    }
 }
 chdir "..";
}

<----End Script---->

Monday, February 13, 2012

iPhone, MythTV, UPnP and miniDLNA

I have a MythTV backend running on Mythbuntu.  Great for recording TV shows, watching videos and listening to music from the MythTV Frontend client.  I want to be able to listen to my music from anywhere around the house.  I prefer FLAC over MP3 and wanted a method to stream music to my stereo or any docking device.  I have an iPhone, Touch and iPad; these should make nice clients given a decent application.  I don't like the UPnP service that MythTV provides.  After a little testing, I settled on miniDLNA on the server with the 8player app on my Apple devices.

MiniDLNA offers a search function for Artists or Albums.  Other UPnP servers did not have the same extended capability (of the ones I tried).  MiniDLNA also shows album art.  8player has a free version of the app to test drive.  I'm still waiting to get a DLNA or UPnP service that can stream a DVD ISO (converting to MP4 stinks).

Update Oct 20, 2012:  I've added a Micca EP600 G2 to my man cave.  This device allows me watch my DVD ISO files and play FLAC music.  It offers support for many formats.  I also have some DTS DVD-Audio that it plays without any issue.  The device offers HDMI connectivity.  Installation is a snap.  I'm using a Samba share on my Mythbuntu box to access the media with the Micca device.  I'm not happy with the inability to auto-display album artwork.  While the documentation reads as though the album artwork is displayed if found, I have yet to see the artwork unless I open it manually.  Hopefully, just an operator malfunction and I'll sort that out.  For now, I can live with that as the good outweighs the bad.

With the addition of the Micca device, I found that I don't use my Mythfrontend.  For me, the main purpose of the Myth services was playback of videos and music.  I never used the service to record television shows.  If you've tried Mythmusic 0.25, maybe you're as pissed about the changes as I am.  I hate it.  This made my decision easier than it might have been otherwise.  I'll stop my rant and move forward.  I've decided to get rid of the Myth service entirely.  With the removal of the Myth software, I will use my VMware ESXi server to host the services mentioned above.  I already have a CentOS 5 guest running as a virtual machine.  My plan is to move all the media data to the NAS device and create some mount points on the CentOS system for music and videos.  I'm waiting for some hardware that I bought on the 'net.  Once that arrives, It will take about a day to migrate the data from my physical Mythbuntu server to the CentOS VM (my music and video collection accounts for nearly 4TB of data).  I'll update this once I complete the physical to virtual migration.

Update Dec 7, 2012:  I moved my media server to a virtual machine.  Settled on openSuSE for the OS on the virtual guest.  The system has been running since I did the migration at the end of Oct.  Still running a samba share as well as miniDLNA.  All still working without any issues.  My iPhone, Touch and iPad still accessing the audio files with 8player and miniDLNA.  My Micca device is accessing the samba share grabbing the ISO images and FLAC audio.  I could not be happier with the performance and the ease of this environment.

Update June 8, 2014:  No real changes.  I updated to openSuSE 13.1 recently and added a second NAS.  I still like the layout and easy access to music and video material in any room of the house.  I did add an antenna booster to my WiFi network allowing me to listen to music while cutting grass or working in the yard.  One of my kids recently gave me their broken iPhone 4.  After a replacement case from Zeetron, the device is excellent for around-the-house work or walking on the treadmill.  I need a new hobby.

Update February 11, 2015:  Still using and happy with the Micca EP600 devices.  I put a television in the treadmill room and needed another media device.  This time, I wanted to try a different solution and took a chance on the Western Digital WD TV Live (model WDBGXT0000NBK).  It's only been a couple of weeks, but I believe the WD is more favorable.  It does everything the Micca does but also includes services like NetFlix.  For the treadmill room, the TV does not have app capabilities so the one device covers everything for me.  I purchased the device on Amazon as a refurbished model for $64.  The device handles Bluray and DVD ISO files along with nearly every possible format for those that rip the media down to something else.  I can also play my music (FLAC, MP3, etc) and audio images (CUE/Wav, CUE/Flac).  Artwork is also displayed while browsing and playing the audio as well.  The only thing I don't like about this model; the time is always off.  Not sure why.  The only options for time are zone and daylight savings.  No way to actually set the time or specify an NTP source.

Sunday, January 29, 2012

DVD to MP4 with HandBrake (vidconv.sh)

Using Handbrake in a bash script.

This script processes my ISO file(s) and converts them to MP4.  The MP4 can then be used via UPNP or DLNA on my network devices.  I rip my DVDs to ISO and store them on my media server.  This is a living script and may change.  It takes a while to process each ISO at the moment.  I just want good quality when I view the video on a widescreen television.  The options for Handbrake I'm using:

-m                    Chapter markers
--main-feature   Find and process the main title.  By default, Title 1 is processed.
-e x264             Video encoder
-q 20                 Video quality
-r 29.97             Video frame rate
-b 2000             Video bitrate
-B 192               Audio bitrate
-i /mnt                This is the mount point for the ISO
-o                      Output file name
2> /dev/null        I don't want to see everything displayed to the screen - sending std error to nowhere land.

The script requires you to run as root since we are running mount and umount.

<<< --- vidconv.sh --->>>
#!/bin/bash

function ConvertIt()
{
    NewName=${1%%\.*}
    EXT=mp4
    if [ -f "$NewName.$EXT" ]; then
        echo "A file by the name of $NewName.$EXT already exists"
        return 1
    fi
    echo Processing:  $NewName
    mount -o loop "$1" /mnt
    HandBrakeCLI -m --main-feature -e x264 -q 20 -r 29.97 -b 2000 -B 192 -i /mnt -o "$NewName.$EXT" 2> /dev/null
    echo
    umount /mnt
}

if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root" 1>&2
   exit 1
fi
if [ "$1" = "" ]; then
    echo "You need to tell me which ISO to convert"
    echo "Use -a to process all ISO files in the currentl directory"
    exit 1
fi

if [ "$1" = "-a" ]; then
        for files in *.iso
        do
                ConvertIt "$files"
        done
else
    if [ ! -f "$1" ]; then
        echo "The input file $1 does not exist."
        exit 1
    fi
    ConvertIt "$1"
fi

exit 0




Thursday, January 12, 2012

listall.pl

Just a quick script to list all of my music files.  Similar to the 'tree' command from the DOS command line utilities.  Set the variable SrcDir to the root of your music data and give it a run.


<<< --- listall.pl --- >>>
#!/usr/bin/perl
use strict;
use warnings;
use Cwd;
use sort '_qsort';
use File::Glob qw(:globally :nocase);
our $DEBUG = 1;

my $SrcDir = "/var/lib/mythtv/music";
opendir (Source_Dir, $SrcDir) || die "can't opendir $SrcDir: $!";
chdir Source_Dir;
my @Artists = grep { !/^\./ && -d "$_" } sort readdir(Source_Dir);
foreach my $Artist (@Artists) {
        opendir (Artist_Dir, $Artist) || die "can't opendir $Artist: $!";
        if ($DEBUG) { print $Artist, "\n"; }
        chdir Artist_Dir;
        my @Albums = grep { !/^\./ && -d "$_" } sort readdir(Artist_Dir);
        foreach my $Album (@Albums) {
                if ($DEBUG) { print "\t", $Album, "\n"; }
                chdir $Album;
                my @Tracks = <*.{flac,mp3}>;
                foreach my $Track (@Tracks) {
                        if ($DEBUG) { print "\t\t", $Track, "\n"; }
                }
                chdir "..";
        }
        chdir "..";
        closedir Artist_Dir;
}
closedir Source_Dir;

Bash scripts to find and fix FLAC files

Three part process to test all FLAC files on my system, attempt to fix the files and lastly, update tag information.

Part 1 - Find the bad files
<<< --- flactest.sh --->>>

#!/bin/bash
#
# Part one of the process.  Looks for flac files with errors.
#
szDate=`/bin/date`
szMusicRoot='/var/lib/mythtv/music'
#szErrFile="$szMusicRoot/flac_errors.txt"
szErrFile="/tmp/flac_errors.txt"

echo Recursively testing flacs in $szMusicRoot
echo Flac decoding errors logged to $szErrFile
echo Flac test of $szMusicRoot started at $szDate >"$szErrFile"

/usr/bin/find "$szMusicRoot/" -name '*.flac' -type f -not -exec /usr/bin/flac -t --totally-silent '{}' \; -and -print >>"$szErrFile"

echo Done!

Part 2 - 'Fix' the bad files
<<< --- flacfix.sh --- >>>

#!/bin/bash
# This script is part two of the process.  This will 'repair' all files that
# have been written to flac_errors.txt
#
# Requires the file flac_errors.txt to already exist
#
szDate=`/bin/date`
szMusicRoot='/var/lib/mythtv/music'
#filelist="$szMusicRoot/flac_errors.txt"
filelist="/tmp/flac_errors.txt"
fixedfile="/tmp/nometa.txt"

flac=/usr/bin/flac

#find "$szMusicRoot" -type f -name "*.flac" > /tmp/flacfiles.list
echo > $szDate > $fixedfile

if [ -f /tmp/since ];
then
        mv /tmp/since /tmp/since."$szDate"
fi
touch /tmp/since

#Get rid of the first line of the file
file=$(head -1 $filelist)
sed -i 1d $filelist
echo "Files to process:  " $(wc -l $filelist)

while [ -s $filelist ] ; do
        file=$(head -1 $filelist)
        echo $file >> $fixedfile
        wav_pref=${file%%flac};
        wav_suff=wav
        wav_file="$wav_pref""$wav_suff"
        if [ -f "$file" ];
                then
                $flac -F -f -s -d --delete-input-file "$file"
        fi
        if [ -f "$wav_file" ];
                then
                $flac -5 -f -s --delete-input-file "$wav_file"
        fi
# remove current file from list
        sed -i 1d $filelist
done

Part 3 - Update the vorbis tag information
<<< ---  fixmeta.sh --- >>>

#!/bin/bash
# This script will read the input file, needmeta.txt which has directories
# that have a newer timestamp than the file /tmp/since.  These directories
# need to have the metadata updated for their audio files.  The script
# fixalbums.pl is executed in each of these directories.
#
# Requirements:
# touch the /tmp/since with your necessary time/date
# the previous script touches the /tmp/since file automatically.
# touch --date "12/25/2012 15:00:00" /tmp/since
#
szDate=`/bin/date`
szMusicRoot='/var/lib/mythtv/music'
filelist=$szMusicRoot/needmeta.txt
flac=/usr/bin/flac

echo Gathering directories to process ...
find $szMusicRoot -type d -newer /tmp/since > $filelist

while read Path
do
if [ "$szMusicRoot" != "$Path" ]
then
echo $Path
        ArtistAlbum=${Path##$szMusicRoot}
        NewArtist=${ArtistAlbum%/*}
        Artist=${NewArtist#/}
        Album=${ArtistAlbum##*/}
        AA=$szMusicRoot/$ArtistAlbum
        cd "$AA" && fixalbums.pl
fidone < "$filelist"

Script to maintain ID3 tags (fixalbums.pl)

This script was written to help maintain files that are already in the destination folder.  If I change a folder name, this script will update the tags that are generated based on the folder name.  Real quick, files are stored in a structure: /Artist/Album/Track. Title.  Tags are then generated for each file based on this structure.  The genre tag is hard coded to 'Rock'.  My goal is to eventually grab the genre from Amazon.  I'm sure they store that info somewhere.  This script can grab artwork from Amazon as well.

Update June 8, 2014 - Minor changes with the addition of CUE file creation.  See my updates to the syncaudio.pl for an explanation of media data structure.  For this script, a added the option to fix the media files (was always assumed and therefore the default).  Now, one can run the script to create cue files and/or fix media tags.

<<< --- fixalbums.pl --->>>

#!/usr/bin/perl
use strict;
use warnings;
use Cwd;
use Audio::FLAC::Header;
use MP3::Info;
use MP3::Tag;
use sort '_qsort';
use File::Glob qw(:globally :nocase);
use Encode qw(encode decode);
use RequestSignatureHelper;
use Data::Dumper;
#use utf8;
#use Encode;

require LWP::UserAgent;
use LWP::Simple;      # libwww-perl providing simple HTML get actions
#use LWP::Simple qw($ua get); # libwww-perl providing simple HTML get actions
use HTML::Entities;
use URI::Escape;
use XML::Simple;

use vars qw($opt_c $opt_d $opt_a $opt_h $opt_f $opt_k $opt_m $opt_p);
use Getopt::Std;

sub Display_Help {
        print "Usage:\n\n";
        print "-a Add artwork to the metadata\n";
        print "-c Grab a cover from Amazon if one doesn't exist\n";
        print "-d Debug mode will desplay more progress information\n";
        print "-f Force FLAC to WAV and back to FLAC (Gets rid of all metadata)\n";
        print "-k Keep the WAV as a backup (use with -f)\n";
        print "-m Update metatags\n";
        print "-p Create a CUEFILE playlist\n";
        print "-h This help screen\n";
}

sub System_Call {
        my @args = @_;
        system (@args);
        if ($? == -1) {
                print "\nFailed to execute: $!\n";
                exit 2;
        }
        elsif ($? & 127) {
                printf "\nChild died with signal %d, %s coredump\n",
                ($? & 127),  ($? & 128) ? 'with' : 'without';
                exit 2;
        }
}

sub Get_Artist_Album {
        return ( split m!/!, cwd() )[-2,-1];
}

sub Get_Album_Cover {
        my $am_artist = "";
        my $am_title = "";
        my $ama_uri = "";
        my $ama_genre = "";
        my $found = 0;
        my $uri = "";

        my $artist = $_[0] or die "\tI don't like the text: $_[0] - $!\n";
        my $album = $_[1] or die "\tI don't like the text: $_[1] - $!\n";
        $album =~ s/ Cd\d*//g;
        $album =~ s/ \(.*\)//g;
        $album =~ s/^\(.*\) //g;
#       my $Full_Path = $_[2] or die "\tI don't like the path text: $_[2] - $!\n";
        if (defined $opt_d) { print "# ", $artist, "\\", $album, "\n"; }
#       The following will replace a space with %20
#       $artist =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
#       $album =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg;
        if (defined $opt_d) { print "Processing: ", $album, "\n"; }

        if (defined $opt_d) {
                print "# starting to look for poster on Amazon.com\n";
        }

#       $ua->proxy('http','http://192.168.2.1:8080');
        use constant myAWSId        => '<From Amazon>';
        use constant myAWSSecret    => '<From Amazon>';
        use constant myEndPoint     => 'ecs.amazonaws.com';
        my $helper = new RequestSignatureHelper (
                +RequestSignatureHelper::kAWSAccessKeyId => myAWSId,
                +RequestSignatureHelper::kAWSSecretKey => myAWSSecret,
                +RequestSignatureHelper::kEndPoint => myEndPoint,
        );
        my $request = {
                Service => 'AWSECommerceService',
                AssociateTag => 'hous06b-20',
                Operation => 'ItemSearch',
                SearchIndex => 'Music',
                Keywords => $album." ".$artist,
#               Artist => $artist,
#               Title => $album,
                Sort => 'relevancerank',
#               ResponseGroup => 'Images,Small',
                ResponseGroup => 'Images,ItemAttributes,BrowseNodes',
        };
        my $signedRequest = $helper->sign($request);

        my $queryString = $helper->canonicalize($signedRequest);
        my $url = "http://" . myEndPoint . "/onca/xml?" . $queryString;
        if (defined $opt_d) { print "Sending request to URL: $url \n"; }
        if (defined $opt_d) { print "# Query URL: $url\n";}

        my $ua = new LWP::UserAgent();
        my $response = $ua->get($url);
        my $content = $response->content();

        my $xmlParser = new XML::Simple();
        my $xml_doc = $xmlParser->XMLin($content);

        if (defined $opt_d) {
                print "Parsed XML is: " . Dumper($xml_doc) . "\n";

                if ($response->is_success()) {
                        my $title=$xml_doc->{Items}->{Request}->{ItemSearchRequest}->{Title};
                } else {
                        my $error = findError($xml_doc);
                        if (defined $error) {
                                print "Error: ".$error->{Code}.": ".$error->{Message}."\n";
                        } else {
                                print "Unknown Error!\n";
                        }
                }
        }

        if (ref($xml_doc->{Items}->{Item}) ne 'ARRAY') {
                my @tmpArray = ($xml_doc->{Items}->{Item});
                $xml_doc->{Items}->{Item} = \@tmpArray;
        }

        my $k = 0;
        do {
                $am_artist = $xml_doc->{Items}->{Item}->[$k]->{ItemAttributes}->{Artist};
                $am_title  = $xml_doc->{Items}->{Item}->[$k]->{ItemAttributes}->{Title};
                $ama_uri = $xml_doc->{Items}->{Item}->[$k]->{LargeImage}->{URL};
                if (defined $ama_uri) {
                        $ama_genre=$xml_doc->{Items}->{Item}->[$k]->{BrowseNodes}->{BrowseNode}->[0]->{Name};
                        if (defined $opt_d) {
                                print "# Amazon: found cover for $am_artist/$am_title: ";
                                print $ama_uri, "\n";
                                print "# Amazon: Genre: $ama_genre \n";
                        }
                        $found = 1;
                }
                $k++;
        } until ($found || $k == 25);
        #only search through first 25 matches

        my $image = get $ama_uri if (defined($ama_uri) && $ama_uri ne "");
        if (!defined($image)) {
                if (defined $opt_d) { print("# No image found for $artist/$album\n"); }
                return;
        }
        if ($ama_uri ne "" && length($image) eq "807") {
                if (defined $opt_d) { printf("# this image is blank\n"); }
                $ama_uri = "";
        }

        if (!defined($ama_uri)) {
                $ama_uri = "";
        }

        if ($ama_uri ne "") {
                if (defined $opt_d) { printf("Found: ", $ama_uri, "\n"); }
                $uri = $ama_uri;
        }

        my $jpeg_image = get $uri;
        my $outfile = "folder.jpg";
        if (defined $opt_d) { printf("OutFile: ", $outfile, "\n"); }
        open(INFO, ">$outfile");

        binmode INFO;
        print INFO $jpeg_image;
        close INFO;
}

sub Convert_Image_Format {
#This function assumes only one image file in the source directory
        my @Src_IMG_Files = <*.{bmp,png}> ;
        my $Num_Src_IMG_Files = @Src_IMG_Files;
        if ($Num_Src_IMG_Files ge 1) {
                my $Src_IMG_File = $Src_IMG_Files[0];
                System_Call ("convert", "$Src_IMG_File", "folder.jpg");
                unlink $Src_IMG_File;
        }
}

sub Convert_To_Flac {
        print "Converting to Flac";
        my $results;
        my @WAVS = <*.wav>;
        foreach my $wav (@WAVS) {
                chomp $wav;
                print ".";
                $results = System_Call ("shntool", "conv", "-w", "-q", "-o", "flac", "$wav");
                if (!$results) {
                        if (! defined $opt_k){
                                unlink $wav;
                        }
                } else {
                        print "Results: ",$results,"\n";
                        print "There is a problem with: ", $wav, "\n";
                        exit 1;
                }
        }
        print "\n";
}

sub Convert_To_Wav {
        print "Converting to Wav";
        my $results;
        my @FILES = <*.{flac,ape}> ;
        foreach my $File (@FILES) {
                chomp $File;
                print ".";
                $results = System_Call("shntool", "conv", "-q", "-o", "wav", "$File");
                if (!$results) {
                        unlink $File;
                } else {
                        print "Results: ",$results,"\n";
                        print "There is a problem with: ", $File, "\n";
                        exit 1;
                }
        }
        print "\n";
}

sub Process_Flac_To_Wav
{
        my $flac_file = $_[0];
        my $results;
        print "$flac_file -> WAV\n";
        my $wav_file = $flac_file;
        $wav_file =~ s/(.).flac/$1.wav/;
        $wav_file =~ tr/ / /s; #Remove unnecessary spaces
        $results=System_Call("flac","--verify","-F","-f","-s","-d","$flac_file");
        if (!$results) {
                unlink $flac_file;
                print "WAV -> $flac_file\n";
                $results=System_Call("flac","-5","--verify","-f","-s","$wav_file");
                if (!$results) {
                        unlink $wav_file;
                }
        } else {
                print "There is a problem with: ", $flac_file, "\n";
#               exit 1;
        }
}

sub Process_Files {
        Convert_Image_Format;
        my @MP3_Files = <*.mp3> ;
        my @FLAC_Files = <*.flac> ;
        my @JPG_Files = <*.jpg> ;
        my $Num_MP3_Files = @MP3_Files;
        my $Num_FLAC_Files = @FLAC_Files;
        my $Num_JPG_Files = @JPG_Files;
        if ($Num_MP3_Files > 0) {
                my ($Artist, $Album) = Get_Artist_Album;
                if (defined $opt_c && $Num_JPG_Files eq 0) {
                        Get_Album_Cover($Artist, $Album);
                }
                $Artist = decode('UTF-8',$Artist);
                $Album = decode('UTF-8',$Album);
                foreach my $MP3_File (@MP3_Files) {
                        chomp $MP3_File;
                        if (defined $opt_d) {print "Fixing: ", $MP3_File, "\n"; }
                        if ($MP3_File =~ /^(\d+[\.\_\-\ ]+)(.*)(\.\w+)$/) {
                                my ($Track, $Title, $Ext) = ($1, $2, lc($3));
                                $Track =~ s/^(\d+)(.*)/$1/;
                                $Track = sprintf("%02d", $Track);
                                $Title = Format_Text ($Title);
                                $Title = decode('UTF-8',$Title);
                                my $New_File = "$Track. $Title$Ext";
                                if (defined $opt_d) { print "\tNew Name: $New_File\n"; }
                                rename ($MP3_File, $New_File) unless $MP3_File eq $New_File;
#                               remove_mp3tag($New_File, 'ALL');
                                my $mp3 = MP3::Tag->new($New_File);
                                $mp3->get_tags;
                                if (exists $mp3->{ID3v1}) {
                                        $mp3->{ID3v1}->remove_tag;
                                }
                                if (exists $mp3->{ID3v2}) {
                                        $mp3->{ID3v2}->remove_tag;
                                }
                                my $id3v1 = $mp3->new_tag("ID3v1");
                                $id3v1->all($Title,$Artist,$Album,"","",$Track,"Rock");
                                $id3v1->write_tag;
                                my $id3v2 = $mp3->new_tag("ID3v2");
                                $id3v2->add_frame('TRCK',$Track);
                                $id3v2->add_frame('TIT2',$Title);
                                $id3v2->add_frame('TPE1',$Artist);
                                $id3v2->add_frame('TALB',$Album);
                                $id3v2->add_frame('TCON',"17");
                                if ((open(PICFILE, "folder.jpg")) && (defined $opt_a)) {
                                        my $filesize = (-s PICFILE);
                                        binmode(PICFILE);
                                        read(PICFILE, my $imgdata, $filesize);
                                        close(PICFILE);
                                        $id3v2->add_frame("APIC",
                                                chr(0x0),
                                                "image/jpeg",
                                                chr(0x0),
                                                "Cover Image",
                                                $imgdata);
                                }
                                $id3v2->write_tag;
                        }
                }
        }
        if ($Num_FLAC_Files > 0) {
                if (defined $opt_f) {
                        Convert_To_Wav;
                        Convert_To_Flac;
                }
                my ($Artist, $Album) = Get_Artist_Album;
                if (defined $opt_c && $Num_JPG_Files eq 0) {
                        Get_Album_Cover($Artist, $Album);
                }
                foreach my $FLAC_File (@FLAC_Files) {
                        chomp $FLAC_File;
                        if (defined $opt_d) {print "Fixing: ", $FLAC_File, "\n"; }
#                       if (defined $opt_f) {
#                               Process_Flac_To_Wav $FLAC_File
#                       }
                        if ($FLAC_File =~ /^(\d+[\.\_\-\ ]+)(.*)(\.\w+)$/) {
                                my ($Track, $Title, $Ext) = ($1, $2, lc($3));
                                $Track =~ s/^(\d+)(.*)/$1/;
                                $Track = sprintf("%02d", $Track);
                                $Title = Format_Text ($Title);
                                my $New_File = "$Track. $Title$Ext";
                                if (defined $opt_d) { print "\t$New_File\n"; }
                                rename ($FLAC_File, $New_File) unless $FLAC_File eq $New_File;
                                my $flac = Audio::FLAC::Header->new($New_File);
#                               %$tags = ();
                                my $tags = $flac->tags();
                                $tags->{TRACKNUMBER} = $Track;
                                $tags->{TITLE} = $Title;
                                $tags->{ARTIST} = $Artist;
#                               $tags->{'ALBUMARTIST'} = $Artist;
                                $tags->{ALBUM} = $Album;
                                $tags->{GENRE} = "Rock";
                                $flac->write();
                                if ((-e "folder.jpg")&& (defined $opt_a)) {
                                        System_Call ("metaflac", "--import-picture-from=folder.jpg",$New_File);
                                }
                        }
                }
        }

}

sub Create_CUE {
        my $i = 1;
        my $Track = 0;
        my @files = sort <*.{mp3,flac}>;
        my ($Artist, $Album) = Get_Artist_Album;
        #open (CUEFILE, '>'.$Artist.'-'.$Album.'.cue');
        open (CUEFILE, '>cd.cue');
        print CUEFILE "PERFORMER \"".$Artist."\"\n";
        print CUEFILE "TITLE \"".$Album."\"\n";
        foreach my $file (@files) {
                print CUEFILE "FILE \"".$file."\" WAVE\n";
                $Track = sprintf("%02d", $i);
                print CUEFILE "  TRACK ".$Track." AUDIO\n";
                print CUEFILE "    INDEX 01 00:00:00\n";
                $i++;
        }
        close (CUEFILE);
}

sub Format_Text {
        my $Text = $_[0] or die "\tI don't like the text: $_[0] - $!\n";
        $Text = lc($Text); #Make everything lowercase
        $Text =~ tr/_/ /; #Remove underscores
        $Text =~ s/\.\.\./\.\.\.\ /g;
        $Text =~ s/(\d),(\d)/$1$2/g;
        $Text =~ s/,/ /g;
        $Text =~ tr/\`\´\’/\'/s;
        $Text =~ s/ and / \& /g;
        $Text =~ tr/\[\{/\(/;
        $Text =~ tr/\]\}/\)/;
        $Text =~ s/\( /\(/g;
        $Text =~ s/ \)/\)/g;
        $Text =~ tr/ / /s; #Remove unnecessary spaces
        $Text =~ s/\·/-/g;
        $Text =~ s/\~/-/g;
        $Text =~ s/\:/-/g;
        $Text =~ s/\s*-\s*/-/g;
#       $Text =~ s/\.$//; #Some titles have an extra period - bye
#       $Text =~ s/(\d)\./$1/g; #Do not need period after numbers here
        my @Words = split(/ /,$Text);
        foreach my $Word (@Words) {
                $Word = ucfirst($Word);
        }
        $Text = "@Words";
        $Text =~ s/([(-])([a-z])/$1\u$2/g;
        $Text =~ s/(\W'\S)/uc($1)/eg; #Some items following ' should be uc
        $Text =~ s/(\.)([a-z])/$1\u$2/g; #Letter.Letter.Letter... is uc
        $Text =~ s/Dis[ck]\ /Cd/;
        $Text =~ s/Dis[ck](\d)/Cd$1/;
        $Text =~ s/Cd\ (\d)/Cd$1/;
        $Text =~ s/\((Cd\d+)\)/$1/;
        $Text =~ s/-Cd/ Cd/;
        my $x = $Text =~ tr/(/(/; #Count open parens
        my $y = $Text =~ tr/)/)/; #Count closing parens
        if ($x > $y) {
                $Text = $Text.")";
        }
        return ($Text);
}

unless (-x "/usr/bin/flac") {
        print "I need the program flac to function\n";
        exit 1;
}

unless (-x "/usr/bin/metaflac") {
        print "I need the program metaflac to function\n";
        exit 1;
}

getopts('cdahfkmp');
if (defined $opt_h) {
        Display_Help;
        exit 0;
}

if (defined $opt_m) {
        Process_Files;
}
if (defined $opt_p) {
        Create_CUE;
}
my $Artist_Dir = cwd();
opendir (Artist_DH, $Artist_Dir) || die "can't opendir $Artist_Dir: $!";
my @Albums = grep { !/^\./ && -d "$_" } sort readdir(Artist_DH);
foreach my $Album (@Albums) {
        chomp $Album;
        print "Processing: ", $Album, "\n";
        my $NewAlbum = Format_Text ($Album);
        rename ($Album, $NewAlbum) unless $Album eq $NewAlbum;
        if (defined $opt_d) { print "$NewAlbum \n"; }
        chdir $NewAlbum or warn "Cannot change to $NewAlbum\n";
        if (defined $opt_m) {
                Process_Files;
        }
        if (defined $opt_p) {
                Create_CUE;
        }
        chdir "..";
}
closedir Artist_DH;

Move audio from source to destination and ensure format is consistent.

Moves audio files from temporary area to permanent location, formatting along the way.

Background:  I had not used perl before this script so there are better methods than what I have here.  This works for me and is a living script.  That only means that I find myself making changes as I encounter situations that need addressed.

I use tagscanner on a Windows workstation that puts files into a holding area on my MythTV server (Amarok does a good job in Linux).  Tagscanner is really one of the best Windows' applications for audio files.  Extremely flexible and it's free.  The files are renamed into a format of "/Working/Directory/Artist/Album/Track. Title".  This script then starts in the source directory: "/Working/Directory" scanning all artists and albums.  From there this script formats all text to a consistent style and case (Linux bound - case matters).  This allows me to prevent some duplicates due to case and other formatting.  This script will grab album artwork from Amazon if I don't already have any in the source or destination directory.

Also, I prefer FLAC over MP3 but I also strive to have the better MP3 so this script will look for a higher bit rate in an MP3 and keep that over a lower bit rate.  The script gets rid of the MP3 if I have a FLAC of the same file.  For FLAC files, I convert them back to WAV to ensure that all Vorbis metatags have been removed and that the FLAC file's integrity has been checked.  I do the same to APE files.  Once in WAV format, I then convert those back to FLAC, adding only the tags that I am interested in having.

In the script, you will see the option (-a) to add artwork to each file.  I usually don't use that option.  If I convert the FLAC to MP3, I have another script that will add the artwork to the MP3.  This way, the MP3 works on my iPhone with artwork.  Just need Apple to start supporting FLAC (hey - we all have dreams).

Bottom line, put the raw files in "/some/base/directory/artist/album/track title" set the SRC_DIR and DEST_DIR variables in this script and let it run.  The location in the script for these two variables show how this script has evolved.  They push me to rewrite much of this thing.  Fortunately, the locations don't change too often.

Update - June 8 2014 - Just some minor changes to the script.  I started using foobar2000 for media playback in place of WinAMP.  With WinAMP, one can right-click a folder and play the content.  This isn't available in foobar.  Instead, foobar can use a cue file.  With that in mind, I created a sub routine to create a basic cue file when the media is being synced from the source to the destination.

I also modified the directory structure on the destination.  Before, I used /Working/Directory/Artist/Album/Track.  I now use /Working/Directory/Alpha/Artist/Album/Track.  An example is /Media/music/A/Adele/19/Track or Media/music/L/Led Zeppelin/II/Track.  I did this to make accessing the data more quickly when browsing, especially from a smartphone or HTPC remote.

<<< --- syncaudio.pl --->>>

#!/usr/bin/perl
use strict;
use warnings;
use Cwd;
#use Cwd qw(chdir);
use Audio::FLAC::Header;
use MP3::Info;
use MP3::Tag;
use sort '_qsort';
use File::Glob qw(:globally :nocase :glob);
use File::Path;
use File::Copy;
use Encode qw(encode decode);
use RequestSignatureHelper;
use Data::Dumper;
use lib '/home/jwhite/bin/lib';
use My::Stuff qw(Format_Text);
#use utf8;
#use Encode;

require LWP::UserAgent;
use LWP::Simple;        # libwww-perl providing simple HTML get actions
#use LWP::Simple qw($ua get);   # libwww-perl providing simple HTML get actions
use HTML::Entities;
use URI::Escape;
use XML::Simple;

use vars qw($opt_a $opt_r $opt_d $opt_f $opt_n);
use Getopt::Std;

#our $DEBUG = 0;

sub System_Call {
        my @args = @_;
        system (@args);
        if ($? == -1) {
                print "\nFailed to execute: $!\n";
                exit 2;
        }
        elsif ($? & 127) {
                printf "\nChild died with signal %d, %s coredump\n",
                ($? & 127), ($? & 128) ? 'with' : 'without';
                exit 2;
        }
        return $?;
}

sub findError {
        my $xml = shift;

        return undef unless ref($xml) eq 'HASH';

        if (exists $xml->{Error}) { return $xml->{Error}; };

        for (keys %$xml) {
        my $error = findError($xml->{$_});
        return $error if defined $error;
        }

        return undef;
}

sub Get_Album_Cover {
        my $am_artist = "";
        my $am_title = "";
        my $ama_uri = "";
        my $ama_genre = "";
        my $found = 0;
        my $uri = "";

        my $artist = $_[0] or die "\tI don't like the text: $_[0] - $!\n";
        my $album = $_[1] or die "\tI don't like the text: $_[1] - $!\n";
        my $Full_Path = $_[2] or die "\tI don't like the path text: $_[2] - $!\n";
        if (defined $opt_d) { print "# ", $artist, "\\", $album, "\n"; }
        $album =~ s/ Cd\d*//g;
        $album =~ s/ \(.*\)//g;
        $album =~ s/^\(.*\) //g;
        if (defined $opt_d) { print "# ", $artist, "\\", $album, "\n"; }
        if (defined $opt_d) { print "# starting to look for cover on Amazon.com\n"; }

#       $ua->proxy('http','http://192.168.2.1:8080');
        use constant myAWSId            => '<Get From Amazon>';
        use constant myAWSSecret        => '<Get From Amazon>';
        use constant myEndPoint  => 'ecs.amazonaws.com';
        my $helper = new RequestSignatureHelper (
                +RequestSignatureHelper::kAWSAccessKeyId => myAWSId,
                +RequestSignatureHelper::kAWSSecretKey => myAWSSecret,
                +RequestSignatureHelper::kEndPoint => myEndPoint,
        );

        my $request = {
                Service => 'AWSECommerceService',
                AssociateTag => '<Get From Amazon>',
                Operation => 'ItemSearch',
                SearchIndex => 'Music',
                Keywords => $album." ".$artist,
                Sort => 'relevancerank',
                ResponseGroup => 'Images,ItemAttributes,BrowseNodes',
        };
        my $signedRequest = $helper->sign($request);

        my $queryString = $helper->canonicalize($signedRequest);
        my $url = "http://" . myEndPoint . "/onca/xml?" . $queryString;
        if (defined $opt_d) { print "# Query URL: $url\n";}

        my $ua = new LWP::UserAgent();
        my $response = $ua->get($url);
        my $content = $response->content();

        my $xmlParser = new XML::Simple();
        my $xml_doc = $xmlParser->XMLin($content);

        if (defined $opt_d) {
                print "Parsed XML is: " . Dumper($xml_doc) . "\n";
        }

        if ($response->is_success()) {
                my $title = $xml_doc->{Items}->{Request}->{ItemSearchRequest}->{Title};
        } else {
                my $error = findError($xml_doc);
                if (defined $error) {
                        print "Error: " . $error->{Code} . ": " . $error->{Message} . "\n";
                } else {
                        print "Unknown Error!\n";
                }
        }

        if (ref($xml_doc->{Items}->{Item}) ne 'ARRAY') {
                my @tmpArray = ($xml_doc->{Items}->{Item});
                $xml_doc->{Items}->{Item} = \@tmpArray;
        }

        my $k = 0;
        do {
                $am_artist=$xml_doc->{Items}->{Item}->[$k]->{ItemAttributes}->{Artist};
                $am_title=$xml_doc->{Items}->{Item}->[$k]->{ItemAttributes}->{Title};
                $ama_uri=$xml_doc->{Items}->{Item}->[$k]->{LargeImage}->{URL};
                if (defined $ama_uri) {
#                       $ama_genre=$xml_doc->{Items}->{Item}->[$k]->{BrowseNodes}->{BrowseNode}->[0]->{Name};
#                       if (defined $opt_d) {
                                print "# Amazon: found cover for $am_artist/$am_title: $ama_uri \n";
#                               print "# Amazon: Genre: $ama_genre \n";
#                       }
                        $found = 1;
                }
                $k++;
        } until ($found || $k == 10);
        #only search through first 10 matches

        my $image = get $ama_uri if (defined($ama_uri) && $ama_uri ne "");
        if (!defined($image)) {
                if (defined $opt_d) { print("# No image found for $artist/$album\n"); }
                return;
        }
        if ($ama_uri ne "" && length($image) eq "807") {
                if (defined $opt_d) { printf("# this image is blank\n"); }
                $ama_uri = "";
        }

        if (!defined($ama_uri)) {
                $ama_uri = "";
        }

        if ($ama_uri ne "") {
                if (defined $opt_d) { printf("Found: ", $ama_uri, "\n"); }
                $uri = $ama_uri;
        }

        my $jpeg_image = get $uri;
        my $outfile = $Full_Path."/folder.jpg";
        if (defined $opt_d) { printf("OutFile: ", $outfile, "\n"); }
        open(INFO, ">$outfile");

        binmode INFO;
        print INFO $jpeg_image;
        close INFO;
}

sub Convert_Image_Format {
#This function assumes only one image file in the source directory
        my @Src_IMG_Files = <*.{bmp,png}> ;
        my $Num_Src_IMG_Files = @Src_IMG_Files;
        if ($Num_Src_IMG_Files ge 1) {
                my $Src_IMG_File = $Src_IMG_Files[0];
                System_Call ("convert", "$Src_IMG_File", "-quiet", "-quality", "100", "folder.jpg");
                unlink $Src_IMG_File;
        }
}

sub Convert_Wav_To_Flac {
        print "Converting Wav to Flac";
        my $results;
        my @WAVS = <*.wav>;
        foreach my $wav (@WAVS) {
                chomp $wav;
                print ".";
#               $results=System_Call ("shntool", "conv", "-w", "-q", "-o", "flac", "$wav");
                $results = System_Call ("flac", "-5", "--verify", "-f", "-s", "$wav");
                if (!$results) {
                        unlink $wav;
                } else {
                        print "Results: ",$results,"\n";
                        print "There is a problem with: ", $wav, "\n";
                        exit 1;
                }
        }
        print "\n";
}

sub Convert_Flac_To_Wav {
        print "Converting Flac to Wav";
        my $results;
        my @FILES = <*.{flac}> ;
        foreach my $File (@FILES) {
                chomp $File;
                print ".";
#               $results = System_Call("shntool", "conv", "-q", "-o", "wav", "$File");
# I had a condition where shntool could not interpret the WAV file correctly.
                $results = System_Call ("flac", "--verify", "-F", "-f", "-s", "-d", "$File");
                if (!$results) {
                        unlink $File;
                } else {
                        print "Results: ",$results,"\n";
                        print "There is a problem with: ", $File, "\n";
                        exit 1;
                }
        }
        print "\n";
}

sub Convert_Ape_To_Wav {
        print "Converting Ape to Wav";
        my $results;
        my @APEFILES = <*.{ape}> ;
        foreach my $ApeFile (@APEFILES) {
                chomp $ApeFile;
#               my $WavFile = $ApeFile;
#               $WavFile =~ s/\.ape$/\.wav/;
                print ".";
                $results = System_Call("shntool", "conv", "-q", "-o", "wav", "$ApeFile");
# mac does not provide a quiet mode. Still using shntool to convert to wav.
#               $results = System_Call ("mac", "$ApeFile", "$WavFile", "-d");
                if (!$results) {
                        unlink $ApeFile;
                } else {
                        print "Results: ",$results,"\n";
                        print "There is a problem with: ", $ApeFile, "\n";
                        exit 1;
                }
        }
        print "\n";
}

sub Create_CUE {
        my $CUEName = $_[0]."/00.cue";
        my $Artist = $_[1];
        my $Album = $_[2];
        my $i = 1;
        my $Track = 0;
        my @files = sort <$_[0]/*.{mp3,flac}>;
        open (CUEFILE, '>'.$CUEName);
        print CUEFILE "PERFORMER \"".$Artist."\"\n";
        print CUEFILE "TITLE \"".$Album."\"\n";
        foreach my $file (@files) {
                $file = ( split m!/!, $file )[-1];
                my $Title = $file;
                $Title =~ s{\.[^.]+$}{}; #Remove the extension
                $Title =~ s(^\d+[\.\_\-\ ]+)(); #Remove the leading track number
                print CUEFILE "FILE \"".$file."\" WAVE\n";
                $Track = sprintf("%02d", $i);
                print CUEFILE " TRACK ".$Track." AUDIO\n";
                print CUEFILE " TITLE \"".$Title."\"\n";
                print CUEFILE " INDEX 01 00:00:00\n";
                $i++;
        }
        close (CUEFILE);
}

sub Copy_Files {
        my $Artist = $_[0] or exit 1;
        my $Album = $_[1] or exit 1;
        my @Src_JPG_Files = {};
        my @Dest_JPG_Files = {};
        $Artist = decode('UTF-8',$Artist);
        $Album = decode('UTF-8',$Album);
        my $Category = substr($Artist,0,1);
        if ($Category =~ /\d/ ) { $Category = "0" };
        my $Dest_Dir = "/Media/music";
        my $Full_Path = $Dest_Dir."/".$Category."/".$Artist."/".$Album;
        my $Picture = $Full_Path."/folder.jpg";
        my @created = mkpath ($Full_Path);
        Convert_Image_Format;
#       If I have any JPG files in the source location, copy them to the dest.
        @Src_JPG_Files = <*.jpg>;
        my $Num_Src_JPG_Files = @Src_JPG_Files;
        if ($Num_Src_JPG_Files ge 1) {
                my $Src_JPG_File = $Src_JPG_Files[0];
                my $Dest_JPG_File = $Full_Path."/folder.jpg";
                print "Found an Image in the source and copying now\n";
                unless (copy($Src_JPG_File,$Dest_JPG_File)) {
                        print "Cannot copy JPG: ", $Src_JPG_File, "\n";
                }
        }
#       Don't bother getting cover from Amazon if I already have a JPG in the dest.
        @Dest_JPG_Files = bsd_glob( $Full_Path."/*.jpg" );
        my $Num_Dest_JPG_Files = @Dest_JPG_Files;
        if ($Num_Dest_JPG_Files eq 0) { Get_Album_Cover ($Artist, $Album, $Full_Path); }
#       Process MP3 and FLAC files from the source to the destination.
        my @Src_Files = sort <*.{mp3,flac}> ;
        my $Num_Files = @Src_Files;
        if ($Num_Files > 0) {
                my $CP_OK = 1;
                my $New_Track = 0;
                foreach my $Src_File (@Src_Files) {
                        my $Dest_File = $Src_File;
                        if ($Dest_File =~ /^(\d+[\.\_\-\ ]+)(.*)(\.\w+)$/) {
                                my ($Track, $Title, $Ext) = ($1, $2, lc($3));
                                $Track =~ s/^(\d+)(.*)/$1/;
                                if (defined $opt_n) {
                                        $Track = ++$New_Track;
                                }
                                $Track = sprintf("%02d", $Track);
                                $Title = Format_Text ($Title);
                                $Title = decode('UTF-8',$Title);
                                $CP_OK = 1;
                                $Dest_File = $Full_Path."/$Track. $Title$Ext";
                                if ( (-e $Dest_File) && ($Ext eq ".mp3") ) {
                                        my $srcinfo = get_mp3info($Src_File);
                                        my $destinfo = get_mp3info($Dest_File);
                                        if ($srcinfo->{BITRATE} <= $destinfo->{BITRATE}) {
                                                $CP_OK = 0;
                                        }
                                }
                                if ($Ext eq ".mp3") { #Don't copy mp3 if flac exists
                                        my $FLAC_Equiv = $Dest_File;
                                        $FLAC_Equiv =~ s/\.mp3$/\.flac/;
                                        if (-e $FLAC_Equiv) {
                                                $CP_OK = 0
                                        }
                                }
                                if ($CP_OK) {
                                        unless (copy($Src_File,$Dest_File)) {
                                                print "File Cannot be copied. Deleting zero-byte file.\n";
                                                unlink ($Dest_File);
                                        }
                                        if ($Ext eq ".flac") { #Replace mp3 with flac
                                                my $MP3_Equiv = $Dest_File;
                                                $MP3_Equiv =~ s/\.flac$/\.mp3/;
                                                if (-e $MP3_Equiv) {
                                                        print "\tReplacing $Track. $Title.mp3 with $Title.flac \n";
                                                        unlink ($MP3_Equiv);
                                                }
                                        }
                                        if ($Ext eq ".mp3") {
#                                               remove_mp3tag($Dest_File, 'ALL');
                                                my $mp3 = MP3::Tag->new($Dest_File);
                                                $mp3->get_tags;
                                                if (exists $mp3->{ID3v1}) {
                                                        $mp3->{ID3v1}->remove_tag;
                                                }
                                                if (exists $mp3->{ID3v2}) {
                                                        $mp3->{ID3v2}->remove_tag;
                                                }
                                                my $id3v1 = $mp3->new_tag("ID3v1");
                                                $id3v1->all($Title,$Artist,$Album,"","",$Track,"Rock");
                                                $id3v1->write_tag;
                                                my $id3v2 = $mp3->new_tag("ID3v2");
                                                $id3v2->add_frame('TRCK',$Track);
                                                $id3v2->add_frame('TIT2',$Title);
                                                $id3v2->add_frame('TPE1',$Artist);
                                                $id3v2->add_frame('TALB',$Album);
                                                $id3v2->add_frame('TCON',"17");
                                                if ((open(PICFILE, $Picture)) && (defined $opt_a)) {
                                                        my $filesize = (-s PICFILE);
                                                        binmode(PICFILE);
                                                        read(PICFILE, my $imgdata, $filesize);
                                                        close(PICFILE);
                                                        $id3v2->add_frame("APIC",
                                                                                        chr(0x0),
                                                                                        "image/jpeg",
                                                                                        chr(0x0),
                                                                                        "Cover Image",
                                                                                        $imgdata);
                                                }
                                                $id3v2->write_tag;
                                        } else {
                                                my $flac = Audio::FLAC::Header->new($Dest_File);
                                                my $tags = $flac->tags();
                                                $tags->{TRACKNUMBER} = $Track;
                                                $tags->{TITLE} = $Title;
                                                $tags->{ARTIST} = $Artist;
                                                $tags->{ALBUM} = $Album;
#                                               $tags->{'ALBUMARTIST'} = $Artist;
                                                $tags->{GENRE} = "Rock";
                                                if ((-e $Picture) && (defined $opt_a)) {
                                                        System_Call ("metaflac", "--import-picture-from=$Picture",$Dest_File);
                                                }
                                                $flac->write();
                                        }
                                }
                        } else {
                                print "File format not recognized: $Src_File \n";
                                exit 1;
                        }
                }
        }
        Create_CUE $Full_Path,$Artist,$Album;
}

unless (-x "/usr/bin/shntool") {
        print "I need the program shntool to function\n";
        exit 1;
}
unless (-x "/usr/bin/mac") {
        print "I need the program mac to function\n";
        exit 1;
}
unless (-x "/usr/bin/flac") {
        print "I need the program flac to function\n";
        exit 1;
}
unless (-x "/usr/bin/metaflac") {
        print "I need the program metaflac to function\n";
        exit 1;
}

getopts('ardfn');
my $Src_Dir = "/Media/work";
opendir (Src_DH, $Src_Dir) || die "can't opendir $Src_Dir: $!";
chdir Src_DH;

my @Src_Artists = grep { !/^\./ && -d "$_" } sort readdir(Src_DH);
chomp @Src_Artists;
foreach my $Src_Artist (@Src_Artists) {
        chomp $Src_Artist;
        if (! ($Src_Artist eq 'Junk')) {
                my $Dest_Artist = Format_Text ($Src_Artist);
                opendir (Src_Artist_DH, $Src_Artist) || die "can't open $Src_Artist: $!";
                chdir($Src_Artist);
                my @Src_Albums = grep { !/^\./ && -d "$_" } sort readdir(Src_Artist_DH);
                foreach my $Src_Album (@Src_Albums) {
                        print "Processing $Src_Artist / $Src_Album \n";
                        my $Dest_Album = Format_Text ($Src_Album);
                        chdir($Src_Album);
                        if (defined $opt_f) {
                                Convert_Flac_To_Wav;
                                Convert_Ape_To_Wav;
                                Convert_Wav_To_Flac;
                        }
                        Copy_Files ($Dest_Artist, $Dest_Album);
                        chdir "..";
                }
                chdir "..";
                closedir Src_Artist_DH;
        }
}
closedir Src_DH;