source: trunk/gsdl/perllib/cpan/MP3/Info.pm@ 7488

Last change on this file since 7488 was 7488, checked in by davidb, 20 years ago

Perl module from CPAN for extracting ID3 tags from MP3 files.

  • Property svn:keywords set to Author Date Id Revision
File size: 36.1 KB
Line 
1package MP3::Info;
2use overload;
3use strict;
4use Carp;
5use Symbol;
6
7use vars qw(
8 @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $REVISION
9 @mp3_genres %mp3_genres @winamp_genres %winamp_genres $try_harder
10 @t_bitrate @t_sampling_freq @frequency_tbl %v1_tag_fields
11 @v1_tag_names %v2_tag_names %v2_to_v1_names $AUTOLOAD
12 @mp3_info_fields
13);
14
15@ISA = 'Exporter';
16@EXPORT = qw(
17 set_mp3tag get_mp3tag get_mp3info remove_mp3tag
18 use_winamp_genres
19);
20@EXPORT_OK = qw(@mp3_genres %mp3_genres use_mp3_utf8);
21%EXPORT_TAGS = (
22 genres => [qw(@mp3_genres %mp3_genres)],
23 utf8 => [qw(use_mp3_utf8)],
24 all => [@EXPORT, @EXPORT_OK]
25);
26
27# $Id: Info.pm 7488 2004-05-28 03:28:26Z davidb $
28($REVISION) = ' $Revision: 7488 $ ' =~ /\$Revision:\s+([^\s]+)/;
29$VERSION = '1.02';
30
31=pod
32
33=head1 NAME
34
35MP3::Info - Manipulate / fetch info from MP3 audio files
36
37=head1 SYNOPSIS
38
39 #!perl -w
40 use MP3::Info;
41 my $file = 'Pearls_Before_Swine.mp3';
42 set_mp3tag($file, 'Pearls Before Swine', q"77's",
43 'Sticks and Stones', '1990',
44 q"(c) 1990 77's LTD.", 'rock & roll');
45
46 my $tag = get_mp3tag($file) or die "No TAG info";
47 $tag->{GENRE} = 'rock';
48 set_mp3tag($file, $tag);
49
50 my $info = get_mp3info($file);
51 printf "$file length is %d:%d\n", $info->{MM}, $info->{SS};
52
53=cut
54
55{
56 my $c = -1;
57 # set all lower-case and regular-cased versions of genres as keys
58 # with index as value of each key
59 %mp3_genres = map {($_, ++$c, lc, $c)} @mp3_genres;
60
61 # do it again for winamp genres
62 $c = -1;
63 %winamp_genres = map {($_, ++$c, lc, $c)} @winamp_genres;
64}
65
66=pod
67
68 my $mp3 = new MP3::Info $file;
69 $mp3->title('Perls Before Swine');
70 printf "$file length is %s, title is %s\n",
71 $mp3->time, $mp3->title;
72
73
74=head1 DESCRIPTION
75
76=over 4
77
78=item $mp3 = MP3::Info-E<gt>new(FILE)
79
80OOP interface to the rest of the module. The same keys
81available via get_mp3info and get_mp3tag are available
82via the returned object (using upper case or lower case;
83but note that all-caps "VERSION" will return the module
84version, not the MP3 version).
85
86Passing a value to one of the methods will set the value
87for that tag in the MP3 file, if applicable.
88
89=cut
90
91sub new {
92 my($pack, $file) = @_;
93
94 my $info = get_mp3info($file) or return undef;
95 my $tags = get_mp3tag($file) || { map { ($_ => undef) } @v1_tag_names };
96 my %self = (
97 FILE => $file,
98 TRY_HARDER => 0
99 );
100
101 @self{@mp3_info_fields, @v1_tag_names, 'file'} = (
102 @{$info}{@mp3_info_fields},
103 @{$tags}{@v1_tag_names},
104 $file
105 );
106
107 return bless \%self, $pack;
108}
109
110sub can {
111 my $self = shift;
112 return $self->SUPER::can(@_) unless ref $self;
113 my $name = uc shift;
114 return sub { $self->$name(@_) } if exists $self->{$name};
115 return undef;
116}
117
118sub AUTOLOAD {
119 my($self) = @_;
120 (my $name = uc $AUTOLOAD) =~ s/^.*://;
121
122 if (exists $self->{$name}) {
123 my $sub = exists $v1_tag_fields{$name}
124 ? sub {
125 if (defined $_[1]) {
126 $_[0]->{$name} = $_[1];
127 set_mp3tag($_[0]->{FILE}, $_[0]);
128 }
129 return $_[0]->{$name};
130 }
131 : sub {
132 return $_[0]->{$name}
133 };
134
135 no strict 'refs';
136 *{$AUTOLOAD} = $sub;
137 goto &$AUTOLOAD;
138
139 } else {
140 carp(sprintf "No method '$name' available in package %s.",
141 __PACKAGE__);
142 }
143}
144
145sub DESTROY {
146
147}
148
149
150=item use_mp3_utf8([STATUS])
151
152Tells MP3::Info to (or not) return TAG info in UTF-8.
153TRUE is 1, FALSE is 0. Default is FALSE.
154
155Will only be able to it on if Unicode::String is available. ID3v2
156tags will be converted to UTF-8 according to the encoding specified
157in each tag; ID3v1 tags will be assumed Latin-1 and converted
158to UTF-8.
159
160Function returns status (TRUE/FALSE). If no argument is supplied,
161or an unaccepted argument is supplied, function merely returns status.
162
163This function is not exported by default, but may be exported
164with the C<:utf8> or C<:all> export tag.
165
166=cut
167
168my $unicode_module = eval { require Unicode::String };
169my $UNICODE = 0;
170
171sub use_mp3_utf8 {
172 my($val) = @_;
173 if ($val == 1) {
174 $UNICODE = 1 if $unicode_module;
175 } elsif ($val == 0) {
176 $UNICODE = 0;
177 }
178 return $UNICODE;
179}
180
181=pod
182
183=item use_winamp_genres()
184
185Puts WinAmp genres into C<@mp3_genres> and C<%mp3_genres>
186(adds 68 additional genres to the default list of 80).
187This is a separate function because these are non-standard
188genres, but they are included because they are widely used.
189
190You can import the data structures with one of:
191
192 use MP3::Info qw(:genres);
193 use MP3::Info qw(:DEFAULT :genres);
194 use MP3::Info qw(:all);
195
196=cut
197
198sub use_winamp_genres {
199 %mp3_genres = %winamp_genres;
200 @mp3_genres = @winamp_genres;
201 return 1;
202}
203
204=pod
205
206=item remove_mp3tag (FILE [, VERSION, BUFFER])
207
208Can remove ID3v1 or ID3v2 tags. VERSION should be C<1> for ID3v1,
209C<2> for ID3v2, and C<ALL> for both.
210
211For ID3v1, removes last 128 bytes from file if those last 128 bytes begin
212with the text 'TAG'. File will be 128 bytes shorter.
213
214For ID3v2, removes ID3v2 tag. Because an ID3v2 tag is at the
215beginning of the file, we rewrite the file after removing the tag data.
216The buffer for rewriting the file is 4MB. BUFFER (in bytes) ca
217change the buffer size.
218
219Returns the number of bytes removed, or -1 if no tag removed,
220or undef if there is an error.
221
222=cut
223
224sub remove_mp3tag {
225 my($file, $version, $buf) = @_;
226 my($fh, $return);
227
228 $buf ||= 4096*1024; # the bigger the faster
229 $version ||= 1;
230
231 if (not (defined $file && $file ne '')) {
232 $@ = "No file specified";
233 return undef;
234 }
235
236 if (not -s $file) {
237 $@ = "File is empty";
238 return undef;
239 }
240
241 if (ref $file) { # filehandle passed
242 $fh = $file;
243 } else {
244 $fh = gensym;
245 if (not open $fh, "+< $file\0") {
246 $@ = "Can't open $file: $!";
247 return undef;
248 }
249 }
250
251 binmode $fh;
252
253 if ($version eq 1 || $version eq 'ALL') {
254 seek $fh, -128, 2;
255 my $tell = tell $fh;
256 if (<$fh> =~ /^TAG/) {
257 truncate $fh, $tell or carp "Can't truncate '$file': $!";
258 $return += 128;
259 }
260 }
261
262 if ($version eq 2 || $version eq 'ALL') {
263 my $h = _get_v2head($fh);
264 if ($h) {
265 local $\;
266 seek $fh, 0, 2;
267 my $eof = tell $fh;
268 my $off = $h->{tag_size};
269
270 while ($off < $eof) {
271 seek $fh, $off, 0;
272 read $fh, my($bytes), $buf;
273 seek $fh, $off - $h->{tag_size}, 0;
274 print $fh $bytes;
275 $off += $buf;
276 }
277
278 truncate $fh, $eof - $h->{tag_size}
279 or carp "Can't truncate '$file': $!";
280 $return += $h->{tag_size};
281 }
282 }
283
284 _close($file, $fh);
285
286 return $return || -1;
287}
288
289
290=pod
291
292=item set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
293
294=item set_mp3tag (FILE, $HASHREF)
295
296Adds/changes tag information in an MP3 audio file. Will clobber
297any existing information in file.
298
299Fields are TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE. All fields have
300a 30-byte limit, except for YEAR, which has a four-byte limit, and GENRE,
301which is one byte in the file. The GENRE passed in the function is a
302case-insensitive text string representing a genre found in C<@mp3_genres>.
303
304Will accept either a list of values, or a hashref of the type
305returned by C<get_mp3tag>.
306
307If TRACKNUM is present (for ID3v1.1), then the COMMENT field can only be
30828 bytes.
309
310ID3v2 support may come eventually. Note that if you set a tag on a file
311with ID3v2, the set tag will be for ID3v1[.1] only, and if you call
312C<get_mp3_tag> on the file, it will show you the (unchanged) ID3v2 tags,
313unless you specify ID3v1.
314
315=cut
316
317sub set_mp3tag {
318 my($file, $title, $artist, $album, $year, $comment, $genre, $tracknum) = @_;
319 my(%info, $oldfh, $ref, $fh);
320 local %v1_tag_fields = %v1_tag_fields;
321
322 # set each to '' if undef
323 for ($title, $artist, $album, $year, $comment, $tracknum, $genre,
324 (@info{@v1_tag_names}))
325 {$_ = defined() ? $_ : ''}
326
327 ($ref) = (overload::StrVal($title) =~ /^(?:.*\=)?([^=]*)\((?:[^\(]*)\)$/)
328 if ref $title;
329 # populate data to hashref if hashref is not passed
330 if (!$ref) {
331 (@info{@v1_tag_names}) =
332 ($title, $artist, $album, $year, $comment, $tracknum, $genre);
333
334 # put data from hashref into hashref if hashref is passed
335 } elsif ($ref eq 'HASH') {
336 %info = %$title;
337
338 # return otherwise
339 } else {
340 carp(<<'EOT');
341Usage: set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
342 set_mp3tag (FILE, $HASHREF)
343EOT
344 return undef;
345 }
346
347 if (not (defined $file && $file ne '')) {
348 $@ = "No file specified";
349 return undef;
350 }
351
352 if (not -s $file) {
353 $@ = "File is empty";
354 return undef;
355 }
356
357 # comment field length 28 if ID3v1.1
358 $v1_tag_fields{COMMENT} = 28 if $info{TRACKNUM};
359
360
361 # only if -w is on
362 if ($^W) {
363 # warn if fields too long
364 foreach my $field (keys %v1_tag_fields) {
365 $info{$field} = '' unless defined $info{$field};
366 if (length($info{$field}) > $v1_tag_fields{$field}) {
367 carp "Data too long for field $field: truncated to " .
368 "$v1_tag_fields{$field}";
369 }
370 }
371
372 if ($info{GENRE}) {
373 carp "Genre `$info{GENRE}' does not exist\n"
374 unless exists $mp3_genres{$info{GENRE}};
375 }
376 }
377
378 if ($info{TRACKNUM}) {
379 $info{TRACKNUM} =~ s/^(\d+)\/(\d+)$/$1/;
380 unless ($info{TRACKNUM} =~ /^\d+$/ &&
381 $info{TRACKNUM} > 0 && $info{TRACKNUM} < 256) {
382 carp "Tracknum `$info{TRACKNUM}' must be an integer " .
383 "from 1 and 255\n" if $^W;
384 $info{TRACKNUM} = '';
385 }
386 }
387
388 if (ref $file) { # filehandle passed
389 $fh = $file;
390 } else {
391 $fh = gensym;
392 if (not open $fh, "+< $file\0") {
393 $@ = "Can't open $file: $!";
394 return undef;
395 }
396 }
397
398 binmode $fh;
399 $oldfh = select $fh;
400 seek $fh, -128, 2;
401 # go to end of file if no tag, beginning of file if tag
402 seek $fh, (<$fh> =~ /^TAG/ ? -128 : 0), 2;
403
404 # get genre value
405 $info{GENRE} = $info{GENRE} && exists $mp3_genres{$info{GENRE}} ?
406 $mp3_genres{$info{GENRE}} : 255; # some default genre
407
408 local $\;
409 # print TAG to file
410 if ($info{TRACKNUM}) {
411 print pack "a3a30a30a30a4a28xCC", 'TAG', @info{@v1_tag_names};
412 } else {
413 print pack "a3a30a30a30a4a30C", 'TAG', @info{@v1_tag_names[0..4, 6]};
414 }
415
416 select $oldfh;
417
418 _close($file, $fh);
419
420 return 1;
421}
422
423=pod
424
425=item get_mp3tag (FILE [, VERSION, RAW_V2])
426
427Returns hash reference containing tag information in MP3 file. The keys
428returned are the same as those supplied for C<set_mp3tag>, except in the
429case of RAW_V2 being set.
430
431If VERSION is C<1>, the information is taken from the ID3v1 tag (if present).
432If VERSION is C<2>, the information is taken from the ID3v2 tag (if present).
433If VERSION is not supplied, or is false, the ID3v1 tag is read if present, and
434then, if present, the ID3v2 tag information will override any existing ID3v1
435tag info.
436
437If RAW_V2 is C<1>, the raw ID3v2 tag data is returned, without any manipulation
438of text encoding. The key name is the same as the frame ID (ID to name mappings
439are in the global %v2_tag_names).
440
441If RAW_V2 is C<2>, the ID3v2 tag data is returned, manipulating for Unicode if
442necessary, etc. It also takes multiple values for a given key (such as comments)
443and puts them in an arrayref.
444
445If the ID3v2 version is older than ID3v2.2.0 or newer than ID3v2.4.0, it will
446not be read.
447
448Strings returned will be in Latin-1, unless UTF-8 is specified (L<use_mp3_utf8>),
449(unless RAW_V2 is C<1>).
450
451Also returns a TAGVERSION key, containing the ID3 version used for the returned
452data (if TAGVERSION argument is C<0>, may contain two versions).
453
454=cut
455
456sub get_mp3tag {
457 my($file, $ver, $raw_v2) = @_;
458 my($tag, $v1, $v2, $v2h, %info, @array, $fh);
459 $raw_v2 ||= 0;
460 $ver = !$ver ? 0 : ($ver == 2 || $ver == 1) ? $ver : 0;
461
462 if (not (defined $file && $file ne '')) {
463 $@ = "No file specified";
464 return undef;
465 }
466
467 if (not -s $file) {
468 $@ = "File is empty";
469 return undef;
470 }
471
472 if (ref $file) { # filehandle passed
473 $fh = $file;
474 } else {
475 $fh = gensym;
476 if (not open $fh, "< $file\0") {
477 $@ = "Can't open $file: $!";
478 return undef;
479 }
480 }
481
482 binmode $fh;
483
484 if ($ver < 2) {
485 seek $fh, -128, 2;
486 while(defined(my $line = <$fh>)) { $tag .= $line }
487
488 if ($tag =~ /^TAG/) {
489 $v1 = 1;
490 if (substr($tag, -3, 2) =~ /\000[^\000]/) {
491 (undef, @info{@v1_tag_names}) =
492 (unpack('a3a30a30a30a4a28', $tag),
493 ord(substr($tag, -2, 1)),
494 $mp3_genres[ord(substr $tag, -1)]);
495 $info{TAGVERSION} = 'ID3v1.1';
496 } else {
497 (undef, @info{@v1_tag_names[0..4, 6]}) =
498 (unpack('a3a30a30a30a4a30', $tag),
499 $mp3_genres[ord(substr $tag, -1)]);
500 $info{TAGVERSION} = 'ID3v1';
501 }
502 if ($UNICODE) {
503 for my $key (keys %info) {
504 next unless $info{$key};
505 my $u = Unicode::String::latin1($info{$key});
506 $info{$key} = $u->utf8;
507 }
508 }
509 } elsif ($ver == 1) {
510 _close($file, $fh);
511 $@ = "No ID3v1 tag found";
512 return undef;
513 }
514 }
515
516 ($v2, $v2h) = _get_v2tag($fh);
517
518 unless ($v1 || $v2) {
519 _close($file, $fh);
520 $@ = "No ID3 tag found";
521 return undef;
522 }
523
524 if (($ver == 0 || $ver == 2) && $v2) {
525 if ($raw_v2 == 1 && $ver == 2) {
526 %info = %$v2;
527 $info{TAGVERSION} = $v2h->{version};
528 } else {
529 my $hash = $raw_v2 == 2 ? { map { ($_, $_) } keys %v2_tag_names } : \%v2_to_v1_names;
530 for my $id (keys %$hash) {
531 if (exists $v2->{$id}) {
532 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
533 $info{$hash->{$id}} = $mp3_genres[$1];
534 } else {
535 my $data1 = $v2->{$id};
536
537 # this is tricky ... if this is an arrayref,
538 # we want to only return one, so we pick the
539 # first one. but if it is a comment, we pick
540 # the first one where the first charcter after
541 # the language is NULL and not an additional
542 # sub-comment, because that is most likely to be
543 # the user-supplied comment
544 if (ref $data1 && !$raw_v2) {
545 if ($id =~ /^COMM?$/) {
546 my($newdata) = grep /^(....\000)/, @{$data1};
547 $data1 = $newdata || $data1->[0];
548 } else {
549 $data1 = $data1->[0];
550 }
551 }
552
553 $data1 = [ $data1 ] if ! ref $data1;
554
555 for my $data (@$data1) {
556 $data =~ s/^(.)//; # strip first char (text encoding)
557 my $encoding = $1;
558 my $desc;
559 if ($id =~ /^COM[M ]?$/) {
560 $data =~ s/^(?:...)//; # strip language
561 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
562 # for sub-comment
563 $desc = $1;
564 }
565
566 if ($UNICODE) {
567 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
568 my $u = Unicode::String::utf16($data);
569 $data = $u->utf8;
570 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
571 } elsif ($encoding eq "\000") {
572 my $u = Unicode::String::latin1($data);
573 $data = $u->utf8;
574 }
575 }
576
577 if ($raw_v2 == 2 && $desc) {
578 $data = { $desc => $data };
579 }
580
581 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
582 if (ref $info{$hash->{$id}} eq 'ARRAY') {
583 push @{$info{$hash->{$id}}}, $data;
584 } else {
585 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
586 }
587 } else {
588 $info{$hash->{$id}} = $data;
589 }
590 }
591 }
592 }
593 }
594 if ($ver == 0 && $info{TAGVERSION}) {
595 $info{TAGVERSION} .= ' / ' . $v2h->{version};
596 } else {
597 $info{TAGVERSION} = $v2h->{version};
598 }
599 }
600 }
601
602 unless ($raw_v2 && $ver == 2) {
603 foreach my $key (keys %info) {
604 if (defined $info{$key}) {
605 $info{$key} =~ s/\000+.*//g;
606 $info{$key} =~ s/\s+$//;
607 }
608 }
609
610 for (@v1_tag_names) {
611 $info{$_} = '' unless defined $info{$_};
612 }
613 }
614
615 if (keys %info && exists $info{GENRE} && ! defined $info{GENRE}) {
616 $info{GENRE} = '';
617 }
618
619 _close($file, $fh);
620
621 return keys %info ? {%info} : undef;
622}
623
624sub _get_v2tag {
625 my($fh) = @_;
626 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
627 $h = {};
628
629 $v2 = _get_v2head($fh) or return;
630 if ($v2->{major_version} < 2) {
631 carp "This is $v2->{version}; " .
632 "ID3v2 versions older than ID3v2.2.0 not supported\n"
633 if $^W;
634 return;
635 }
636
637 if ($v2->{major_version} == 2) {
638 $hlen = 6;
639 $num = 3;
640 } else {
641 $hlen = 10;
642 $num = 4;
643 }
644
645 $myseek = sub {
646 seek $fh, $off, 0;
647 read $fh, my($bytes), $hlen;
648 return unless $bytes =~ /^([A-Z0-9]{$num})/
649 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes
650 my($id, $size) = ($1, $hlen);
651 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num);
652 for my $i (0 .. ($num - 1)) {
653 $size += $bytes[$i] * 256 ** $i;
654 }
655 return($id, $size);
656 };
657
658 $off = $v2->{ext_header_size} + 10;
659
660 while ($off < $v2->{tag_size}) {
661 my($id, $size) = &$myseek or last;
662 seek $fh, $off + $hlen, 0;
663 read $fh, my($bytes), $size - $hlen;
664 if (exists $h->{$id}) {
665 if (ref $h->{$id} eq 'ARRAY') {
666 push @{$h->{$id}}, $bytes;
667 } else {
668 $h->{$id} = [$h->{$id}, $bytes];
669 }
670 } else {
671 $h->{$id} = $bytes;
672 }
673 $off += $size;
674 }
675
676 return($h, $v2);
677}
678
679
680=pod
681
682=item get_mp3info (FILE)
683
684Returns hash reference containing file information for MP3 file.
685This data cannot be changed. Returned data:
686
687 VERSION MPEG audio version (1, 2, 2.5)
688 LAYER MPEG layer description (1, 2, 3)
689 STEREO boolean for audio is in stereo
690
691 VBR boolean for variable bitrate
692 BITRATE bitrate in kbps (average for VBR files)
693 FREQUENCY frequency in kHz
694 SIZE bytes in audio stream
695
696 SECS total seconds
697 MM minutes
698 SS leftover seconds
699 MS leftover milliseconds
700 TIME time in MM:SS
701
702 COPYRIGHT boolean for audio is copyrighted
703 PADDING boolean for MP3 frames are padded
704 MODE channel mode (0 = stereo, 1 = joint stereo,
705 2 = dual channel, 3 = single channel)
706 FRAMES approximate number of frames
707 FRAME_LENGTH approximate length of a frame
708 VBR_SCALE VBR scale from VBR header
709
710On error, returns nothing and sets C<$@>.
711
712=cut
713
714sub get_mp3info {
715 my($file) = @_;
716 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
717
718 if (not (defined $file && $file ne '')) {
719 $@ = "No file specified";
720 return undef;
721 }
722
723 if (not -s $file) {
724 $@ = "File is empty";
725 return undef;
726 }
727
728 if (ref $file) { # filehandle passed
729 $fh = $file;
730 } else {
731 $fh = gensym;
732 if (not open $fh, "< $file\0") {
733 $@ = "Can't open $file: $!";
734 return undef;
735 }
736 }
737
738 $off = 0;
739 $tot = 4096;
740
741 $myseek = sub {
742 seek $fh, $off, 0;
743 read $fh, $byte, 4;
744 };
745
746 binmode $fh;
747 &$myseek;
748
749 if ($off == 0) {
750 if (my $id3v2 = _get_v2head($fh)) {
751 $tot += $off += $id3v2->{tag_size};
752 &$myseek;
753 }
754 }
755
756 $h = _get_head($byte);
757 until (_is_mp3($h)) {
758 $off++;
759 &$myseek;
760 $h = _get_head($byte);
761 if ($off > $tot && !$try_harder) {
762 _close($file, $fh);
763 $@ = "Couldn't find MP3 header (perhaps set " .
764 '$MP3::Info::try_harder and retry)';
765 return undef;
766 }
767 }
768
769 my $vbr = _get_vbr($fh, $h, \$off);
770
771 seek $fh, 0, 2;
772 $eof = tell $fh;
773 seek $fh, -128, 2;
774 $off += 128 if <$fh> =~ /^TAG/ ? 1 : 0;
775
776 _close($file, $fh);
777
778 $h->{size} = $eof - $off;
779
780 return _get_info($h, $vbr);
781}
782
783sub _get_info {
784 my($h, $vbr) = @_;
785 my $i;
786
787 $i->{VERSION} = $h->{IDR} == 2 ? 2 : $h->{IDR} == 3 ? 1 :
788 $h->{IDR} == 0 ? 2.5 : 0;
789 $i->{LAYER} = 4 - $h->{layer};
790 $i->{VBR} = defined $vbr ? 1 : 0;
791
792 $i->{COPYRIGHT} = $h->{copyright} ? 1 : 0;
793 $i->{PADDING} = $h->{padding_bit} ? 1 : 0;
794 $i->{STEREO} = $h->{mode} == 3 ? 0 : 1;
795 $i->{MODE} = $h->{mode};
796
797 $i->{SIZE} = $vbr && $vbr->{bytes} ? $vbr->{bytes} : $h->{size};
798
799 my $mfs = $h->{fs} / ($h->{ID} ? 144000 : 72000);
800 $i->{FRAMES} = int($vbr && $vbr->{frames}
801 ? $vbr->{frames}
802 : $i->{SIZE} / $h->{bitrate} / $mfs
803 );
804
805 if ($vbr) {
806 $i->{VBR_SCALE} = $vbr->{scale} if $vbr->{scale};
807 $h->{bitrate} = $i->{SIZE} / $i->{FRAMES} * $mfs;
808 if (not $h->{bitrate}) {
809 $@ = "Couldn't determine VBR bitrate";
810 return undef;
811 }
812 }
813
814 $h->{'length'} = ($i->{SIZE} * 8) / $h->{bitrate} / 10;
815 $i->{SECS} = $h->{'length'} / 100;
816 $i->{MM} = int $i->{SECS} / 60;
817 $i->{SS} = int $i->{SECS} % 60;
818 $i->{MS} = (($i->{SECS} - ($i->{MM} * 60) - $i->{SS}) * 1000);
819# $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS});
820# int($i->{MS} / 100 * 75); # is this right?
821 $i->{TIME} = sprintf "%.2d:%.2d", @{$i}{'MM', 'SS'};
822
823 $i->{BITRATE} = int $h->{bitrate};
824 # should we just return if ! FRAMES?
825 $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES};
826 $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}];
827
828 return $i;
829}
830
831sub _get_head {
832 my($byte) = @_;
833 my($bytes, $h);
834
835 $bytes = _unpack_head($byte);
836 @$h{qw(IDR ID layer protection_bit
837 bitrate_index sampling_freq padding_bit private_bit
838 mode mode_extension copyright original
839 emphasis version_index bytes)} = (
840 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1,
841 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1,
842 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1,
843 $bytes&3, ($bytes>>19)&3, $bytes
844 );
845
846 $h->{bitrate} = $t_bitrate[$h->{ID}][3 - $h->{layer}][$h->{bitrate_index}];
847 $h->{fs} = $t_sampling_freq[$h->{IDR}][$h->{sampling_freq}];
848
849 return $h;
850}
851
852sub _is_mp3 {
853 my $h = $_[0] or return undef;
854 return ! ( # all below must be false
855 $h->{bitrate_index} == 0
856 ||
857 $h->{version_index} == 1
858 ||
859 ($h->{bytes} & 0xFFE00000) != 0xFFE00000
860 ||
861 !$h->{fs}
862 ||
863 !$h->{bitrate}
864 ||
865 $h->{bitrate_index} == 15
866 ||
867 !$h->{layer}
868 ||
869 $h->{sampling_freq} == 3
870 ||
871 $h->{emphasis} == 2
872 ||
873 !$h->{bitrate_index}
874 ||
875 ($h->{bytes} & 0xFFFF0000) == 0xFFFE0000
876 ||
877 ($h->{ID} == 1 && $h->{layer} == 3 && $h->{protection_bit} == 1)
878 ||
879 ($h->{mode_extension} != 0 && $h->{mode} != 1)
880 );
881}
882
883sub _get_vbr {
884 my($fh, $h, $roff) = @_;
885 my($off, $bytes, @bytes, $myseek, %vbr);
886
887 $off = $$roff;
888 @_ = (); # closure confused if we don't do this
889
890 $myseek = sub {
891 my $n = $_[0] || 4;
892 seek $fh, $off, 0;
893 read $fh, $bytes, $n;
894 $off += $n;
895 };
896
897 $off += 4;
898
899 if ($h->{ID}) { # MPEG1
900 $off += $h->{mode} == 3 ? 17 : 32;
901 } else { # MPEG2
902 $off += $h->{mode} == 3 ? 9 : 17;
903 }
904
905 &$myseek;
906 return unless $bytes eq 'Xing';
907
908 &$myseek;
909 $vbr{flags} = _unpack_head($bytes);
910
911 if ($vbr{flags} & 1) {
912 &$myseek;
913 $vbr{frames} = _unpack_head($bytes);
914 }
915
916 if ($vbr{flags} & 2) {
917 &$myseek;
918 $vbr{bytes} = _unpack_head($bytes);
919 }
920
921 if ($vbr{flags} & 4) {
922 $myseek->(100);
923# Not used right now ...
924# $vbr{toc} = _unpack_head($bytes);
925 }
926
927 if ($vbr{flags} & 8) { # (quality ind., 0=best 100=worst)
928 &$myseek;
929 $vbr{scale} = _unpack_head($bytes);
930 } else {
931 $vbr{scale} = -1;
932 }
933
934 $$roff = $off;
935 return \%vbr;
936}
937
938sub _get_v2head {
939 my $fh = $_[0] or return;
940 my($h, $bytes, @bytes);
941
942 # check first three bytes for 'ID3'
943 seek $fh, 0, 0;
944 read $fh, $bytes, 3;
945 return unless $bytes eq 'ID3';
946
947 # get version
948 read $fh, $bytes, 2;
949 $h->{version} = sprintf "ID3v2.%d.%d",
950 @$h{qw[major_version minor_version]} =
951 unpack 'c2', $bytes;
952
953 # get flags
954 read $fh, $bytes, 1;
955 if ($h->{major_version} == 2) {
956 @$h{qw[unsync compression]} =
957 (unpack 'b8', $bytes)[7, 6];
958 $h->{ext_header} = 0;
959 $h->{experimental} = 0;
960 } else {
961 @$h{qw[unsync ext_header experimental]} =
962 (unpack 'b8', $bytes)[7, 6, 5];
963 }
964
965 # get ID3v2 tag length from bytes 7-10
966 $h->{tag_size} = 10; # include ID3v2 header size
967 read $fh, $bytes, 4;
968 @bytes = reverse unpack 'C4', $bytes;
969 foreach my $i (0 .. 3) {
970 # whoaaaaaa nellllllyyyyyy!
971 $h->{tag_size} += $bytes[$i] * 128 ** $i;
972 }
973
974 # get extended header size
975 $h->{ext_header_size} = 0;
976 if ($h->{ext_header}) {
977 $h->{ext_header_size} += 10;
978 read $fh, $bytes, 4;
979 @bytes = reverse unpack 'C4', $bytes;
980 for my $i (0..3) {
981 $h->{ext_header_size} += $bytes[$i] * 256 ** $i;
982 }
983 }
984
985 return $h;
986}
987
988sub _unpack_head {
989 unpack('l', pack('L', unpack('N', $_[0])));
990}
991
992sub _close {
993 my($file, $fh) = @_;
994 unless (ref $file) { # filehandle not passed
995 close $fh or carp "Problem closing '$file': $!";
996 }
997}
998
999BEGIN {
1000 @mp3_genres = (
1001 'Blues',
1002 'Classic Rock',
1003 'Country',
1004 'Dance',
1005 'Disco',
1006 'Funk',
1007 'Grunge',
1008 'Hip-Hop',
1009 'Jazz',
1010 'Metal',
1011 'New Age',
1012 'Oldies',
1013 'Other',
1014 'Pop',
1015 'R&B',
1016 'Rap',
1017 'Reggae',
1018 'Rock',
1019 'Techno',
1020 'Industrial',
1021 'Alternative',
1022 'Ska',
1023 'Death Metal',
1024 'Pranks',
1025 'Soundtrack',
1026 'Euro-Techno',
1027 'Ambient',
1028 'Trip-Hop',
1029 'Vocal',
1030 'Jazz+Funk',
1031 'Fusion',
1032 'Trance',
1033 'Classical',
1034 'Instrumental',
1035 'Acid',
1036 'House',
1037 'Game',
1038 'Sound Clip',
1039 'Gospel',
1040 'Noise',
1041 'AlternRock',
1042 'Bass',
1043 'Soul',
1044 'Punk',
1045 'Space',
1046 'Meditative',
1047 'Instrumental Pop',
1048 'Instrumental Rock',
1049 'Ethnic',
1050 'Gothic',
1051 'Darkwave',
1052 'Techno-Industrial',
1053 'Electronic',
1054 'Pop-Folk',
1055 'Eurodance',
1056 'Dream',
1057 'Southern Rock',
1058 'Comedy',
1059 'Cult',
1060 'Gangsta',
1061 'Top 40',
1062 'Christian Rap',
1063 'Pop/Funk',
1064 'Jungle',
1065 'Native American',
1066 'Cabaret',
1067 'New Wave',
1068 'Psychadelic',
1069 'Rave',
1070 'Showtunes',
1071 'Trailer',
1072 'Lo-Fi',
1073 'Tribal',
1074 'Acid Punk',
1075 'Acid Jazz',
1076 'Polka',
1077 'Retro',
1078 'Musical',
1079 'Rock & Roll',
1080 'Hard Rock',
1081 );
1082
1083 @winamp_genres = (
1084 @mp3_genres,
1085 'Folk',
1086 'Folk-Rock',
1087 'National Folk',
1088 'Swing',
1089 'Fast Fusion',
1090 'Bebob',
1091 'Latin',
1092 'Revival',
1093 'Celtic',
1094 'Bluegrass',
1095 'Avantgarde',
1096 'Gothic Rock',
1097 'Progressive Rock',
1098 'Psychedelic Rock',
1099 'Symphonic Rock',
1100 'Slow Rock',
1101 'Big Band',
1102 'Chorus',
1103 'Easy Listening',
1104 'Acoustic',
1105 'Humour',
1106 'Speech',
1107 'Chanson',
1108 'Opera',
1109 'Chamber Music',
1110 'Sonata',
1111 'Symphony',
1112 'Booty Bass',
1113 'Primus',
1114 'Porn Groove',
1115 'Satire',
1116 'Slow Jam',
1117 'Club',
1118 'Tango',
1119 'Samba',
1120 'Folklore',
1121 'Ballad',
1122 'Power Ballad',
1123 'Rhythmic Soul',
1124 'Freestyle',
1125 'Duet',
1126 'Punk Rock',
1127 'Drum Solo',
1128 'Acapella',
1129 'Euro-House',
1130 'Dance Hall',
1131 'Goa',
1132 'Drum & Bass',
1133 'Club-House',
1134 'Hardcore',
1135 'Terror',
1136 'Indie',
1137 'BritPop',
1138 'Negerpunk',
1139 'Polsk Punk',
1140 'Beat',
1141 'Christian Gangsta Rap',
1142 'Heavy Metal',
1143 'Black Metal',
1144 'Crossover',
1145 'Contemporary Christian',
1146 'Christian Rock',
1147 'Merengue',
1148 'Salsa',
1149 'Thrash Metal',
1150 'Anime',
1151 'JPop',
1152 'Synthpop',
1153 );
1154
1155 @t_bitrate = ([
1156 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
1157 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
1158 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
1159 ],[
1160 [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
1161 [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
1162 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
1163 ]);
1164
1165 @t_sampling_freq = (
1166 [11025, 12000, 8000],
1167 [undef, undef, undef], # reserved
1168 [22050, 24000, 16000],
1169 [44100, 48000, 32000]
1170 );
1171
1172 @frequency_tbl = map { $_ ? eval "${_}e-3" : 0 }
1173 map { @$_ } @t_sampling_freq;
1174
1175 @mp3_info_fields = qw(
1176 VERSION
1177 LAYER
1178 STEREO
1179 VBR
1180 BITRATE
1181 FREQUENCY
1182 SIZE
1183 SECS
1184 MM
1185 SS
1186 MS
1187 TIME
1188 COPYRIGHT
1189 PADDING
1190 MODE
1191 FRAMES
1192 FRAME_LENGTH
1193 VBR_SCALE
1194 );
1195
1196 %v1_tag_fields =
1197 (TITLE => 30, ARTIST => 30, ALBUM => 30, COMMENT => 30, YEAR => 4);
1198
1199 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1200
1201 %v2_to_v1_names = (
1202 # v2.2 tags
1203 'TT2' => 'TITLE',
1204 'TP1' => 'ARTIST',
1205 'TAL' => 'ALBUM',
1206 'TYE' => 'YEAR',
1207 'COM' => 'COMMENT',
1208 'TRK' => 'TRACKNUM',
1209 'TCO' => 'GENRE', # not clean mapping, but ...
1210 # v2.3 tags
1211 'TIT2' => 'TITLE',
1212 'TPE1' => 'ARTIST',
1213 'TALB' => 'ALBUM',
1214 'TYER' => 'YEAR',
1215 'COMM' => 'COMMENT',
1216 'TRCK' => 'TRACKNUM',
1217 'TCON' => 'GENRE',
1218 );
1219
1220 %v2_tag_names = (
1221 # v2.2 tags
1222 'BUF' => 'Recommended buffer size',
1223 'CNT' => 'Play counter',
1224 'COM' => 'Comments',
1225 'CRA' => 'Audio encryption',
1226 'CRM' => 'Encrypted meta frame',
1227 'ETC' => 'Event timing codes',
1228 'EQU' => 'Equalization',
1229 'GEO' => 'General encapsulated object',
1230 'IPL' => 'Involved people list',
1231 'LNK' => 'Linked information',
1232 'MCI' => 'Music CD Identifier',
1233 'MLL' => 'MPEG location lookup table',
1234 'PIC' => 'Attached picture',
1235 'POP' => 'Popularimeter',
1236 'REV' => 'Reverb',
1237 'RVA' => 'Relative volume adjustment',
1238 'SLT' => 'Synchronized lyric/text',
1239 'STC' => 'Synced tempo codes',
1240 'TAL' => 'Album/Movie/Show title',
1241 'TBP' => 'BPM (Beats Per Minute)',
1242 'TCM' => 'Composer',
1243 'TCO' => 'Content type',
1244 'TCR' => 'Copyright message',
1245 'TDA' => 'Date',
1246 'TDY' => 'Playlist delay',
1247 'TEN' => 'Encoded by',
1248 'TFT' => 'File type',
1249 'TIM' => 'Time',
1250 'TKE' => 'Initial key',
1251 'TLA' => 'Language(s)',
1252 'TLE' => 'Length',
1253 'TMT' => 'Media type',
1254 'TOA' => 'Original artist(s)/performer(s)',
1255 'TOF' => 'Original filename',
1256 'TOL' => 'Original Lyricist(s)/text writer(s)',
1257 'TOR' => 'Original release year',
1258 'TOT' => 'Original album/Movie/Show title',
1259 'TP1' => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
1260 'TP2' => 'Band/Orchestra/Accompaniment',
1261 'TP3' => 'Conductor/Performer refinement',
1262 'TP4' => 'Interpreted, remixed, or otherwise modified by',
1263 'TPA' => 'Part of a set',
1264 'TPB' => 'Publisher',
1265 'TRC' => 'ISRC (International Standard Recording Code)',
1266 'TRD' => 'Recording dates',
1267 'TRK' => 'Track number/Position in set',
1268 'TSI' => 'Size',
1269 'TSS' => 'Software/hardware and settings used for encoding',
1270 'TT1' => 'Content group description',
1271 'TT2' => 'Title/Songname/Content description',
1272 'TT3' => 'Subtitle/Description refinement',
1273 'TXT' => 'Lyricist/text writer',
1274 'TXX' => 'User defined text information frame',
1275 'TYE' => 'Year',
1276 'UFI' => 'Unique file identifier',
1277 'ULT' => 'Unsychronized lyric/text transcription',
1278 'WAF' => 'Official audio file webpage',
1279 'WAR' => 'Official artist/performer webpage',
1280 'WAS' => 'Official audio source webpage',
1281 'WCM' => 'Commercial information',
1282 'WCP' => 'Copyright/Legal information',
1283 'WPB' => 'Publishers official webpage',
1284 'WXX' => 'User defined URL link frame',
1285
1286 # v2.3 tags
1287 'AENC' => 'Audio encryption',
1288 'APIC' => 'Attached picture',
1289 'COMM' => 'Comments',
1290 'COMR' => 'Commercial frame',
1291 'ENCR' => 'Encryption method registration',
1292 'EQUA' => 'Equalization',
1293 'ETCO' => 'Event timing codes',
1294 'GEOB' => 'General encapsulated object',
1295 'GRID' => 'Group identification registration',
1296 'IPLS' => 'Involved people list',
1297 'LINK' => 'Linked information',
1298 'MCDI' => 'Music CD identifier',
1299 'MLLT' => 'MPEG location lookup table',
1300 'OWNE' => 'Ownership frame',
1301 'PCNT' => 'Play counter',
1302 'POPM' => 'Popularimeter',
1303 'POSS' => 'Position synchronisation frame',
1304 'PRIV' => 'Private frame',
1305 'RBUF' => 'Recommended buffer size',
1306 'RVAD' => 'Relative volume adjustment',
1307 'RVRB' => 'Reverb',
1308 'SYLT' => 'Synchronized lyric/text',
1309 'SYTC' => 'Synchronized tempo codes',
1310 'TALB' => 'Album/Movie/Show title',
1311 'TBPM' => 'BPM (beats per minute)',
1312 'TCOM' => 'Composer',
1313 'TCON' => 'Content type',
1314 'TCOP' => 'Copyright message',
1315 'TDAT' => 'Date',
1316 'TDLY' => 'Playlist delay',
1317 'TENC' => 'Encoded by',
1318 'TEXT' => 'Lyricist/Text writer',
1319 'TFLT' => 'File type',
1320 'TIME' => 'Time',
1321 'TIT1' => 'Content group description',
1322 'TIT2' => 'Title/songname/content description',
1323 'TIT3' => 'Subtitle/Description refinement',
1324 'TKEY' => 'Initial key',
1325 'TLAN' => 'Language(s)',
1326 'TLEN' => 'Length',
1327 'TMED' => 'Media type',
1328 'TOAL' => 'Original album/movie/show title',
1329 'TOFN' => 'Original filename',
1330 'TOLY' => 'Original lyricist(s)/text writer(s)',
1331 'TOPE' => 'Original artist(s)/performer(s)',
1332 'TORY' => 'Original release year',
1333 'TOWN' => 'File owner/licensee',
1334 'TPE1' => 'Lead performer(s)/Soloist(s)',
1335 'TPE2' => 'Band/orchestra/accompaniment',
1336 'TPE3' => 'Conductor/performer refinement',
1337 'TPE4' => 'Interpreted, remixed, or otherwise modified by',
1338 'TPOS' => 'Part of a set',
1339 'TPUB' => 'Publisher',
1340 'TRCK' => 'Track number/Position in set',
1341 'TRDA' => 'Recording dates',
1342 'TRSN' => 'Internet radio station name',
1343 'TRSO' => 'Internet radio station owner',
1344 'TSIZ' => 'Size',
1345 'TSRC' => 'ISRC (international standard recording code)',
1346 'TSSE' => 'Software/Hardware and settings used for encoding',
1347 'TXXX' => 'User defined text information frame',
1348 'TYER' => 'Year',
1349 'UFID' => 'Unique file identifier',
1350 'USER' => 'Terms of use',
1351 'USLT' => 'Unsychronized lyric/text transcription',
1352 'WCOM' => 'Commercial information',
1353 'WCOP' => 'Copyright/Legal information',
1354 'WOAF' => 'Official audio file webpage',
1355 'WOAR' => 'Official artist/performer webpage',
1356 'WOAS' => 'Official audio source webpage',
1357 'WORS' => 'Official internet radio station homepage',
1358 'WPAY' => 'Payment',
1359 'WPUB' => 'Publishers official webpage',
1360 'WXXX' => 'User defined URL link frame',
1361
1362 # v2.4 additional tags
1363 # note that we don't restrict tags from 2.3 or 2.4,
1364 'ASPI' => 'Audio seek point index',
1365 'EQU2' => 'Equalisation (2)',
1366 'RVA2' => 'Relative volume adjustment (2)',
1367 'SEEK' => 'Seek frame',
1368 'SIGN' => 'Signature frame',
1369 'TDEN' => 'Encoding time',
1370 'TDOR' => 'Original release time',
1371 'TDRC' => 'Recording time',
1372 'TDRL' => 'Release time',
1373 'TDTG' => 'Tagging time',
1374 'TIPL' => 'Involved people list',
1375 'TMCL' => 'Musician credits list',
1376 'TMOO' => 'Mood',
1377 'TPRO' => 'Produced notice',
1378 'TSOA' => 'Album sort order',
1379 'TSOP' => 'Performer sort order',
1380 'TSOT' => 'Title sort order',
1381 'TSST' => 'Set subtitle',
1382
1383 # grrrrrrr
1384 'COM ' => 'Broken iTunes comments',
1385 );
1386}
1387
13881;
1389
1390__END__
1391
1392=pod
1393
1394=back
1395
1396=head1 TROUBLESHOOTING
1397
1398If you find a bug, please send me a patch (see the project page in L<"SEE ALSO">).
1399If you cannot figure out why it does not work for you, please put the MP3 file in
1400a place where I can get it (preferably via FTP, or HTTP, or .Mac iDisk) and send me
1401mail regarding where I can get the file, with a detailed description of the problem.
1402
1403If I download the file, after debugging the problem I will not keep the MP3 file
1404if it is not legal for me to have it. Just let me know if it is legal for me to
1405keep it or not.
1406
1407
1408=head1 TODO
1409
1410=over 4
1411
1412=item ID3v2 Support
1413
1414Still need to do more for reading tags, such as using Compress::Zlib to decompress
1415compressed tags. But until I see this in use more, I won't bother. If something
1416does not work properly with reading, follow the instructions above for
1417troubleshooting.
1418
1419ID3v2 I<writing> is coming soon.
1420
1421=item Get data from scalar
1422
1423Instead of passing a file spec or filehandle, pass the
1424data itself. Would take some work, converting the seeks, etc.
1425
1426=item Padding bit ?
1427
1428Do something with padding bit.
1429
1430=item Test suite
1431
1432Test suite could use a bit of an overhaul and update. Patches very welcome.
1433
1434=over 4
1435
1436=item *
1437
1438Revamp getset.t. Test all the various get_mp3tag args.
1439
1440=item *
1441
1442Test Unicode.
1443
1444=item *
1445
1446Test OOP API.
1447
1448=item *
1449
1450Test error handling, check more for missing files, bad MP3s, etc.
1451
1452=back
1453
1454=item Other VBR
1455
1456Right now, only Xing VBR is supported.
1457
1458=back
1459
1460
1461=head1 THANKS
1462
1463Edward Allen E<lt>[email protected]<gt>,
1464Vittorio Bertola E<lt>[email protected]<gt>,
1465Michael Blakeley E<lt>[email protected]<gt>,
1466Per Bolmstedt E<lt>[email protected]<gt>,
1467Tony Bowden E<lt>[email protected]<gt>,
1468Tom Brown E<lt>[email protected]<gt>,
1469Sergio Camarena E<lt>[email protected]<gt>,
1470Chris Dawson E<lt>[email protected]<gt>,
1471Luke Drumm E<lt>[email protected]<gt>,
1472Kyle Farrell E<lt>[email protected]<gt>,
1473Jeffrey Friedl E<lt>[email protected]<gt>,
1474brian d foy E<lt>[email protected]<gt>,
1475Ben Gertzfield E<lt>[email protected]<gt>,
1476Brian Goodwin E<lt>[email protected]<gt>,
1477Todd Hanneken E<lt>[email protected]<gt>,
1478Todd Harris E<lt>[email protected]<gt>,
1479Woodrow Hill E<lt>[email protected]<gt>,
1480Kee Hinckley E<lt>[email protected]<gt>,
1481Roman Hodek E<lt>[email protected]<gt>,
1482Peter Kovacs E<lt>[email protected]<gt>,
1483Johann Lindvall,
1484Peter Marschall E<lt>[email protected]<gt>,
1485Trond Michelsen E<lt>[email protected]<gt>,
1486Dave O'Neill E<lt>[email protected]<gt>,
1487Christoph Oberauer E<lt>[email protected]<gt>,
1488Jake Palmer E<lt>[email protected]<gt>,
1489Andrew Phillips E<lt>[email protected]<gt>,
1490David Reuteler E<lt>[email protected]<gt>,
1491John Ruttenberg E<lt>[email protected]<gt>,
1492Matthew Sachs E<lt>[email protected]<gt>,
1493E<lt>[email protected]<gt>,
1494Hermann Schwaerzler E<lt>[email protected]<gt>,
1495Chris Sidi E<lt>[email protected]<gt>,
1496Roland Steinbach E<lt>[email protected]<gt>,
1497Stuart E<lt>[email protected]<gt>,
1498Jeffery Sumler E<lt>[email protected]<gt>,
1499Predrag Supurovic E<lt>[email protected]<gt>,
1500Bogdan Surdu E<lt>[email protected]<gt>,
1501E<lt>[email protected]<gt>,
1502Pass F. B. Travis E<lt>[email protected]<gt>,
1503Tobias Wagener E<lt>[email protected]<gt>,
1504Ronan Waide E<lt>[email protected]<gt>,
1505Andy Waite E<lt>[email protected]<gt>,
1506Ken Williams E<lt>[email protected]<gt>,
1507Meng Weng Wong E<lt>[email protected]<gt>.
1508
1509
1510=head1 AUTHOR AND COPYRIGHT
1511
1512Chris Nandor E<lt>[email protected]<gt>, http://pudge.net/
1513
1514Copyright (c) 1998-2003 Chris Nandor. All rights reserved. This program is
1515free software; you can redistribute it and/or modify it under the terms
1516of the Artistic License, distributed with Perl.
1517
1518
1519=head1 SEE ALSO
1520
1521=over 4
1522
1523=item MP3::Info Project Page
1524
1525 http://projects.pudge.net/
1526
1527=item mp3tools
1528
1529 http://www.zevils.com/linux/mp3tools/
1530
1531=item mpgtools
1532
1533 http://www.dv.co.yu/mpgscript/mpgtools.htm
1534 http://www.dv.co.yu/mpgscript/mpeghdr.htm
1535
1536=item mp3tool
1537
1538 http://www.dtek.chalmers.se/~d2linjo/mp3/mp3tool.html
1539
1540=item ID3v2
1541
1542 http://www.id3.org/
1543
1544=item Xing Variable Bitrate
1545
1546 http://www.xingtech.com/support/partner_developer/mp3/vbr_sdk/
1547
1548=item MP3Ext
1549
1550 http://rupert.informatik.uni-stuttgart.de/~mutschml/MP3ext/
1551
1552=item Xmms
1553
1554 http://www.xmms.org/
1555
1556
1557=back
1558
1559=head1 VERSION
1560
1561v1.02, Sunday, March 2, 2003
1562
1563=cut
Note: See TracBrowser for help on using the repository browser.