#!/usr/bin/perl -w -- -*- cperl -*-
#
#  gnump3d-index - Create a database of all tag information for audio files.
#
#  GNU MP3D - A portable(ish) MP3 server.
#
# Homepage:
#   http://www.gnump3d.org/
#
# Author:
#  Steve Kemp <steve@steve.org.uk>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
#  Steve Kemp
#  ---
#  http://www.steve.org.uk/
#
#

use strict;
use Getopt::Long;
use File::Find;

use gnump3d::config;               # For reading our configuration file.
use gnump3d::files;                # For testing if a file is audio
use gnump3d::ogginfo;              # Pure Perl OGG Vorbis tag parsing.
use gnump3d::oggtagreader;         # Local vorbis code
use gnump3d::mp3info;	           # Local copy of MP3::Info.



# Version identifier for this script.
my $VERSION_NUMBER = '$Revision: 1.11 $';

#
# Determine which configuration file to read.
#
my $CONFIG_FILE = "";
if ( ( $ENV{"HOME"} ) &&
     ( -e $ENV{"HOME"} . "/.gnump3drc" ) )
{
    $CONFIG_FILE = $ENV{"HOME"} . "/.gnump3drc";
}
elsif ( -e "/etc/gnump3d/gnump3d.conf" )
{
    $CONFIG_FILE = "/etc/gnump3d/gnump3d.conf";
}
elsif ( -e "gnump3d.conf" )
{
    # This is mainly here for Windows users.
    $CONFIG_FILE = "gnump3d.conf";
}


#
# Command line flags
#
my $SHOW_HELP    = 0;
my $SHOW_VERSION = 0;
my $SHOW_STATS   = 0;
my $VERBOSE      = 0;

#
# Global variables.
#
my $root     = "" ;
my $cache    = "" ;

my @FOUND = ( );  # An array to hold filenames of audio files we locate.


#
#  Parse the command line arguments.
#
&parseArguments();


#
#  Make sure that we can read a configuration file.
#
if ( ! -e $CONFIG_FILE )
{
    print "The configuration file which I've tried to raad doesn't exist:\n";
    print "'$CONFIG_FILE'\n";
    print "Aborting.\n";
    exit;
}

#
# Initialize ourself from the configuration file.
#
&readConfig( $CONFIG_FILE );

#
# Read various options from the configuration file - unless they have been
# specified upon the command line.
#
if ( !length( $root ) )
{
  $root = &getConfig( "root",      "/home/mp3" ); 
}
if ( !length( $cache ) )
{
  $cache    = &getConfig( "tag_cache", "/tmp/tags.cache" );
}


# Take any immediate actions...
if ( $SHOW_HELP )
{
    &showHelp();
    exit;
}
if ( $SHOW_VERSION )
{
    &showVersion();
    exit;
}


#
# Sanity check the code.
#
if ( ! -d $root )
{
    print "The server root directory you are trying to index isnt a directory.\n";
    print $root . "\n";
    exit;
}

#
# Do the indexing
find(\&findAudio, $root);

#
# Show statistics if that's all the user wants.
if ( $SHOW_STATS )
{
    &showStats();
    exit;
}


#
# Process the list of found files.
#
&indexFiles( );

#
# Finished
exit;


#
#  Use the excellent File::Find module to locate all the files beneath
# our archive root.
#
sub findAudio( )
{
    my ( $file ) = $File::Find::name;
    return if ( ! isAudio( $file ) );

    push @FOUND, $file;
}


#
#  Show the number of files found, and their total size.
#
sub showStats( )
{
   my $sizeTotal = 0;
   my $fileTotal = $#FOUND + 1;

   my ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime,  $blksize, $blocks );

   #
   #  Work with the global list of found files.
   foreach my $file ( sort @FOUND )
   {
       # Read the file information.
       ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime,  $blksize, $blocks ) = stat( $file );

	# Increase the running total of sizes.
   	$sizeTotal += $size;
   }

    #
    #  Make the size a human readable number without too much precision.
    $sizeTotal = $sizeTotal < (1024)      ?
                     $sizeTotal . " bytes" : (
            $sizeTotal < (1024 ** 2) ? 
                     (int (10 * $sizeTotal/1024)/10) . "K" : (
            $sizeTotal < (1024 ** 3) ? 
                     (int (10 * $sizeTotal/(1024 ** 2) )/10) . "MB" :
                     ((int (10 * $sizeTotal/(1024 ** 3) )/10) . "GB")));

   #
   #  All done - print the results.
   print "Beneath $root there are:\n";
   print "$fileTotal audio files - totalling $sizeTotal.\n";

}


