#!/usr/bin/perl -w
use strict;
use warnings;

# Munge an MP3 file into a nice directory structure, derived from MP3 tags
# Actual file format comes from ~/.griprc, if present, or defaults to
#   Artist/Album/Tracknum - Title.mp3
# Also fix up mp3 tags where possible, using CDDB and file location and
#   anything else that seems plausible.
# Waider May 2000
#
# 15/05/2003 Removed dependency on id3convert
# 25/11/2003 Added hooks to XMMS via external program
# early 2011 Added some support for refiling into iTunes-friendly shape
#
# Search on freedb for artist/album:
# http://www.freedb.org/freedb_search.php?words=<searchwords>&allfields=NO&fields=artist&fields=title&allcats=YES&grouping=none
# fields can also have: track (track name), rest (full-text search over the rest of the data)
#
# FreeDB HTTP Query Format: (when you know the genre and the ID)
# Note, there's another interface that only appears to work directly
# on www.freedb.org and not any of the mirrors.
# http://www.freedb.org/~cddb/cddb.cgi?cmd=cddb+read+rock+12345678&hello=joe+my.host.com+xmcd+2.1&proto=1
#
# FIXMEs:
# Should be more aware of directory trees - use File::Find?
# Store old filename in COMMENT field?
# smarter about finding files when we know the album
# what about files with multiple matches?
# what about files with > 1 page of matches?
# unknown album & unknown artist should probably do an exact title match
# option to do exact or fuzzy search
# proper fuzzy search?
# clean up code!
# Various / Various Artists handling
# Provide track number on command line.
# If filename = title, be smarter about retaining rather than truncating
#  (not an issue if I hurry up and do ID3v2.3 tagging)
# Pull discid file if present, then find cddb file or look up CDDB and parse.
# Create new discid file if not present.
# Move discid files around or delete them as necessary
#
# Needs more work:
# Duplicate/Namespace collision should ignore MP3 tags
#   (need to extract raw mp3 data, I guess)
# -> this is working now, but using external programs to do the
#    stripping and comparison (id3convert, from id3lib, and cmp).
#    Comparison can be done just fine in Perl, but the stripping
#    really requires a smarter MP3Info module.
#
# match track numbers where available
# -> yes, but what if the track number is wrong, ted?
#
# Broken as designed:
# why can't I use CDDB.pm?
# -> because cddb.com's HTML ID's != cddb id's. losers.
# -> HOWEVER. We could fake up a CDDB id using jwz's code, then look up
#    based on that. Chances are it should be pretty close to the real ID.
#
# Remove any extraneous info like older ID tags
# -> for now, could simply discard v2.2 info since it screws up XMMS'
#    notion of the display name; XMMS uses its own ID3 parser
#    (hello! id3lib!) which can't hack v2.3 tags.
# Hacking on MPEG::MP3Info has helped this.
use MP3::Info;
use MP3::ID3Lib;
use File::Basename;
use LWP::UserAgent;
use URI::Escape;
use File::Find;
use File::Copy;
use POSIX;
use Getopt::Long;
use CDDB;
use Data::Dumper;
use File::Basename;
use File::Path;
use File::Spec;
use Encode qw(:all);

my $cddb = 0;                   # by default, don't use CDDB.
my $dryrun = 1;                 # don't REALLY move stuff
my $recursive = 0;              # skip the find
my $verbose = 0;                # be kinda quiet
my $mp3root = "$ENV{'HOME'}/lib/mp3";
# http://nostatic.org/grip/doc/ar01s04.html#gripswitches
my $mp3fileformat = "%a/%d/%t - %n%x";
my $no_lower_case = 1;
my $no_underscore = 1;
my $allow_these_chars = "";
my $frobcase = 0;
my $force = 0;                # rename, even if there are missing data
my $debug = 0;
my $itunes = 1;

