New weird syntax
[kugel-rb.git] / tools / songdb.pl
blobd4b3989e01bc8815634c4580cfceed7992d716fb
1 #!/usr/bin/perl
3 # Very sparse docs:
4 # http://search.cpan.org/~cnandor/MP3-Info-1.02/Info.pm
6 # MP3::Info is installed on debian using package 'libmp3-info-perl'
8 # Rockbox song database docs:
9 # http://www.rockbox.org/twiki/bin/view/Main/TagDatabase
11 use MP3::Info;
13 my $db;
14 my $dir;
15 my $strip;
17 while($ARGV[0]) {
18 if($ARGV[0] eq "--db") {
19 $db = $ARGV[1];
20 shift @ARGV;
21 shift @ARGV;
23 elsif($ARGV[0] eq "--path") {
24 $dir = $ARGV[1];
25 shift @ARGV;
26 shift @ARGV;
28 elsif($ARGV[0] eq "--strip") {
29 $strip = $ARGV[1];
30 shift @ARGV;
31 shift @ARGV;
33 else {
34 shift @ARGV;
37 my %entries;
38 my %genres;
39 my %albums;
40 my %years;
41 my %filename;
43 my $dbver = 1;
45 if(! -d $dir) {
46 print "songdb [--db <file>] --path <dir> [--strip <path>]\n";
47 print "given argument is not a directory!\n";
48 exit;
51 # return ALL directory entries in the given dir
52 sub getdir {
53 my ($dir) = @_;
55 opendir(DIR, $dir) || die "can't opendir $dir: $!";
56 # my @mp3 = grep { /\.mp3$/ && -f "$dir/$_" } readdir(DIR);
57 my @all = readdir(DIR);
58 closedir DIR;
59 return @all;
62 sub extractmp3 {
63 my ($dir, @files) = @_;
64 my @mp3;
65 for(@files) {
66 if( /\.mp3$/ && -f "$dir/$_" ) {
67 push @mp3, $_;
70 return @mp3;
73 sub extractdirs {
74 my ($dir, @files) = @_;
75 my @dirs;
76 for(@files) {
77 if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) {
78 push @dirs, $_;
81 return @dirs;
84 sub singlefile {
85 my ($file) = @_;
87 # print "Check $file\n";
89 my $hash = get_mp3tag($file);
90 # my $hash = get_mp3info($file);
92 # for(keys %$hash) {
93 # print "Info: $_ ".$hash->{$_}."\n";
94 # }
95 return $hash; # a hash reference
98 my $maxsongperalbum;
100 sub dodir {
101 my ($dir)=@_;
103 # getdir() returns all entries in the given dir
104 my @a = getdir($dir);
106 # extractmp3 filters out only the mp3 files from all given entries
107 my @m = extractmp3($dir, @a);
109 my $f;
111 for $f (sort @m) {
113 my $id3 = singlefile("$dir/$f");
115 # ARTIST
116 # COMMENT
117 # ALBUM
118 # TITLE
119 # GENRE
120 # TRACKNUM
121 # YEAR
123 #printf "Artist: %s\n", $id3->{'ARTIST'};
124 $entries{"$dir/$f"}= $id3;
125 $filename{$id3}="$dir/$f";
126 $artists{$id3->{'ARTIST'}}++ if($id3->{'ARTIST'});
127 $genres{$id3->{'GENRE'}}++ if($id3->{'GENRE'});
128 $years{$id3->{'YEAR'}}++ if($id3->{'YEAR'});
130 $id3->{'FILE'}="$dir/$f"; # store file name
132 $$id3{'ARTIST'} = "<no artist tag>" if ($$id3{'ARTIST'} eq "");
133 $$id3{'ALBUM'} = "<no album tag>" if ($$id3{'ALBUM'} eq "");
134 $$id3{'TITLE'} = "<no title tag>" if ($$id3{'TITLE'} eq "");
136 # prepend Artist name to handle duplicate album names from other
137 # artists
138 my $albumid = $id3->{'ALBUM'}."___".$id3->{'ARTIST'};
139 if($id3->{'ALBUM'}) {
140 my $num = ++$albums{$albumid};
141 if($num > $maxsongperalbum) {
142 $maxsongperalbum = $num;
144 $album2songs{$albumid}{$$id3{TITLE}} = $id3;
145 $artist2albums{$$id3{ARTIST}}{$$id3{ALBUM}} = $id3;
149 # extractdirs filters out only subdirectories from all given entries
150 my @d = extractdirs($dir, @a);
152 for $d (sort @d) {
153 #print "Subdir: $d\n";
154 dodir("$dir/$d");
159 dodir($dir);
161 print "File name table\n";
162 my $fc;
163 for(sort keys %entries) {
164 printf(" %s\n", $_);
165 $fc += length($_)+1;
168 my $maxsonglen;
169 my $sc;
170 print "\nSong title table\n";
171 #for(sort {$entries{$a}->{'TITLE'} cmp $entries{$b}->{'TITLE'}} %entries) {
172 for(sort {$entries{$a}->{'TITLE'} cmp $entries{$b}->{'TITLE'}} keys %entries) {
173 printf(" %s\n", $entries{$_}->{'TITLE'} );
174 my $l = length($entries{$_}->{'TITLE'});
175 if($l > $maxsonglen) {
176 $maxsonglen = $l;
179 $maxsonglen++; # include zero termination byte
180 while($maxsonglen&3) {
181 $maxsonglen++;
184 my $maxartistlen;
185 print "\nArtist table\n";
186 my $i=0;
187 my %artistcount;
188 for(sort keys %artists) {
189 printf(" %s\n", $_);
190 $artistcount{$_}=$i++;
191 my $l = length($_);
192 if($l > $maxartistlen) {
193 $maxartistlen = $l;
196 $l = scalar keys %{$artist2albums{$_}};
197 if ($l > $maxalbumsperartist) {
198 $maxalbumsperartist = $l;
201 $maxartistlen++; # include zero termination byte
202 while($maxartistlen&3) {
203 $maxartistlen++;
206 print "\nGenre table\n";
207 for(sort keys %genres) {
208 printf(" %s\n", $_);
211 print "\nYear table\n";
212 for(sort keys %years) {
213 printf(" %s\n", $_);
216 print "\nAlbum table\n";
217 my $maxalbumlen;
218 my %albumcount;
219 $i=0;
220 for(sort keys %albums) {
221 my @moo=split(/___/, $_);
222 printf(" %s\n", $moo[0]);
223 $albumcount{$_} = $i++;
224 my $l = length($moo[0]);
225 if($l > $maxalbumlen) {
226 $maxalbumlen = $l;
229 $maxalbumlen++; # include zero termination byte
230 while($maxalbumlen&3) {
231 $maxalbumlen++;
236 sub dumpint {
237 my ($num)=@_;
239 # print STDERR "int: $num\n";
241 printf DB ("%c%c%c%c",
242 $num>>24,
243 ($num&0xff0000)>>16,
244 ($num&0xff00)>>8,
245 ($num&0xff));
248 if($db) {
249 print STDERR "\nCreating db $db\n";
251 my $songentrysize = $maxsonglen + 12;
252 my $albumentrysize = $maxalbumlen + 4 + $maxsongperalbum*4;
253 my $artistentrysize = $maxartistlen + $maxalbumsperartist*4;
255 print STDERR "Max song length: $maxsonglen\n";
256 print STDERR "Max album length: $maxalbumlen\n";
257 print STDERR "Max artist length: $maxartistlen\n";
258 print STDERR "Database version: $dbver\n";
260 open(DB, ">$db") || die "couldn't make $db";
261 printf DB "RDB%c", $dbver;
263 $pathindex = 48; # paths always start at index 48
265 $songindex = $pathindex + $fc; # fc is size of all paths
266 $songindex++ while ($songindex & 3); # align to 32 bits
268 dumpint($songindex); # file position index of song table
269 dumpint(scalar(keys %entries)); # number of songs
270 dumpint($maxsonglen); # length of song name field
272 # set total size of song title table
273 $sc = scalar(keys %entries) * $songentrysize;
275 $albumindex = $songindex + $sc; # sc is size of all songs
276 dumpint($albumindex); # file position index of album table
277 dumpint(scalar(keys %albums)); # number of albums
278 dumpint($maxalbumlen); # length of album name field
279 dumpint($maxsongperalbum); # number of entries in songs-per-album array
281 my $ac = scalar(keys %albums) * $albumentrysize;
283 $artistindex = $albumindex + $ac; # ac is size of all albums
284 dumpint($artistindex); # file position index of artist table
285 dumpint(scalar(keys %artists)); # number of artists
286 dumpint($maxartistlen); # length of artist name field
287 dumpint($maxalbumsperartist); # number of entries in albums-per-artist array
289 my $l=0;
291 #### TABLE of file names ###
292 # path1
294 my %filenamepos;
295 for $f (sort keys %entries) {
296 printf DB ("%s\x00", $f);
297 $filenamepos{$f}= $l;
298 $l += length($f)+1;
300 while ($l & 3) {
301 print DB "\x00";
302 $l++;
305 #### TABLE of songs ###
306 # title of song1
307 # pointer to artist of song1
308 # pointer to album of song1
309 # pointer to filename of song1
311 my $offset = $songindex;
312 for(sort {$entries{$a}->{'TITLE'} cmp $entries{$b}->{'TITLE'}} keys %entries) {
313 my $f = $_;
314 my $id3 = $entries{$f};
315 my $t = $id3->{'TITLE'};
316 my $str = $t."\x00" x ($maxsonglen- length($t));
318 print DB $str; # title
320 my $a = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
321 dumpint($a + $artistindex); # pointer to artist of this song
323 $a = $albumcount{"$$id3{ALBUM}___$$id3{ARTIST}"} * $albumentrysize;
324 dumpint($a + $albumindex); # pointer to album of this song
326 # pointer to filename of this song
327 dumpint($filenamepos{$f} + $pathindex);
329 $$id3{'songoffset'} = $offset;
330 $offset += $songentrysize;
333 #### TABLE of albums ###
334 # name of album1
335 # pointers to artists of album1
336 # pointers to songs on album1
338 for(sort keys %albums) {
339 my $albumid = $_;
340 my @moo=split(/___/, $_);
341 my $t = $moo[0];
342 my $str = $t."\x00" x ($maxalbumlen - length($t));
343 print DB $str;
345 my $aoffset = $artistcount{$moo[0]} * $artistentrysize;
346 dumpint($aoffset + $artistindex); # pointer to artist of this album
348 my @songlist = keys %{$album2songs{$albumid}};
349 my $id3 = $album2songs{$albumid}{$songlist[0]};
350 if (defined $id3->{'TRACKNUM'}) {
351 @songlist = sort {
352 $album2songs{$albumid}{$a}->{'TRACKNUM'} <=>
353 $album2songs{$albumid}{$b}->{'TRACKNUM'}
354 } @songlist;
356 else {
357 @songlist = sort @songlist;
360 for (@songlist) {
361 my $id3 = $album2songs{$albumid}{$_};
362 dumpint($$id3{'songoffset'});
365 for (scalar keys %{$album2songs{$albumid}} .. $maxsongperalbum-1) {
366 print DB "\x00\x00\x00\x00";
370 #### TABLE of artists ###
371 # name of artist1
372 # pointers to albums of artist1
374 for (sort keys %artists) {
375 my $artist = $_;
376 my $str = $_."\x00" x ($maxartistlen - length($_));
377 print DB $str;
379 for (sort keys %{$artist2albums{$artist}}) {
380 my $id3 = $artist2albums{$artist}{$_};
381 my $a = $albumcount{"$$id3{'ALBUM'}___$$id3{'ARTIST'}"} * $albumentrysize;
382 dumpint($a + $albumindex);
385 for (scalar keys %{$artist2albums{$artist}} .. $maxalbumsperartist-1) {
386 print DB "\x00\x00\x00\x00";
391 close(DB);