#!/usr/bin/perl
#
# Author: jordan ritter <jpr5@darkridge.com>
# I borrowed a bunch of stuff from gnupod-utils to make this.
#

$|++;

use POSIX;
use Getopt::Std;

my ($MAGIC_HEADER) = "6d 68 62 64 68 00 00 00";
my ($MAGIC_MHIT)   = "6d 68 69 74";
my ($MAGIC_MHOD)   = "6d 68 6f 64";
my ($MHIT_START)   = 292;

my ($CP)      = "/bin/cp";
my ($SRC)     = "/mnt/ipod";
my ($DEST)    = "/proj/mp3";
my ($PATTERN) = "";
my ($DEBUG)   = 0;

my (@SONGS);
my ($DBFILE, $DB);

############
### MAIN ###
############

my(%opts);
getopts("hvs:d:p:", \%opts);

usage()               if (defined $opts{"h"});
$DEBUG++              if (defined $opts{"v"});
$SRC     = $opts{"s"} if (defined $opts{"s"});
$DEST    = $opts{"d"} if (defined $opts{"d"});
$PATTERN = $opts{"p"} if (defined $opts{"p"});


print "$0: starting\n";

read_db();
pare_list();
copy_files();

print "$0: exiting\n";
exit(0);

#################
### FUNCTIONS ###
#################

sub usage {
    print "$0 [options]\n";
    print "\t-v\tbe verbose\n";
    print "\t-s\tspecify source (ipod mount)\n";
    print "\t-d\tspecify destination (place to put mp3s)\n";
    print "\t-p\tspecify pattern to match against filename\n";
    exit(1);
}

#
# Copy the music from the iPod to disk!
#
# FIXME: should instead use fdesc or something to infer extension, maybe.
#
sub copy_files {

    foreach $SONG (@SONGS) {

        my ($TARGDIR)  = "$DEST/" . $SONG->{"artist"};
        if (! -d $TARGDIR) {
            if (!mkdir($TARGDIR, 0755)) {
                print "$0: mkdir $TARGDIR: $!\n";
                next;
            }
        }

        my ($SRCFILE)  = "$SRC" . $SONG->{"path"};
        my ($TARGFILE) = "$TARGDIR/" . $SONG->{"artist"} . "_" . $SONG->{"album"} . "_" . $SONG->{"title"} . "." . $SONG->{"extension"};

        print "$SRCFILE -> $TARGFILE\n" if ($DEBUG);

        system($CP, ("-v", $SRCFILE, $TARGFILE));

    }

}


#
# Match the pattern against any of (artist, title, album).
#
sub pare_list {
    my (@KEEP);

    foreach $SONG (@SONGS) {
        next if ($SONG->{"title"}  !~ /$PATTERN/i &&
                 $SONG->{"album"}  !~ /$PATTERN/i &&
                 $SONG->{"artist"} !~ /$PATTERN/i);

        push @KEEP, $SONG;
    }

    @SONGS = @KEEP;

    print "$0: pared songlist down to " . scalar(@SONGS) . " entries\n";
}


#
# Parse the iTunesDB (largely taken from the gnupod utils).
#

sub read_db {
    $DBFILE = "$SRC/iPod_Control/iTunes/iTunesDB";

    print "$0: reading db from $DBFILE\n" if ($DEBUG);

    open($DB, "<$DBFILE") || die "failed to open $DBFILE: $!\n";
    binmode($DB);

    die "$0: fatal: header doesn't look like ipod (magic)\n"
        if (getdata($DB, 0, (length($MAGIC_HEADER)+2)/3) ne $MAGIC_HEADER);

    @SONGS = get_songlist($DB);

    print "$0: $DBFILE: got " . scalar(@SONGS) . " songs\n";
}

sub get_songlist {
    my ($fh, $sum) = ($_[0], $MHIT_START);
    my ($zip, $state, $sa, $sl, $sb, $sid);
    my (@songs);

    use vars qw(@fields);
    $fields[1] = "title";
    $fields[2] = "path";
    $fields[3] = "album";
    $fields[4] = "artist";
    $fields[5] = "genre";
    $fields[6] = "fdesc";
    $fields[8] = "comment";

    while (getdata($fh, $sum, 4) eq $MAGIC_MHIT) {

        print "MHIT: $sum\n" if ($DEBUG);

        $zip = 0;
        $sid = hex(getdata($fh, $sum+19, 1) . getdata($fh, $sum+18, 1) . getdata($fh, $sum+17, 1) . getdata($fh, $sum+16, 1));
        $sa  = hex(getdata($fh, $sum+39, 1) . getdata($fh, $sum+38, 1) . getdata($fh, $sum+37, 1) . getdata($fh, $sum+36, 1));
        $sl  = hex(getdata($fh, $sum+43, 1) . getdata($fh, $sum+42, 1) . getdata($fh, $sum+41, 1) . getdata($fh, $sum+40, 1));
        $sbr = hex(getdata($fh, $sum+59, 1) . getdata($fh, $sum+58, 1) . getdata($fh, $sum+57, 1) . getdata($fh, $sum+56, 1));

        $sum += 156;

        my %song;

        $song{"id"}      = $sid;
        $song{"bitrate"} = $sbr;
        $song{"length"}  = $sl;
        $song{"size"}    = $sa;

        while ($zip != -1) {
            $sum += $zip;
            ($zip, $oid, $otxt) = get_mhod($fh, $sum);
            next if (!length($otxt));
            $song{$fields[$oid]} = $otxt;
        }

        $song{"path"}      =~ s/\:/\//g;
        $song{"extension"} = substr($song{"path"}, length($song{"path"})-3, 3);

        push @songs, { %song };

        if ($DEBUG) {
            foreach $key (keys %song) {
                print "$key=$song{$key}\t";
            }
            print"\n";
        }

        $sum -= $zip + 1;
    }

    return @songs;
}

sub get_mhod {
    my($fh, $seek, $xl, $ml, $mty, $str) = @_;

    $id = getdata($fh, $seek, 4);

    if ($id ne $MAGIC_MHOD) {

        $ml = -1;

    } else {

        $ml  = hex(getdata($fh, $seek+8,  1));
        $mty = hex(getdata($fh, $seek+12, 1));
        $xl  = hex(getdata($fh, $seek+28, 1));

        $str = getstr($fh, $seek+40, $xl);
        $str =~ tr/\0//d;

        print "MHOD: ml=$ml, il=$xl (" . ($xl/2) . " chars), ht=$str\n" if ($DEBUG);

    }

    return ($ml, $mty, $str);
}

sub getdata {
    my($str) = getstr(@_);
    my($xx, $xr);

    foreach (split(//, $str)) {
        $xx = sprintf("%02x ", ord($_));
        $xr = "$xr$xx";
    }

    chop($xr);

    return $xr;
}

sub getstr {
    my($fh, $start, $len, $noseek) = @_;
    my($xx, $xr, $buf);

    $start = 0 if (!$start);
    $len   = 1 if (!$len);

    seek($fh, $start, 0);
    read($fh, $buf, $len);

    return $buf;
}