#
#  Read in the tag files of our found files, and write out their values.
#
sub indexFiles( )
{
    my $count = 0;
    my $total = $#FOUND + 1;

    my $error = 0;
    open ( OUT, ">$cache" ) or $error = 1;
    if ( $error )
    {
	print "Error opening the cache file '$cache' - $!\n";
	exit;
    }


    foreach my $file ( sort @FOUND )
    {

	# Holder for the tags within the files.
	my %TAGS;

	# Populate the tag lists.
	if ( $file =~ /mp3$/i )
	{
	    %TAGS = &getMP3Display($file);
	}
	elsif( $file =~ /ogg$/i )
	{
	    %TAGS = &getOGGDisplay($file);
	}


	#
	# Make sure filename is defined..
	#
	my $base = $file;
	if ( $base =~ /(.*)\/(.*)/ )
	{
	    # Strip away the leading directory name.
	    $base = $2;
	}
	if ( $base =~ /(.*)\.(.*)/ )
	{
	    # Remove any suffix.
	    $base = $1;
	}
	$TAGS{ 'FILENAME' } = $base;

	#
	#  Show the tags we've found if the user wanted verbosity.
	if ( $VERBOSE )
	{
	    print $file . "\n";
	    foreach my $k ( keys %TAGS )
	    {
		print "\t$k\t" . $TAGS{ $k } . "\n";
	    }
	}

	#
	# Write the filename and tag details to the cache file, as
	# tab seperated values.
	#
	print OUT $file;
	foreach my $k ( keys %TAGS )
	{
	    my $value = $TAGS{ $k };

	    # Replace tabs in tag values with spaces so that the reading
	    # code doesn't get confused by excessive deliminators
	    $value =~ s/\t/     /g;

	    print OUT "\t" . $k . "=" . $value;
	}
	print OUT "\n";


	# Update our processed count.
	$count += 1;
    }

    #
    # Close the output file.
    close( OUT );
}



#
#  Get the meta tags from an MP3 file.
#
sub getMP3Display($)
{
    my ( $file ) = (@_);

    my %TAGS;

    #
    # MPEG file information - get this regardless
    # of the presence of an ID3 tag or not.
    #
    my $inf         = &get_mp3info( $file );
    $TAGS{'LENGTH'} = $inf->{TIME}     || "";
    $TAGS{'BITRATE'}= $inf->{BITRATE}  || "";
    $TAGS{'SIZE'}   = $inf->{SIZE}     || "";


    #
    # Now look for tag information.
    #
    my $tag = &get_mp3tag( $file );

    # Early termination.
    if ( not defined $tag )
    {
	return( %TAGS );
    }


    #
    #  We have some tags .. so store them
    #
    $TAGS{'ARTIST'} = $tag->{ARTIST}   || "";
    $TAGS{'TITLE'}  = $tag->{TITLE}    || "";
    $TAGS{'ALBUM'}  = $tag->{ALBUM}    || "";
    $TAGS{'YEAR'}   = $tag->{YEAR}     || "";
    $TAGS{'COMMENT'}= $tag->{COMMENT}  || "";
    $TAGS{'TRACK'}  = $tag->{TRACKNUM} || "";
    $TAGS{'GENRE'}  = $tag->{GENRE}    || "";


    return( %TAGS );
}


#
#  Get the display text for an OGG Vorbis file.
#
sub getOGGDisplay($)
{
    my ($file) = (@_);

    my $reader = gnump3d::ogginfo->new($file);
    my %TAGS;

    # info
    while (my ($key, $v) = each %{$reader->info})
    {
      $TAGS{uc($key)} = $v;
    }


    my $comment = gnump3d::oggtagreader->new( );
    my %tags = $comment->getTags($file);

    if ( keys( %tags ) )
    {
	$TAGS{'ARTIST'} = $tags{'artist'}  || "";
	$TAGS{'COMMENT'}= $tags{'comment'} || "";
	$TAGS{'GENRE'}  = $tags{'genre'}   || "";
	$TAGS{'TRACK'}  = $tags{'track'}   || "";
	$TAGS{'ALBUM'}  = $tags{'album'}   || "";
	$TAGS{'TITLE'}  = $tags{'title'}   || "";
     }

    return( %TAGS );
}



#
#  Parse the command line options.
sub parseArguments()
{
    GetOptions(
	       "config=s", \$CONFIG_FILE,
               "help", \$SHOW_HELP,
	       "output=s", \$cache,
	       "root", \$root,
	       "stats", \$SHOW_STATS,
               "verbose", \$VERBOSE,
               "version", \$SHOW_VERSION,
               );

}

#
#  Show help for this script.
sub showHelp()
{
    showVersion();
    print <<END_OF_USAGE;

Usage: gnump3d-index [options]

  gnump3d-index is a simple script to index the tag information located
 within the audio files of your archive.

  The script will index the files found beneath your root, as defined
 in the gnump3d.conf file - and write out a cache for speedy access.

  (See also: 'man gnump3d.conf', 'man gnump3d', and man 'gnump3d-index')

Options:
    --config file      The configuration file to read.
    --help             Show this help.
    --output file      Write the output to the given file.
    --root directory   Start the indexing at the given directory.
    --stats            Don't update the cache file, just display audio stats.
    --verbose          Display all the tag values read.
    --version          Show the version number.
END_OF_USAGE
}


#
#  Show the version number of this script.
sub showVersion()
{
    my $revision = $VERSION_NUMBER;

    #
    # Extract the version from the CVS revision marker,
    # the only tricky bit is making sure the words "$" Revision " $"
    # don't appear here - because they'd be replaced - this
    # has confused me before.
    #
    if (  $VERSION_NUMBER =~ /\$([a-zA-Z:]+) ([0-9\.]+) \$/ )
    {
        $revision = $2;
    }

    print "gnump3d-index - version $revision - http://www.gnump3d.org/\n";

}
