#!/usr/bin/perl -w
#Last Updated: 2003.11.02 (xris)

#Code based on mp3ascd.pl:  http://mymp3s.sourceforge.net/mp3ascd.html

#Import some routines
    use utf8;
    use strict;
    use Encode;
    use Getopt::Long;
    use MP3::Info;
    use MP3::Tag;
    use CDDB;
    use Cwd;

#used for editing data in an external editor
    use POSIX qw(:sys_wait_h);
    use File::Temp qw(tempfile);
    use Fcntl qw(F_SETFD);

#Load in the options
    my %Args;
    GetOptions(\%Args, 'help',
                       'url',
                       'nolookup',
                       'h|host=s',
                       'port=i',
                       'editor=s',
                       'dumb',
                       'reverse');

    $Args{editor} ||= $ENV{EDITOR} ? $ENV{EDITOR} : 'nano';
    $Args{host} ||= 'freedb.freedb.org';
    $Args{port} ||= 888;

#Some minor error checking
    die "Bad hostname:  $Args{host}\n\n" if ($Args{host} !~ /^\w/ or $Args{host} !~ /\w$/ or $Args{host} =~ /[^\w\.\-]/);

    my $editor_prog = $Args{editor};
    $editor_prog =~ s/\s.*$//s;
    unless (-e $editor_prog or -e "/bin/$editor_prog" or -e "/usr/bin/$editor_prog" or -e "/usr/local/bin/$editor_prog") {
        die "Unknown editor:  $Args{editor}\n\n";
    }

#Show the help?
    &ShowHelp if ($Args{help});

#Gather in the directory to parse, or die
    my $Dir = shift @ARGV;
    if ($Dir) {
        $Dir =~ s/\/+$//s;
    }
    else {
        print "No directory specified; using current.\n";
        $Dir = '.';
    }
    die "$Dir isn't a directory!\n\n" unless (-d $Dir);

#Read in the files in the directory
    die "Can't open $Dir:  $!\n\n" unless (opendir DIR, $Dir);
    my @Files = map { {name => $_ } } sort byTrackNum grep(!/^\./ && /\.(?:mp3|ogg)$/i, readdir DIR);
    closedir DIR;
    die "$Dir contains no .mp3 or .ogg files.\n\n" unless (@Files);

#Parse the files and generate the cddb id
    my $numtracks = 0;
    my $totalid = 0;
    my $totalsecs = 2;
    my $totalframes = 150;
    my $framestring = '150';
    foreach my $file (@Files) {
        $file->{type} = $file->{name} =~ /\.ogg$/i ? 'ogg' : 'mp3';
        my ($info, $secs);
    #Pull out the time info from mp3 files
        if ($file->{type} eq 'mp3') {
            $info = get_mp3info("$Dir/$file->{name}");
            $secs = (60 * $info->{MM}) + $info->{SS};
        }
    #Pull out the time info from ogg files
        elsif ($file->{type} eq 'ogg') {
            my $safe = ShellSafe("$Dir/$file->{name}");
            $info = `ogginfo $safe`;
            my ($hour, $min, $sec) = $info =~ m/Playback\s+length:\s+(?:(?:(\d+)h:)?(\d+)m:)?(\d+)s/s;
            die "Unknown output from ogginfo:\n\n$info\n\n" unless ($sec);
            $hour ||= 0;
            $min ||= 0;
            $secs = (3600 * $hour) + (60 * $min) + $sec;
        }
    #Use this info to create a cddb lookup id
        $totalsecs += $secs;
        $framestring .= " $totalframes" if ($numtracks);    #Don't do this for the first track, we already have an entry
        $totalframes += $secs * 75;                            #Count some frames
        while ($secs > 0) {                                    #Count up some cddb-related id info
            $totalid += $secs % 10;
            $secs /= 10;
        }
    #On to the next
        $numtracks++;
    }

#Generate the CDDB lookup
    my $CDDB_ID = sprintf ("%08x", ((($totalid % 0xFF) << 24) | ($totalsecs << 8) | $numtracks));

