File Coverage

blib/lib/MP3/Info.pm
Criterion Covered Total %
statement 322 481 66.9
branch 141 294 47.9
condition 43 120 35.8
subroutine 22 30 73.3
pod 7 8 87.5
total 535 933 57.3


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