Don't print to STDERR.
[kugel-rb.git] / tools / songdb.pl
blobf61bc296611d80835934ea9b7010d1ee177ae1d5
1 #!/usr/bin/perl
3 # Rockbox song database docs:
4 # http://www.rockbox.org/twiki/bin/view/Main/TagDatabase
6 # MP3::Info by Chris Nandor is included verbatim in this script to make
7 # it runnable standalone on removable drives. See below.
10 my $db = "rockbox.id3db";
11 my $dir;
12 my $strip;
13 my $verbose;
14 my $help;
16 while($ARGV[0]) {
17 if($ARGV[0] eq "--db") {
18 $db = $ARGV[1];
19 shift @ARGV;
20 shift @ARGV;
22 elsif($ARGV[0] eq "--path") {
23 $dir = $ARGV[1];
24 shift @ARGV;
25 shift @ARGV;
27 elsif($ARGV[0] eq "--strip") {
28 $strip = $ARGV[1];
29 shift @ARGV;
30 shift @ARGV;
32 elsif($ARGV[0] eq "--verbose") {
33 $verbose = 1;
34 shift @ARGV;
36 elsif($ARGV[0] eq "--help" or ($ARGV[0] eq "-h")) {
37 $help = 1;
38 shift @ARGV;
40 else {
41 shift @ARGV;
44 my %entries;
45 my %genres;
46 my %albums;
47 my %years;
48 my %filename;
50 my $dbver = 1;
52 if(! -d $dir or $help) {
53 print "'$dir' is not a directory\n" if ($dir ne "" and ! -d $dir);
54 print "songdb --path <dir> [--db <file>] [--strip <path>] [--verbose] [--help]\n";
55 exit;
58 # return ALL directory entries in the given dir
59 sub getdir {
60 my ($dir) = @_;
62 opendir(DIR, $dir) || die "can't opendir $dir: $!";
63 # my @mp3 = grep { /\.mp3$/ && -f "$dir/$_" } readdir(DIR);
64 my @all = readdir(DIR);
65 closedir DIR;
66 return @all;
69 sub extractmp3 {
70 my ($dir, @files) = @_;
71 my @mp3;
72 for(@files) {
73 if( /\.mp3$/ && -f "$dir/$_" ) {
74 push @mp3, $_;
77 return @mp3;
80 sub extractdirs {
81 my ($dir, @files) = @_;
82 my @dirs;
83 for(@files) {
84 if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) {
85 push @dirs, $_;
88 return @dirs;
91 sub singlefile {
92 my ($file) = @_;
94 # print "Check $file\n";
96 my $hash = get_mp3tag($file);
97 # my $hash = get_mp3info($file);
99 # for(keys %$hash) {
100 # print "Info: $_ ".$hash->{$_}."\n";
102 return $hash; # a hash reference
105 my $maxsongperalbum;
107 sub dodir {
108 my ($dir)=@_;
110 print "$dir\n";
112 # getdir() returns all entries in the given dir
113 my @a = getdir($dir);
115 # extractmp3 filters out only the mp3 files from all given entries
116 my @m = extractmp3($dir, @a);
118 my $f;
120 for $f (sort @m) {
122 my $id3 = singlefile("$dir/$f");
124 # ARTIST
125 # COMMENT
126 # ALBUM
127 # TITLE
128 # GENRE
129 # TRACKNUM
130 # YEAR
132 # don't index songs without tags
133 if (not defined $$id3{'ARTIST'} and
134 not defined $$id3{'ALBUM'} and
135 not defined $$id3{'TITLE'})
137 next;
140 #printf "Artist: %s\n", $id3->{'ARTIST'};
141 my $path = "$dir/$f";
142 if ($strip ne "" and $path =~ /^$strip(.*)/) {
143 $path = $1;
146 $entries{$path}= $id3;
147 $artists{$id3->{'ARTIST'}}++ if($id3->{'ARTIST'});
148 $genres{$id3->{'GENRE'}}++ if($id3->{'GENRE'});
149 $years{$id3->{'YEAR'}}++ if($id3->{'YEAR'});
151 # fallback names
152 $$id3{'ARTIST'} = "<no artist tag>" if ($$id3{'ARTIST'} eq "");
153 $$id3{'ALBUM'} = "<no album tag>" if ($$id3{'ALBUM'} eq "");
154 $$id3{'TITLE'} = "<no title tag>" if ($$id3{'TITLE'} eq "");
156 # prepend Artist name to handle duplicate album names from other
157 # artists
158 my $albumid = $id3->{'ALBUM'}."___".$id3->{'ARTIST'};
159 if($albumid ne "<no album tag>___<no artist tag>") {
160 my $num = ++$albums{$albumid};
161 if($num > $maxsongperalbum) {
162 $maxsongperalbum = $num;
163 $longestalbum = $albumid;
165 $album2songs{$albumid}{$$id3{TITLE}} = $id3;
166 $artist2albums{$$id3{ARTIST}}{$$id3{ALBUM}} = $id3;
170 # extractdirs filters out only subdirectories from all given entries
171 my @d = extractdirs($dir, @a);
173 for $d (sort @d) {
174 #print "Subdir: $d\n";
175 dodir("$dir/$d");
180 dodir($dir);
181 print "\n";
183 print "File name table\n" if ($verbose);
184 my $fc;
185 for(sort keys %entries) {
186 printf(" %s\n", $_) if ($verbose);
187 $fc += length($_)+1;
190 my $maxsonglen = 0;
191 my $sc;
192 print "\nSong title table\n" if ($verbose);
194 for(sort {$entries{$a}->{'TITLE'} cmp $entries{$b}->{'TITLE'}} keys %entries) {
195 printf(" %s\n", $entries{$_}->{'TITLE'} ) if ($verbose);
196 my $l = length($entries{$_}->{'TITLE'});
197 if($l > $maxsonglen) {
198 $maxsonglen = $l;
199 $longestsong = $entries{$_}->{'TITLE'};
202 $maxsonglen++; # include zero termination byte
203 while($maxsonglen&3) {
204 $maxsonglen++;
207 my $maxartistlen = 0;
208 print "\nArtist table\n" if ($verbose);
209 my $i=0;
210 my %artistcount;
211 for(sort keys %artists) {
212 printf(" %s\n", $_) if ($verbose);
213 $artistcount{$_}=$i++;
214 my $l = length($_);
215 if($l > $maxartistlen) {
216 $maxartistlen = $l;
217 $longestartist = $_;
220 $l = scalar keys %{$artist2albums{$_}};
221 if ($l > $maxalbumsperartist) {
222 $maxalbumsperartist = $l;
225 $maxartistlen++; # include zero termination byte
226 while($maxartistlen&3) {
227 $maxartistlen++;
230 if ($verbose) {
231 print "\nGenre table\n";
232 for(sort keys %genres) {
233 printf(" %s\n", $_);
236 print "\nYear table\n";
237 for(sort keys %years) {
238 printf(" %s\n", $_);
242 print "\nAlbum table\n" if ($verbose);
243 my $maxalbumlen = 0;
244 my %albumcount;
245 $i=0;
246 for(sort keys %albums) {
247 my @moo=split(/___/, $_);
248 printf(" %s\n", $moo[0]) if ($verbose);
249 $albumcount{$_} = $i++;
250 my $l = length($moo[0]);
251 if($l > $maxalbumlen) {
252 $maxalbumlen = $l;
253 $longestalbumname = $moo[0];
256 $maxalbumlen++; # include zero termination byte
257 while($maxalbumlen&3) {
258 $maxalbumlen++;
263 sub dumpint {
264 my ($num)=@_;
266 # print "int: $num\n";
268 printf DB ("%c%c%c%c",
269 $num>>24,
270 ($num&0xff0000)>>16,
271 ($num&0xff00)>>8,
272 ($num&0xff));
275 if (!scalar keys %entries) {
276 print "No songs found. Did you specify the right --path ?\n";
277 print "Use the --help parameter to see all options.\n";
278 exit;
281 if ($db) {
282 my $songentrysize = $maxsonglen + 12;
283 my $albumentrysize = $maxalbumlen + 4 + $maxsongperalbum*4;
284 my $artistentrysize = $maxartistlen + $maxalbumsperartist*4;
286 printf "Number of artists : %d\n", scalar keys %artists;
287 printf "Number of albums : %d\n", scalar keys %albums;
288 printf "Number of songs : %d\n", scalar keys %entries;
289 print "Max artist length : $maxartistlen ($longestartist)\n";
290 print "Max album length : $maxalbumlen ($longestalbumname)\n";
291 print "Max song length : $maxsonglen ($longestsong)\n";
292 print "Max songs per album: $maxsongperalbum ($longestalbum)\n";
293 print "Database version: $dbver\n" if ($verbose);
295 open(DB, ">$db") || die "couldn't make $db";
296 printf DB "RDB%c", $dbver;
298 $pathindex = 48; # paths always start at index 48
300 $songindex = $pathindex + $fc; # fc is size of all paths
301 $songindex++ while ($songindex & 3); # align to 32 bits
303 dumpint($songindex); # file position index of song table
304 dumpint(scalar(keys %entries)); # number of songs
305 dumpint($maxsonglen); # length of song name field
307 # set total size of song title table
308 $sc = scalar(keys %entries) * $songentrysize;
310 $albumindex = $songindex + $sc; # sc is size of all songs
311 dumpint($albumindex); # file position index of album table
312 dumpint(scalar(keys %albums)); # number of albums
313 dumpint($maxalbumlen); # length of album name field
314 dumpint($maxsongperalbum); # number of entries in songs-per-album array
316 my $ac = scalar(keys %albums) * $albumentrysize;
318 $artistindex = $albumindex + $ac; # ac is size of all albums
319 dumpint($artistindex); # file position index of artist table
320 dumpint(scalar(keys %artists)); # number of artists
321 dumpint($maxartistlen); # length of artist name field
322 dumpint($maxalbumsperartist); # number of entries in albums-per-artist array
324 my $l=0;
326 #### TABLE of file names ###
327 # path1
329 my %filenamepos;
330 for $f (sort keys %entries) {
331 printf DB ("%s\x00", $f);
332 $filenamepos{$f}= $l;
333 $l += length($f)+1;
335 while ($l & 3) {
336 print DB "\x00";
337 $l++;
340 #### TABLE of songs ###
341 # title of song1
342 # pointer to artist of song1
343 # pointer to album of song1
344 # pointer to filename of song1
346 my $offset = $songindex;
347 for(sort {$entries{$a}->{'TITLE'} cmp $entries{$b}->{'TITLE'}} keys %entries) {
348 my $f = $_;
349 my $id3 = $entries{$f};
350 my $t = $id3->{'TITLE'};
351 my $str = $t."\x00" x ($maxsonglen- length($t));
353 print DB $str; # title
355 my $a = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
356 dumpint($a + $artistindex); # pointer to artist of this song
358 $a = $albumcount{"$$id3{ALBUM}___$$id3{ARTIST}"} * $albumentrysize;
359 dumpint($a + $albumindex); # pointer to album of this song
361 # pointer to filename of this song
362 dumpint($filenamepos{$f} + $pathindex);
364 $$id3{'songoffset'} = $offset;
365 $offset += $songentrysize;
368 #### TABLE of albums ###
369 # name of album1
370 # pointers to artists of album1
371 # pointers to songs on album1
373 for(sort keys %albums) {
374 my $albumid = $_;
375 my @moo=split(/___/, $_);
376 my $t = $moo[0];
377 my $str = $t."\x00" x ($maxalbumlen - length($t));
378 print DB $str;
380 my $aoffset = $artistcount{$moo[0]} * $artistentrysize;
381 dumpint($aoffset + $artistindex); # pointer to artist of this album
383 my @songlist = keys %{$album2songs{$albumid}};
384 my $id3 = $album2songs{$albumid}{$songlist[0]};
385 if (defined $id3->{'TRACKNUM'}) {
386 @songlist = sort {
387 $album2songs{$albumid}{$a}->{'TRACKNUM'} <=>
388 $album2songs{$albumid}{$b}->{'TRACKNUM'}
389 } @songlist;
391 else {
392 @songlist = sort @songlist;
395 for (@songlist) {
396 my $id3 = $album2songs{$albumid}{$_};
397 dumpint($$id3{'songoffset'});
400 for (scalar keys %{$album2songs{$albumid}} .. $maxsongperalbum-1) {
401 print DB "\x00\x00\x00\x00";
405 #### TABLE of artists ###
406 # name of artist1
407 # pointers to albums of artist1
409 for (sort keys %artists) {
410 my $artist = $_;
411 my $str = $_."\x00" x ($maxartistlen - length($_));
412 print DB $str;
414 for (sort keys %{$artist2albums{$artist}}) {
415 my $id3 = $artist2albums{$artist}{$_};
416 my $a = $albumcount{"$$id3{'ALBUM'}___$$id3{'ARTIST'}"} * $albumentrysize;
417 dumpint($a + $albumindex);
420 for (scalar keys %{$artist2albums{$artist}} .. $maxalbumsperartist-1) {
421 print DB "\x00\x00\x00\x00";
426 close(DB);
430 ### Here follows module MP3::Info Copyright (c) 1998-2004 Chris Nandor
431 ### Modified by Björn Stenberg to remove use of external libraries
434 our(
435 @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION, $REVISION,
436 @mp3_genres, %mp3_genres, @winamp_genres, %winamp_genres, $try_harder,
437 @t_bitrate, @t_sampling_freq, @frequency_tbl, %v1_tag_fields,
438 @v1_tag_names, %v2_tag_names, %v2_to_v1_names, $AUTOLOAD,
439 @mp3_info_fields
442 @ISA = 'Exporter';
443 @EXPORT = qw(
444 set_mp3tag get_mp3tag get_mp3info remove_mp3tag
445 use_winamp_genres
447 @EXPORT_OK = qw(@mp3_genres %mp3_genres use_mp3_utf8);
448 %EXPORT_TAGS = (
449 genres => [qw(@mp3_genres %mp3_genres)],
450 utf8 => [qw(use_mp3_utf8)],
451 all => [@EXPORT, @EXPORT_OK]
454 # $Id$
455 ($REVISION) = ' $Revision$ ' =~ /\$Revision:\s+([^\s]+)/;
456 $VERSION = '1.02';
458 =pod
460 =head1 NAME
462 MP3::Info - Manipulate / fetch info from MP3 audio files
464 =head1 SYNOPSIS
466 #!perl -w
467 use MP3::Info;
468 my $file = 'Pearls_Before_Swine.mp3';
469 set_mp3tag($file, 'Pearls Before Swine', q"77's",
470 'Sticks and Stones', '1990',
471 q"(c) 1990 77's LTD.", 'rock & roll');
473 my $tag = get_mp3tag($file) or die "No TAG info";
474 $tag->{GENRE} = 'rock';
475 set_mp3tag($file, $tag);
477 my $info = get_mp3info($file);
478 printf "$file length is %d:%d\n", $info->{MM}, $info->{SS};
480 =cut
483 my $c = -1;
484 # set all lower-case and regular-cased versions of genres as keys
485 # with index as value of each key
486 %mp3_genres = map {($_, ++$c, lc, $c)} @mp3_genres;
488 # do it again for winamp genres
489 $c = -1;
490 %winamp_genres = map {($_, ++$c, lc, $c)} @winamp_genres;
493 =pod
495 my $mp3 = new MP3::Info $file;
496 $mp3->title('Perls Before Swine');
497 printf "$file length is %s, title is %s\n",
498 $mp3->time, $mp3->title;
501 =head1 DESCRIPTION
503 =over 4
505 =item $mp3 = MP3::Info-E<gt>new(FILE)
507 OOP interface to the rest of the module. The same keys
508 available via get_mp3info and get_mp3tag are available
509 via the returned object (using upper case or lower case;
510 but note that all-caps "VERSION" will return the module
511 version, not the MP3 version).
513 Passing a value to one of the methods will set the value
514 for that tag in the MP3 file, if applicable.
516 =cut
518 sub new {
519 my($pack, $file) = @_;
521 my $info = get_mp3info($file) or return undef;
522 my $tags = get_mp3tag($file) || { map { ($_ => undef) } @v1_tag_names };
523 my %self = (
524 FILE => $file,
525 TRY_HARDER => 0
528 @self{@mp3_info_fields, @v1_tag_names, 'file'} = (
529 @{$info}{@mp3_info_fields},
530 @{$tags}{@v1_tag_names},
531 $file
534 return bless \%self, $pack;
537 sub can {
538 my $self = shift;
539 return $self->SUPER::can(@_) unless ref $self;
540 my $name = uc shift;
541 return sub { $self->$name(@_) } if exists $self->{$name};
542 return undef;
545 sub AUTOLOAD {
546 my($self) = @_;
547 (my $name = uc $AUTOLOAD) =~ s/^.*://;
549 if (exists $self->{$name}) {
550 my $sub = exists $v1_tag_fields{$name}
551 ? sub {
552 if (defined $_[1]) {
553 $_[0]->{$name} = $_[1];
554 set_mp3tag($_[0]->{FILE}, $_[0]);
556 return $_[0]->{$name};
558 : sub {
559 return $_[0]->{$name}
562 no strict 'refs';
563 *{$AUTOLOAD} = $sub;
564 goto &$AUTOLOAD;
566 } else {
567 warn(sprintf "No method '$name' available in package %s.",
568 __PACKAGE__);
572 sub DESTROY {
577 =item use_mp3_utf8([STATUS])
579 Tells MP3::Info to (or not) return TAG info in UTF-8.
580 TRUE is 1, FALSE is 0. Default is FALSE.
582 Will only be able to it on if Unicode::String is available. ID3v2
583 tags will be converted to UTF-8 according to the encoding specified
584 in each tag; ID3v1 tags will be assumed Latin-1 and converted
585 to UTF-8.
587 Function returns status (TRUE/FALSE). If no argument is supplied,
588 or an unaccepted argument is supplied, function merely returns status.
590 This function is not exported by default, but may be exported
591 with the C<:utf8> or C<:all> export tag.
593 =cut
595 my $unicode_module = eval { require Unicode::String };
596 my $UNICODE = 0;
598 sub use_mp3_utf8 {
599 my($val) = @_;
600 if ($val == 1) {
601 $UNICODE = 1 if $unicode_module;
602 } elsif ($val == 0) {
603 $UNICODE = 0;
605 return $UNICODE;
608 =pod
610 =item use_winamp_genres()
612 Puts WinAmp genres into C<@mp3_genres> and C<%mp3_genres>
613 (adds 68 additional genres to the default list of 80).
614 This is a separate function because these are non-standard
615 genres, but they are included because they are widely used.
617 You can import the data structures with one of:
619 use MP3::Info qw(:genres);
620 use MP3::Info qw(:DEFAULT :genres);
621 use MP3::Info qw(:all);
623 =cut
625 sub use_winamp_genres {
626 %mp3_genres = %winamp_genres;
627 @mp3_genres = @winamp_genres;
628 return 1;
631 =pod
633 =item remove_mp3tag (FILE [, VERSION, BUFFER])
635 Can remove ID3v1 or ID3v2 tags. VERSION should be C<1> for ID3v1,
636 C<2> for ID3v2, and C<ALL> for both.
638 For ID3v1, removes last 128 bytes from file if those last 128 bytes begin
639 with the text 'TAG'. File will be 128 bytes shorter.
641 For ID3v2, removes ID3v2 tag. Because an ID3v2 tag is at the
642 beginning of the file, we rewrite the file after removing the tag data.
643 The buffer for rewriting the file is 4MB. BUFFER (in bytes) ca
644 change the buffer size.
646 Returns the number of bytes removed, or -1 if no tag removed,
647 or undef if there is an error.
649 =cut
651 sub remove_mp3tag {
652 my($file, $version, $buf) = @_;
653 my($fh, $return);
655 $buf ||= 4096*1024; # the bigger the faster
656 $version ||= 1;
658 if (not (defined $file && $file ne '')) {
659 $@ = "No file specified";
660 return undef;
663 if (not -s $file) {
664 $@ = "File is empty";
665 return undef;
668 if (ref $file) { # filehandle passed
669 $fh = $file;
670 } else {
671 $fh = gensym;
672 if (not open $fh, "+< $file\0") {
673 $@ = "Can't open $file: $!";
674 return undef;
678 binmode $fh;
680 if ($version eq 1 || $version eq 'ALL') {
681 seek $fh, -128, 2;
682 my $tell = tell $fh;
683 if (<$fh> =~ /^TAG/) {
684 truncate $fh, $tell or warn "Can't truncate '$file': $!";
685 $return += 128;
689 if ($version eq 2 || $version eq 'ALL') {
690 my $h = _get_v2head($fh);
691 if ($h) {
692 local $\;
693 seek $fh, 0, 2;
694 my $eof = tell $fh;
695 my $off = $h->{tag_size};
697 while ($off < $eof) {
698 seek $fh, $off, 0;
699 read $fh, my($bytes), $buf;
700 seek $fh, $off - $h->{tag_size}, 0;
701 print $fh $bytes;
702 $off += $buf;
705 truncate $fh, $eof - $h->{tag_size}
706 or warn "Can't truncate '$file': $!";
707 $return += $h->{tag_size};
711 _close($file, $fh);
713 return $return || -1;
717 =pod
719 =item set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
721 =item set_mp3tag (FILE, $HASHREF)
723 Adds/changes tag information in an MP3 audio file. Will clobber
724 any existing information in file.
726 Fields are TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE. All fields have
727 a 30-byte limit, except for YEAR, which has a four-byte limit, and GENRE,
728 which is one byte in the file. The GENRE passed in the function is a
729 case-insensitive text string representing a genre found in C<@mp3_genres>.
731 Will accept either a list of values, or a hashref of the type
732 returned by C<get_mp3tag>.
734 If TRACKNUM is present (for ID3v1.1), then the COMMENT field can only be
735 28 bytes.
737 ID3v2 support may come eventually. Note that if you set a tag on a file
738 with ID3v2, the set tag will be for ID3v1[.1] only, and if you call
739 C<get_mp3_tag> on the file, it will show you the (unchanged) ID3v2 tags,
740 unless you specify ID3v1.
742 =cut
744 sub set_mp3tag {
745 my($file, $title, $artist, $album, $year, $comment, $genre, $tracknum) = @_;
746 my(%info, $oldfh, $ref, $fh);
747 local %v1_tag_fields = %v1_tag_fields;
749 # set each to '' if undef
750 for ($title, $artist, $album, $year, $comment, $tracknum, $genre,
751 (@info{@v1_tag_names}))
752 {$_ = defined() ? $_ : ''}
754 ($ref) = (overload::StrVal($title) =~ /^(?:.*\=)?([^=]*)\((?:[^\(]*)\)$/)
755 if ref $title;
756 # populate data to hashref if hashref is not passed
757 if (!$ref) {
758 (@info{@v1_tag_names}) =
759 ($title, $artist, $album, $year, $comment, $tracknum, $genre);
761 # put data from hashref into hashref if hashref is passed
762 } elsif ($ref eq 'HASH') {
763 %info = %$title;
765 # return otherwise
766 } else {
767 warn(<<'EOT');
768 Usage: set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
769 set_mp3tag (FILE, $HASHREF)
771 return undef;
774 if (not (defined $file && $file ne '')) {
775 $@ = "No file specified";
776 return undef;
779 if (not -s $file) {
780 $@ = "File is empty";
781 return undef;
784 # comment field length 28 if ID3v1.1
785 $v1_tag_fields{COMMENT} = 28 if $info{TRACKNUM};
788 # only if -w is on
789 if ($^W) {
790 # warn if fields too long
791 foreach my $field (keys %v1_tag_fields) {
792 $info{$field} = '' unless defined $info{$field};
793 if (length($info{$field}) > $v1_tag_fields{$field}) {
794 warn "Data too long for field $field: truncated to " .
795 "$v1_tag_fields{$field}";
799 if ($info{GENRE}) {
800 warn "Genre `$info{GENRE}' does not exist\n"
801 unless exists $mp3_genres{$info{GENRE}};
805 if ($info{TRACKNUM}) {
806 $info{TRACKNUM} =~ s/^(\d+)\/(\d+)$/$1/;
807 unless ($info{TRACKNUM} =~ /^\d+$/ &&
808 $info{TRACKNUM} > 0 && $info{TRACKNUM} < 256) {
809 warn "Tracknum `$info{TRACKNUM}' must be an integer " .
810 "from 1 and 255\n" if $^W;
811 $info{TRACKNUM} = '';
815 if (ref $file) { # filehandle passed
816 $fh = $file;
817 } else {
818 $fh = gensym;
819 if (not open $fh, "+< $file\0") {
820 $@ = "Can't open $file: $!";
821 return undef;
825 binmode $fh;
826 $oldfh = select $fh;
827 seek $fh, -128, 2;
828 # go to end of file if no tag, beginning of file if tag
829 seek $fh, (<$fh> =~ /^TAG/ ? -128 : 0), 2;
831 # get genre value
832 $info{GENRE} = $info{GENRE} && exists $mp3_genres{$info{GENRE}} ?
833 $mp3_genres{$info{GENRE}} : 255; # some default genre
835 local $\;
836 # print TAG to file
837 if ($info{TRACKNUM}) {
838 print pack "a3a30a30a30a4a28xCC", 'TAG', @info{@v1_tag_names};
839 } else {
840 print pack "a3a30a30a30a4a30C", 'TAG', @info{@v1_tag_names[0..4, 6]};
843 select $oldfh;
845 _close($file, $fh);
847 return 1;
850 =pod
852 =item get_mp3tag (FILE [, VERSION, RAW_V2])
854 Returns hash reference containing tag information in MP3 file. The keys
855 returned are the same as those supplied for C<set_mp3tag>, except in the
856 case of RAW_V2 being set.
858 If VERSION is C<1>, the information is taken from the ID3v1 tag (if present).
859 If VERSION is C<2>, the information is taken from the ID3v2 tag (if present).
860 If VERSION is not supplied, or is false, the ID3v1 tag is read if present, and
861 then, if present, the ID3v2 tag information will override any existing ID3v1
862 tag info.
864 If RAW_V2 is C<1>, the raw ID3v2 tag data is returned, without any manipulation
865 of text encoding. The key name is the same as the frame ID (ID to name mappings
866 are in the global %v2_tag_names).
868 If RAW_V2 is C<2>, the ID3v2 tag data is returned, manipulating for Unicode if
869 necessary, etc. It also takes multiple values for a given key (such as comments)
870 and puts them in an arrayref.
872 If the ID3v2 version is older than ID3v2.2.0 or newer than ID3v2.4.0, it will
873 not be read.
875 Strings returned will be in Latin-1, unless UTF-8 is specified (L<use_mp3_utf8>),
876 (unless RAW_V2 is C<1>).
878 Also returns a TAGVERSION key, containing the ID3 version used for the returned
879 data (if TAGVERSION argument is C<0>, may contain two versions).
881 =cut
883 sub get_mp3tag {
884 my($file, $ver, $raw_v2) = @_;
885 my($tag, $v1, $v2, $v2h, %info, @array, $fh);
886 $raw_v2 ||= 0;
887 $ver = !$ver ? 0 : ($ver == 2 || $ver == 1) ? $ver : 0;
889 if (not (defined $file && $file ne '')) {
890 $@ = "No file specified";
891 return undef;
894 if (not -s $file) {
895 $@ = "File is empty";
896 return undef;
899 if (ref $file) { # filehandle passed
900 $fh = $file;
901 } else {
902 $fh = gensym;
903 if (not open $fh, "< $file\0") {
904 $@ = "Can't open $file: $!";
905 return undef;
909 binmode $fh;
911 if ($ver < 2) {
912 seek $fh, -128, 2;
913 while(defined(my $line = <$fh>)) { $tag .= $line }
915 if ($tag =~ /^TAG/) {
916 $v1 = 1;
917 if (substr($tag, -3, 2) =~ /\000[^\000]/) {
918 (undef, @info{@v1_tag_names}) =
919 (unpack('a3a30a30a30a4a28', $tag),
920 ord(substr($tag, -2, 1)),
921 $mp3_genres[ord(substr $tag, -1)]);
922 $info{TAGVERSION} = 'ID3v1.1';
923 } else {
924 (undef, @info{@v1_tag_names[0..4, 6]}) =
925 (unpack('a3a30a30a30a4a30', $tag),
926 $mp3_genres[ord(substr $tag, -1)]);
927 $info{TAGVERSION} = 'ID3v1';
929 if ($UNICODE) {
930 for my $key (keys %info) {
931 next unless $info{$key};
932 my $u = Unicode::String::latin1($info{$key});
933 $info{$key} = $u->utf8;
936 } elsif ($ver == 1) {
937 _close($file, $fh);
938 $@ = "No ID3v1 tag found";
939 return undef;
943 ($v2, $v2h) = _get_v2tag($fh);
945 unless ($v1 || $v2) {
946 _close($file, $fh);
947 $@ = "No ID3 tag found";
948 return undef;
951 if (($ver == 0 || $ver == 2) && $v2) {
952 if ($raw_v2 == 1 && $ver == 2) {
953 %info = %$v2;
954 $info{TAGVERSION} = $v2h->{version};
955 } else {
956 my $hash = $raw_v2 == 2 ? { map { ($_, $_) } keys %v2_tag_names } : \%v2_to_v1_names;
957 for my $id (keys %$hash) {
958 if (exists $v2->{$id}) {
959 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
960 $info{$hash->{$id}} = $mp3_genres[$1];
961 } else {
962 my $data1 = $v2->{$id};
964 # this is tricky ... if this is an arrayref,
965 # we want to only return one, so we pick the
966 # first one. but if it is a comment, we pick
967 # the first one where the first charcter after
968 # the language is NULL and not an additional
969 # sub-comment, because that is most likely to be
970 # the user-supplied comment
971 if (ref $data1 && !$raw_v2) {
972 if ($id =~ /^COMM?$/) {
973 my($newdata) = grep /^(....\000)/, @{$data1};
974 $data1 = $newdata || $data1->[0];
975 } else {
976 $data1 = $data1->[0];
980 $data1 = [ $data1 ] if ! ref $data1;
982 for my $data (@$data1) {
983 $data =~ s/^(.)//; # strip first char (text encoding)
984 my $encoding = $1;
985 my $desc;
986 if ($id =~ /^COM[M ]?$/) {
987 $data =~ s/^(?:...)//; # strip language
988 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
989 # for sub-comment
990 $desc = $1;
993 if ($UNICODE) {
994 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
995 my $u = Unicode::String::utf16($data);
996 $data = $u->utf8;
997 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
998 } elsif ($encoding eq "\000") {
999 my $u = Unicode::String::latin1($data);
1000 $data = $u->utf8;
1004 if ($raw_v2 == 2 && $desc) {
1005 $data = { $desc => $data };
1008 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
1009 if (ref $info{$hash->{$id}} eq 'ARRAY') {
1010 push @{$info{$hash->{$id}}}, $data;
1011 } else {
1012 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
1014 } else {
1015 $info{$hash->{$id}} = $data;
1021 if ($ver == 0 && $info{TAGVERSION}) {
1022 $info{TAGVERSION} .= ' / ' . $v2h->{version};
1023 } else {
1024 $info{TAGVERSION} = $v2h->{version};
1029 unless ($raw_v2 && $ver == 2) {
1030 foreach my $key (keys %info) {
1031 if (defined $info{$key}) {
1032 $info{$key} =~ s/\000+.*//g;
1033 $info{$key} =~ s/\s+$//;
1037 for (@v1_tag_names) {
1038 $info{$_} = '' unless defined $info{$_};
1042 if (keys %info && exists $info{GENRE} && ! defined $info{GENRE}) {
1043 $info{GENRE} = '';
1046 _close($file, $fh);
1048 return keys %info ? {%info} : undef;
1051 sub _get_v2tag {
1052 my($fh) = @_;
1053 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
1054 $h = {};
1056 $v2 = _get_v2head($fh) or return;
1057 if ($v2->{major_version} < 2) {
1058 warn "This is $v2->{version}; " .
1059 "ID3v2 versions older than ID3v2.2.0 not supported\n"
1060 if $^W;
1061 return;
1064 if ($v2->{major_version} == 2) {
1065 $hlen = 6;
1066 $num = 3;
1067 } else {
1068 $hlen = 10;
1069 $num = 4;
1072 $myseek = sub {
1073 seek $fh, $off, 0;
1074 read $fh, my($bytes), $hlen;
1075 return unless $bytes =~ /^([A-Z0-9]{$num})/
1076 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes
1077 my($id, $size) = ($1, $hlen);
1078 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num);
1079 for my $i (0 .. ($num - 1)) {
1080 $size += $bytes[$i] * 256 ** $i;
1082 return($id, $size);
1085 $off = $v2->{ext_header_size} + 10;
1087 while ($off < $v2->{tag_size}) {
1088 my($id, $size) = &$myseek or last;
1089 seek $fh, $off + $hlen, 0;
1090 read $fh, my($bytes), $size - $hlen;
1091 if (exists $h->{$id}) {
1092 if (ref $h->{$id} eq 'ARRAY') {
1093 push @{$h->{$id}}, $bytes;
1094 } else {
1095 $h->{$id} = [$h->{$id}, $bytes];
1097 } else {
1098 $h->{$id} = $bytes;
1100 $off += $size;
1103 return($h, $v2);
1107 =pod
1109 =item get_mp3info (FILE)
1111 Returns hash reference containing file information for MP3 file.
1112 This data cannot be changed. Returned data:
1114 VERSION MPEG audio version (1, 2, 2.5)
1115 LAYER MPEG layer description (1, 2, 3)
1116 STEREO boolean for audio is in stereo
1118 VBR boolean for variable bitrate
1119 BITRATE bitrate in kbps (average for VBR files)
1120 FREQUENCY frequency in kHz
1121 SIZE bytes in audio stream
1123 SECS total seconds
1124 MM minutes
1125 SS leftover seconds
1126 MS leftover milliseconds
1127 TIME time in MM:SS
1129 COPYRIGHT boolean for audio is copyrighted
1130 PADDING boolean for MP3 frames are padded
1131 MODE channel mode (0 = stereo, 1 = joint stereo,
1132 2 = dual channel, 3 = single channel)
1133 FRAMES approximate number of frames
1134 FRAME_LENGTH approximate length of a frame
1135 VBR_SCALE VBR scale from VBR header
1137 On error, returns nothing and sets C<$@>.
1139 =cut
1141 sub get_mp3info {
1142 my($file) = @_;
1143 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
1145 if (not (defined $file && $file ne '')) {
1146 $@ = "No file specified";
1147 return undef;
1150 if (not -s $file) {
1151 $@ = "File is empty";
1152 return undef;
1155 if (ref $file) { # filehandle passed
1156 $fh = $file;
1157 } else {
1158 $fh = gensym;
1159 if (not open $fh, "< $file\0") {
1160 $@ = "Can't open $file: $!";
1161 return undef;
1165 $off = 0;
1166 $tot = 4096;
1168 $myseek = sub {
1169 seek $fh, $off, 0;
1170 read $fh, $byte, 4;
1173 binmode $fh;
1174 &$myseek;
1176 if ($off == 0) {
1177 if (my $id3v2 = _get_v2head($fh)) {
1178 $tot += $off += $id3v2->{tag_size};
1179 &$myseek;
1183 $h = _get_head($byte);
1184 until (_is_mp3($h)) {
1185 $off++;
1186 &$myseek;
1187 $h = _get_head($byte);
1188 if ($off > $tot && !$try_harder) {
1189 _close($file, $fh);
1190 $@ = "Couldn't find MP3 header (perhaps set " .
1191 '$MP3::Info::try_harder and retry)';
1192 return undef;
1196 my $vbr = _get_vbr($fh, $h, \$off);
1198 seek $fh, 0, 2;
1199 $eof = tell $fh;
1200 seek $fh, -128, 2;
1201 $off += 128 if <$fh> =~ /^TAG/ ? 1 : 0;
1203 _close($file, $fh);
1205 $h->{size} = $eof - $off;
1207 return _get_info($h, $vbr);
1210 sub _get_info {
1211 my($h, $vbr) = @_;
1212 my $i;
1214 $i->{VERSION} = $h->{IDR} == 2 ? 2 : $h->{IDR} == 3 ? 1 :
1215 $h->{IDR} == 0 ? 2.5 : 0;
1216 $i->{LAYER} = 4 - $h->{layer};
1217 $i->{VBR} = defined $vbr ? 1 : 0;
1219 $i->{COPYRIGHT} = $h->{copyright} ? 1 : 0;
1220 $i->{PADDING} = $h->{padding_bit} ? 1 : 0;
1221 $i->{STEREO} = $h->{mode} == 3 ? 0 : 1;
1222 $i->{MODE} = $h->{mode};
1224 $i->{SIZE} = $vbr && $vbr->{bytes} ? $vbr->{bytes} : $h->{size};
1226 my $mfs = $h->{fs} / ($h->{ID} ? 144000 : 72000);
1227 $i->{FRAMES} = int($vbr && $vbr->{frames}
1228 ? $vbr->{frames}
1229 : $i->{SIZE} / $h->{bitrate} / $mfs
1232 if ($vbr) {
1233 $i->{VBR_SCALE} = $vbr->{scale} if $vbr->{scale};
1234 $h->{bitrate} = $i->{SIZE} / $i->{FRAMES} * $mfs;
1235 if (not $h->{bitrate}) {
1236 $@ = "Couldn't determine VBR bitrate";
1237 return undef;
1241 $h->{'length'} = ($i->{SIZE} * 8) / $h->{bitrate} / 10;
1242 $i->{SECS} = $h->{'length'} / 100;
1243 $i->{MM} = int $i->{SECS} / 60;
1244 $i->{SS} = int $i->{SECS} % 60;
1245 $i->{MS} = (($i->{SECS} - ($i->{MM} * 60) - $i->{SS}) * 1000);
1246 # $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS});
1247 # int($i->{MS} / 100 * 75); # is this right?
1248 $i->{TIME} = sprintf "%.2d:%.2d", @{$i}{'MM', 'SS'};
1250 $i->{BITRATE} = int $h->{bitrate};
1251 # should we just return if ! FRAMES?
1252 $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES};
1253 $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}];
1255 return $i;
1258 sub _get_head {
1259 my($byte) = @_;
1260 my($bytes, $h);
1262 $bytes = _unpack_head($byte);
1263 @$h{qw(IDR ID layer protection_bit
1264 bitrate_index sampling_freq padding_bit private_bit
1265 mode mode_extension copyright original
1266 emphasis version_index bytes)} = (
1267 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1,
1268 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1,
1269 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1,
1270 $bytes&3, ($bytes>>19)&3, $bytes
1273 $h->{bitrate} = $t_bitrate[$h->{ID}][3 - $h->{layer}][$h->{bitrate_index}];
1274 $h->{fs} = $t_sampling_freq[$h->{IDR}][$h->{sampling_freq}];
1276 return $h;
1279 sub _is_mp3 {
1280 my $h = $_[0] or return undef;
1281 return ! ( # all below must be false
1282 $h->{bitrate_index} == 0
1284 $h->{version_index} == 1
1286 ($h->{bytes} & 0xFFE00000) != 0xFFE00000
1288 !$h->{fs}
1290 !$h->{bitrate}
1292 $h->{bitrate_index} == 15
1294 !$h->{layer}
1296 $h->{sampling_freq} == 3
1298 $h->{emphasis} == 2
1300 !$h->{bitrate_index}
1302 ($h->{bytes} & 0xFFFF0000) == 0xFFFE0000
1304 ($h->{ID} == 1 && $h->{layer} == 3 && $h->{protection_bit} == 1)
1306 ($h->{mode_extension} != 0 && $h->{mode} != 1)
1310 sub _get_vbr {
1311 my($fh, $h, $roff) = @_;
1312 my($off, $bytes, @bytes, $myseek, %vbr);
1314 $off = $$roff;
1315 @_ = (); # closure confused if we don't do this
1317 $myseek = sub {
1318 my $n = $_[0] || 4;
1319 seek $fh, $off, 0;
1320 read $fh, $bytes, $n;
1321 $off += $n;
1324 $off += 4;
1326 if ($h->{ID}) { # MPEG1
1327 $off += $h->{mode} == 3 ? 17 : 32;
1328 } else { # MPEG2
1329 $off += $h->{mode} == 3 ? 9 : 17;
1332 &$myseek;
1333 return unless $bytes eq 'Xing';
1335 &$myseek;
1336 $vbr{flags} = _unpack_head($bytes);
1338 if ($vbr{flags} & 1) {
1339 &$myseek;
1340 $vbr{frames} = _unpack_head($bytes);
1343 if ($vbr{flags} & 2) {
1344 &$myseek;
1345 $vbr{bytes} = _unpack_head($bytes);
1348 if ($vbr{flags} & 4) {
1349 $myseek->(100);
1350 # Not used right now ...
1351 # $vbr{toc} = _unpack_head($bytes);
1354 if ($vbr{flags} & 8) { # (quality ind., 0=best 100=worst)
1355 &$myseek;
1356 $vbr{scale} = _unpack_head($bytes);
1357 } else {
1358 $vbr{scale} = -1;
1361 $$roff = $off;
1362 return \%vbr;
1365 sub _get_v2head {
1366 my $fh = $_[0] or return;
1367 my($h, $bytes, @bytes);
1369 # check first three bytes for 'ID3'
1370 seek $fh, 0, 0;
1371 read $fh, $bytes, 3;
1372 return unless $bytes eq 'ID3';
1374 # get version
1375 read $fh, $bytes, 2;
1376 $h->{version} = sprintf "ID3v2.%d.%d",
1377 @$h{qw[major_version minor_version]} =
1378 unpack 'c2', $bytes;
1380 # get flags
1381 read $fh, $bytes, 1;
1382 if ($h->{major_version} == 2) {
1383 @$h{qw[unsync compression]} =
1384 (unpack 'b8', $bytes)[7, 6];
1385 $h->{ext_header} = 0;
1386 $h->{experimental} = 0;
1387 } else {
1388 @$h{qw[unsync ext_header experimental]} =
1389 (unpack 'b8', $bytes)[7, 6, 5];
1392 # get ID3v2 tag length from bytes 7-10
1393 $h->{tag_size} = 10; # include ID3v2 header size
1394 read $fh, $bytes, 4;
1395 @bytes = reverse unpack 'C4', $bytes;
1396 foreach my $i (0 .. 3) {
1397 # whoaaaaaa nellllllyyyyyy!
1398 $h->{tag_size} += $bytes[$i] * 128 ** $i;
1401 # get extended header size
1402 $h->{ext_header_size} = 0;
1403 if ($h->{ext_header}) {
1404 $h->{ext_header_size} += 10;
1405 read $fh, $bytes, 4;
1406 @bytes = reverse unpack 'C4', $bytes;
1407 for my $i (0..3) {
1408 $h->{ext_header_size} += $bytes[$i] * 256 ** $i;
1412 return $h;
1415 sub _unpack_head {
1416 unpack('l', pack('L', unpack('N', $_[0])));
1419 sub _close {
1420 my($file, $fh) = @_;
1421 unless (ref $file) { # filehandle not passed
1422 close $fh or warn "Problem closing '$file': $!";
1426 BEGIN {
1427 @mp3_genres = (
1428 'Blues',
1429 'Classic Rock',
1430 'Country',
1431 'Dance',
1432 'Disco',
1433 'Funk',
1434 'Grunge',
1435 'Hip-Hop',
1436 'Jazz',
1437 'Metal',
1438 'New Age',
1439 'Oldies',
1440 'Other',
1441 'Pop',
1442 'R&B',
1443 'Rap',
1444 'Reggae',
1445 'Rock',
1446 'Techno',
1447 'Industrial',
1448 'Alternative',
1449 'Ska',
1450 'Death Metal',
1451 'Pranks',
1452 'Soundtrack',
1453 'Euro-Techno',
1454 'Ambient',
1455 'Trip-Hop',
1456 'Vocal',
1457 'Jazz+Funk',
1458 'Fusion',
1459 'Trance',
1460 'Classical',
1461 'Instrumental',
1462 'Acid',
1463 'House',
1464 'Game',
1465 'Sound Clip',
1466 'Gospel',
1467 'Noise',
1468 'AlternRock',
1469 'Bass',
1470 'Soul',
1471 'Punk',
1472 'Space',
1473 'Meditative',
1474 'Instrumental Pop',
1475 'Instrumental Rock',
1476 'Ethnic',
1477 'Gothic',
1478 'Darkwave',
1479 'Techno-Industrial',
1480 'Electronic',
1481 'Pop-Folk',
1482 'Eurodance',
1483 'Dream',
1484 'Southern Rock',
1485 'Comedy',
1486 'Cult',
1487 'Gangsta',
1488 'Top 40',
1489 'Christian Rap',
1490 'Pop/Funk',
1491 'Jungle',
1492 'Native American',
1493 'Cabaret',
1494 'New Wave',
1495 'Psychadelic',
1496 'Rave',
1497 'Showtunes',
1498 'Trailer',
1499 'Lo-Fi',
1500 'Tribal',
1501 'Acid Punk',
1502 'Acid Jazz',
1503 'Polka',
1504 'Retro',
1505 'Musical',
1506 'Rock & Roll',
1507 'Hard Rock',
1510 @winamp_genres = (
1511 @mp3_genres,
1512 'Folk',
1513 'Folk-Rock',
1514 'National Folk',
1515 'Swing',
1516 'Fast Fusion',
1517 'Bebob',
1518 'Latin',
1519 'Revival',
1520 'Celtic',
1521 'Bluegrass',
1522 'Avantgarde',
1523 'Gothic Rock',
1524 'Progressive Rock',
1525 'Psychedelic Rock',
1526 'Symphonic Rock',
1527 'Slow Rock',
1528 'Big Band',
1529 'Chorus',
1530 'Easy Listening',
1531 'Acoustic',
1532 'Humour',
1533 'Speech',
1534 'Chanson',
1535 'Opera',
1536 'Chamber Music',
1537 'Sonata',
1538 'Symphony',
1539 'Booty Bass',
1540 'Primus',
1541 'Porn Groove',
1542 'Satire',
1543 'Slow Jam',
1544 'Club',
1545 'Tango',
1546 'Samba',
1547 'Folklore',
1548 'Ballad',
1549 'Power Ballad',
1550 'Rhythmic Soul',
1551 'Freestyle',
1552 'Duet',
1553 'Punk Rock',
1554 'Drum Solo',
1555 'Acapella',
1556 'Euro-House',
1557 'Dance Hall',
1558 'Goa',
1559 'Drum & Bass',
1560 'Club-House',
1561 'Hardcore',
1562 'Terror',
1563 'Indie',
1564 'BritPop',
1565 'Negerpunk',
1566 'Polsk Punk',
1567 'Beat',
1568 'Christian Gangsta Rap',
1569 'Heavy Metal',
1570 'Black Metal',
1571 'Crossover',
1572 'Contemporary Christian',
1573 'Christian Rock',
1574 'Merengue',
1575 'Salsa',
1576 'Thrash Metal',
1577 'Anime',
1578 'JPop',
1579 'Synthpop',
1582 @t_bitrate = ([
1583 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
1584 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
1585 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
1587 [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
1588 [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
1589 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
1592 @t_sampling_freq = (
1593 [11025, 12000, 8000],
1594 [undef, undef, undef], # reserved
1595 [22050, 24000, 16000],
1596 [44100, 48000, 32000]
1599 @frequency_tbl = map { $_ ? eval "${_}e-3" : 0 }
1600 map { @$_ } @t_sampling_freq;
1602 @mp3_info_fields = qw(
1603 VERSION
1604 LAYER
1605 STEREO
1607 BITRATE
1608 FREQUENCY
1609 SIZE
1610 SECS
1614 TIME
1615 COPYRIGHT
1616 PADDING
1617 MODE
1618 FRAMES
1619 FRAME_LENGTH
1620 VBR_SCALE
1623 %v1_tag_fields =
1624 (TITLE => 30, ARTIST => 30, ALBUM => 30, COMMENT => 30, YEAR => 4);
1626 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1628 %v2_to_v1_names = (
1629 # v2.2 tags
1630 'TT2' => 'TITLE',
1631 'TP1' => 'ARTIST',
1632 'TAL' => 'ALBUM',
1633 'TYE' => 'YEAR',
1634 'COM' => 'COMMENT',
1635 'TRK' => 'TRACKNUM',
1636 'TCO' => 'GENRE', # not clean mapping, but ...
1637 # v2.3 tags
1638 'TIT2' => 'TITLE',
1639 'TPE1' => 'ARTIST',
1640 'TALB' => 'ALBUM',
1641 'TYER' => 'YEAR',
1642 'COMM' => 'COMMENT',
1643 'TRCK' => 'TRACKNUM',
1644 'TCON' => 'GENRE',
1647 %v2_tag_names = (
1648 # v2.2 tags
1649 'BUF' => 'Recommended buffer size',
1650 'CNT' => 'Play counter',
1651 'COM' => 'Comments',
1652 'CRA' => 'Audio encryption',
1653 'CRM' => 'Encrypted meta frame',
1654 'ETC' => 'Event timing codes',
1655 'EQU' => 'Equalization',
1656 'GEO' => 'General encapsulated object',
1657 'IPL' => 'Involved people list',
1658 'LNK' => 'Linked information',
1659 'MCI' => 'Music CD Identifier',
1660 'MLL' => 'MPEG location lookup table',
1661 'PIC' => 'Attached picture',
1662 'POP' => 'Popularimeter',
1663 'REV' => 'Reverb',
1664 'RVA' => 'Relative volume adjustment',
1665 'SLT' => 'Synchronized lyric/text',
1666 'STC' => 'Synced tempo codes',
1667 'TAL' => 'Album/Movie/Show title',
1668 'TBP' => 'BPM (Beats Per Minute)',
1669 'TCM' => 'Composer',
1670 'TCO' => 'Content type',
1671 'TCR' => 'Copyright message',
1672 'TDA' => 'Date',
1673 'TDY' => 'Playlist delay',
1674 'TEN' => 'Encoded by',
1675 'TFT' => 'File type',
1676 'TIM' => 'Time',
1677 'TKE' => 'Initial key',
1678 'TLA' => 'Language(s)',
1679 'TLE' => 'Length',
1680 'TMT' => 'Media type',
1681 'TOA' => 'Original artist(s)/performer(s)',
1682 'TOF' => 'Original filename',
1683 'TOL' => 'Original Lyricist(s)/text writer(s)',
1684 'TOR' => 'Original release year',
1685 'TOT' => 'Original album/Movie/Show title',
1686 'TP1' => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
1687 'TP2' => 'Band/Orchestra/Accompaniment',
1688 'TP3' => 'Conductor/Performer refinement',
1689 'TP4' => 'Interpreted, remixed, or otherwise modified by',
1690 'TPA' => 'Part of a set',
1691 'TPB' => 'Publisher',
1692 'TRC' => 'ISRC (International Standard Recording Code)',
1693 'TRD' => 'Recording dates',
1694 'TRK' => 'Track number/Position in set',
1695 'TSI' => 'Size',
1696 'TSS' => 'Software/hardware and settings used for encoding',
1697 'TT1' => 'Content group description',
1698 'TT2' => 'Title/Songname/Content description',
1699 'TT3' => 'Subtitle/Description refinement',
1700 'TXT' => 'Lyricist/text writer',
1701 'TXX' => 'User defined text information frame',
1702 'TYE' => 'Year',
1703 'UFI' => 'Unique file identifier',
1704 'ULT' => 'Unsychronized lyric/text transcription',
1705 'WAF' => 'Official audio file webpage',
1706 'WAR' => 'Official artist/performer webpage',
1707 'WAS' => 'Official audio source webpage',
1708 'WCM' => 'Commercial information',
1709 'WCP' => 'Copyright/Legal information',
1710 'WPB' => 'Publishers official webpage',
1711 'WXX' => 'User defined URL link frame',
1713 # v2.3 tags
1714 'AENC' => 'Audio encryption',
1715 'APIC' => 'Attached picture',
1716 'COMM' => 'Comments',
1717 'COMR' => 'Commercial frame',
1718 'ENCR' => 'Encryption method registration',
1719 'EQUA' => 'Equalization',
1720 'ETCO' => 'Event timing codes',
1721 'GEOB' => 'General encapsulated object',
1722 'GRID' => 'Group identification registration',
1723 'IPLS' => 'Involved people list',
1724 'LINK' => 'Linked information',
1725 'MCDI' => 'Music CD identifier',
1726 'MLLT' => 'MPEG location lookup table',
1727 'OWNE' => 'Ownership frame',
1728 'PCNT' => 'Play counter',
1729 'POPM' => 'Popularimeter',
1730 'POSS' => 'Position synchronisation frame',
1731 'PRIV' => 'Private frame',
1732 'RBUF' => 'Recommended buffer size',
1733 'RVAD' => 'Relative volume adjustment',
1734 'RVRB' => 'Reverb',
1735 'SYLT' => 'Synchronized lyric/text',
1736 'SYTC' => 'Synchronized tempo codes',
1737 'TALB' => 'Album/Movie/Show title',
1738 'TBPM' => 'BPM (beats per minute)',
1739 'TCOM' => 'Composer',
1740 'TCON' => 'Content type',
1741 'TCOP' => 'Copyright message',
1742 'TDAT' => 'Date',
1743 'TDLY' => 'Playlist delay',
1744 'TENC' => 'Encoded by',
1745 'TEXT' => 'Lyricist/Text writer',
1746 'TFLT' => 'File type',
1747 'TIME' => 'Time',
1748 'TIT1' => 'Content group description',
1749 'TIT2' => 'Title/songname/content description',
1750 'TIT3' => 'Subtitle/Description refinement',
1751 'TKEY' => 'Initial key',
1752 'TLAN' => 'Language(s)',
1753 'TLEN' => 'Length',
1754 'TMED' => 'Media type',
1755 'TOAL' => 'Original album/movie/show title',
1756 'TOFN' => 'Original filename',
1757 'TOLY' => 'Original lyricist(s)/text writer(s)',
1758 'TOPE' => 'Original artist(s)/performer(s)',
1759 'TORY' => 'Original release year',
1760 'TOWN' => 'File owner/licensee',
1761 'TPE1' => 'Lead performer(s)/Soloist(s)',
1762 'TPE2' => 'Band/orchestra/accompaniment',
1763 'TPE3' => 'Conductor/performer refinement',
1764 'TPE4' => 'Interpreted, remixed, or otherwise modified by',
1765 'TPOS' => 'Part of a set',
1766 'TPUB' => 'Publisher',
1767 'TRCK' => 'Track number/Position in set',
1768 'TRDA' => 'Recording dates',
1769 'TRSN' => 'Internet radio station name',
1770 'TRSO' => 'Internet radio station owner',
1771 'TSIZ' => 'Size',
1772 'TSRC' => 'ISRC (international standard recording code)',
1773 'TSSE' => 'Software/Hardware and settings used for encoding',
1774 'TXXX' => 'User defined text information frame',
1775 'TYER' => 'Year',
1776 'UFID' => 'Unique file identifier',
1777 'USER' => 'Terms of use',
1778 'USLT' => 'Unsychronized lyric/text transcription',
1779 'WCOM' => 'Commercial information',
1780 'WCOP' => 'Copyright/Legal information',
1781 'WOAF' => 'Official audio file webpage',
1782 'WOAR' => 'Official artist/performer webpage',
1783 'WOAS' => 'Official audio source webpage',
1784 'WORS' => 'Official internet radio station homepage',
1785 'WPAY' => 'Payment',
1786 'WPUB' => 'Publishers official webpage',
1787 'WXXX' => 'User defined URL link frame',
1789 # v2.4 additional tags
1790 # note that we don't restrict tags from 2.3 or 2.4,
1791 'ASPI' => 'Audio seek point index',
1792 'EQU2' => 'Equalisation (2)',
1793 'RVA2' => 'Relative volume adjustment (2)',
1794 'SEEK' => 'Seek frame',
1795 'SIGN' => 'Signature frame',
1796 'TDEN' => 'Encoding time',
1797 'TDOR' => 'Original release time',
1798 'TDRC' => 'Recording time',
1799 'TDRL' => 'Release time',
1800 'TDTG' => 'Tagging time',
1801 'TIPL' => 'Involved people list',
1802 'TMCL' => 'Musician credits list',
1803 'TMOO' => 'Mood',
1804 'TPRO' => 'Produced notice',
1805 'TSOA' => 'Album sort order',
1806 'TSOP' => 'Performer sort order',
1807 'TSOT' => 'Title sort order',
1808 'TSST' => 'Set subtitle',
1810 # grrrrrrr
1811 'COM ' => 'Broken iTunes comments',
1817 __END__
1819 =pod
1821 =back
1823 =head1 TROUBLESHOOTING
1825 If you find a bug, please send me a patch (see the project page in L<"SEE ALSO">).
1826 If you cannot figure out why it does not work for you, please put the MP3 file in
1827 a place where I can get it (preferably via FTP, or HTTP, or .Mac iDisk) and send me
1828 mail regarding where I can get the file, with a detailed description of the problem.
1830 If I download the file, after debugging the problem I will not keep the MP3 file
1831 if it is not legal for me to have it. Just let me know if it is legal for me to
1832 keep it or not.
1835 =head1 TODO
1837 =over 4
1839 =item ID3v2 Support
1841 Still need to do more for reading tags, such as using Compress::Zlib to decompress
1842 compressed tags. But until I see this in use more, I won't bother. If something
1843 does not work properly with reading, follow the instructions above for
1844 troubleshooting.
1846 ID3v2 I<writing> is coming soon.
1848 =item Get data from scalar
1850 Instead of passing a file spec or filehandle, pass the
1851 data itself. Would take some work, converting the seeks, etc.
1853 =item Padding bit ?
1855 Do something with padding bit.
1857 =item Test suite
1859 Test suite could use a bit of an overhaul and update. Patches very welcome.
1861 =over 4
1863 =item *
1865 Revamp getset.t. Test all the various get_mp3tag args.
1867 =item *
1869 Test Unicode.
1871 =item *
1873 Test OOP API.
1875 =item *
1877 Test error handling, check more for missing files, bad MP3s, etc.
1879 =back
1881 =item Other VBR
1883 Right now, only Xing VBR is supported.
1885 =back
1888 =head1 THANKS
1890 Edward Allen E<lt>allenej@c51844-a.spokn1.wa.home.comE<gt>,
1891 Vittorio Bertola E<lt>v.bertola@vitaminic.comE<gt>,
1892 Michael Blakeley E<lt>mike@blakeley.comE<gt>,
1893 Per Bolmstedt E<lt>tomten@kol14.comE<gt>,
1894 Tony Bowden E<lt>tony@tmtm.comE<gt>,
1895 Tom Brown E<lt>thecap@usa.netE<gt>,
1896 Sergio Camarena E<lt>scamarena@users.sourceforge.netE<gt>,
1897 Chris Dawson E<lt>cdawson@webiphany.comE<gt>,
1898 Luke Drumm E<lt>lukedrumm@mypad.comE<gt>,
1899 Kyle Farrell E<lt>kyle@cantametrix.comE<gt>,
1900 Jeffrey Friedl E<lt>jfriedl@yahoo.comE<gt>,
1901 brian d foy E<lt>comdog@panix.comE<gt>,
1902 Ben Gertzfield E<lt>che@debian.orgE<gt>,
1903 Brian Goodwin E<lt>brian@fuddmain.comE<gt>,
1904 Todd Hanneken E<lt>thanneken@hds.harvard.eduE<gt>,
1905 Todd Harris E<lt>harris@cshl.orgE<gt>,
1906 Woodrow Hill E<lt>asim@mindspring.comE<gt>,
1907 Kee Hinckley E<lt>nazgul@somewhere.comE<gt>,
1908 Roman Hodek E<lt>Roman.Hodek@informatik.uni-erlangen.deE<gt>,
1909 Peter Kovacs E<lt>kovacsp@egr.uri.eduE<gt>,
1910 Johann Lindvall,
1911 Peter Marschall E<lt>peter.marschall@mayn.deE<gt>,
1912 Trond Michelsen E<lt>mike@crusaders.noE<gt>,
1913 Dave O'Neill E<lt>dave@nexus.carleton.caE<gt>,
1914 Christoph Oberauer E<lt>christoph.oberauer@sbg.ac.atE<gt>,
1915 Jake Palmer E<lt>jake.palmer@db.comE<gt>,
1916 Andrew Phillips E<lt>asp@wasteland.orgE<gt>,
1917 David Reuteler E<lt>reuteler@visi.comE<gt>,
1918 John Ruttenberg E<lt>rutt@chezrutt.comE<gt>,
1919 Matthew Sachs E<lt>matthewg@zevils.comE<gt>,
1920 E<lt>scfc_de@users.sf.netE<gt>,
1921 Hermann Schwaerzler E<lt>Hermann.Schwaerzler@uibk.ac.atE<gt>,
1922 Chris Sidi E<lt>sidi@angband.orgE<gt>,
1923 Roland Steinbach E<lt>roland@support-system.comE<gt>,
1924 Stuart E<lt>schneis@users.sourceforge.netE<gt>,
1925 Jeffery Sumler E<lt>jsumler@mediaone.netE<gt>,
1926 Predrag Supurovic E<lt>mpgtools@dv.co.yuE<gt>,
1927 Bogdan Surdu E<lt>tim@go.roE<gt>,
1928 E<lt>tim@tim-landscheidt.deE<gt>,
1929 Pass F. B. Travis E<lt>pftravis@bellsouth.netE<gt>,
1930 Tobias Wagener E<lt>tobias@wagener.nuE<gt>,
1931 Ronan Waide E<lt>waider@stepstone.ieE<gt>,
1932 Andy Waite E<lt>andy@mailroute.comE<gt>,
1933 Ken Williams E<lt>ken@forum.swarthmore.eduE<gt>,
1934 Meng Weng Wong E<lt>mengwong@pobox.comE<gt>.
1937 =head1 AUTHOR AND COPYRIGHT
1939 Chris Nandor E<lt>pudge@pobox.comE<gt>, http://pudge.net/
1941 Copyright (c) 1998-2003 Chris Nandor. All rights reserved. This program is
1942 free software; you can redistribute it and/or modify it under the terms
1943 of the Artistic License, distributed with Perl.
1946 =head1 SEE ALSO
1948 =over 4
1950 =item MP3::Info Project Page
1952 http://projects.pudge.net/
1954 =item mp3tools
1956 http://www.zevils.com/linux/mp3tools/
1958 =item mpgtools
1960 http://www.dv.co.yu/mpgscript/mpgtools.htm
1961 http://www.dv.co.yu/mpgscript/mpeghdr.htm
1963 =item mp3tool
1965 http://www.dtek.chalmers.se/~d2linjo/mp3/mp3tool.html
1967 =item ID3v2
1969 http://www.id3.org/
1971 =item Xing Variable Bitrate
1973 http://www.xingtech.com/support/partner_developer/mp3/vbr_sdk/
1975 =item MP3Ext
1977 http://rupert.informatik.uni-stuttgart.de/~mutschml/MP3ext/
1979 =item Xmms
1981 http://www.xmms.org/
1984 =back
1986 =head1 VERSION
1988 v1.02, Sunday, March 2, 2003
1990 =cut