summaryrefslogtreecommitdiffstats
path: root/tools/songdb.pl
diff options
context:
space:
mode:
authorHardeep Sidhu <dyp@pobox.com>2006-06-28 15:44:51 +0000
committerHardeep Sidhu <dyp@pobox.com>2006-06-28 15:44:51 +0000
commit4e88de837e0cd0217c50da305e59cce2342ee1ec (patch)
tree3795e79ed70691887f0d89dafcb6e979aa877bda /tools/songdb.pl
parent71cf604d8d317b7c2b167aac37493795046431cd (diff)
downloadrockbox-4e88de837e0cd0217c50da305e59cce2342ee1ec.tar.gz
rockbox-4e88de837e0cd0217c50da305e59cce2342ee1ec.tar.bz2
rockbox-4e88de837e0cd0217c50da305e59cce2342ee1ec.zip
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
Diffstat (limited to 'tools/songdb.pl')
-rwxr-xr-xtools/songdb.pl448
1 files changed, 448 insertions, 0 deletions
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 <<MOO
+
+songdb --path <dir> [--db <file>] [--strip <path>] [--add <path>] [--dirisalbum] [--littleendian] [--verbose] [--help]
+
+Options:
+
+ --path <dir> Where your music collection is found
+ --db <file> Prefix for output files. Defaults to tagcache.
+ --strip <path> Removes this string from the left of all file names
+ --add <path> 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'} = "<Untagged>";
+ }
+
+ # 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'} = "<Untagged>";
+ 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'} = "<Untagged>";
+ }
+ #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'} = "<Untagged>";
+ }
+ #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);
+}