#!/usr/bin/perl

# 2003.09.25 -- jsled

# Copyright (C) 2003 Joshua Sled <jsled@asynchronous.org>. Some Rights Reserved.
#
# This work is licensed under the Creative Commons
# Attribution-NonCommercial-ShareAlike License. To view a copy of this
# license, visit http://creativecommons.org/licenses/by-nc-sa/1.0/ or send a
# letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California
# 94305, USA.

# ./mserver-indexer.pl [--output <dir>] <full media root path>

# Creates the appropriate index files for the Prismiq Media Server [3.11b]
# given a source directory to index.

# The following files need to be created:
#    * media.map : maps uid's to .tag files and actual files.
#    * media.xml : master data for different media.
#    * playlist.xml : one per directory.

# This script finds all directories which contain *.mp3 files and outputs the
# appropriate entries for each.

# Give it '--output <directory>' for an output-directory location, otherwise
# '.' is used.

$debug = 0;
$verbose = 1;
$maxCount = 1000000;

$outputDir = ".";
$tagRoot = "/usr/tmp";

$lastIdx = 0;
if ( $ARGV[0] eq "--output" )
{
    $outputDir = $ARGV[1];
    $lastIdx = 2;
}
print $outputDir ."\n" if $debug;

# The unique ID counter
$ID = 1;