# Read .grip file if it's present
if ( open( GRIPRC, "$ENV{'HOME'}/.grip" )) {
  griprc:
    while (<GRIPRC> ) {
        # things we're interested in, which I could probably parse with an eval.
        # mp3fileformat
        if ( /^mp3fileformat (.*)$/) {
            $mp3fileformat = $1;
            next griprc;
        }

        # no_lower_case
        if ( /^no_lower_case (.*)$/) {
            $no_lower_case = $1;
            next griprc;
        }

        # no_underscore
        if ( /^no_underscore (.*)$/) {
            $no_underscore = $1;
            next griprc;
        }

        # allow_these_chars
        if ( /^allow_these_chars (.*)$/) {
            $allow_these_chars = $1;
            next griprc;
        }
    }
    close( GRIPRC );
}

my $doxmms = 0;
my $swap = 0;

my ( $artist_override, $album_override, $title_override, $tracknum_override, $mp3root_override );

GetOptions( "cddb!" => \$cddb,
            "dry-run!"       => \$dryrun,
            "dryrun!"        => \$dryrun,
            "force!"         => \$force,
            "recursive!"   => \$recursive,
            "verbose!" => \$verbose,
            "artist:s" => \$artist_override,
            "album:s" => \$album_override,
            "title:s" => \$title_override,
            "tracknum:s" => \$tracknum_override,
            "swap!" => \$swap,
            # Grip stuff
            "mp3fileformat:s" => \$mp3fileformat,
            "no_lower_case!" => \$no_lower_case,
            "no_underscore!" => \$no_underscore,
            "allow_these_chars:s" => \$allow_these_chars,
            "xmms!" => \$doxmms,
            "debug!" => \$debug,
            "itunes!" => \$itunes,
            "mp3root=s" => \$mp3root_override,
          ) or die;

# Refigure mp3root - it's the deepest we can get without hitting a "%" name.
# I'm just too fucking clever by far.
if ( $mp3root_override ) {
    $mp3root = $mp3root_override;
} else {
    ( $mp3root ) = $mp3fileformat =~ m|^(.*?)/[^/]*%|;
}

# Allow these chars:
$allow_these_chars = "[^A-Za-z0-9" . quotemeta( $allow_these_chars );
$allow_these_chars .= " " if $no_underscore;
$allow_these_chars .= ""; # lowercase
$allow_these_chars .= "ŁāÁɁȁʁˁρ́΁́ҁԁՁցЁӁفځہ܁сƁǁ؁ށ݁"; # uppercase

$allow_these_chars .= "_";

# itunes
if ( $itunes ) {
    $allow_these_chars .= "()':\\-&,\\[\\]\\?\\/\\!\"\\.\@\\*\%\#\$;^\\+";
    $mp3fileformat = File::Spec->join( $mp3root, "%a/%d/%t %n%x" );
}

$mp3fileformat =~ s/%(.)/%{$1}/g;

$allow_these_chars .= "]";

# cache area for Web-retrieved CDDB listings
! -d "$ENV{'HOME'}/.cddb-web" && mkdir "$ENV{'HOME'}/.cddb-web";

# Piping hot
$| = 1;

if ( $#ARGV == -1 ) {
    die "Usage: $0 mp3file(s)\nStopped";
}

if ( $recursive ) {
    $#ARGV = -1;                # clear all the args.

    # now load up ARGV with all the files we can find from here.
    find( { wanted =>
            sub { push @ARGV, $File::Find::name if !-d $File::Find::name; },
            no_chdir => 1 }, '.');
}

my ( $cddb_file );