#Print out the requested cddb url
    if ($Args{url}) {
        my $safe = $framestring;
        $safe =~ tr/ /+/s;
        print "URL:\n\n",
              "http://$Args{host}/~cddb/cddb.cgi?cmd=cddb+query+$CDDB_ID+",
              "$numtracks+$safe+$totalsecs&hello=blah+my.host.com+fads+2&proto=1\n\n";
    }

#Look up the album
    my (%Album, @Tracks);
    $Album{genre} = [];
    $Album{title} = [];
    $Album{artist} = [];
    $Album{year} = [];

    unless ($Args{nolookup}) {
        my $found;
        my $cddb = CDDB->new(Host => $Args{host},
                             Port => $Args{port});
        die "Can't create CDDB lookup at $Args{host}:$Args{port}:  $!\n\n" unless ($cddb);
    #Get a list of matching disks
        my @disks = $cddb->get_discs($CDDB_ID, "$numtracks $framestring", $totalsecs);
        foreach my $disk (@disks) {
            my ($genre, $id, $title) = @{$disk};
            next unless ($id);
            $found++;
        #Extract some more info about this album
            my $info = $cddb->get_disc_details($genre, $id);
        #Print out the full cddb info record
        #    print "$info->{xmcd_record}\n\n";
        #Touch up the info and try to extract the artist name
            $genre =~ s/\bnewage\b/New Age/si;
            Titlecase(\$title);
            Titlecase(\$genre);
             $title =~ s/^\s*(.+?)\s*\/\s*//s;    #Extract the artist
            my $artist = ($1 or 'Various');
                $artist = 'Various' if ($artist =~ /^\s*various\s*artists\s*$/i);
            Titlecase(\$title) if ($title =~ s/^(.+?),\s+the$/The $1/si);    #Just in case
        #Add this stuff to the bunch
            AddUnique($Album{genre}, $genre);
            AddUnique($Album{title}, $title);
            AddUnique($Album{artist}, $artist);
        #Try to extract a year
            while ($info->{xmcd_record} =~ /YEAR\s*:\s*(\d{4})\b/sg) {
                AddUnique($Album{year}, $1);
            }
        #Scan the tracks
            my $i = 0;
            foreach my $track (@{$info->{ttitles}}) {
                my ($title, $artist);
                next if ($track =~ /^\s*unknown\s*(?:\(unknown\)\s*)$/si);
            #Try to extract the artist from the cddb song title
                unless ($Args{dumb}) {
                    if ($track =~ /^\s*([^\:\(\)]+)(?:\s+:\s*|\s*:\s+)(.+)\s*$/
                            or $track =~ /^\s*([^\-\(\)]+?)(?:\s+-\s*|\s*-\s+)([^\-]+?)\s*$/
                            or $track =~ /^\s*([^\/\(\)]+?)(?:\s+\/\s*|\s*\/\s+)([^\/]+?)\s*$/) {
                        if ($Args{reverse}) {
                            ($title, $artist) = ($1, $2);
                        }
                        else {
                            ($artist, $title) = ($1, $2);
                        }
                    }
                    elsif ($track =~ /^\s*(.+)(?:\s+[\-\/]\s*|\s*[\-\/]\s+)([^\-\/\(\)]+?)\s*$/) {
                        ($title, $artist) = ($1, $2);
                    }
                    $title ||= $track;
                    $title =~ s/\s*\/\s*/; /sg;
                    $title =~ s/\s+[\-\|]\s+/; /sg;
                    $title =~ s/\s*,\s*/; /sg;
                    if ($artist) {
                        $artist =~ s/\s*\/\s*/, /sg;
                    }
                }
            #Create the track object
                my %track = (title  => Titlecase($title or $track),
                             artist => Titlecase($artist));
                my $exists = 0;
                foreach my $othertrack (@{$Tracks[$i]}) {
                    if ($othertrack->{title} eq $track{title}) {
                        $exists = 1;
                        $othertrack->{artist} = $track{artist} unless ($othertrack->{artist});
                    }
                }
                push @{$Tracks[$i]}, \%track unless ($exists);
                $i++;
            }
            $Album{Tracks} = $i if (!$Album{Tracks} or $Album{Tracks} < $i);
        }
        unless ($found) {
            print "\nSorry, no match was found for this disk.\n\n";
            exit;
        }

#Generate the cddb display information, and perform any requested user edits
        $Album{Genre} =  (shift @{$Album{genre}} or '');
        $Album{Artist} = (shift @{$Album{artist}} or '');
        $Album{Title} =  (shift @{$Album{title}} or '');
        $Album{Year} =   (shift @{$Album{year}} or '');
        while (1) {
            my $multiples = 0;
            my $display = '';
        #Genre
            print "Genre:   $Album{Genre}\n";
            foreach my $genre (@{$Album{genre}}) {
                print "         (also:  $genre)\n";
                $multiples++;
            }
        #Artist
            print "Artist:  $Album{Artist}\n";
            foreach my $artist (@{$Album{artist}}) {
                print "         (aka:  $artist)\n";
                $multiples++;
            }
        #Title
            print "Title:   $Album{Title}\n";
            foreach my $title (@{$Album{title}}) {
                print "         (aka:  $title)\n";
                $multiples++;
            }
        #Title
            print "Year:    $Album{Year}\n";
            foreach my $year (@{$Album{year}}) {
                print "         (also:  $year)\n";
                $multiples++;
            }
        #Track Info
            print "\n";
            my $tracknum;
            foreach my $trackgroup (@Tracks) {
                my $i;
                $tracknum++;
                $tracknum =~ s/^0*(?=\d$)/0/;
                foreach my $track (@{$trackgroup}) {
                    print $i ? "    (aka:  " : "  $tracknum.  ";
                    print "$track->{artist}:  " if ($track->{artist} and $track->{artist} ne $Album{Artist});
                    print $track->{title};
                    print ')' if ($i);
                    print "\n";
                    foreach my $field (sort keys %{$track}) {
                        next if ($field eq 'artist' or $field eq 'title');
                        print "    \u$field: ";
                        print ' ' x (5 - length $field) if (length $field < 5);
                        print "$track->{$field}\n";
                    }
                    $i++;
                }
                $multiples += $i if ($i > 1);
            }
            print "\n";
        #Inform the user that there were multiple matches.
            if ($multiples) {
                print "There were multiple possible matches for this album.\n",
                      "You will now be forwarded to $Args{editor} for editing.\n",
                      "Press ENTER to continue.";
                <>;
            }
        #Force an edit if we have to, but otherwise, just ask the user
            else {
                my $answer;
                until ($answer and $answer =~ /^[yn]/i) {
                    print "Would you like to edit the above CDDB information (Y/n)?  ";
                    $answer = <>;
                }
                last if ($answer =~ /^n/i);
            }
            &EditInfo;
        }

    #Add the information to the mp3 tracks
        foreach my $i (0 .. @Tracks - 1) {
            my $track = $Tracks[$i][0];
            my $file = $Files[$i];
            $track->{number} = sprintf('%02d', $i + 1);
        #Save the tags to the file
            if ($file->{type} eq 'mp3') {
                SaveID3Tags($file, $track);
            }
            elsif ($file->{type} eq 'ogg') {
                SaveOggTags($file, $track);
            }
            print "Wrote tag for $file->{name}\n";
        #Rename the file
            RenameFile($file, $track);
        }
    #Rename the directory to match the artist/album names
        my $dirname = $Album{Title};
        if ($Album{Artist} !~ /^various$/i and $Album{Genre} !~ /\bsoundtrack\b/i) {
            substr($dirname, 0, 0) = "$Album{Artist} - ";
        }
        $dirname =~ s/^\s*the\s+//si;
        fix_utf8(\$dirname);
        if ($Dir eq '.') {
            my $thisdir = getcwd;
        }
        else {
            if (fix_utf8($Dir) ne $dirname and -e $dirname) {
                print "A directory named $dirname already exists; skipping rename.\n";
            }
            else {
                rename $Dir, $dirname or die "Can't rename $Dir:  $!\n\n";
                print "Renamed $Dir\n    to: $dirname\n" unless ($Dir eq $dirname);
            }
        }
    }

