#!/usr/bin/perl
#
# Version 0.1 vom 04.02.2007
#
# Author: Mike Constabel Portal: <vejoun @ vdrportal>
#                         eMail: <vejoun @ toppoint . de>
#
#
# Perl-modules needed:
#
# libmp3-tag-perl
# libmp3-info-perl
# libogg-vorbis-header-perl
#

use strict;
use warnings;
use MP3::Tag;
use Ogg::Vorbis::Header;
use locale;
use POSIX qw(locale_h);

setlocale(LC_ALL, 'deutsch');

my $filemask	= '\.(mp3|ogg)$';
my $chars	= '^[\w \-_\.,\'\\~#\/\(\)\[\]\{\}"!§\$%&\`?*:;0-9\t\+]+$';
my $errorfile	= "/tmp/music_archive_errors.log";
my $Format	= '%a;%t\n';

my $Usage = qq{
This script reads the artist and title information from the
file types mp3 and ogg-vorbis and generates output which can be
written into blacklists.conf used by the vdr plugin deluxe.

Usage: $0 /path_to_music_archiv

Examples:

./mp3archiv2blacklist.pl /mp3/Music

./mp3archiv2blacklist.pl /mp3/Music >> blacklist.conf

};

if ( ! $ARGV[0] ) {
  print $Usage;
  exit;
}

my $mp3dir = shift;
$mp3dir =~ s/\/+$//;

if ( $ARGV[0] ) {
  $filemask = shift;
}

unless ( $mp3dir && $filemask && -d $mp3dir ) {
  print $Usage;
  exit;
}

my %count = (all => 0, mp3 => 0, ogg => 0, error => 0, mp3ok => 0, oggok => 0, mp3sec => 0, oggsec => 0);

sub listdir {
  my $path = shift;
  my $mask = shift;
  my (@dir, @subdirs, @files);
  my $full;

  opendir(DIR, "$path") || return ();
  @dir = readdir(DIR);
  closedir DIR;

  for (@dir) {
    my $fullname = "$path/$_";
    if (/$mask/i) {
      push(@files, $fullname);  # finds files and directories
      printf STDERR ("\r\033[0KBuilding file list: %i files found...", ++$count{all});
    }
    push(@subdirs, $fullname) if ((-d $fullname) && !/^\.\.?/);
  }
  for (@subdirs) {
    push @files, listdir($_, $mask);
  }
  return @files;
}

sub sec2human {
  my ($time_sec) = @_;
  my $sec = $time_sec % 60;
  $time_sec = ($time_sec - $sec) / 60;
  my $minute = $time_sec % 60;
  $time_sec = ($time_sec - $minute) / 60;
  my $hour = $time_sec % 24;
  $time_sec = ($time_sec - $hour) / 24;
  my $day = $time_sec % 7;
  my $week = ($time_sec - $day) / 7;
  return ($week, $day, $hour, $minute, $sec);
}

sub sornos {
  my ($t, $k) = @_;
  unless ($t == 1) {
    return "s".($k ? "" : ",");
  }
  return ", " if ! $k;
}

my %list;
my @errorlist;
my @files = listdir($mp3dir, $filemask);

print STDERR "\nExtracting infos, please wait...\n";

