#!/usr/bin/perl

# mp3cddb
# -------
# originally  : mp3tocddb.pl by Meng Weng Wong (MPEG::MP3Info)
# modified by : Andras BALI <drewie@bigfoot.com>
# license     : GPL
# version     : 0.1

use strict;
use vars qw($dir);
use MPEG::MP3Info;
use CDDB;
use File::Basename;

$dir =~ s/~/$ENV{'HOME'}/ if (defined($dir));

if (! @ARGV) { die "mp3cddb - performs cddb lookups for your mp3 albums\nusage: mp3cddb *.mp3\n" }

my %cdtoc = ();
my %cddb_query = ();
my %tracks_in;

if (@ARGV) {
    my $album     = "cddbinfo";
    foreach my $file (@ARGV) {

	if ($file =~ /\.mp3$/i) {
	    my $info  = get_mp3info($file);

    	    push (@{$cdtoc{$album}}, $info );
	    push (@{$tracks_in{$album}}, basename($file));
	}
    }
    $cddb_query{$album} = [&build_cddb_query($cdtoc{$album})];
}

my $cddb = new CDDB (Debug=>0, Host=>'freedb.freedb.org', Port=>8880) or die "unable to connect to CDDB: $!";


my %disc_info;

ALBUM: foreach my $album (sort keys %cddb_query) {

    my ($my_disc_id,
	$my_total_tracks,
	$my_total_time,
	@my_frames) = @{$cddb_query{$album}};

    if ($my_total_tracks == 1) {
	print "only one track. skipping.\n";
	next;
    }

    my @discs = ();
       @discs = $cddb->get_discs($my_disc_id, [@my_frames], $my_total_time);

    if (grep ! defined($_), @discs) { warn "get_discs didn't return anything! skipping $album.\n"; next }

    foreach my $disc (@discs) {
       my ($genre, $cddb_id, $title) = @$disc;

       print "match: $genre \t $cddb_id \t $title\n";

       $disc_info{$album}{"$cddb_id ($genre)"}              = $cddb->get_disc_details($genre, $cddb_id);
       $disc_info{$album}{"$cddb_id ($genre)"}->{'genre'} ||= $genre;
    }

    print "no match in cddb database.\n" and next ALBUM if (! @discs);
    
    push (@my_frames, $my_total_time * 75);
    my %distance;
    foreach my $cddb_id (sort keys %{$disc_info{$album}}) {

	my $disc_info = $disc_info{$album}{$cddb_id};

	my $disc_time	   = ($disc_info->{'disc length'} =~ /(\d+)/)[0];
	my $disc_id	           =  $disc_info->{'discid'};
	my $disc_title	   =  $disc_info->{'dtitle'};
	my @track_offsets = @{$disc_info->{'offsets'}};
	my @track_titles  = @{$disc_info->{'ttitles'}};


	push (@track_offsets, $disc_time * 75 + $track_offsets[0]);

	my @track_lengths = &offsets_to_seconds (@track_offsets);
	my @my_lengths    = &offsets_to_seconds (@my_frames);


	my $distance = &sqr_distance(\@track_lengths, \@my_lengths);

	$distance{$album}{$cddb_id} = $distance;
    }

    my @ranking = sort { $distance{$album}{$a} <=> $distance{$album}{$b} } keys %{$distance{$album}};

	my $outfile = (defined($dir)?"$dir/":"") . "$album.txt";
	printf "saving information to cddbinfo.txt...\n";
	if (! open (OUT, ">$outfile")) {
	    warn "unable to open $outfile: $!\n";
	    next;
	}
	
	print OUT map {   "$_\n" } &textamp_info($album, shift @ranking);
	print OUT "\n" and
	print OUT map { "# $_\n" } &textamp_info($album, shift @ranking) while @ranking;

	close OUT or warn "unable to close $outfile: $!\n";
}