#All done!
    print "Done.\n\n";

    #####
    ## Beware:  Subroutines lurk below!
    #####

sub EditInfo {
# Collect out the album info
    my $file = <<EOF;
##
## !!! When saving this file, do NOT change the filename !!!
##
## Album Info (also acts as the default for each track)
##
##

<album>
\tGenre:   $Album{Genre}
EOF
    foreach my $genre (@{$Album{genre}}) {
        $file .= fix_utf8("###\tGenre:   $genre\n");
    }
    $file .= "\tArtist:  $Album{Artist}\n";
    foreach my $artist (@{$Album{artist}}) {
        $file .= "###\tArtist:  $artist\n";
    }
    $file .= "\tTitle:   $Album{Title}\n";
    foreach my $title (@{$Album{title}}) {
        $file .= "###\tTitle:   $title\n";
    }
    $file .= "\tYear:    $Album{Year}\n";
    foreach my $year (@{$Album{year}}) {
        $file .= "###\tYear:    $year\n";
    }
    $file .= <<EOF;
</album>

##
## Track Info:  (Written in the order it appears below)
##
##  <track #>
##     Artist:  Performer
##     Title:   Song Title
##  </track>
##

EOF
    my $count = 0;
    foreach my $trackgroup (@Tracks) {
        my (@titles, @artists);
        foreach my $track (@{$trackgroup}) {
            push @titles, "\tTitle:   $track->{title}\n";
            push @artists, "\tArtist:  $track->{artist}\n" if ($track->{artist} and $track->{artist} ne $Album{Artist});
        }
        $count++;
        $file .= join('', "<track $count>\n",
                          @artists ? shift @artists : '',
                          @artists ? (map {"#$_"} @artists) : '',
                          shift @titles,
                          @titles ? (map {"#$_"} @titles) : '',
                          "</track>\n\n");
    }
#Create a new temp file, convert to UTF-8 and print
    my $fh = tempfile(DIR => '/tmp');
    fix_utf8(\$file);
    print $fh $file;
#fnctl makes sure the filehandle $fh does not closed when we execute the editor
    fcntl $fh, F_SETFD, 0;
    system ("$Args{editor} /dev/fd/".fileno($fh));
    seek $fh, 0, 0;
#Read in the data
    my $data = '';
    while (my $line = <$fh>) {
        $line =~ s/^\s*#[^\n]*(?:\n|$)//;
        $data .= $line if ($line =~ /\S/);
    }
    close $fh;
#Extract the album info
    unless ($data =~ s/<album>\s*(.+?)\s*<\/album>//si) {
        print "\n!!!  Editor returned malformed <album/> information.\n!!!  Please try again.\n\n";
        return;
    }
    my $albuminfo = $1;
    my %album;
    while ($albuminfo =~ /(?:^|\n)\s*(\w+):\s*([^\n]*?)\s*(?=\n|$)/sg) {
        my $field = lc $1;
        my $val = $2;
        $val = '' unless ($val =~ /\S/);
        $album{"\u$field"} = $val;
    }
#Verify the album fields
    unless ($album{Title}) {
        print "\n!!!  No album title was specified.\n!!!  Please try again.\n\n";
        return;
    }
    unless ($album{Artist}) {
        print "\n!!!  No album artist was specified.\n!!!  Please try again.\n\n";
        return;
    }
    unless ($album{Genre}) {
        print "\n!!!  No album genre was specified.\n!!!  Please try again.\n\n";
        return;
    }
    if ($album{Year} and $album{Year} !~ /^\d{4}$/) {
        print "\n!!!  Album year must be a 4 digit number.\n!!!  Please try again.\n\n";
        return;
    }
#Store the album information
    my $numtracks = $Album{Tracks};    #back up the number of tracks
    %Album = %album;
    $Album{Tracks} = $numtracks;    #restore the number of tracks
#Extract the track information
    my @tracks;
    my $tracknum;
#### should probably do something here to extract the tracks in order, in case someone decides to reorder them.....
    while ($data =~ s/<track\b(?:\s*\d+)?>\s*(.+?)\s*<\/track>//si) {
        my $trackinfo = $1;
        my %track;
        $tracknum++;
        while ($trackinfo =~ /(?:^|\n)\s*(\w+):\s*([^\n]+?)\s*(?=\n|$)/sg) {
            $track{lc $1} = $2;
        }
    #Verify the track fields
        unless ($track{title}) {
            print "\n!!!  No title specified for track $tracknum.\n!!!  Please try again.\n\n";
            return;
        }
        if ($track{year} and $track{year} !~ /^\d{4}$/) {
            print "\n!!!  Year for track $tracknum must be a 4 digit number.\n!!!  Please try again.\n\n";
            return;
        }
    #Add it to the list
        push @tracks, [\%track];
    }
    if ($tracknum != $Album{Tracks}) {
        print "\n!!!  Editor returned a different number of tracks than were passed in.\n!!!  Please try again.\n\n";
        return;
    }
#Store the track information
    @Tracks = @tracks;
}