foreach ( @files ) {
  my $file = $_;
  unless (-r $file && -s $file) {
    $count{error}++;
    push (@errorlist, "File not readable or epmty: ".$file);
    next;
  }
  my $filetype = ( $file =~ /\.ogg$/i ) ? "ogg" : "mp3";
  $file =~ /^.*\/(.*?)$/;
  my $clear = $1;
  $clear =~ /^([\w\s\-_\.,]{1,40})/;
  $clear = $1;
  if ( $filetype eq "mp3" ) {
    my $mp3 = MP3::Tag->new($file);
    my ($title, $track, $artist, $album, $comment, $year, $genre) = $mp3->autoinfo();
    if ( $artist && $title && ! exists $list{$artist}{$title} ) {
      if ( $artist =~ /${chars}/ && $title =~ /${chars}/ ) {
        $list{$artist}{$title} = 1;
        $count{mp3ok}++;
        $count{mp3sec} += $mp3->interpolate("%S");
        $mp3->close;
        $count{mp3}++;
      } else {
        push (@errorlist, "Unwanted chars (>$artist< >$title<): ".$file);
        $mp3->close;
        $count{error}++;
      }
    } elsif ( ! exists $list{$artist}{$title} ) {
      push (@errorlist, "Empty artist or title (>$artist< >$title<): ".$file);
      $mp3->close;
      $count{error}++;
    } else {
      push (@errorlist, "Double or unknown error (>$artist< >$title<): ".$file);
      $count{error}++;
    }
  } elsif ( $filetype eq "ogg" ) {
    my $ogg = Ogg::Vorbis::Header->new($file);
    my $artist = ($ogg->comment("artist"))[0] ?($ogg->comment("artist"))[0] : "";
    my $title = ($ogg->comment("title"))[0] ? ($ogg->comment("title"))[0] : "";
    if ( $artist && $title && ! exists $list{$artist}{$title} ) {
      if ( $artist =~ /${chars}/ && $title =~ /${chars}/ ) {
        $list{$artist}{$title} = 1;
        $count{oggok}++;
        $count{oggsec} += int($ogg->info("length"));
        $count{ogg}++;
      } else {
        push (@errorlist, "Unwanted chars (>$artist< >$title<): ".$file);
        $count{error}++;
      }
    } elsif ( ! exists $list{$artist}{$title} ) {
      push (@errorlist, "Empty artist or title (>$artist< >$title<): ".$file);
      $count{error}++;
    } else {
      push (@errorlist, "Double or unknown error (>$artist< >$title<): ".$file);
      $count{error}++;
    }
  }
  printf STDERR ("\r\033[0KProcessing files (%i x mp3, %i x ogg, %i x error/unknown): %s...", $count{mp3}, $count{ogg}, $count{error}, $clear);
}

printf STDERR ("\r\033[0KProcessing files (%i x mp3, %i x ogg, %i x error/unknown)\n\n", $count{mp3}, $count{ogg}, $count{error});

foreach my $artist ( sort keys %list ) {
  foreach my $title ( sort keys %{$list{$artist}} ) {
    $artist =~ s/;//; $title =~ s/;//;
    my %FormatHash = ( '%a' => $artist, '%t' => $title );
    my $Output = $Format;
    foreach ('%a', '%t') {
      $Output =~ s/$_/$FormatHash{$_}/ if ( $Output  =~ /$_/ );
    }
    $Output =~ s/(?<!\\)\\n/\n/g;
    $Output =~ s/\\\\n/\\n/g;
    print $Output;
  }
}

printf STDERR ("\nFinished. %i files (%i x mp3, %i x ogg and %i x error/unknown) processed. Using data of %i mp3 and %i ogg.\n\n", $count{all}, $count{mp3}, $count{ogg}, $count{error}, $count{mp3ok}, $count{oggok});
my ($week, $day, $hour, $minute, $sec) = &sec2human($count{mp3sec});
printf STDERR ("%-17s %3i week%s %2i day%s %02i hour%s %02i minute%s %02i second%s.\n", "mp3 stats:", $week, sornos($week), $day, sornos($day), $hour, sornos($hour), $minute, sornos($minute), $sec, sornos($sec, 1));
($week, $day, $hour, $minute, $sec) = &sec2human($count{oggsec});
printf STDERR ("%-17s %3i week%s %2i day%s %02i hour%s %02i minute%s %02i second%s.\n", "ogg-vorbis stats:", $week, sornos($week), $day, sornos($day), $hour, sornos($hour), $minute, sornos($minute), $sec, sornos($sec, 1));

if (scalar @errorlist) {
  open(ERR, ">$errorfile") || die "Cannot open $errorfile: $!";
  printf STDERR ("\nWriting errors to %s.\n", $errorfile);
  print ERR join("\n", @errorlist)."\n";
  close ERR;
}

__END__
