source: gs2-extensions/parallel-building/trunk/src/perllib/cpan/Ogg/Vorbis/Header/PurePerl.pm@ 24626

Last change on this file since 24626 was 24626, checked in by jmt12, 13 years ago

An (almost) complete copy of the perllib directory from a (circa SEP2011) head checkout from Greenstone 2 trunk - in order to try and make merging in this extension a little easier later on (as there have been some major changes to buildcol.pl commited in the main trunk but not in the x64 branch)

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