sub SaveID3Tags {
    my $file = shift;
    my $track = shift;
#Completely wipe out the old tags - v2.0 tags mess up MP3::Tag
    my $safe = ShellSafe("$Dir/$file->{name}");
    `id3v2 -D $safe`;
#open the mp3 and grab the tags
    my $mp3 = MP3::Tag->new("$Dir/$file->{name}");
    $mp3->get_tags;
#wipe out the existing ID3 tags
    $mp3->{ID3v1}->remove_tag if (exists $mp3->{ID3v1});
    $mp3->{ID3v2}->remove_tag if (exists $mp3->{ID3v2});
#Build a new ID3v1 tag
    $mp3->new_tag('ID3v1');
    $mp3->{ID3v1}->track($track->{number});
    $mp3->{ID3v1}->song(  fix_utf8($track->{title},                    1));
    $mp3->{ID3v1}->artist(fix_utf8($track->{artist} or $Album{Artist}, 1));
    $mp3->{ID3v1}->album( fix_utf8($track->{album}  or $Album{Title},  1));
    $mp3->{ID3v1}->genre( fix_utf8($track->{genre}  or $Album{Genre},  1));
    $mp3->{ID3v1}->year(  fix_utf8($track->{year}   or $Album{Year},   1)) if ($track->{year} or $Album{Year});
#Build a new ID3v2 tag
    $mp3->new_tag('ID3v2');
    $mp3->{ID3v2}->add_frame('TRCK', "$track->{number}/$Album{Tracks}");
    $mp3->{ID3v2}->add_frame('TIT2', fix_utf8($track->{title},                    1));
    $mp3->{ID3v2}->add_frame('TPE1', fix_utf8($track->{artist} or $Album{Artist}, 1));
    $mp3->{ID3v2}->add_frame('TALB', fix_utf8($track->{album}  or $Album{Title},  1));
    $mp3->{ID3v2}->add_frame('TCON', fix_utf8($track->{genre}  or $Album{Genre},  1));
    $mp3->{ID3v2}->add_frame('MCDI', fix_utf8($CDDB_ID,                           1));
    $mp3->{ID3v2}->add_frame('TYER', fix_utf8($track->{year}   or $Album{Year},   1)) if ($track->{year} or $Album{Year});
#Save the tags
    $mp3->{ID3v1}->write_tag;
    $mp3->{ID3v2}->write_tag;
    $mp3->close();
}

