summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xtools/songdb.pl49
-rw-r--r--tools/vorbiscomm.pm682
2 files changed, 722 insertions, 9 deletions
diff --git a/tools/songdb.pl b/tools/songdb.pl
index b2e248c898..9ed0c541bf 100755
--- a/tools/songdb.pl
+++ b/tools/songdb.pl
@@ -7,6 +7,8 @@
7# it runnable standalone on removable drives. See below. 7# it runnable standalone on removable drives. See below.
8# 8#
9 9
10use vorbiscomm;
11
10my $db = "rockbox.id3db"; 12my $db = "rockbox.id3db";
11my $dir; 13my $dir;
12my $strip; 14my $strip;
@@ -76,6 +78,36 @@ if(! -d $dir or $help) {
76 exit; 78 exit;
77} 79}
78 80
81sub get_oggtag {
82 my $fn = shift;
83 my %hash;
84
85 my $ogg = vorbiscomm->new($fn);
86
87 $ogg->load;
88
89 # Convert this format into the same format used by the id3 parser hash
90
91 foreach my $k ($ogg->comment_tags())
92 {
93 foreach my $cmmt ($ogg->comment($k))
94 {
95 my $n;
96 if($k =~ /^artist$/i) {
97 $n = 'ARTIST';
98 }
99 elsif($k =~ /^album$/i) {
100 $n = 'ALBUM';
101 }
102 $hash{$n}=$cmmt if($n);
103 # print $k, '=', $cmmt, "\n";
104 }
105 }
106
107 return \%hash;
108}
109
110
79# return ALL directory entries in the given dir 111# return ALL directory entries in the given dir
80sub getdir { 112sub getdir {
81 my ($dir) = @_; 113 my ($dir) = @_;
@@ -83,7 +115,6 @@ sub getdir {
83 $dir =~ s|/$|| if ($dir ne "/"); 115 $dir =~ s|/$|| if ($dir ne "/");
84 116
85 if (opendir(DIR, $dir)) { 117 if (opendir(DIR, $dir)) {
86 # my @mp3 = grep { /\.mp3$/ && -f "$dir/$_" } readdir(DIR);
87 my @all = readdir(DIR); 118 my @all = readdir(DIR);
88 closedir DIR; 119 closedir DIR;
89 return @all; 120 return @all;
@@ -97,7 +128,7 @@ sub extractmp3 {
97 my ($dir, @files) = @_; 128 my ($dir, @files) = @_;
98 my @mp3; 129 my @mp3;
99 for(@files) { 130 for(@files) {
100 if( /\.mp[23]$/ && -f "$dir/$_" ) { 131 if( (/\.mp[23]$/i || /\.ogg$/i) && -f "$dir/$_" ) {
101 push @mp3, $_; 132 push @mp3, $_;
102 } 133 }
103 } 134 }
@@ -118,15 +149,15 @@ sub extractdirs {
118 149
119sub singlefile { 150sub singlefile {
120 my ($file) = @_; 151 my ($file) = @_;
152 my $hash;
121 153
122# print "Check $file\n"; 154 if($file =~ /\.ogg$/i) {
123 155 $hash = get_oggtag($file);
124 my $hash = get_mp3tag($file); 156 }
125 # my $hash = get_mp3info($file); 157 else {
158 $hash = get_mp3tag($file);
159 }
126 160
127# for(keys %$hash) {
128# print "Info: $_ ".$hash->{$_}."\n";
129# }
130 return $hash; # a hash reference 161 return $hash; # a hash reference
131} 162}
132 163
diff --git a/tools/vorbiscomm.pm b/tools/vorbiscomm.pm
new file mode 100644
index 0000000000..4577a700a5
--- /dev/null
+++ b/tools/vorbiscomm.pm
@@ -0,0 +1,682 @@
1#############################################################################
2# This is
3# http://search.cpan.org/~amolloy/Ogg-Vorbis-Header-PurePerl-0.07/PurePerl.pm
4# written by Andrew Molloy
5# Code under GNU GENERAL PUBLIC LICENCE v2
6# $Id$
7#############################################################################
8
9package vorbiscomm;
10
11use 5.005;
12use strict;
13use warnings;
14
15use Fcntl qw/SEEK_END/;
16
17our $VERSION = '0.07';
18
19sub new
20{
21 my $class = shift;
22 my $file = shift;
23
24 return load($class, $file);
25}
26
27sub load
28{
29 my $class = shift;
30 my $file = shift;
31 my $from_new = shift;
32 my %data;
33 my $self;
34
35 # there must be a better way...
36 if ($class eq 'vorbiscomm')
37 {
38 $self = bless \%data, $class;
39 }
40 else
41 {
42 $self = $class;
43 }
44
45 if ($self->{'FILE_LOADED'})
46 {
47 return $self;
48 }
49
50 $self->{'FILE_LOADED'} = 1;
51
52 # check that the file exists and is readable
53 unless ( -e $file && -r _ )
54 {
55 warn "File does not exist or cannot be read.";
56 # file does not exist, can't do anything
57 return undef;
58 }
59 # open up the file
60 open FILE, $file;
61 # make sure dos-type systems can handle it...
62 binmode FILE;
63
64 $data{'filename'} = $file;
65 $data{'fileHandle'} = \*FILE;
66
67 _init(\%data);
68 _loadInfo(\%data);
69 _loadComments(\%data);
70 _calculateTrackLength(\%data);
71
72 close FILE;
73
74 return $self;
75}
76
77sub info
78{
79 my $self = shift;
80 my $key = shift;
81
82 # if the user did not supply a key, return the entire hash
83 unless ($key)
84 {
85 return $self->{'INFO'};
86 }
87
88 # otherwise, return the value for the given key
89 return $self->{'INFO'}{lc $key};
90}
91
92sub comment_tags
93{
94 my $self = shift;
95
96 return @{$self->{'COMMENT_KEYS'}};
97}
98
99sub comment
100{
101 my $self = shift;
102 my $key = shift;
103
104 # if the user supplied key does not exist, return undef
105 unless($self->{'COMMENTS'}{lc $key})
106 {
107 return undef;
108 }
109
110 return @{$self->{'COMMENTS'}{lc $key}};
111}
112
113sub add_comments
114{
115 warn "Ogg::Vorbis::Header::PurePerl add_comments() unimplemented.";
116}
117
118sub edit_comment
119{
120 warn "Ogg::Vorbis::Header::PurePerl edit_comment() unimplemented.";
121}
122
123sub delete_comment
124{
125 warn "Ogg::Vorbis::Header::PurePerl delete_comment() unimplemented.";
126}
127
128sub clear_comments
129{
130 warn "Ogg::Vorbis::Header::PurePerl clear_comments() unimplemented.";
131}
132
133sub path
134{
135 my $self = shift;
136
137 return $self->{'fileName'};
138}
139
140sub write_vorbis
141{
142 warn "Ogg::Vorbis::Header::PurePerl write_vorbis unimplemented.";
143}
144
145# "private" methods
146
147sub _init
148{
149 my $data = shift;
150 my $fh = $data->{'fileHandle'};
151 my $byteCount = 0;
152
153 # check the header to make sure this is actually an Ogg-Vorbis file
154 $byteCount = _checkHeader($data);
155
156 unless($byteCount)
157 {
158 # if it's not, we can't do anything
159 return undef;
160 }
161
162 $data->{'startInfoHeader'} = $byteCount;
163}
164
165sub _checkHeader
166{
167 my $data = shift;
168 my $fh = $data->{'fileHandle'};
169 my $buffer;
170 my $pageSegCount;
171 my $byteCount = 0; # stores how far into the file we've read,
172 # so later reads into the file can skip right
173 # past all of the header stuff
174
175 # check that the first four bytes are 'OggS'
176 read($fh, $buffer, 4);
177 if ($buffer ne 'OggS')
178 {
179 warn "This is not an Ogg bitstream (no OggS header).";
180 return undef;
181 }
182 $byteCount += 4;
183
184 # check the stream structure version (1 byte, should be 0x00)
185 read($fh, $buffer, 1);
186 if (ord($buffer) != 0x00)
187 {
188 warn "This is not an Ogg bitstream (invalid structure version).";
189 return undef;
190 }
191 $byteCount += 1;
192
193 # check the header type flag
194 # This is a bitfield, so technically we should check all of the bits
195 # that could potentially be set. However, the only value this should
196 # possibly have at the beginning of a proper Ogg-Vorbis file is 0x02,
197 # so we just check for that. If it's not that, we go on anyway, but
198 # give a warning (this behavior may (should?) be modified in the future.
199 read($fh, $buffer, 1);
200 if (ord($buffer) != 0x02)
201 {
202 warn "Invalid header type flag (trying to go ahead anyway).";
203 }
204 $byteCount += 1;
205
206 # skip to the page_segments count
207 read($fh, $buffer, 20);
208 $byteCount += 20;
209 # we do nothing with this data
210
211 # read the number of page segments
212 read($fh, $buffer, 1);
213 $pageSegCount = ord($buffer);
214 $byteCount += 1;
215
216 # read $pageSegCount bytes, then throw 'em out
217 read($fh, $buffer, $pageSegCount);
218 $byteCount += $pageSegCount;
219
220 # check packet type. Should be 0x01 (for indentification header)
221 read($fh, $buffer, 1);
222 if (ord($buffer) != 0x01)
223 {
224 warn "Wrong vorbis header type, giving up.";
225 return undef;
226 }
227 $byteCount += 1;
228
229 # check that the packet identifies itself as 'vorbis'
230 read($fh, $buffer, 6);
231 if ($buffer ne 'vorbis')
232 {
233 warn "This does not appear to be a vorbis stream, giving up.";
234 return undef;
235 }
236 $byteCount += 6;
237
238 # at this point, we assume the bitstream is valid
239 return $byteCount;
240}
241
242sub _loadInfo
243{
244 my $data = shift;
245 my $start = $data->{'startInfoHeader'};
246 my $fh = $data->{'fileHandle'};
247 my $buffer;
248 my $byteCount = $start;
249 my %info;
250
251 seek $fh, $start, 0;
252
253 # read the vorbis version
254 read($fh, $buffer, 4);
255 $info{'version'} = _decodeInt($buffer);
256 $byteCount += 4;
257
258 # read the number of audio channels
259 read($fh, $buffer, 1);
260 $info{'channels'} = ord($buffer);
261 $byteCount += 1;
262
263 # read the sample rate
264 read($fh, $buffer, 4);
265 $info{'rate'} = _decodeInt($buffer);
266 $byteCount += 4;
267
268 # read the bitrate maximum
269 read($fh, $buffer, 4);
270 $info{'bitrate_upper'} = _decodeInt($buffer);
271 $byteCount += 4;
272
273 # read the bitrate nominal
274 read($fh, $buffer, 4);
275 $info{'bitrate_nominal'} = _decodeInt($buffer);
276 $byteCount += 4;
277
278 # read the bitrate minimal
279 read($fh, $buffer, 4);
280 $info{'bitrate_lower'} = _decodeInt($buffer);
281 $byteCount += 4;
282
283 # read the blocksize_0 and blocksize_1
284 read($fh, $buffer, 1);
285 # these are each 4 bit fields, whose actual value is 2 to the power
286 # of the value of the field
287 $info{'blocksize_0'} = 2 << ((ord($buffer) & 0xF0) >> 4);
288 $info{'blocksize_1'} = 2 << (ord($buffer) & 0x0F);
289 $byteCount += 1;
290
291 # read the framing_flag
292 read($fh, $buffer, 1);
293 $info{'framing_flag'} = ord($buffer);
294 $byteCount += 1;
295
296 # bitrate_window is -1 in the current version of vorbisfile
297 $info{'bitrate_window'} = -1;
298
299 $data->{'startCommentHeader'} = $byteCount;
300
301 $data->{'INFO'} = \%info;
302}
303
304sub _loadComments
305{
306 my $data = shift;
307 my $fh = $data->{'fileHandle'};
308 my $start = $data->{'startCommentHeader'};
309 my $buffer;
310 my $page_segments;
311 my $vendor_length;
312 my $user_comment_count;
313 my $byteCount = $start;
314 my %comments;
315
316 seek $fh, $start, 0;
317
318 # check that the first four bytes are 'OggS'
319 read($fh, $buffer, 4);
320 if ($buffer ne 'OggS')
321 {
322 warn "No comment header?";
323 return undef;
324 }
325 $byteCount += 4;
326
327 # skip over next ten bytes
328 read($fh, $buffer, 10);
329 $byteCount += 10;
330
331 # read the stream serial number
332 read($fh, $buffer, 4);
333 push @{$data->{'commentSerialNumber'}}, _decodeInt($buffer);
334 $byteCount += 4;
335
336 # read the page sequence number (should be 0x01)
337 read($fh, $buffer, 4);
338 if (_decodeInt($buffer) != 0x01)
339 {
340 warn "Comment header page sequence number is not 0x01: " +
341 _decodeInt($buffer);
342 warn "Going to keep going anyway.";
343 }
344 $byteCount += 4;
345
346 # and ignore the page checksum for now
347 read($fh, $buffer, 4);
348 $byteCount += 4;
349
350 # get the number of entries in the segment_table...
351 read($fh, $buffer, 1);
352 $page_segments = _decodeInt($buffer);
353 $byteCount += 1;
354 # then skip on past it
355 read($fh, $buffer, $page_segments);
356 $byteCount += $page_segments;
357
358 # check the header type (should be 0x03)
359 read($fh, $buffer, 1);
360 if (ord($buffer) != 0x03)
361 {
362 warn "Wrong header type: " . ord($buffer);
363 }
364 $byteCount += 1;
365
366 # now we should see 'vorbis'
367 read($fh, $buffer, 6);
368 if ($buffer ne 'vorbis')
369 {
370 warn "Missing comment header. Should have found 'vorbis', found " .
371 $buffer;
372 }
373 $byteCount += 6;
374
375 # get the vendor length
376 read($fh, $buffer, 4);
377 $vendor_length = _decodeInt($buffer);
378 $byteCount += 4;
379
380 # read in the vendor
381 read($fh, $buffer, $vendor_length);
382 $comments{'vendor'} = $buffer;
383 $byteCount += $vendor_length;
384
385 # read in the number of user comments
386 read($fh, $buffer, 4);
387 $user_comment_count = _decodeInt($buffer);
388 $byteCount += 4;
389
390 $data->{'COMMENT_KEYS'} = [];
391
392 # finally, read the comments
393 for (my $i = 0; $i < $user_comment_count; $i++)
394 {
395 # first read the length
396 read($fh, $buffer, 4);
397 my $comment_length = _decodeInt($buffer);
398 $byteCount += 4;
399
400 # then the comment itself
401 read($fh, $buffer, $comment_length);
402 $byteCount += $comment_length;
403
404 my ($key) = $buffer =~ /^([^=]+)/;
405 my ($value) = $buffer =~ /=(.*)$/;
406
407 push @{$comments{lc $key}}, $value;
408 push @{$data->{'COMMENT_KEYS'}}, lc $key;
409 }
410
411 # read past the framing_bit
412 read($fh, $buffer, 1);
413 $byteCount += 1;
414
415 $data->{'INFO'}{'offset'} = $byteCount;
416
417 $data->{'COMMENTS'} = \%comments;
418}
419
420sub _calculateTrackLength
421{
422 my $data = shift;
423 my $fh = $data->{'fileHandle'};
424 my $buffer;
425 my $pageSize;
426 my $granule_position;
427
428 seek($fh,-8500,SEEK_END); # that magic number is from vorbisfile.c
429 # in the constant CHUNKSIZE, which comes
430 # with the comment /* a shade over 8k;
431 # anyone using pages well over 8k gets
432 # what they deserve */
433
434 # we just keep looking through the headers until we get to the last one
435 # (there might be a couple of blocks here)
436 while(_findPage($fh))
437 {
438 # stream structure version - must be 0x00
439 read($fh, $buffer, 1);
440 if (ord($buffer) != 0x00)
441 {
442 warn "Invalid stream structure version: " .
443 sprintf("%x", ord($buffer));
444 return;
445 }
446
447 # header type flag
448 read($fh, $buffer, 1);
449 # we should check this, but for now we'll just ignore it
450
451 # absolute granule position - this is what we need!
452 read($fh, $buffer, 8);
453 $granule_position = _decodeInt($buffer);
454
455 # skip past stream_serial_number, page_sequence_number, and crc
456 read($fh, $buffer, 12);
457
458 # page_segments
459 read($fh, $buffer, 1);
460 my $page_segments = ord($buffer);
461
462 # reset pageSize
463 $pageSize = 0;
464
465 # calculate approx. page size
466 for (my $i = 0; $i < $page_segments; $i++)
467 {
468 read($fh, $buffer, 1);
469 $pageSize += ord($buffer);
470 }
471
472 seek $fh, $pageSize, 1;
473 }
474
475 $data->{'INFO'}{'length'} =
476 int($granule_position / $data->{'INFO'}{'rate'});
477}
478
479sub _findPage
480{
481 # search forward in the file for the 'OggS' page header
482 my $fh = shift;
483 my $char;
484 my $curStr = '';
485
486 while (read($fh, $char, 1))
487 {
488 $curStr = $char . $curStr;
489 $curStr = substr($curStr, 0, 4);
490
491 # we are actually looking for the string 'SggO' because we
492 # tack character on to our test string backwards, to make
493 # trimming it to 4 characters easier.
494 if ($curStr eq 'SggO')
495 {
496 return 1;
497 }
498 }
499
500 return undef;
501}
502
503sub _decodeInt
504{
505 my $bytes = shift;
506 my $num = 0;
507 my @byteList = split //, $bytes;
508 my $numBytes = @byteList;
509 my $mult = 1;
510
511 for (my $i = 0; $i < $numBytes; $i ++)
512 {
513 $num += ord($byteList[$i]) * $mult;
514 $mult *= 256;
515 }
516
517 return $num;
518}
519
520sub _decodeInt5Bit
521{
522 my $byte = ord(shift);
523
524 $byte = $byte & 0xF8; # clear out the bottm 3 bits
525 $byte = $byte >> 3; # and shifted down to where it belongs
526
527 return $byte;
528}
529
530sub _decodeInt4Bit
531{
532 my $byte = ord(shift);
533
534 $byte = $byte & 0xFC; # clear out the bottm 4 bits
535 $byte = $byte >> 4; # and shifted down to where it belongs
536
537 return $byte;
538}
539
540sub _ilog
541{
542 my $x = shift;
543 my $ret = 0;
544
545 unless ($x > 0)
546 {
547 return 0;
548 }
549
550 while ($x > 0)
551 {
552 $ret++;
553 $x = $x >> 1;
554 }
555
556 return $ret;
557}
558
5591;
560__DATA__
561
562=head1 NAME
563
564Ogg::Vorbis::Header::PurePerl - An object-oriented interface to Ogg Vorbis
565information and comment fields, implemented entirely in Perl. Intended to be
566a drop in replacement for Ogg::Vobis::Header.
567
568Unlike Ogg::Vorbis::Header, this module will go ahead and fill in all of the
569information fields as soon as you construct the object. In other words,
570the C<new> and C<load> constructors have identical behavior.
571
572=head1 SYNOPSIS
573
574 use Ogg::Vorbis::Header::PurePerl;
575 my $ogg = Ogg::Vorbis::Header::PurePerl->new("song.ogg");
576 while (my ($k, $v) = each %{$ogg->info}) {
577 print "$k: $v\n";
578 }
579 foreach my $com ($ogg->comment_tags) {
580 print "$com: $_\n" foreach $ogg->comment($com);
581 }
582
583=head1 DESCRIPTION
584
585This module is intended to be a drop in replacement for Ogg::Vorbis::Header,
586implemented entirely in Perl. It provides an object-oriented interface to
587Ogg Vorbis information and comment fields. (NOTE: This module currently
588supports only read operations).
589
590=head1 CONSTRUCTORS
591
592=head2 C<new ($filename)>
593
594Opens an Ogg Vorbis file, ensuring that it exists and is actually an
595Ogg Vorbis stream. This method does not actually read any of the
596information or comment fields, and closes the file immediately.
597
598=head2 C<load ([$filename])>
599
600Opens an Ogg Vorbis file, ensuring that it exists and is actually an
601Ogg Vorbis stream, then loads the information and comment fields. This
602method can also be used without a filename to load the information
603and fields of an already constructed instance.
604
605=head1 INSTANCE METHODS
606
607=head2 C<info ([$key])>
608
609Returns a hashref containing information about the Ogg Vorbis file from
610the file's information header. Hash fields are: version, channels, rate,
611bitrate_upper, bitrate_nominal, bitrate_lower, bitrate_window, and length.
612The bitrate_window value is not currently used by the vorbis codec, and
613will always be -1.
614
615The optional parameter, key, allows you to retrieve a single value from
616the object's hash. Returns C<undef> if the key is not found.
617
618=head2 C<comment_tags ()>
619
620Returns an array containing the key values for the comment fields.
621These values can then be passed to C<comment> to retrieve their values.
622
623=head2 C<comment ($key)>
624
625Returns an array of comment values associated with the given key.
626
627=head2 C<add_comments ($key, $value, [$key, $value, ...])>
628
629Unimplemented.
630
631=head2 C<edit_comment ($key, $value, [$num])>
632
633Unimplemented.
634
635=head2 C<delete_comment ($key, [$num])>
636
637Unimplemented.
638
639=head2 C<clear_comments ([@keys])>
640
641Unimplemented.
642
643=head2 C<write_vorbis ()>
644
645Unimplemented.
646
647=head2 C<path ()>
648
649Returns the path/filename of the file the object represents.
650
651=head1 NOTE
652
653This is ALPHA SOFTWARE. It may very well be very broken. Do not use it in
654a production environment. You have been warned.
655
656=head1 ACKNOWLEDGEMENTS
657
658Dave Brown <cpan@dagbrown.com> made this module significantly faster
659at calculating the length of ogg files.
660
661Robert Moser II <rlmoser@earthlink.net> fixed a problem with files that
662have no comments.
663
664=head1 AUTHOR
665
666Andrew Molloy E<lt>amolloy@kaizolabs.comE<gt>
667
668=head1 COPYRIGHT
669
670Copyright (c) 2003, Andrew Molloy. All Rights Reserved.
671
672This program is free software; you can redistribute it and/or modify it
673under the terms of the GNU General Public License as published by the
674Free Software Foundation; either version 2 of the License, or (at
675your option) any later version. A copy of this license is included
676with this module (LICENSE.GPL).
677
678=head1 SEE ALSO
679
680L<Ogg::Vorbis::Header>, L<Ogg::Vorbis::Decoder>
681
682=cut