@mediaRoots = @ARGV[$lastIdx .. $#ARGV];
print "roots: [" . join( ";", @mediaRoots ) . "]\n" if $debug;

# ++ main() {
open MEDIAMAP, ">" . $outputDir . "/media.map" || die "Cannot open \"$outputDir/media.map\"";
open MEDIA, ">" . $outputDir . "/media.xml" || die "Cannot open \"$outputDir/media.xml\"";
open PLAYLIST, ">" . $outputDir . "/playlist.xml" || die "Cannot open \"$outputDir/playlist.xml\"";

print MEDIA <<MEDIAHEAD;
<!-- converted for linux -->
<xml>
MEDIAHEAD
        ;

print PLAYLIST "<xml>\n";

foreach $mediaRoot ( @mediaRoots )
{
    print "indexing [" . $mediaRoot . "]\n" if $debug;
    mediaIndex( $mediaRoot, $mediaRoot, \$maxCount );
}

print MEDIA "</xml>\n";
print PLAYLIST "</xml>\n";

close MEDIA;
close MEDIAMAP;
close PLAYLIST;
# -- } main()

# Indexes a directory; recursive.
sub mediaIndex
{
    my ( $root, $dir, $maxCount ) = @_;

    my $status = opendir DIR, $dir || warn "Cannot open \"$dir\".";
    return if ! $status;
    my @subDirs = grep { ( ! /^\./ ) && -d "$dir/$_" } readdir(DIR);
    rewinddir DIR;
    my @files = grep { ( ! /^\./ ) && -f "$dir/$_" } readdir(DIR);
    closedir DIR;
    @subDirs = sort { $a cmp $b; } @subDirs;
    @files = sort { $a cmp $b; } @files;
    my @audioIds = ();
    my @videoIds = ();
    $dir =~ /([^\/]+)$/;
    my $actualSubDir = $1;

    foreach my $file ( @files )
    {
        # determine type
        # if ( audio ) { write audio entries }
        # else if ( video ) { write video entries }
        # else { ignore }

        if ( $$maxCount <= 0 )
        {
            return;
        }
        print "" . $$maxCount . "\n" if $debug;

        if ( $file =~ /\.mp3$/ )
        { # audio
            $$maxCount--;
            print "audio file [$dir/$file]\n" if $verbose;
            # media.xml entry
            my @mp3data = getMp3Data( "$dir/$file" );
            if ( $mp3data[0] eq "" )
            {
                $mp3data[0] = $file;
            }
            if ( $mp3data[6] eq "" )
            {
                $mp3data[6] = $actualSubDir;
            }
            my $id = $ID++;
            push @mp3data, ( $id, "mp3", $dir, $file );
            print MEDIA formatMediaXML_audio( @mp3data );
            print MEDIAMAP getMediaMapEntry( $id, $root, $dir, $file );
	    # misc
            push @audioIds, $id;
        }
        elsif ( $file =~ /\.(mpe?g|avi|divx)$/ )
        { # video
          # FIXME
    	  print "video file [$dir/$file]\n" if $verbose;
          my $id = $ID++;
          print MEDIA formatMediaXML_video( $id, $dir, $file );
          print MEDIAMAP getMediaMapEntry( $id, $root, $dir, $file );
          push @videoIds, $id;
        }
        else
        {
            print "unknown [$dir/$file]\n" if $debug;
        }
    }

    my $plName = xmlEscape($actualSubDir);
    if ( scalar( @audioIds ) > 0 )
    {
        print PLAYLIST formatPlaylistEntry( "audio", $plName, \@audioIds );
    }
    if ( scalar( @videoIds ) > 0 )
    {
        print PLAYLIST formatPlaylistEntry( "video", $plName, \@videoIds );
    }

    foreach my $subDir ( @subDirs )
    {
        mediaIndex( $root, "$dir/$subDir", $maxCount );
    }
}

sub getMediaMapEntry
{
    my ( $id, $root, $dir, $file ) = @_;
    my $tagPath = "${tagRoot}/.tags/${file}_${id}.tag"; # $dir;
    # $tagPath =~ s/${root}(\/.*)?/${root}\/\.tags\/${file}_${id}.tag/;
    return <<MEDIAMAPENTRY;
[${id}]
"+$tagPath"
"$dir/$file"

MEDIAMAPENTRY
;
}

sub formatMediaXML_video
{
    my ( $id, $dir, $file ) = @_;
    if ( ! $file =~ /\.(.*)$/ )
    {
        warn "Couldn't find file extension in [" . $file . "]";
        return "";
    }
    $type = $1;
    $dir = xmlEscape( $dir );
    $file = xmlEscape( $file );
    
    return <<VMX;
<video>
    <id>${id}</id>
    <name>${file}</name>
    <type>${type}</type>
    <path>${dir}</path>
    <filename0>${dir}/${file}</filename0>
</video>
VMX
}

sub formatMediaXML_audio
{
    my ( $title, $runtime, $year, $genre, $bitrate, $artist, $album, $trackNo, $id, $type, $path, $file ) = @_;
    $title = xmlEscape( $title );
    $genre = xmlEscape( $genre );
    $artist = xmlEscape( $artist );
    $album = xmlEscape( $album );
    $path = xmlEscape( $path );
    $file = xmlEscape( $file );

return <<MEDIA;
<audio>
  <id>$id</id>
  <artist>${artist}</artist>
  <title>${title}</title>
  <album>${album}</album>
  <type>${type}</type>
  <released>${year}</released>
  <genre>${genre}</genre>
  <bitrate>${bitrate}</bitrate>
  <runningtime>${runtime}</runningtime>
  <path>${path}</path>
  <tracknumber>${trackNo}</tracknumber>
  <filename0>${path}/${file}</filename0>
</audio>
MEDIA
;
}

# return an 8-element array:
# title, minutes/seconds, year, genre, bitrate, artist, album, track-number
sub getMp3Data
{
    my ( $file ) = @_;
    my @output = `mp3info -r a -p "%t|%m:%s|%y|%g|%r|%a|%l|%n" "$file" 2>&1`;
    print "mp3info status code: " . $? . " [" . join( ',', @output ) . "]\n" if $debug;
    my $realOutput = $output[0];
    if ( $output[0] =~ /does not have/ )
    {
        # FIXME : attempt parsing data from directory/file name.
        $realOutput = $output[1];
    }

    my @data = ( );
    @data = split( '\|', $realOutput );
    for ( my $i = 0; $i < 8; $i++ )
    {
        $data[$i] = $data[$i];
    }
    if ( $data[1] =~ /(\d):(\d)/ )
    {
        # clean up no-zero-padded-seconds formatting issue. :P
        my ( $min, $sec ) = ( $1, $2 );
        $data[1] = "${min}:0${sec}";
    }   
    return @data;
}


### BE VERY CAREFUL HERE
#
# This particular XML file is being parsed by a braindead monkey who has
# spent TOO MUCH TIME using python -- whitespace is _quite_ significant,
# here, specifically: tabs [^I].
# 
# :( :P
sub formatPlaylistEntry
{
    my ( $type, $name, $entryListRef ) = @_;
    if ( scalar( @$entryListRef ) == 0 )
    {
        return "";
    }
    my $entry = <<PLAYLISTENT;
	<playlist>
		<name>${name}</name>
		<type>${type}</type>
PLAYLISTENT
;

    $entry .= "\t\t<entry>";
    $entry .= join( "</entry>\n\t\t<entry>", @$entryListRef );
    $entry .= "</entry>\n\t</playlist>\n";
    return $entry;
}

sub xmlEscape
{
    my ( $str ) = @_;

    $str =~ s/&/&amp;/g;
    $str =~ s/</&lt;/g;
    $str =~ s/>/&gt;/g;

    my @chars = split( //, $str );
    my $newStr = "";
    CHAR: foreach my $char ( @chars )
    {
        if ( ord $char > 128 )
        {
            $char = "&#" . ord( $char ) . ";";
        }
        $newStr .= $char;
    }
    $str = $newStr;
    return $str;
}