sub SaveOggTags {
    #cddb returns ISO-8859-1 encodings
    my $file = shift;
    my $track = shift;
    my $safe = ShellSafe("$Dir/$file->{name}");
    open(OGG, "| vorbiscomment -w $safe") or die "Can't open pipe to vorbiscomment:  $!\n\n";
    print OGG "TRACKNUMBER=$track->{number}/$Album{Tracks}\n",
              "TITLE=",  fix_utf8($track->{title}), "\n",
              'ARTIST=', fix_utf8($track->{artist} or $Album{Artist}), "\n",
              'ALBUM=',  fix_utf8($track->{album}  or $Album{Title}),  "\n",
              'GENRE=',  fix_utf8($track->{genre}  or $Album{Genre}),  "\n";
    print OGG 'DATE=',   fix_utf8($track->{year}   or $Album{Year}),   "\n" if ($track->{year} or $Album{Year});
    close OGG;
    #VERSION ORGANIZATION DESCRIPTION LOCATION COPYRIGHT ISRC
}

sub RenameFile {
    my $file = shift;
    my $track = shift;
    my $title = fix_utf8($track->{title});
    $title =~ s/(?<=\S)\s*\(.*?\)//sg;
    $title =~ s/\//_/sg;
    $title =~ s/^\s+//s;
    $title =~ s/\s+$//s;
# Get the current direectory and chdir to $Dir - renaming in UTF-8 doesn't work properly if we don't
    my $cwd = getcwd();
    chdir $Dir;
#File with this name already exists
    if (fix_utf8($file->{name}) ne "$track->{number}. $title.$file->{type}" and -e "$track->{number}. $title.$file->{type}") {
        my $answer;
        until ($answer and $answer =~ /^[yn]/i) {
            print "A File named \"$track->{number}. $title.$file->{type}\" already exists.\nOverwrite (Y/n}?  ";
            $answer = <>;
        }
        if ($answer =~ /^y/i) {
            rename $file->{name}, "$track->{number}. $title.$file->{type}" or die "Can't rename $Dir/$file->{name}:  $!\n\n";
            print "Renamed $Dir/$file->{name}\n    to: $Dir/$track->{number}. $title.$file->{type}\n";
        }
        else {
            print "Skipping rename of \"$file->{name}\"\n";
        }
    }
#Just rename the file, it's unique
    else {
        rename $file->{name}, "$track->{number}. $title.$file->{type}" or die "Can't rename $Dir/$file->{name}:  $!\n\n";
        unless ($file->{name} eq "$track->{number}. $title.$file->{type}") {
            print "Renamed $Dir/$file->{name}\n    to: $Dir/$track->{number}. $title.$file->{type}\n";
        }
    }
# chdir back to where we were
    chdir $cwd;
}

