diff options
author | Hardeep Sidhu <dyp@pobox.com> | 2006-06-28 15:44:51 +0000 |
---|---|---|
committer | Hardeep Sidhu <dyp@pobox.com> | 2006-06-28 15:44:51 +0000 |
commit | 4e88de837e0cd0217c50da305e59cce2342ee1ec (patch) | |
tree | 3795e79ed70691887f0d89dafcb6e979aa877bda /tools/songdb.pl | |
parent | 71cf604d8d317b7c2b167aac37493795046431cd (diff) | |
download | rockbox-4e88de837e0cd0217c50da305e59cce2342ee1ec.tar.gz 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-x | tools/songdb.pl | 448 |
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 @@ | |||
1 | #!/usr/bin/perl | ||
2 | # | ||
3 | # Rockbox song database docs: | ||
4 | # http://www.rockbox.org/twiki/bin/view/Main/TagCache | ||
5 | # | ||
6 | |||
7 | use mp3info; | ||
8 | use vorbiscomm; | ||
9 | |||
10 | # configuration settings | ||
11 | my $db = "tagcache"; | ||
12 | my $dir; | ||
13 | my $strip; | ||
14 | my $add; | ||
15 | my $verbose; | ||
16 | my $help; | ||
17 | my $dirisalbum; | ||
18 | my $littleendian = 0; | ||
19 | my $dbver = 0x54434804; | ||
20 | |||
21 | # file data | ||
22 | my %entries; | ||
23 | |||
24 | while($ARGV[0]) { | ||
25 | if($ARGV[0] eq "--path") { | ||
26 | $dir = $ARGV[1]; | ||
27 | shift @ARGV; | ||
28 | shift @ARGV; | ||
29 | } | ||
30 | elsif($ARGV[0] eq "--db") { | ||
31 | $db = $ARGV[1]; | ||
32 | shift @ARGV; | ||
33 | shift @ARGV; | ||
34 | } | ||
35 | elsif($ARGV[0] eq "--strip") { | ||
36 | $strip = $ARGV[1]; | ||
37 | shift @ARGV; | ||
38 | shift @ARGV; | ||
39 | } | ||
40 | elsif($ARGV[0] eq "--add") { | ||
41 | $add = $ARGV[1]; | ||
42 | shift @ARGV; | ||
43 | shift @ARGV; | ||
44 | } | ||
45 | elsif($ARGV[0] eq "--dirisalbum") { | ||
46 | $dirisalbum = 1; | ||
47 | shift @ARGV; | ||
48 | } | ||
49 | elsif($ARGV[0] eq "--littleendian") { | ||
50 | $littleendian = 1; | ||
51 | shift @ARGV; | ||
52 | } | ||
53 | elsif($ARGV[0] eq "--verbose") { | ||
54 | $verbose = 1; | ||
55 | shift @ARGV; | ||
56 | } | ||
57 | elsif($ARGV[0] eq "--help" or ($ARGV[0] eq "-h")) { | ||
58 | $help = 1; | ||
59 | shift @ARGV; | ||
60 | } | ||
61 | else { | ||
62 | shift @ARGV; | ||
63 | } | ||
64 | } | ||
65 | |||
66 | if(! -d $dir or $help) { | ||
67 | print "'$dir' is not a directory\n" if ($dir ne "" and ! -d $dir); | ||
68 | print <<MOO | ||
69 | |||
70 | songdb --path <dir> [--db <file>] [--strip <path>] [--add <path>] [--dirisalbum] [--littleendian] [--verbose] [--help] | ||
71 | |||
72 | Options: | ||
73 | |||
74 | --path <dir> Where your music collection is found | ||
75 | --db <file> Prefix for output files. Defaults to tagcache. | ||
76 | --strip <path> Removes this string from the left of all file names | ||
77 | --add <path> Adds this string to the left of all file names | ||
78 | --dirisalbum Use dir name as album name if the album name is missing in the | ||
79 | tags | ||
80 | --littleendian Write out data as little endian (for simulator) | ||
81 | --verbose Shows more details while working | ||
82 | --help This text | ||
83 | MOO | ||
84 | ; | ||
85 | exit; | ||
86 | } | ||
87 | |||
88 | sub get_oggtag { | ||
89 | my $fn = shift; | ||
90 | my %hash; | ||
91 | |||
92 | my $ogg = vorbiscomm->new($fn); | ||
93 | |||
94 | my $h= $ogg->load; | ||
95 | |||
96 | # Convert this format into the same format used by the id3 parser hash | ||
97 | |||
98 | foreach my $k ($ogg->comment_tags()) | ||
99 | { | ||
100 | foreach my $cmmt ($ogg->comment($k)) | ||
101 | { | ||
102 | my $n; | ||
103 | if($k =~ /^artist$/i) { | ||
104 | $n = 'ARTIST'; | ||
105 | } | ||
106 | elsif($k =~ /^album$/i) { | ||
107 | $n = 'ALBUM'; | ||
108 | } | ||
109 | elsif($k =~ /^title$/i) { | ||
110 | $n = 'TITLE'; | ||
111 | } | ||
112 | $hash{$n}=$cmmt if($n); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | return \%hash; | ||
117 | } | ||
118 | |||
119 | sub get_ogginfo { | ||
120 | my $fn = shift; | ||
121 | my %hash; | ||
122 | |||
123 | my $ogg = vorbiscomm->new($fn); | ||
124 | |||
125 | my $h= $ogg->load; | ||
126 | |||
127 | return $ogg->{'INFO'}; | ||
128 | } | ||
129 | |||
130 | # return ALL directory entries in the given dir | ||
131 | sub getdir { | ||
132 | my ($dir) = @_; | ||
133 | |||
134 | $dir =~ s|/$|| if ($dir ne "/"); | ||
135 | |||
136 | if (opendir(DIR, $dir)) { | ||
137 | my @all = readdir(DIR); | ||
138 | closedir DIR; | ||
139 | return @all; | ||
140 | } | ||
141 | else { | ||
142 | warn "can't opendir $dir: $!\n"; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | sub extractmp3 { | ||
147 | my ($dir, @files) = @_; | ||
148 | my @mp3; | ||
149 | for(@files) { | ||
150 | if( (/\.mp[23]$/i || /\.ogg$/i) && -f "$dir/$_" ) { | ||
151 | push @mp3, $_; | ||
152 | } | ||
153 | } | ||
154 | return @mp3; | ||
155 | } | ||
156 | |||
157 | sub extractdirs { | ||
158 | my ($dir, @files) = @_; | ||
159 | $dir =~ s|/$||; | ||
160 | my @dirs; | ||
161 | for(@files) { | ||
162 | if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) { | ||
163 | push @dirs, $_; | ||
164 | } | ||
165 | } | ||
166 | return @dirs; | ||
167 | } | ||
168 | |||
169 | sub singlefile { | ||
170 | my ($file) = @_; | ||
171 | my $hash; | ||
172 | my $info; | ||
173 | |||
174 | if($file =~ /\.ogg$/i) { | ||
175 | $hash = get_oggtag($file); | ||
176 | $info = get_ogginfo($file); | ||
177 | } | ||
178 | else { | ||
179 | $hash = get_mp3tag($file); | ||
180 | $info = get_mp3info($file); | ||
181 | if (defined $$info{'BITRATE'}) { | ||
182 | $$hash{'BITRATE'} = $$info{'BITRATE'}; | ||
183 | } | ||
184 | |||
185 | if (defined $$info{'SECS'}) { | ||
186 | $$hash{'SECS'} = $$info{'SECS'}; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | return $hash; | ||
191 | } | ||
192 | |||
193 | sub dodir { | ||
194 | my ($dir)=@_; | ||
195 | |||
196 | my %lcartists; | ||
197 | my %lcalbums; | ||
198 | |||
199 | print "$dir\n"; | ||
200 | |||
201 | # getdir() returns all entries in the given dir | ||
202 | my @a = getdir($dir); | ||
203 | |||
204 | # extractmp3 filters out only the mp3 files from all given entries | ||
205 | my @m = extractmp3($dir, @a); | ||
206 | |||
207 | my $f; | ||
208 | |||
209 | for $f (sort @m) { | ||
210 | |||
211 | my $id3 = singlefile("$dir/$f"); | ||
212 | |||
213 | if (not defined $$id3{'ARTIST'} or $$id3{'ARTIST'} eq "") { | ||
214 | $$id3{'ARTIST'} = "<Untagged>"; | ||
215 | } | ||
216 | |||
217 | # Only use one case-variation of each artist | ||
218 | if (exists($lcartists{lc($$id3{'ARTIST'})})) { | ||
219 | $$id3{'ARTIST'} = $lcartists{lc($$id3{'ARTIST'})}; | ||
220 | } | ||
221 | else { | ||
222 | $lcartists{lc($$id3{'ARTIST'})} = $$id3{'ARTIST'}; | ||
223 | } | ||
224 | #printf "Artist: %s\n", $$id3{'ARTIST'}; | ||
225 | |||
226 | if (not defined $$id3{'ALBUM'} or $$id3{'ALBUM'} eq "") { | ||
227 | $$id3{'ALBUM'} = "<Untagged>"; | ||
228 | if ($dirisalbum) { | ||
229 | $$id3{'ALBUM'} = $dir; | ||
230 | } | ||
231 | } | ||
232 | |||
233 | # Only use one case-variation of each album | ||
234 | if (exists($lcalbums{lc($$id3{'ALBUM'})})) { | ||
235 | $$id3{'ALBUM'} = $lcalbums{lc($$id3{'ALBUM'})}; | ||
236 | } | ||
237 | else { | ||
238 | $lcalbums{lc($$id3{'ALBUM'})} = $$id3{'ALBUM'}; | ||
239 | } | ||
240 | #printf "Album: %s\n", $$id3{'ALBUM'}; | ||
241 | |||
242 | if (not defined $$id3{'GENRE'} or $$id3{'GENRE'} eq "") { | ||
243 | $$id3{'GENRE'} = "<Untagged>"; | ||
244 | } | ||
245 | #printf "Genre: %s\n", $$id3{'GENRE'}; | ||
246 | |||
247 | if (not defined $$id3{'TITLE'} or $$id3{'TITLE'} eq "") { | ||
248 | # fall back on basename of the file if no title tag. | ||
249 | ($$id3{'TITLE'} = $f) =~ s/\.\w+$//; | ||
250 | } | ||
251 | #printf "Title: %s\n", $$id3{'TITLE'}; | ||
252 | |||
253 | my $path = "$dir/$f"; | ||
254 | if ($strip ne "" and $path =~ /^$strip(.*)/) { | ||
255 | $path = $1; | ||
256 | } | ||
257 | |||
258 | if ($add ne "") { | ||
259 | $path = $add . $path; | ||
260 | } | ||
261 | #printf "Path: %s\n", $path; | ||
262 | |||
263 | if (not defined $$id3{'COMPOSER'} or $$id3{'COMPOSER'} eq "") { | ||
264 | $$id3{'COMPOSER'} = "<Untagged>"; | ||
265 | } | ||
266 | #printf "Composer: %s\n", $$id3{'COMPOSER'}; | ||
267 | |||
268 | if (not defined $$id3{'YEAR'} or $$id3{'YEAR'} eq "") { | ||
269 | $$id3{'YEAR'} = "-1"; | ||
270 | } | ||
271 | #printf "Year: %s\n", $$id3{'YEAR'}; | ||
272 | |||
273 | if (not defined $$id3{'TRACKNUM'} or $$id3{'TRACKNUM'} eq "") { | ||
274 | $$id3{'TRACKNUM'} = "-1"; | ||
275 | } | ||
276 | #printf "Track num: %s\n", $$id3{'TRACKNUM'}; | ||
277 | |||
278 | if (not defined $$id3{'BITRATE'} or $$id3{'BITRATE'} eq "") { | ||
279 | $$id3{'BITRATE'} = "-1"; | ||
280 | } | ||
281 | #printf "Bitrate: %s\n", $$id3{'BITRATE'}; | ||
282 | |||
283 | if (not defined $$id3{'SECS'} or $$id3{'SECS'} eq "") { | ||
284 | $$id3{'SECS'} = "-1"; | ||
285 | } | ||
286 | #printf "Length: %s\n", $$id3{'SECS'}; | ||
287 | |||
288 | $$id3{'PATH'} = $path; | ||
289 | $entries{$path} = $id3; | ||
290 | } | ||
291 | |||
292 | # extractdirs filters out only subdirectories from all given entries | ||
293 | my @d = extractdirs($dir, @a); | ||
294 | my $d; | ||
295 | |||
296 | for $d (sort @d) { | ||
297 | $dir =~ s|/$||; | ||
298 | dodir("$dir/$d"); | ||
299 | } | ||
300 | } | ||
301 | |||
302 | use_mp3_utf8(1); | ||
303 | dodir($dir); | ||
304 | print "\n"; | ||
305 | |||
306 | sub dumpshort { | ||
307 | my ($num)=@_; | ||
308 | |||
309 | # print "int: $num\n"; | ||
310 | |||
311 | if ($littleendian) { | ||
312 | print DB pack "v", $num; | ||
313 | } | ||
314 | else { | ||
315 | print DB pack "n", $num; | ||
316 | } | ||
317 | } | ||
318 | |||
319 | sub dumpint { | ||
320 | my ($num)=@_; | ||
321 | |||
322 | # print "int: $num\n"; | ||
323 | |||
324 | if ($littleendian) { | ||
325 | print DB pack "V", $num; | ||
326 | } | ||
327 | else { | ||
328 | print DB pack "N", $num; | ||
329 | } | ||
330 | } | ||
331 | |||
332 | sub dump_tag_string { | ||
333 | my ($s, $index) = @_; | ||
334 | |||
335 | my $strlen = length($s)+1; | ||
336 | my $padding = $strlen%4; | ||
337 | if ($padding > 0) { | ||
338 | $padding = 4 - $padding; | ||
339 | $strlen += $padding; | ||
340 | } | ||
341 | |||
342 | dumpshort($strlen); | ||
343 | dumpshort($index); | ||
344 | print DB $s."\0"; | ||
345 | |||
346 | for (my $i = 0; $i < $padding; $i++) { | ||
347 | print DB "X"; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | sub dump_tag_header { | ||
352 | my ($entry_count) = @_; | ||
353 | |||
354 | my $size = tell(DB) - 12; | ||
355 | seek(DB, 0, 0); | ||
356 | |||
357 | dumpint($dbver); | ||
358 | dumpint($size); | ||
359 | dumpint($entry_count); | ||
360 | } | ||
361 | |||
362 | sub openfile { | ||
363 | my ($f) = @_; | ||
364 | open(DB, "> $f") || die "couldn't open $f"; | ||
365 | binmode(DB); | ||
366 | } | ||
367 | |||
368 | sub create_tagcache_index_file { | ||
369 | my ($index, $key, $unique) = @_; | ||
370 | |||
371 | my $num = 0; | ||
372 | my $prev = ""; | ||
373 | my $offset = 12; | ||
374 | |||
375 | openfile $db ."_".$index.".tcd"; | ||
376 | dump_tag_header(0); | ||
377 | |||
378 | for(sort {uc($entries{$a}->{$key}) cmp uc($entries{$b}->{$key})} keys %entries) { | ||
379 | if (!$unique || !($entries{$_}->{$key} eq $prev)) { | ||
380 | my $index; | ||
381 | |||
382 | $num++; | ||
383 | $prev = $entries{$_}->{$key}; | ||
384 | $offset = tell(DB); | ||
385 | printf(" %s\n", $prev) if ($verbose); | ||
386 | |||
387 | if ($unique) { | ||
388 | $index = 0xFFFF; | ||
389 | } | ||
390 | else { | ||
391 | $index = $entries{$_}->{'INDEX'}; | ||
392 | } | ||
393 | dump_tag_string($prev, $index); | ||
394 | } | ||
395 | $entries{$_}->{$key."_OFFSET"} = $offset; | ||
396 | } | ||
397 | |||
398 | dump_tag_header($num); | ||
399 | close(DB); | ||
400 | } | ||
401 | |||
402 | if (!scalar keys %entries) { | ||
403 | print "No songs found. Did you specify the right --path ?\n"; | ||
404 | print "Use the --help parameter to see all options.\n"; | ||
405 | exit; | ||
406 | } | ||
407 | |||
408 | my $i = 0; | ||
409 | for (sort keys %entries) { | ||
410 | $entries{$_}->{'INDEX'} = $i; | ||
411 | $i++; | ||
412 | } | ||
413 | |||
414 | if ($db) { | ||
415 | # Artists | ||
416 | create_tagcache_index_file(0, 'ARTIST', 1); | ||
417 | # Albums | ||
418 | create_tagcache_index_file(1, 'ALBUM', 1); | ||
419 | # Genres | ||
420 | create_tagcache_index_file(2, 'GENRE', 1); | ||
421 | # Titles | ||
422 | create_tagcache_index_file(3, 'TITLE', 0); | ||
423 | # Filenames | ||
424 | create_tagcache_index_file(4, 'PATH', 0); | ||
425 | # Composers | ||
426 | create_tagcache_index_file(5, 'COMPOSER', 1); | ||
427 | |||
428 | # Master index file | ||
429 | openfile $db ."_idx.tcd"; | ||
430 | dump_tag_header(0); | ||
431 | |||
432 | for (sort keys %entries) { | ||
433 | dumpint($entries{$_}->{'ARTIST_OFFSET'}); | ||
434 | dumpint($entries{$_}->{'ALBUM_OFFSET'}); | ||
435 | dumpint($entries{$_}->{'GENRE_OFFSET'}); | ||
436 | dumpint($entries{$_}->{'TITLE_OFFSET'}); | ||
437 | dumpint($entries{$_}->{'PATH_OFFSET'}); | ||
438 | dumpint($entries{$_}->{'COMPOSER_OFFSET'}); | ||
439 | dumpint($entries{$_}->{'YEAR'}); | ||
440 | dumpint($entries{$_}->{'TRACKNUM'}); | ||
441 | dumpint($entries{$_}->{'BITRATE'}); | ||
442 | dumpint($entries{$_}->{'SECS'}); | ||
443 | dumpint(0); | ||
444 | } | ||
445 | |||
446 | dump_tag_header(scalar keys %entries); | ||
447 | close(DB); | ||
448 | } | ||