From 4e88de837e0cd0217c50da305e59cce2342ee1ec Mon Sep 17 00:00:00 2001 From: Hardeep Sidhu Date: Wed, 28 Jun 2006 15:44:51 +0000 Subject: Re-adding songdb.pl with support for tagcache. Works with mp3 and has partial support for ogg. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@10150 a1c6a512-1295-4272-9138-f99709370657 --- tools/songdb.pl | 448 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100755 tools/songdb.pl (limited to 'tools/songdb.pl') diff --git a/tools/songdb.pl b/tools/songdb.pl new file mode 100755 index 0000000000..cba30492da --- /dev/null +++ b/tools/songdb.pl @@ -0,0 +1,448 @@ +#!/usr/bin/perl +# +# Rockbox song database docs: +# http://www.rockbox.org/twiki/bin/view/Main/TagCache +# + +use mp3info; +use vorbiscomm; + +# configuration settings +my $db = "tagcache"; +my $dir; +my $strip; +my $add; +my $verbose; +my $help; +my $dirisalbum; +my $littleendian = 0; +my $dbver = 0x54434804; + +# file data +my %entries; + +while($ARGV[0]) { + if($ARGV[0] eq "--path") { + $dir = $ARGV[1]; + shift @ARGV; + shift @ARGV; + } + elsif($ARGV[0] eq "--db") { + $db = $ARGV[1]; + shift @ARGV; + shift @ARGV; + } + elsif($ARGV[0] eq "--strip") { + $strip = $ARGV[1]; + shift @ARGV; + shift @ARGV; + } + elsif($ARGV[0] eq "--add") { + $add = $ARGV[1]; + shift @ARGV; + shift @ARGV; + } + elsif($ARGV[0] eq "--dirisalbum") { + $dirisalbum = 1; + shift @ARGV; + } + elsif($ARGV[0] eq "--littleendian") { + $littleendian = 1; + shift @ARGV; + } + elsif($ARGV[0] eq "--verbose") { + $verbose = 1; + shift @ARGV; + } + elsif($ARGV[0] eq "--help" or ($ARGV[0] eq "-h")) { + $help = 1; + shift @ARGV; + } + else { + shift @ARGV; + } +} + +if(! -d $dir or $help) { + print "'$dir' is not a directory\n" if ($dir ne "" and ! -d $dir); + print < [--db ] [--strip ] [--add ] [--dirisalbum] [--littleendian] [--verbose] [--help] + +Options: + + --path Where your music collection is found + --db Prefix for output files. Defaults to tagcache. + --strip Removes this string from the left of all file names + --add Adds this string to the left of all file names + --dirisalbum Use dir name as album name if the album name is missing in the + tags + --littleendian Write out data as little endian (for simulator) + --verbose Shows more details while working + --help This text +MOO +; + exit; +} + +sub get_oggtag { + my $fn = shift; + my %hash; + + my $ogg = vorbiscomm->new($fn); + + my $h= $ogg->load; + + # Convert this format into the same format used by the id3 parser hash + + foreach my $k ($ogg->comment_tags()) + { + foreach my $cmmt ($ogg->comment($k)) + { + my $n; + if($k =~ /^artist$/i) { + $n = 'ARTIST'; + } + elsif($k =~ /^album$/i) { + $n = 'ALBUM'; + } + elsif($k =~ /^title$/i) { + $n = 'TITLE'; + } + $hash{$n}=$cmmt if($n); + } + } + + return \%hash; +} + +sub get_ogginfo { + my $fn = shift; + my %hash; + + my $ogg = vorbiscomm->new($fn); + + my $h= $ogg->load; + + return $ogg->{'INFO'}; +} + +# return ALL directory entries in the given dir +sub getdir { + my ($dir) = @_; + + $dir =~ s|/$|| if ($dir ne "/"); + + if (opendir(DIR, $dir)) { + my @all = readdir(DIR); + closedir DIR; + return @all; + } + else { + warn "can't opendir $dir: $!\n"; + } +} + +sub extractmp3 { + my ($dir, @files) = @_; + my @mp3; + for(@files) { + if( (/\.mp[23]$/i || /\.ogg$/i) && -f "$dir/$_" ) { + push @mp3, $_; + } + } + return @mp3; +} + +sub extractdirs { + my ($dir, @files) = @_; + $dir =~ s|/$||; + my @dirs; + for(@files) { + if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) { + push @dirs, $_; + } + } + return @dirs; +} + +sub singlefile { + my ($file) = @_; + my $hash; + my $info; + + if($file =~ /\.ogg$/i) { + $hash = get_oggtag($file); + $info = get_ogginfo($file); + } + else { + $hash = get_mp3tag($file); + $info = get_mp3info($file); + if (defined $$info{'BITRATE'}) { + $$hash{'BITRATE'} = $$info{'BITRATE'}; + } + + if (defined $$info{'SECS'}) { + $$hash{'SECS'} = $$info{'SECS'}; + } + } + + return $hash; +} + +sub dodir { + my ($dir)=@_; + + my %lcartists; + my %lcalbums; + + print "$dir\n"; + + # getdir() returns all entries in the given dir + my @a = getdir($dir); + + # extractmp3 filters out only the mp3 files from all given entries + my @m = extractmp3($dir, @a); + + my $f; + + for $f (sort @m) { + + my $id3 = singlefile("$dir/$f"); + + if (not defined $$id3{'ARTIST'} or $$id3{'ARTIST'} eq "") { + $$id3{'ARTIST'} = ""; + } + + # Only use one case-variation of each artist + if (exists($lcartists{lc($$id3{'ARTIST'})})) { + $$id3{'ARTIST'} = $lcartists{lc($$id3{'ARTIST'})}; + } + else { + $lcartists{lc($$id3{'ARTIST'})} = $$id3{'ARTIST'}; + } + #printf "Artist: %s\n", $$id3{'ARTIST'}; + + if (not defined $$id3{'ALBUM'} or $$id3{'ALBUM'} eq "") { + $$id3{'ALBUM'} = ""; + if ($dirisalbum) { + $$id3{'ALBUM'} = $dir; + } + } + + # Only use one case-variation of each album + if (exists($lcalbums{lc($$id3{'ALBUM'})})) { + $$id3{'ALBUM'} = $lcalbums{lc($$id3{'ALBUM'})}; + } + else { + $lcalbums{lc($$id3{'ALBUM'})} = $$id3{'ALBUM'}; + } + #printf "Album: %s\n", $$id3{'ALBUM'}; + + if (not defined $$id3{'GENRE'} or $$id3{'GENRE'} eq "") { + $$id3{'GENRE'} = ""; + } + #printf "Genre: %s\n", $$id3{'GENRE'}; + + if (not defined $$id3{'TITLE'} or $$id3{'TITLE'} eq "") { + # fall back on basename of the file if no title tag. + ($$id3{'TITLE'} = $f) =~ s/\.\w+$//; + } + #printf "Title: %s\n", $$id3{'TITLE'}; + + my $path = "$dir/$f"; + if ($strip ne "" and $path =~ /^$strip(.*)/) { + $path = $1; + } + + if ($add ne "") { + $path = $add . $path; + } + #printf "Path: %s\n", $path; + + if (not defined $$id3{'COMPOSER'} or $$id3{'COMPOSER'} eq "") { + $$id3{'COMPOSER'} = ""; + } + #printf "Composer: %s\n", $$id3{'COMPOSER'}; + + if (not defined $$id3{'YEAR'} or $$id3{'YEAR'} eq "") { + $$id3{'YEAR'} = "-1"; + } + #printf "Year: %s\n", $$id3{'YEAR'}; + + if (not defined $$id3{'TRACKNUM'} or $$id3{'TRACKNUM'} eq "") { + $$id3{'TRACKNUM'} = "-1"; + } + #printf "Track num: %s\n", $$id3{'TRACKNUM'}; + + if (not defined $$id3{'BITRATE'} or $$id3{'BITRATE'} eq "") { + $$id3{'BITRATE'} = "-1"; + } + #printf "Bitrate: %s\n", $$id3{'BITRATE'}; + + if (not defined $$id3{'SECS'} or $$id3{'SECS'} eq "") { + $$id3{'SECS'} = "-1"; + } + #printf "Length: %s\n", $$id3{'SECS'}; + + $$id3{'PATH'} = $path; + $entries{$path} = $id3; + } + + # extractdirs filters out only subdirectories from all given entries + my @d = extractdirs($dir, @a); + my $d; + + for $d (sort @d) { + $dir =~ s|/$||; + dodir("$dir/$d"); + } +} + +use_mp3_utf8(1); +dodir($dir); +print "\n"; + +sub dumpshort { + my ($num)=@_; + + # print "int: $num\n"; + + if ($littleendian) { + print DB pack "v", $num; + } + else { + print DB pack "n", $num; + } +} + +sub dumpint { + my ($num)=@_; + +# print "int: $num\n"; + + if ($littleendian) { + print DB pack "V", $num; + } + else { + print DB pack "N", $num; + } +} + +sub dump_tag_string { + my ($s, $index) = @_; + + my $strlen = length($s)+1; + my $padding = $strlen%4; + if ($padding > 0) { + $padding = 4 - $padding; + $strlen += $padding; + } + + dumpshort($strlen); + dumpshort($index); + print DB $s."\0"; + + for (my $i = 0; $i < $padding; $i++) { + print DB "X"; + } +} + +sub dump_tag_header { + my ($entry_count) = @_; + + my $size = tell(DB) - 12; + seek(DB, 0, 0); + + dumpint($dbver); + dumpint($size); + dumpint($entry_count); +} + +sub openfile { + my ($f) = @_; + open(DB, "> $f") || die "couldn't open $f"; + binmode(DB); +} + +sub create_tagcache_index_file { + my ($index, $key, $unique) = @_; + + my $num = 0; + my $prev = ""; + my $offset = 12; + + openfile $db ."_".$index.".tcd"; + dump_tag_header(0); + + for(sort {uc($entries{$a}->{$key}) cmp uc($entries{$b}->{$key})} keys %entries) { + if (!$unique || !($entries{$_}->{$key} eq $prev)) { + my $index; + + $num++; + $prev = $entries{$_}->{$key}; + $offset = tell(DB); + printf(" %s\n", $prev) if ($verbose); + + if ($unique) { + $index = 0xFFFF; + } + else { + $index = $entries{$_}->{'INDEX'}; + } + dump_tag_string($prev, $index); + } + $entries{$_}->{$key."_OFFSET"} = $offset; + } + + dump_tag_header($num); + close(DB); +} + +if (!scalar keys %entries) { + print "No songs found. Did you specify the right --path ?\n"; + print "Use the --help parameter to see all options.\n"; + exit; +} + +my $i = 0; +for (sort keys %entries) { + $entries{$_}->{'INDEX'} = $i; + $i++; +} + +if ($db) { + # Artists + create_tagcache_index_file(0, 'ARTIST', 1); + # Albums + create_tagcache_index_file(1, 'ALBUM', 1); + # Genres + create_tagcache_index_file(2, 'GENRE', 1); + # Titles + create_tagcache_index_file(3, 'TITLE', 0); + # Filenames + create_tagcache_index_file(4, 'PATH', 0); + # Composers + create_tagcache_index_file(5, 'COMPOSER', 1); + + # Master index file + openfile $db ."_idx.tcd"; + dump_tag_header(0); + + for (sort keys %entries) { + dumpint($entries{$_}->{'ARTIST_OFFSET'}); + dumpint($entries{$_}->{'ALBUM_OFFSET'}); + dumpint($entries{$_}->{'GENRE_OFFSET'}); + dumpint($entries{$_}->{'TITLE_OFFSET'}); + dumpint($entries{$_}->{'PATH_OFFSET'}); + dumpint($entries{$_}->{'COMPOSER_OFFSET'}); + dumpint($entries{$_}->{'YEAR'}); + dumpint($entries{$_}->{'TRACKNUM'}); + dumpint($entries{$_}->{'BITRATE'}); + dumpint($entries{$_}->{'SECS'}); + dumpint(0); + } + + dump_tag_header(scalar keys %entries); + close(DB); +} -- cgit v1.2.3