sub AddUnique {
    my $list = shift;
    my $value = shift;
    return unless ($value =~ /\S/);
    my $exists = 0;
    if ($list and @{$list}) {
        foreach my $item (@{$list}) {
            next unless ($item =~ /^$value$/i);
            $exists = 1;
        }
    }
    push @{$list}, $value unless ($exists);
}

sub Titlecase {
#In English the first letter of the first and last words should always be capitalized and
#the first letter of all intervening words should all be capitalized except for:
#  (i) prepositions having less than five letters, except "From", which should be capitalized
#    (e.g. "for", "in", "with" or "to", but "From" or "Under"),
#  (ii) conjunctions ("and", "or" and "but") and (ii) articles ("the", "a" and "an").
    my $val = shift;
    my $str = ref $val eq 'SCALAR' ? $val : \$val;
    return $$str unless ($$str);
    $$str =~ tr/A-Z/a-z/;
    $$str =~ s/(?<!['\xE1-\xF1])\b((?!(?:a[ns]?|and|for|in|of|on|the|to|with|del?|la|el)\b)\w)/\u$1/sgi;
    $$str =~ s/\((\w)/(\u$1/sgi;
    $$str =~ s/([\xC0-\xD6\xD8-\xDD\xE0-\xF6\xF9-\xFD][A-Z])/\L$1/sg;    #make sure that diacritical characters show up as "lowercase" and not auto-cap characters after them
    $$str =~ s/\b([ivx]+)\b/\U$1/sgi;
    $$str =~ s/([\xC0-\xD6\xD8-\xDD\xE0-\xF6\xF9-\xFD\w'\-]+)$/\u$1/sg;
    $$str =~ s/((?:^|[\/\.\!\?\;\:])\s*[\d\s\.\-]*\w)/\U$1/sg;
    $$str =~ s/(-\s+\w)/\U$1/sg;
    $$str =~ s/\b(\d+(?:st|nd|rd|th))\b/\L$1/sgi;
    $$str =~ s/('[st]\b)/\L$1/sgi;
# Fix some specific issues with musical names
    $$str =~ s/\bin\s+([A-G])\s+minor\b/in \u$1 minor/si;
    $$str =~ s/\bin\s+A\s+major\b/in A Major/si;
# Fix some Irish/Gaelic word capitalization
    $$str =~ s/\b(Ma?c(?=(?!hine)[^aeiouy][aeiou]|[aeiou][^aeiouy]|[^aeiouy]r)|O\'(?=[^aeiouy]))(\w)/$1\u$2/sgi;
    $$str =~ s/(\s)ng/$1nG/sgi;
    $$str =~ s/(\s)gc/$1gC/sgi;
#Return
    return $$str;
}

sub fix_utf8 {
    my $val  = shift;
    my $str  = ref $val ? $val : \$val;
    my $undo = shift;
# Return Early?
    return '' unless ($$str and length($$str));
# Get a temp var so we don't actually modify $$str
    my $tmp = $$str;
# Decode the string to UTF-8 and check for malformed characters - if there are some, this isn't already UTF-8
    Encode::_utf8_on($tmp);
    my $is_utf8 = Encode::is_utf8($tmp, Encode::FB_QUIET);
# Undoing utf-8?
    if ($Args{undo} || $undo) {
    # Malformed utf-8 characters, this is probably NOT utf-8
        return $$str if (!$is_utf8);
    # Now we convert back to iso-8859-1
        Encode::from_to($$str, 'utf-8', 'iso-8859-1');
        return $$str;
    }
# No malformed characters - this is already UTF-8 - convert it back to latin1 check again to make sure that it's encoded properly
    if ($is_utf8) {
        Encode::from_to($$str, 'utf-8', 'iso-8859-1');
    # Check again to see if it wasn't just a malformed string
        $tmp = $$str;
        Encode::_utf8_on($tmp);
        $is_utf8 = Encode::is_utf8($tmp, Encode::FB_QUIET);
        if ($is_utf8) {
            Encode::from_to($$str, 'utf-8', 'iso-8859-1');
        }
    }
# Now we decode from iso-8859-1
    Encode::from_to($$str, 'iso-8859-1', 'utf-8');
    return $$str;
}

sub ShowHelp {
    print <<EOF;
Usage: cddbmp3.pl [options] directory

Options:

  -u  --url       Print out a URL that will perform the cddb lookup.
  -n  --nolookup  Do not query cddb server or alter the files.
  -h  --host      Alternate cddb hostname (default:  freedb)
  -p  --port      Alternate cddb port (default:  888)
  -h  --help      Display this help screen.

  -d  --dumb      Don't try to guess artist/title information
  -r  --reverse   Reverses artist/album order guesses

EOF
    exit(0);
}

sub ShellSafe {
    my $str = (shift or '');
    $str =~ s/"/\\"/sg;
    return "\"$str\"";
}

sub byTrackNum {
    my ($anum) = $a =~ /^\s*(\d\d?)\b/;
        ($anum) = $a =~ /\s*-\s*\b(\d\d?)\s*-\s*\b/    unless ($anum);
        ($anum) = $a =~ /\b(\d\d?)\s*-\s*\b/        unless ($anum);
        ($anum) = $a =~ /\b(\d\d?)\W*(?:\.\w+)?$/    unless ($anum);
    my ($bnum) = $b =~ /^\s*(\d\d?)\b/;
        ($bnum) = $b =~ /\s*-\s*\b(\d\d?)\s*-\s*\b/    unless ($bnum);
        ($bnum) = $b =~ /\b(\d\d?)\s*-\s*\b/        unless ($bnum);
        ($bnum) = $b =~ /\b(\d\d?)\W*(?:\.\w+)?$/    unless ($bnum);
    if ($anum and $bnum) {
        $anum <=> $bnum;
    }
    else {
        $a cmp $b;
    }
}