sub textamp_info {
    my ($album, $cddb_id) = @_;
    my @toreturn = ();

    my $disc_info          =  $disc_info{$album}{$cddb_id};
    my $disc_time	   = ($disc_info->{'disc length'} =~ /(\d+)/)[0];
    my $disc_id	           =  $disc_info->{'discid'};
    my $disc_title	   =  $disc_info->{'dtitle'};
    my $genre	   	   =  $disc_info->{'genre'};
    my @track_offsets = @{$disc_info->{'offsets'}};

    my ($artist, $real_title) = &split_title($disc_info->{'dtitle'});
    push (@toreturn,
	  "ARTIST_ALBUM: $artist",
	  "ALBUM: $real_title",
	  "GENRE: $genre",
	  "",
	  );
    
    my @track_titles  = @{$disc_info->{'ttitles'}};

    foreach my $track_number (0 .. $#{$tracks_in{$album}}) {
	my ($artist, $title) = &split_title($track_titles[$track_number]);
	if ($artist eq $title) { undef $artist }	
	
	my $track_number_real = $track_number + 1;
	push (@toreturn, "$track_number_real.FILE: $tracks_in{$album}->[$track_number]");
	push (@toreturn, "$track_number_real.ARTIST: $artist") if defined $artist;
	push (@toreturn, "$track_number_real.TITLE: $title");
	push (@toreturn, "");
    }

    return @toreturn;
}

sub split_title {
    
    local $_ = shift;
    
    my ($artist, $title);

       if (/(.*?)\s*\/\s*(.*)/)  { ($artist, $title) = ($1, $2) }
    elsif (/(.*?)\s+-+\s+(.*)/)  { ($artist, $title) = ($2, $1) }
    else                         { ($artist, $title) = ($_, $_) }

    for ($artist, $title) { s/^\s*//; s/\s*$// }

    return ($artist, $title);
}

sub sqr_distance {
    my @vector1 = @{+shift};
    my @vector2 = @{+shift};
    my $total = 0;

    foreach my $dimension (0 .. ($#vector1 < $#vector2 ? $#vector1 : $#vector2)) { # too much paranoia never hurt anyone
	my $difference = abs($vector1[$dimension] - $vector2[$dimension]);
	my $square     = $difference ** 2;
	$total        += $square;
    }
    
    return $total;
}

sub frames_to_ss {
    my $frames = shift;
    my $ss = int($frames / 75);
    return $ss;
}

sub ss_to_mmss {
    my $ss = shift;
    my $mm = $ss / 60;
       $ss = $ss % 60;
    return sprintf ("%02d:%02d", $mm, $ss);
}

sub offsets_to_seconds {
    my @offsets = @_;
    my @track_lengths = ();
    while (@offsets > 1) {
	unshift(@track_lengths, pop (@offsets) - $offsets[-1]);
    }
    return map { &frames_to_ss ($_) } @track_lengths;
}

sub build_cddb_query {
    my @cdtoc = @{+shift};
    my $count = 1;

    foreach (@cdtoc) {
	my ($mm, $ss) = ($_->{MM}, $_->{SS});
    }

    my $discid = cddb_discid(@cdtoc);

    my @frames         = &invent_frame_numbers(@cdtoc);
    my $total_time     = &total_time(@cdtoc);
    my $total_tracks   = @cdtoc;

    my $login          = $ENV{USER};
    my $hostname       = &hostname; use Sys::Hostname; $hostname = `hostname` if $hostname !~ /\./;
    my $client_name    = "mp3cddb";
    my $client_version = "alpha";


    printf ("searching for discid: $discid, total tracks: $total_tracks, total time: $total_time ...\n");

    return ( $discid,
	     $total_tracks,
	     $total_time,
	     @frames );
}

sub cddb_sum {
    my ($n, $ret) = (shift, 0);
    for (split //, $n) { $ret += $_ }
    return $ret;
}

sub total_time {
    my @cdtoc      = @_;
    my $total_time = 0;

    foreach my $track (@cdtoc) {
	my $track_time = $track->{MM} * 60 + $track->{SS};
	$total_time +=           $track_time;
    }

    return $total_time;
}


sub cddb_discid {
    my @cdtoc      = @_;
    my $n          = 0;
    my $total_time = 0;

    foreach my $track (@cdtoc) {
	my $track_time = $track->{MM} * 60 + $track->{SS};

	$n          += &cddb_sum($total_time);

	$total_time +=           $track_time;
    }

    return sprintf("%08x", ($n % 0xFF) << 24 | $total_time << 8 | @cdtoc);
}

sub invent_frame_numbers {

    my @cdtoc      = @_;
    my $n          = 0;
    my $total_time = 0;

    foreach my $track (@cdtoc) {

	$track->{FRAME_OFFSET} = $total_time * 75;

	my $track_time  = $track->{MM} * 60 + $track->{SS};
	$total_time    += $track_time;

    }

    return map { $_->{FRAME_OFFSET} } @cdtoc;
}

