#!/usr/bin/perl # 2003.09.25 -- jsled # Copyright (C) 2003 Joshua Sled . 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 ] # 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 ' 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 ; print PLAYLIST "\n"; foreach $mediaRoot ( @mediaRoots ) { print "indexing [" . $mediaRoot . "]\n" if $debug; mediaIndex( $mediaRoot, $mediaRoot, \$maxCount ); } print MEDIA "\n"; print PLAYLIST "\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 < ${id} ${file} ${type} ${dir} ${dir}/${file} 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 < $id ${artist} ${title} ${album} ${type} ${year} ${genre} ${bitrate} ${runtime} ${path} ${trackNo} ${path}/${file} 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 = < ${name} ${type} PLAYLISTENT ; $entry .= "\t\t"; $entry .= join( "\n\t\t", @$entryListRef ); $entry .= "\n\t\n"; return $entry; } sub xmlEscape { my ( $str ) = @_; $str =~ s/&/&/g; $str =~ s//>/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; }