/usr/local/CPAN/MP3-Album/MP3/Album.pm


package MP3::Album;

use strict;
use MP3::Info;
use MP3::Album::Track;
use MP3::Album::Layout::Fetcher;
use MP3::Album::Layout;
use File::Basename;

our $VERSION = '0.14';

sub new {
   my $c = shift;
   my %a = @_;
   my $s = { };

   $s = bless $s, $c; 
   
   $s->{tracks} = [];

   if ($a{files}) {
   	$a{files} = [ $a{files} ] if (  ref($a{files}) ne 'ARRAY' );
	foreach my $f (@{$a{files}}) {
	    my $tr = MP3::Album::Track->new(filename => $f);
	    unless ($tr) { return undef; }
	    push @{$s->{tracks}}, $tr; 
	}
   }

   $s->{current_layout}    = undef;

   return $s;
}

sub rename_files {
   my $s = shift;
   my %a = @_;

   # %t track title;
   # %a album name
   # %n track number
   # %p artist name

   $a{format}     = '%p - %a - %n - %t.mp3' unless $a{format};
   $a{keep_copy}  |= 0;
   
   my $layout = $s->layout();
   my $li     = $layout->info();

   for (my $i = 0; $i < scalar(@{$s->{tracks}}); $i++) {
	next unless $s->{tracks}->[$i];

	my $track_number = sprintf("%02d",$i+1);

	my $fn = $a{format};

	$fn =~ s/\%p/$li->{tracks}->[$i]->{artist}/g;
	$fn =~ s/\%a/$li->{title}/g;
	$fn =~ s/\%n/$track_number/g;
	$fn =~ s/\%t/$li->{tracks}->[$i]->{title}/g;

	$fn =~ s/\//_/g;

	my $dirname = $a{out_dir} ? $a{out_dir} : dirname($s->{tracks}->[$i]->filename());

	my $r = $s->{tracks}->[$i]->rename(filename=>"$dirname/$fn", keep_copy => $a{keep_copy});

	return undef unless $r;
   };

   return 1;
}

sub generate_tags {
   my $s = shift;
   $s->_init unless $s->{_init_done};

   my $layout = $s->layout();
   my $li     = $layout->info();

   for (my $i = 0; $i < scalar(@{$s->{tracks}}); $i++) {
   	next unless $s->{tracks}->[$i];

	my $track_number = sprintf("%02d",$i+1);
	
	my $r = $s->{tracks}->[$i]->set_tag(
		title  		=> $li->{tracks}->[$i]->{title},
		artist 		=> $li->{tracks}->[$i]->{artist},
		album  		=> $li->{title},
		year   		=> $li->{year},
		genre           => $li->{genre},
		comment		=> $li->{comment},
		track_number	=> $track_number
	);

	return undef unless $r;
   }

   return 1;

}

sub info {
   my $s = shift;
   $s->_init unless $s->{_init_done};
   
   my %info = ( 
   		number_oF_tracks  => scalar(@{$s->{tracks}}),
		bitrates          => [ $s->_album_bitrate() ],
		uniform_bitrate   => scalar($s->_album_bitrate()) == 1 ? 1 : 0,
		frequencies       => [ $s->_album_frequencies() ],
		uniform_frequency => scalar($s->_album_frequency()) == 1 ? 1 : 0,
		cddb_disc_id      => $s->{cddb_query}->{discid}
   	      );
   return wantarray ? %info : \%info;
}

sub frequency_check {
   my $s = shift;
   $s->_init unless $s->{_init_done};

   return scalar($s->_album_frequencies()) == 1 ? 1 : 0;
}

sub bitrate_check{
   my $s = shift;
   $s->_init unless $s->{_init_done};

   return scalar($s->_album_bitrate()) == 1 ? 1 : 0;
}

sub tracks {
  my $s = shift;

  return @{$s->{tracks}};
}

sub add {
 my $s = shift;
 my %a = @_;

 $s->_init unless $s->{_init_done};

 unless ( $a{track} ) {
     $@ = "missing param track";
     return undef;
 }

 my $tr = MP3::Album::Track->new(filename => $a{track});
 unless ($tr) { return undef }

 $a{position} = scalar(@{$s->{tracks}})+1 unless $a{position};

 if ( $a{position} > scalar(@{$s->{tracks}})+1 ) {
     $@ = "position must be between start and end of the album";
     return undef;
 }

 my @tracks = @{$s->{tracks}};

 my @f = splice(@tracks, 0, ($a{position}-1));

 $s->{tracks} = [@f, $tr, @tracks];

 delete $s->{_init_done};
 return 1;

};

sub layout {
  my $s = shift;
  $s->_init unless $s->{_init_done};

  if (@_) {
      unless ( $_[0] && (ref $_[0] eq 'MP3::Album::Layout') ) {
        $@ = "I need a MP3::Album::Layout to set up the album layout";
	return undef;
      }
      $s->{current_layout} = $_[0];
     
  } elsif (! $s->{current_layout} ) {
	my $l = $s->fetch_layout();
	return undef if (!$l); 
	$s->{current_layout} = $l->[0];
  }

  return $s->{current_layout};
}

sub fetch_layout {
  my $s = shift;
  my %a = @_;

  $s->_init unless $s->{_init_done};

  $a{method} = 'Tag' unless $a{method};

  my $layouts = MP3::Album::Layout::Fetcher->fetch(album=>$s, method=>$a{method});

  return undef unless $layouts;

  return wantarray ? @$layouts : $layouts;
};

sub available_fetchers {
  return MP3::Album::Layout::Fetcher->available_fetchers();
}

sub _album_frequencies {
  my $s = shift;

  my %freqs;
  
  foreach my $t (@{$s->{tracks}}) {
	$freqs{$t->bitrate()}=1 if $t->bitrate();	
  }

  return keys(%freqs);
}

sub _album_bitrate {
  my $s = shift;

  my %bitrates;
  
  foreach my $t (@{$s->{tracks}}) {
	$bitrates{$t->bitrate()}=1 if $t->bitrate();	
  }

  return keys(%bitrates);
}

sub _generate_toc {
  my $s = shift;
  my @toc;
  foreach my $f (@{$s->{tracks}}) {
       push @toc, $f->{info};
  }
  return @toc;
}

sub _cddb_query_builder {
  my $s = shift;
  my %a = @_;
  die "need a toc to build a query\n" unless $a{toc};

  my $discid       = $s->_disc_id(toc => $a{toc});
  my $disctime     = $s->_disc_time(toc => $a{toc});
  my $total_tracks = @{$a{toc}};
  my $frames       = $s->_get_frames(toc=> $a{toc});

  return { discid=> $discid, disctime=>$disctime, disctracks=>$total_tracks, frames=>$frames };
  
}

sub _get_frames {
   my $s = shift;
   my %a = @_;

   my @frames;
   my $t = 0;
   foreach my $track ( @{$a{toc}} ) {
      push @frames, $t * 75;
      $t += ( $track->{MM} * 60 + $track->{SS} );
   }
   return \@frames;
}

sub _disc_time {
   my $s = shift;
   my %a = @_;
   my $total_time = 0;

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

   return $total_time;
}

sub _disc_id {
   my $s = shift;
   my %a = @_;
   my $n          = 0;
   my $total_time = 0;

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

        $n          += $s->_cddb_sum($total_time);
        $total_time += $track_time;
    }

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

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

sub _init {
    my $s = shift;

    @{$s->{toc}}       = $s->_generate_toc;
    $s->{cddb_query}   = $s->_cddb_query_builder(toc=>$s->{toc});

    $s->{_init_done} = 1;

    return 1;
}

1;