for my $mp3file ( @ARGV ) {
    my ( $artist, $album, $title, $tracknum, $genre, $year, $comment, $title_with_tracknum );

    print "Looking at $mp3file\n" if $debug;
    # If we've been passed a directory, go dig into it. Screw the recursive
    # flag, really.
    if ( -d $mp3file ) {
        print "$mp3file is a directory, scanning it...\n" if $verbose;
        find( { wanted =>
                sub { push @ARGV, $File::Find::name if !-d $File::Find::name; },
                no_chdir => 1 }, $mp3file);
        next;          # so we don't end up processing the damn thing!
    }
    $mp3file =~ s|^\./||;

    next if !-f $mp3file;       # because sometimes we nuke 'em.

    my $did = dirname( $mp3file ) . "/discid";

    if ( -f $did or $mp3file =~ m|/discid$| ) { # because it's special
        if ( open( DISCID, "$did" )) {
            my $discid = <DISCID>;
            chomp( $discid );
            if ( -f "$ENV{HOME}/.cddb/$discid" ) {
                if ( open( CDDB, "$ENV{HOME}/.cddb/$discid" )) {
                    my @lines = <CDDB>;
                    my $disc_details = CDDB::parse_xmcd_file( \@lines );
                    ( $artist, $album ) = $disc_details->{dtitle} =~
                      /^(.*) \/ (.*)$/;
                }
            }
        } else {
            warn "failed to open $mp3file: $!";
        }
    }

    print "Processing $mp3file\n" if $verbose;

    # Hum. Let's see what's in there:
    my $tag = get_mp3tag( $mp3file );

    # what tag version?
    if ( defined( $tag->{'TAGVERSION'})) {
        print " MP3 tags v ". $tag->{'TAGVERSION'} . "\n" if $verbose;

        # id3lib doesn't pull the TCMP frame because it's empty?
        $tag->{TCMP} = get_tcmp( $mp3file );

        my $id3 = new MP3::ID3Lib( $mp3file );
        foreach my $frame (@{$id3->frames}) {
            my $code = $frame->code;
            my $description = $frame->description;
            my $value = $frame->value;

            if ( $verbose ) {
                print "  $code / $description: $value\n";
            }

            if ( $code eq 'TRCK' ) {
                $tag->{TRACKNUM} = $value;
                if ( $tag->{TRACKNUM} =~ s@/(\d+)@@ ) {
                    $tag->{TRACKMAX} = $1;
                }
            }

            if ( $code eq 'TPOS' ) {
                $tag->{TPA} = $value;
            }
        }
    } else {
        print " No MP3 tags found\n";
    }

    # Play with the file's extension
    my ( $oldname, $ext ) = $mp3file =~ m|^(.*)(\.[^./]+)$|;
    $oldname ||= $mp3file;      # if there's no extension...
    $ext ||= ".mp3";
    $ext = lc( $ext );

    my $type = get_file_type( $mp3file );
    if ( $type eq "audio/x-aiff" ) {
        $ext = '.aiff';
    } elsif ( $type eq "audio/x-wav" ) {
        $ext = '.wav';
    } elsif ( $type eq "audio/mp4" ) {
        $ext = '.m4a';
    } elsif ( $type eq "audio/mpeg" ) {
        $ext = ".mp3" unless $ext =~ /\.mp[234]/; # XXX
    } else {
        warn "unknown type $type for $mp3file\n";
        $ext = ".mp3" unless $ext =~ /\.mp[234]/; # XXX
    }

    # Successive sources of artist
    if ( $artist_override ) {
        $artist = $artist_override;
    }
    $artist ||= $tag->{'ARTIST'} || "";
    if ( defined( $artist ) and ref $artist ) {
        $artist = $tag->{ARTIST}->[-1];
    }
    $artist ||= "Unknown_Artist";

    # Album name
    $album = $album_override;
    $album ||= $tag->{'ALBUM'} || "";
    if ( defined $album and ref $album ) {
        $album = $tag->{ALBUM}->[-1];
    }

    if (!$album or ( $album eq "Unknown_Album" )) {
        # try and figure out from where we are
        my (undef, $dirs, undef ) = fileparse( $mp3file, '\.[^.\/]*' );
        my @dirs = reverse split( "/", $dirs );
        $album = shift @dirs;
        $album = "" if $album eq "."; # file is in cwd

        $tag->{'ALBUM'} = $album;

        if ( $artist eq "Unknown_Artist" ) {
            $artist = shift @dirs;
            $tag->{'ARTIST'} = $artist;
            $artist ||= "";
            $artist =~ s/_/ /g;
        }
    }

    $album ||= "Unknown_Album";

    # hackety hack
    $artist =~ s/_/ /g;
    $album =~ s/_/ /g;

    # Get the title
    $title = $title_override;
    $title ||= $tag->{'TITLE'} || "";
    if ( defined $title and  ref $title ) {
        $title = $tag->{TITLE}->[-1];
    }
    $title ||= basename( $oldname );

    print "  We're starting with a title of $title\n" if $verbose;

    # Harsh, but workable
    if ( $tag->{TAGVERSION} and $tag->{TAGVERSION} !~ /v2\./ ) { # ugly
        if ( not $title_override and length( $title ) == 30 and
             length( basename( $oldname )) > length( basename( $title ))) {
            print "  Using filename because it looks like your tag is truncated\n"
              if $verbose;
            $title = basename( $oldname );
            if ( $artist and $title =~ /^$artist\b/i ) {
                print "  Removing artist from filename\n" if $verbose;
                $title =~ s/^$artist\b//i;
            }
        }
    }

    # Do some cleanup, maybe
    my ( undef, $mp3title ) = $title =~ m|^\s*(\d{1-3})[\-\.\_ ]+([^0-9]+?)|;
    if ( defined( $mp3title ) and $mp3title and ( $mp3title ne $title )) {
        $mp3title =~ s/_/ /g;   # taking liberties XXX only if requested?
        $tag->{'TITLE'} = $mp3title;
    }

    # See if we have a track number:
    $tracknum = $tracknum_override;
    # Obvious place
    $tracknum ||= $tag->{'TRACKNUM'};
    if ( ref $tracknum ) {
        $tracknum = $tag->{TRACKNUM}->[-1];
    }

    # Title contains tracknum?
    if ( !defined( $tracknum ) or $tracknum !~ /^\s*\d+\s*$/ ) {
        ( $tracknum, undef ) = $title =~ m|^\s*(\d{1,3})[\-\.\_ ]+([^0-9,]+?)|;
        if ( !$tracknum ) {
            undef( $tracknum );
        } else {
            print "  Found tracknum $tracknum in title\n" if $verbose and
              defined( $tracknum );
            $title =~ s/^\s*(\d{1,3})[\-\.\_ ]+//;
        }
    } elsif ( defined( $tracknum )) {
        if ( !defined( $title_override ) and
             $title =~ s/^[-_\. ]*0?$tracknum[-_\. ]+// ) {
            print "  Removing track number from title\n" if $verbose;
        }
    }

    # Or maybe the filename?
    if ( !defined( $tracknum ) or $tracknum !~ /^\s*\d+\s*$/ ) {
        ( $tracknum, undef ) =
          basename( $oldname ) =~ m|^\s*(\d{1,3})[\-\.\_ ]+([^0-9,]+?)|;

        # waitasec
        if ( !$tracknum ) {
            undef( $tracknum );
        } else {
            print "  Found tracknum $tracknum in filename\n" if $verbose and
              defined( $tracknum );
        }
    }

    # What about having it stuffed into the comment?
    if ( !defined( $tracknum ) or $tracknum !~ /^\s*\d+\s*$/ ) {
        $tracknum = $tag->{'COMMENT'};
        $tracknum ||= "";
        if ( ref $tracknum ) {
            $tracknum = $tag->{COMMENT}->[-1];
        }
        $tracknum =~ s|^\s*(\d{1,3})[\-\.\_ ]+([^0-9,]+?)|$1|;
    }

    # Well, then!
    if ( !defined( $tracknum ) or $tracknum !~ /^\s*\d+\s*$/ ) {
        # Bugger that, then. FIXME CDDB can help! CDDB is definitive, I guess.
        $tracknum = 0;
    }

    # Do a CDDB lookup
    if ( $cddb ) {
        $cddb_file =
          ask_cddb( $tag->{'ARTIST'}||"", $album, $title, $tracknum );
    } else {
        $cddb_file = undef;
    }

    $title ||= $oldname;        # fallback
    $title =~ s/ +/ /;          # hack
    # still taking liberties
    $title =~ s/_/ /g unless $itunes; # xxx

    # and now
    if ( $frobcase ) {
        $title =~ s/\b(\w)(\w+)\b/\U$1\E$2/g;
        $artist =~ s/\b(\w)(\w+)\b/\U$1\E$2/g;
        $album =~ s/\b(\w)(\w+)\b/\U$1\E$2/g;
    }

    print "  After all that, the title is: $title\n" if $verbose;

    # finally, check for swaps...
    if ( $swap ) {
        my $tmp = $artist;
        $artist = $title;
        $title = $tmp;
    }

    # Fix the tagging
    $year = $tag->{'YEAR'} || "";
    if ( defined( $year ) and ref $year ) {
        $year = $tag->{YEAR}->[-1];
    }
    $comment = "";              # screw comments
    $genre = $tag->{'GENRE'} || "";
    if ( defined( $genre ) and ref $genre ) {
        $genre = $tag->{GENRE}->[-1];
    }
    $tracknum =~ s/ \- //;
    if ( $tracknum !~ /^\d+$/ ||
         (( $tracknum < 1 ) || ( $tracknum > 255 ))) {
        $tracknum = "";
    }

    if ( ! $dryrun and $mp3file !~ m|/discid$| and !$itunes ) {
        remove_mp3tag( $mp3file, 'ALL' );

        my $id3 = new MP3::ID3Lib( $mp3file );
        $id3->add_frame( "TIT2", $title ) if $title;
        $id3->add_frame( "TPE1", $artist ) if $artist;
        $id3->add_frame( "TALB", $album ) if $album;
        $id3->add_frame( "TYER", $year ) if $year;
        $id3->add_frame( "COMM", $comment ) if $comment;
        $id3->add_frame( "TCON", $genre ) if $genre;

        my $trck = $tracknum;
        if ( defined( $tracknum ) && defined( $tag->{TRACKMAX} )) {
            $trck = $tracknum . "/" . $tag->{TRACKMAX};
        }


        $id3->add_frame( "TRCK", $trck ) if defined( $trck );
        $id3->commit();
    } elsif ( $itunes ) {
        print "  iTunes mode: not retagging\n" if $verbose;
    }

    if ( $verbose ) {
        print "  $title ($artist/$album)\n";
    }

    # tweak for filesystem
    $artist =~ s/$allow_these_chars//ig;
    $title =~ s/$allow_these_chars//ig;
    $album  =~ s/$allow_these_chars//ig;

    # cleanup
    if ( $mp3file =~ m|/discid$| ) {
        $title = "discid";
        $ext = "";
    } else {
        if ( !$force and ( !$artist or !$album or !$title or !$tracknum )) {
            print "  Can't rename $mp3file\n";
            print "  - missing artist\n" if !$artist;
            print "  - missing title\n" if !$title;
            print "  - missing album\n" if !$album;
            print "  - missing tracknum\n" if !$tracknum;
            next;
        }
    }

    $tracknum = sprintf( "%02d", $tracknum ) if $tracknum =~ /^\d+$/;
    $artist =~ s/^$/Unknown Artist/g;
    $album =~ s/^$/Unknown Album/g;
    $title =~ s/^$/Unknown Track/g;

    # tweak tracknum to include disc if $tag->{TPOS} / $tag->{TPA}
    if ( $tag->{TPA} and $tag->{TPA} ne "1/1" ) {
        my ( $tpos ) = $tag->{TPA} =~ /^(\d+)/;
        $tracknum = $tpos . "-$tracknum";
    }

    $title_with_tracknum = $title;
    if ( $tracknum =~ /^\d+$/ && $tracknum > 0 ) {
        $title_with_tracknum = sprintf( "%02d - %s", $tracknum, $title );
    }

    # ok, let's make this iso8859-1 (finally finally)
    if ( $itunes ) {
        from_to( $artist, "iso-8859-1", "unicode" );
        from_to( $album, "iso-8859-1", "unicode" );
        from_to( $title, "iso-8859-1", "unicode" );
    } else {
        utf8::downgrade( $artist );
        utf8::downgrade( $album );
        utf8::downgrade( $title );
    }

    # feck
    if ( $itunes ) {
        $artist =~ s@/@_@g;
        $album =~ s@/@_@g;
        $title =~ s@/@_@g;
        $title_with_tracknum =~ s@/@_@g;
    }

    if ( $tag->{TCMP} and $itunes ) {
        print "   part of a compilation\n" if $verbose;
        $artist = 'Compilations'; # xxx
    }

    # use mp3fileformat
    my $newname = $mp3fileformat;
    $newname =~ s/%{a}/$artist/g;
    $newname =~ s/%{d}/$album/g;
    $newname =~ s/%{t}/$tracknum/g;
    $newname =~ s/%{x}/$ext/g;

    my $newname_with_tracknum = $newname;

    $newname =~ s/%{n}/$title/g;
    $newname_with_tracknum =~ s/%{n}/$title_with_tracknum/g;

    if ( $itunes ) {
        $newname =~ s/[\*":\?]/_/g;
        $newname_with_tracknum =~ s/[\*":\?]/_/g;

        # WEIRD.
        $newname =~ s@(^|/)\.@/_@g;
        $newname_with_tracknum =~ s@\./@_/@g;

        $newname =~ s@/(^|/)\.@/_@g;
        $newname_with_tracknum =~ s@\./@_/@g;
    }

    if (($mp3file ne $newname_with_tracknum ) and ( $newname ne $newname_with_tracknum ) and -f "$newname_with_tracknum"
        and compare_mp3_files( $mp3file, $newname_with_tracknum )) {

        my @file1 = stat( $mp3file );
        my @file2 = stat( $newname_with_tracknum );
        if ( $file1[0] != $file2[0] or $file1[1] != $file2[1] ) {
            print "(1) Identical files, nuking second one:\n> $mp3file\n> $newname_with_tracknum\n";
            #unlink "$newname_with_tracknum" unless $dryrun;
        }
    }

    # Check new name against existing files AND old name
    if ( -f "$newname" ) {
        if (( $mp3file eq "$newname" ) ||
            ( $mp3file eq "./$newname" )) {
            # nothin' doin'
            print "  File is already in place\n" if $verbose;
        } else {
            # Namespace collision. Check if the files are identical.
            if ( compare_mp3_files( $mp3file, $newname )) {
                my @file1 = stat( $mp3file );
                my @file2 = stat( $newname );
                if ( $file1[0] != $file2[0] or $file1[1] != $file2[1] ) {
                    print "(2) Identical files, nuking first one:\n> $mp3file\n> $newname\n";
                    #unlink "$mp3file" unless $dryrun;
                    # also, see if the old directory is empty, and clean it up if it is
                    my ( $pdir ) = $mp3file =~ m|^(.*)/[^/]+$|;
                    if ( defined( $pdir )) {
                        rmdir $pdir;
                        ( $pdir ) = $pdir =~ m|^(.*)/[^/]+$|;
                        if ( defined( $pdir )) {
                            rmdir $pdir if defined( $pdir );
                        }
                    }
                } else {
                    print "  File is already in place at $newname\n" if $verbose;
                }
            } else {
                print "Namespace collision:\n> $mp3file\n> $newname\n";
                # check file quality
                my $info1 = get_mp3info( $mp3file );
                my $info2 = get_mp3info( $newname );
                if ( defined( $info1 ) and defined( $info2 )) {
                    printf( "$mp3file: %dKHz %d bits %ds\n",
                            $info1->{FREQUENCY}, $info1->{BITRATE},
                            $info1->{SECS} );
                    printf( "$newname: %dKHz %d bits %ds\n",
                            $info2->{FREQUENCY}, $info2->{BITRATE},
                            $info2->{SECS} );
                }
            }
        }
    } else {
        print "$mp3file -> $newname\n";
        if ( !$dryrun ) {
            mkpath( [ dirname $newname] , 0, 0755 );
            if ( move( "$mp3file", "$newname" )) {
                # Clean up permissions, since sometimes they're bogued out
                chmod 0644, "$newname";

                # Tell XMMS what we've done, if need be
                my ( $old, $new ) = ( $mp3file, $newname );
                $old = getcwd() . "/$old" unless $old =~ m@^/@;
                $new = getcwd() . "/$new" unless $new =~ m@^/@;
                system( "xmms-frob.pl", "-start", "-mode=rename", $old, $new )
                  if $doxmms;

                # also, see if the old directory is empty, and clean it up if it is
                my ( $pdir ) = $mp3file =~ m|^(.*)/[^/]+$|;
                if ( defined( $pdir )) {
                    rmdir $pdir;
                    ( $pdir ) = $pdir =~ m|^(.*)/[^/]+$|;
                    if ( defined( $pdir )) {
                        rmdir $pdir if defined( $pdir );
                    }
                }
            } else {
                print "$mp3file: rename failed: $!\n";
            }
        }
    }

    print "\n" if $verbose;
}

# Jan 2003: Complete rewrite to use FreeDB instead. Go FreeDB!
sub ask_cddb {
    my ( $tmpartist, $tmpalbum, $tmptitle, $tmptracknum ) = @_;
    my ( @results );
    my %diskinfo;
    my @qbits;

    $tmpartist =~ s/_/ /g;
    $tmpalbum =~ s/_/ /g;

    if ( $tmpartist eq "Unknown Artist" ) {
        $tmpartist = "";
    } else {
        push @qbits, $tmpartist;
    }

    if ( $tmpalbum eq "Unknown Album" ) {
        $tmpalbum = "";
    } else {
        push @qbits, $tmpalbum;
    }

    $tmptitle =~ s/_/ /g;       # what fules you are.
    push @qbits, $tmptitle;

    my $querystring = join( " / ", @qbits );

    $querystring =~ s/\(/%28/g;
    $querystring =~ s/\)/%29/g;
    $querystring =~ uri_escape( $querystring );

    $querystring .= "&field=artist" if $tmpartist;
    $querystring .= "&field=title" if $tmpalbum;
    $querystring .= "&field=track" if $tmptitle;

    my $ua = new LWP::UserAgent;
    $ua->env_proxy();

    my $req = new HTTP::Request
      GET => "http://www.freedb.org/freedb_search.php?words=$querystring";

    print "Asking FreeDB about $querystring: " if $verbose;
    my $res = $ua->request( $req );
    if ( $res->is_success ) {
        my $content = $res->content;

        print $content;

        die;
    } else {
        print "HTTP error.\nanalyze this:\n";
        print "-" x 79, "\n", $res->content, "\n", "-" x 79, "\n";
    }

    return $results[ 0 ];
}

# Horrifically compare the actual mp3 data in two mp3 files.
# by side effect, use far too much disk space.
sub compare_mp3_files {
    my ( $file1, $file2 ) = @_;
    my ( $name1, $name2 ) = ( "tmp1", "tmp2" );# xxx generate

    # 1. duplicate both files
    $debug and print STDERR "copying $name1\n";
    copy( $file1, "/tmp/$name1" ) || return 0;
    $debug and print STDERR "copying $name2\n";
    copy( $file2, "/tmp/$name2" ) || return 0;

    # 2. Strip any mp3 info out of both files
    for my $file ( $name1, $name2 ) {
        remove_mp3tag( "/tmp/$file", 'ALL' );
    }

    # 3. Compare files
    $debug and print STDERR "comparing $name1 and $name2\n";
    my $result = `cmp "/tmp/$name1" "/tmp/$name2"`;
    $debug and print STDERR $result . "\n";

    # 4. Clean up.
    !$debug and unlink( "/tmp/$name1" );
    !$debug and unlink( "/tmp/$name2" );

    return $result eq "";
}

# Remove leading & trailing space from a string.
sub trim {
    my ( $string ) = @_;
    $string =~ s/^\s+//;
    $string =~ s/\s+$//;

    $string;
}

# the following chunk of code comes more or less verbatim from
#
# Gronk, Copyright (c) 2000 by Jamie Zawinski <jwz@jwz.org>
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation.  No representations are made about the suitability of this
# software for any purpose.  It is provided "as is" without express or
# implied warranty.

sub capitalize {
    my ($s) = @_;
    $s =~ s/_/ /g;
    # capitalize words, from the perl faq...
    $s =~ s/((^\w)|(\s\w))/\U$1/xg;
    $s =~ s/([\w\']+)/\u\L$1/g;
    return $s;
}

sub cddb_sum {
    # a number like 2344 becomes 2+3+4+4 (13).
    my ($n) = @_;
    my $ret = 0;
    while ($n > 0) {
        $ret += ($n % 10);
        $n /= 10;
    }
    return $ret;
}


sub compute_discid {
    my @frames = @_;

    my $tracks = $#frames + 1;
    my $n = 0;

    my @start_secs;
    my $i;

    for ($i = 0; $i < $tracks; $i++) {
        $start_secs[$i] = POSIX::floor ($frames[$i] / 75);
    }

    for ($i = 0; $i < $tracks-1; $i++) {
        $n = $n + cddb_sum ($start_secs[$i]);
    }

    my $t = $start_secs[$tracks-1] - $start_secs[0];

    my $id = ((($n % 0xFF) << 24) | ($t << 8) | $tracks-1);
    return sprintf ("%08x", $id);
}

sub handle_directory {
    my ( $artist, $album, $genre, @songs ) = @_;

    my @frames;
    my $leader = 150;           # typical leader value

    my $i = 0;
    foreach my $song (@songs) {
        #   $secs[ $i ] = 0;
        $frames[ $i ] = 0;
        #    $secs[$i] = `mp3info -f '%S' $dir/$song`;
        #    $frames[$i] = POSIX::ceil (($secs[$i] + 0.5) * 75);
        $i++;
    }

    print "# xmcd CD database file generated by mp3name.pl\n";
    print "# \n";
    print "# Track frame offsets:\n";

    my $total = $leader;
    for ($i = 0; $i <= $#songs; $i++) {
        print "#       $total\n";
        $total += $frames[$i];
    }

    print "# \n";
    print "# Disc length: " . POSIX::floor($total / 75) . " seconds\n";
    print "# \n";
    print "# Revision: 3\n";
    print "# Submitted via: mp3name\n";
    print "# \n";
    print "# WARNING: These track offsets and this discid are fiction.\n";
    print "#          These numbers are a guess, based on existing MP3\n";
    print "#          files, not based on data from an actual Compact Disc.\n";
    print "# \n";
    print "# \n";

    print "DISCID=";
    print compute_discid ($leader, @frames);
    print "\n";

    #  screw capitalisation. if cddb fucks up, fine.
    #  if ($dir =~ m@([^/]+)/([^/]+)$@) {
    #    $artist = capitalize($1);
    #    $album = capitalize($2);
    #  } elsif ($dir =~ m@([^/]+)$@) {
    #    $album = capitalize($1);
    #  }

    print "DTITLE=$artist / $album\n";
    print "DGENRE=$genre\n";

    for ($i = 0; $i <= $#songs; $i++) {
        print "TTITLE$i=";
        $_ = $songs[$i];
        $_ = capitalize($_);

        print "$_\n";
    }
    print "EXTD=\n";
    for ($i = 0; $i <= $#songs; $i++) {
        print "EXTT$i=\n";
    }
    print "PLAYORDER=\n";
}

sub get_file_type {
    my $mp3file = shift;

    print "   getting filetype..." if $verbose;

    my $pid = open( my $FILE, "-|" );
    die $! if !defined( $pid );
    if ( !$pid ) {
        open STDERR, ">&", STDOUT;
        exec( "/usr/bin/file", "-b", "-I", $mp3file );
    }
    my $type = <$FILE>;
    waitpid( $pid, 0 );
    close( $FILE );

    $type =~ s/; charset=[^ ]+//;

    $type = trim( $type );

    print "$type\n" if $verbose;

    $type;
}

sub get_tcmp {
    my $mp3file = shift;

    print "   getting TCMP..." if $verbose;
    my $pid = open( my $FILE, "-|" );
    die $! if !defined( $pid );
    if ( !$pid ) {
        open STDERR, ">&", STDOUT;
        exec( "/sw/bin/id3info", $mp3file );
    }
    my $tcmp = grep /=== TCM?P\b/, <$FILE>;
    waitpid( $pid, 0 );
    close( $FILE );

    print $tcmp . "\n" if $verbose;

    $tcmp;
}
