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.
12 my $db = "rockbox.tagdb";
23 if($ARGV[0] eq "--db") {
28 elsif($ARGV[0] eq "--path") {
33 elsif($ARGV[0] eq "--strip") {
38 elsif($ARGV[0] eq "--add") {
43 elsif($ARGV[0] eq "--verbose") {
47 elsif($ARGV[0] eq "--nocrc") {
51 elsif($ARGV[0] eq "--dirisalbum") {
55 elsif($ARGV[0] eq "--dirisalbumname") {
59 elsif($ARGV[0] eq "--help" or ($ARGV[0] eq "-h")) {
80 if(! -d
$dir or $help) {
81 print "'$dir' is not a directory\n" if ($dir ne "" and ! -d
$dir);
84 songdb --path <dir> [--dirisalbum] [--dirisalbumname] [--db <file>] [--strip <path>] [--add <path>] [--verbose] [--help]
88 --path <dir> Where your music collection is found
89 --dirisalbum Use dir name as album name if the album name is missing in the
91 --dirisalbumname Uh, isn\'t this the same as the above?
92 --db <file> What to call the output file. Defaults to rockbox.tagdb
93 --strip <path> Removes this string from the left of all file names
94 --add <path> Adds this string to the left of all file names
95 --nocrc Disables the CRC32 checksums. It makes the output database not
96 suitable for runtimedb but it makes this script run much
98 --verbose Shows more details while working
109 my $ogg = vorbiscomm
->new($fn);
113 # Convert this format into the same format used by the id3 parser hash
115 foreach my $k ($ogg->comment_tags())
117 foreach my $cmmt ($ogg->comment($k))
120 if($k =~ /^artist$/i) {
123 elsif($k =~ /^album$/i) {
126 elsif($k =~ /^title$/i) {
129 $hash{$n}=$cmmt if($n);
140 my $ogg = vorbiscomm
->new($fn);
144 return $ogg->{'INFO'};
147 # return ALL directory entries in the given dir
151 $dir =~ s
|/$|| if ($dir ne "/");
153 if (opendir(DIR, $dir)) {
154 my @all = readdir(DIR);
159 warn "can
't opendir $dir: $!\n";
164 my ($dir, @files) = @_;
167 if( (/\.mp[23]$/i || /\.ogg$/i) && -f "$dir/$_" ) {
175 my ($dir, @files) = @_;
179 if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) {
186 # CRC32 32KB of data (use less if there isn't
32KB available
)
189 my ($filename, $index) = @_;
194 return 0; # fixed bad CRC when disabled!
195 # The runtimedb treats a CRC zero as CRC disabled!
198 if(!open(FILE
, "<$filename")) {
199 print "failed to open \"$filename\" $!\n";
203 # read $data from index $index to $buffer from the file, may return fewer
204 # bytes when dealing with a very small file.
206 # TODO: make sure we don't include a trailer with metadata when doing this.
208 my $nread = sysread FILE
, $buffer, $len, $index;
213 ( # CRC32 lookup table for polynomial 0x04C11DB7
214 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B,
215 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
216 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7,
217 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
218 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3,
219 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
220 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF,
221 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
222 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB,
223 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
224 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0,
225 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
226 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4,
227 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE,
228 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08,
229 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
230 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC,
231 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6,
232 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050,
233 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
234 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34,
235 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637,
236 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1,
237 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
238 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5,
239 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
240 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9,
241 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
242 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD,
243 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
244 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71,
245 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,
246 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2,
247 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8,
248 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E,
249 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
250 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A,
251 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0,
252 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676,
253 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
254 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662,
255 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668,
256 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4
259 my $crc = 0xffffffff;
260 for ($i = 0; $i < $nread; $i++) {
261 # get the numeric for the byte of the $i index
262 $buf = ord(substr($buffer, $i, 1));
264 $crc = ($crc << 8) ^ $crc_table[(($crc >> 24) ^ $buf) & 0xFF];
266 # printf("%08x\n", $crc);
270 # rule out the very small risk that this actually returns a zero, as
271 # the current rockbox code assumes a zero CRC means it is disabled!
272 # TODO: fix the Rockbox code. This is just a hack.
284 if($file =~ /\.ogg$/i) {
285 $hash = get_oggtag
($file);
287 $info = get_ogginfo
($file);
289 $hash->{FILECRC
} = crc32
($file, $info->{audio_offset
});
292 $hash = get_mp3tag
($file);
294 $info = get_mp3info
($file);
296 $hash->{FILECRC
} = crc32
($file, $info->{headersize
});
299 return $hash; # a hash reference
309 # getdir() returns all entries in the given dir
310 my @a = getdir
($dir);
312 # extractmp3 filters out only the mp3 files from all given entries
313 my @m = extractmp3
($dir, @a);
319 my $id3 = singlefile
("$dir/$f");
329 # don't index songs without tags
331 if (not defined $$id3{'ARTIST'} and
332 not defined $$id3{'ALBUM'} and
333 not defined $$id3{'TITLE'})
338 #printf "Artist: %s\n", $id3->{'ARTIST'};
339 my $path = "$dir/$f";
340 if ($strip ne "" and $path =~ /^$strip(.*)/) {
345 $path = $add . $path;
348 # Only use one case-variation of each album/artist
349 if (exists($lcalbums{lc($$id3{'ALBUM'})})) {
350 # if another album with different case exists
351 # use that case (store it in $$id3{'ALBUM'}
352 $$id3{'ALBUM'} = $lcalbums{lc($$id3{'ALBUM'})};
355 # else create a new entry in the hash
356 $lcalbums{lc($$id3{'ALBUM'})} = $$id3{'ALBUM'};
359 if (exists($lcartists{lc($$id3{'ARTIST'})})) {
360 $$id3{'ARTIST'} = $lcartists{lc($$id3{'ARTIST'})};
363 $lcartists{lc($$id3{'ARTIST'})} = $$id3{'ARTIST'};
366 $entries{$path}= $id3;
367 $artists{$id3->{'ARTIST'}}++ if($id3->{'ARTIST'});
368 $genres{$id3->{'GENRE'}}++ if($id3->{'GENRE'});
369 $years{$id3->{'YEAR'}}++ if($id3->{'YEAR'});
372 $$id3{'ARTIST'} = "<no artist tag>" if ($$id3{'ARTIST'} eq "");
373 # Fall back on the directory name (not full path dirname),
376 if($dir2albumname{$dir} eq "") {
377 $dir2albumname{$dir} = $$id3{'ALBUM'};
379 elsif($dir2albumname{$dir} ne $$id3{'ALBUM'}) {
380 $dir2albumname{$dir} = (split m
[/], $dir)[-1];
384 if ($dirisalbumname) {
385 $$id3{'ALBUM'} = (split m
[/], $dir)[-1] if ($$id3{'ALBUM'} eq "");
387 $$id3{'ALBUM'} = "<no album tag>" if ($$id3{'ALBUM'} eq "");
388 # fall back on basename of the file if no title tag.
390 ($base = $f) =~ s/\.\w+$//;
391 $$id3{'TITLE'} = $base if ($$id3{'TITLE'} eq "");
393 # Append dirname, to handle multi-artist albums
397 $albumid=$$id3{'DIR'};
400 $albumid= $id3->{'ALBUM'}."___".$$id3{'DIR'};
402 #printf "album id: %s\n", $albumid;
404 # if($id3->{'ALBUM'}."___".$id3->{'DIR'} ne "<no album tag>___<no artist tag>") {
405 my $num = ++$albums{$albumid};
406 if($num > $maxsongperalbum) {
407 $maxsongperalbum = $num;
408 $longestalbum = $albumid;
410 $album2songs{$albumid}{$$id3{TITLE
}} = $id3;
412 $artist2albums{$$id3{ARTIST
}}{$$id3{DIR
}} = $id3;
415 $artist2albums{$$id3{ARTIST
}}{$$id3{ALBUM
}} = $id3;
420 if($dirisalbum and $dir2albumname{$dir} eq "") {
421 $dir2albumname{$dir} = (split m
[/], $dir)[-1];
422 printf "%s\n", $dir2albumname{$dir};
425 # extractdirs filters out only subdirectories from all given entries
426 my @d = extractdirs
($dir, @a);
438 print "File name table\n" if ($verbose);
439 for(sort keys %entries) {
440 printf(" %s\n", $_) if ($verbose);
442 if($l > $maxfilelen) {
444 $longestfilename = $_;
447 $maxfilelen++; # include zero termination byte
448 while($maxfilelen&3) {
454 print "\nSong title table\n" if ($verbose);
456 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
457 printf(" %s\n", $entries{$_}->{'TITLE'} ) if ($verbose);
458 my $l = length($entries{$_}->{'TITLE'});
459 if($l > $maxsonglen) {
461 $longestsong = $entries{$_}->{'TITLE'};
464 $maxsonglen++; # include zero termination byte
465 while($maxsonglen&3) {
469 my $maxartistlen = 0;
470 print "\nArtist table\n" if ($verbose);
473 for(sort {uc($a) cmp uc($b)} keys %artists) {
474 printf(" %s: %d\n", $_, $i) if ($verbose);
475 $artistcount{$_}=$i++;
477 if($l > $maxartistlen) {
482 $l = scalar keys %{$artist2albums{$_}};
483 if ($l > $maxalbumsperartist) {
484 $maxalbumsperartist = $l;
485 $longestartistalbum = $_;
488 $maxartistlen++; # include zero termination byte
489 while($maxartistlen&3) {
493 print "\nGenre table\n" if ($verbose);
494 for(sort keys %genres) {
496 if($l > $maxgenrelen) {
498 $longestgenrename = $_;
502 $maxgenrelen++; #include zero termination byte
503 while($maxgenrelen&3) {
509 print "\nYear table\n";
510 for(sort keys %years) {
515 print "\nAlbum table\n" if ($verbose);
521 @albumssort = sort {uc($dir2albumname{$a}) cmp uc($dir2albumname{$b})} keys %albums;
524 @albumssort = sort {uc($a) cmp uc($b)} keys %albums;
527 my @moo=split(/___/, $_);
528 printf(" %s\n", $moo[0]) if ($verbose);
529 $albumcount{$_} = $i++;
532 $l = length($dir2albumname{$_});
535 $l = length($moo[0]);
537 if($l > $maxalbumlen) {
540 $longestalbumname = $dir2albumname{$_};
543 $longestalbumname = $moo[0];
547 $maxalbumlen++; # include zero termination byte
548 while($maxalbumlen&3) {
557 # print "int: $num\n";
559 print DB
pack "n", $num;
565 # print "int: $num\n";
567 print DB
pack "N", $num;
570 if (!scalar keys %entries) {
571 print "No songs found. Did you specify the right --path ?\n";
572 print "Use the --help parameter to see all options.\n";
577 my $songentrysize = $maxsonglen + 12 + $maxgenrelen+ 12;
578 my $albumentrysize = $maxalbumlen + 4 + $maxsongperalbum*4;
579 my $artistentrysize = $maxartistlen + $maxalbumsperartist*4;
580 my $fileentrysize = $maxfilelen + 12;
582 printf "Number of artists : %d\n", scalar keys %artists;
583 printf "Number of albums : %d\n", scalar keys %albums;
584 printf "Number of songs / files : %d\n", scalar keys %entries;
585 print "Max artist length : $maxartistlen ($longestartist)\n";
586 print "Max album length : $maxalbumlen ($longestalbumname)\n";
587 print "Max song length : $maxsonglen ($longestsong)\n";
588 print "Max songs per album : $maxsongperalbum ($longestalbum)\n";
589 print "Max albums per artist: $maxalbumsperartist ($longestartistalbum)\n";
590 print "Max genre length : $maxgenrelen ($longestgenrename)\n";
591 print "Max file length : $maxfilelen ($longestfilename)\n";
592 print "Database version: $dbver\n" if ($verbose);
593 print "Song Entry Size : $songentrysize ($maxsonglen + 12 + $maxgenrelen + 4)\n" if ($verbose);
594 print "Album Entry Size: $albumentrysize ($maxalbumlen + 4 + $maxsongperalbum * 4)\n" if ($verbose);
595 print "Artist Entry Size: $artistentrysize ($maxartistlen + $maxalbumsperartist * 4)\n" if ($verbose);
596 print "File Entry Size: $fileentrysize ($maxfilelen + 12)\n" if ($verbose);
599 open(DB
, ">$db") || die "couldn't make $db";
601 printf DB
"RDB%c", $dbver;
603 $pathindex = 68; # paths always start at index 68
605 $artistindex = $pathindex;
607 # set total size of song title table
608 $sc = scalar(keys %entries) * $songentrysize;
609 my $ac = scalar(keys %albums) * $albumentrysize;
610 my $arc = scalar(keys %artists) * $artistentrysize;
611 $albumindex = $artistindex + $arc; # arc is size of all artists
612 $songindex = $albumindex + $ac; # ac is size of all albums
613 my $fileindex = $songindex + $sc; # sc is size of all songs
615 dumpint
($artistindex); # file position index of artist table
616 dumpint
($albumindex); # file position index of album table
617 dumpint
($songindex); # file position index of song table
618 dumpint
($fileindex); # file position index of file table
619 dumpint
(scalar(keys %artists)); # number of artists
620 dumpint
(scalar(keys %albums)); # number of albums
621 dumpint
(scalar(keys %entries)); # number of songs
622 dumpint
(scalar(keys %entries)); # number of files
623 dumpint
($maxartistlen); # length of artist name field
624 dumpint
($maxalbumlen); # length of album name field
625 dumpint
($maxsonglen); # length of song name field
626 dumpint
($maxgenrelen); #length of genre field
627 dumpint
($maxfilelen); # length of file field
628 dumpint
($maxsongperalbum); # number of entries in songs-per-album array
629 dumpint
($maxalbumsperartist); # number of entries in albums-per-artist array
630 dumpint
(-1); # rundb dirty
632 #### TABLE of artists ###
634 # pointers to albums of artist1
636 for (sort {uc($a) cmp uc($b)} keys %artists) {
638 my $str = $_."\x00" x
($maxartistlen - length($_));
641 for (sort keys %{$artist2albums{$artist}}) {
642 my $id3 = $artist2albums{$artist}{$_};
645 $a = $albumcount{"$$id3{'DIR'}"} * $albumentrysize;
648 $a = $albumcount{"$$id3{'ALBUM'}___$$id3{'DIR'}"} * $albumentrysize;
650 dumpint
($a + $albumindex);
653 for (scalar keys %{$artist2albums{$artist}} .. $maxalbumsperartist-1) {
654 print DB
"\x00\x00\x00\x00";
659 ### Build song offset info.
660 my $offset = $songindex;
661 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
662 my $id3 = $entries{$_};
663 $$id3{'songoffset'} = $offset;
664 $offset += $songentrysize;
668 #### TABLE of albums ###
670 # pointers to artists of album1
671 # pointers to songs on album1
675 my @moo=split(/___/, $_);
679 $t = $dir2albumname{$albumid};
684 $str = $t."\x00" x
($maxalbumlen - length($t));
687 my @songlist = keys %{$album2songs{$albumid}};
688 my $id3 = $album2songs{$albumid}{$songlist[0]};
690 #printf "(d) albumid: %s artist: %s\n",$albumid, $id3->{'ARTIST'};
692 my $aoffset = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
693 dumpint
($aoffset + $artistindex); # pointer to artist of this album
695 if (defined $id3->{'TRACKNUM'}) {
697 $album2songs{$albumid}{$a}->{'TRACKNUM'} <=>
698 $album2songs{$albumid}{$b}->{'TRACKNUM'}
702 @songlist = sort @songlist;
706 my $id3 = $album2songs{$albumid}{$_};
707 dumpint
($$id3{'songoffset'});
710 for (scalar keys %{$album2songs{$albumid}} .. $maxsongperalbum-1) {
711 print DB
"\x00\x00\x00\x00";
715 #### Build filename offset info
718 for $f (sort {uc($a) cmp uc($b)} keys %entries) {
719 $filenamepos{$f}= $l;
720 $l += $fileentrysize;
723 #### TABLE of songs ###
725 # pointer to artist of song1
726 # pointer to album of song1
727 # pointer to filename of song1
729 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
731 my $id3 = $entries{$f};
732 my $t = $id3->{'TITLE'};
733 my $g = $id3->{'GENRE'};
734 my $str = $t."\x00" x
($maxsonglen- length($t));
736 print DB
$str; # title
737 $str = $g."\x00" x
($maxgenrelen - length($g));
739 my $a = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
740 dumpint
($a + $artistindex); # pointer to artist of this song
743 $a = $albumcount{"$$id3{DIR}"} * $albumentrysize;
746 $a = $albumcount{"$$id3{ALBUM}___$$id3{DIR}"} * $albumentrysize;
748 dumpint
($a + $albumindex); # pointer to album of this song
750 # pointer to filename of this song
751 dumpint
($filenamepos{$f});
752 print DB
$str; #genre
754 dumpshort
($id3->{'YEAR'});
756 dumpshort
($id3->{'TRACKNUM'});
760 #### TABLE of file names ###
763 for $f (sort {uc($a) cmp uc($b)} %entries) {
764 my $str = $f."\x00" x
($maxfilelen- length($f));
765 my $id3 = $entries{$f};
767 #print STDERR "CRC: ".."\n";
768 dumpint
($id3->{'FILECRC'}); # CRC32 of the song data
769 dumpint
($id3->{'songoffset'}); # offset to song data
770 dumpint
(-1); # offset to rundb data. always set to -1. this is updated by rockbox code on the player.
777 ### Here follows module MP3::Info Copyright (c) 1998-2004 Chris Nandor
778 ### Modified by Björn Stenberg to remove use of external libraries
782 @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION, $REVISION,
783 @mp3_genres, %mp3_genres, @winamp_genres, %winamp_genres, $try_harder,
784 @t_bitrate, @t_sampling_freq, @frequency_tbl, %v1_tag_fields,
785 @v1_tag_names, %v2_tag_names, %v2_to_v1_names, $AUTOLOAD,
791 set_mp3tag get_mp3tag get_mp3info remove_mp3tag
794 @EXPORT_OK = qw(@mp3_genres %mp3_genres use_mp3_utf8);
796 genres => [qw(@mp3_genres %mp3_genres)],
797 utf8 => [qw(use_mp3_utf8)],
798 all
=> [@EXPORT, @EXPORT_OK]
802 ($REVISION) = ' $Revision$ ' =~ /\$Revision:\s+([^\s]+)/;
809 MP3::Info - Manipulate / fetch info from MP3 audio files
815 my $file = 'Pearls_Before_Swine.mp3';
816 set_mp3tag($file, 'Pearls Before Swine', q"77's",
817 'Sticks and Stones', '1990',
818 q"(c) 1990 77's LTD.", 'rock & roll');
820 my $tag = get_mp3tag($file) or die "No TAG info";
821 $tag->{GENRE} = 'rock';
822 set_mp3tag($file, $tag);
824 my $info = get_mp3info($file);
825 printf "$file length is %d:%d\n", $info->{MM}, $info->{SS};
831 # set all lower-case and regular-cased versions of genres as keys
832 # with index as value of each key
833 %mp3_genres = map {($_, ++$c, lc, $c)} @mp3_genres;
835 # do it again for winamp genres
837 %winamp_genres = map {($_, ++$c, lc, $c)} @winamp_genres;
842 my $mp3 = new MP3::Info $file;
843 $mp3->title('Perls Before Swine');
844 printf "$file length is %s, title is %s\n",
845 $mp3->time, $mp3->title;
852 =item $mp3 = MP3::Info-E<gt>new(FILE)
854 OOP interface to the rest of the module. The same keys
855 available via get_mp3info and get_mp3tag are available
856 via the returned object (using upper case or lower case;
857 but note that all-caps "VERSION" will return the module
858 version, not the MP3 version).
860 Passing a value to one of the methods will set the value
861 for that tag in the MP3 file, if applicable.
866 my($pack, $file) = @_;
868 my $info = get_mp3info
($file) or return undef;
869 my $tags = get_mp3tag
($file) || { map { ($_ => undef) } @v1_tag_names };
875 @self{@mp3_info_fields, @v1_tag_names, 'file'} = (
876 @
{$info}{@mp3_info_fields},
877 @
{$tags}{@v1_tag_names},
881 return bless \
%self, $pack;
886 return $self->SUPER::can
(@_) unless ref $self;
888 return sub { $self->$name(@_) } if exists $self->{$name};
894 (my $name = uc $AUTOLOAD) =~ s/^.*://;
896 if (exists $self->{$name}) {
897 my $sub = exists $v1_tag_fields{$name}
900 $_[0]->{$name} = $_[1];
901 set_mp3tag
($_[0]->{FILE
}, $_[0]);
903 return $_[0]->{$name};
906 return $_[0]->{$name}
913 warn(sprintf "No method '$name' available in package %s.",
923 =item use_mp3_utf8([STATUS])
925 Tells MP3::Info to (or not) return TAG info in UTF-8.
926 TRUE is 1, FALSE is 0. Default is FALSE.
928 Will only be able to it on if Unicode::String is available. ID3v2
929 tags will be converted to UTF-8 according to the encoding specified
930 in each tag; ID3v1 tags will be assumed Latin-1 and converted
933 Function returns status (TRUE/FALSE). If no argument is supplied,
934 or an unaccepted argument is supplied, function merely returns status.
936 This function is not exported by default, but may be exported
937 with the C<:utf8> or C<:all> export tag.
941 my $unicode_module = eval { require Unicode
::String
};
947 $UNICODE = 1 if $unicode_module;
948 } elsif ($val == 0) {
956 =item use_winamp_genres()
958 Puts WinAmp genres into C<@mp3_genres> and C<%mp3_genres>
959 (adds 68 additional genres to the default list of 80).
960 This is a separate function because these are non-standard
961 genres, but they are included because they are widely used.
963 You can import the data structures with one of:
965 use MP3::Info qw(:genres);
966 use MP3::Info qw(:DEFAULT :genres);
967 use MP3::Info qw(:all);
971 sub use_winamp_genres
{
972 %mp3_genres = %winamp_genres;
973 @mp3_genres = @winamp_genres;
981 =item get_mp3tag (FILE [, VERSION, RAW_V2])
983 Returns hash reference containing tag information in MP3 file. The keys
984 returned are the same as those supplied for C<set_mp3tag>, except in the
985 case of RAW_V2 being set.
987 If VERSION is C<1>, the information is taken from the ID3v1 tag (if present).
988 If VERSION is C<2>, the information is taken from the ID3v2 tag (if present).
989 If VERSION is not supplied, or is false, the ID3v1 tag is read if present, and
990 then, if present, the ID3v2 tag information will override any existing ID3v1
993 If RAW_V2 is C<1>, the raw ID3v2 tag data is returned, without any manipulation
994 of text encoding. The key name is the same as the frame ID (ID to name mappings
995 are in the global %v2_tag_names).
997 If RAW_V2 is C<2>, the ID3v2 tag data is returned, manipulating for Unicode if
998 necessary, etc. It also takes multiple values for a given key (such as comments)
999 and puts them in an arrayref.
1001 If the ID3v2 version is older than ID3v2.2.0 or newer than ID3v2.4.0, it will
1004 Strings returned will be in Latin-1, unless UTF-8 is specified (L<use_mp3_utf8>),
1005 (unless RAW_V2 is C<1>).
1007 Also returns a TAGVERSION key, containing the ID3 version used for the returned
1008 data (if TAGVERSION argument is C<0>, may contain two versions).
1013 my($file, $ver, $raw_v2) = @_;
1014 my($tag, $v1, $v2, $v2h, %info, @array, $fh);
1016 $ver = !$ver ?
0 : ($ver == 2 || $ver == 1) ?
$ver : 0;
1018 if (not (defined $file && $file ne '')) {
1019 $@
= "No file specified";
1024 $@
= "File is empty";
1028 if (ref $file) { # filehandle passed
1032 if (not open $fh, "< $file\0") {
1033 $@
= "Can't open $file: $!";
1042 while(defined(my $line = <$fh>)) { $tag .= $line }
1044 if ($tag =~ /^TAG/) {
1046 if (substr($tag, -3, 2) =~ /\000[^\000]/) {
1047 (undef, @info{@v1_tag_names}) =
1048 (unpack('a3a30a30a30a4a28', $tag),
1049 ord(substr($tag, -2, 1)),
1050 $mp3_genres[ord(substr $tag, -1)]);
1051 $info{TAGVERSION
} = 'ID3v1.1';
1053 (undef, @info{@v1_tag_names[0..4, 6]}) =
1054 (unpack('a3a30a30a30a4a30', $tag),
1055 $mp3_genres[ord(substr $tag, -1)]);
1056 $info{TAGVERSION
} = 'ID3v1';
1059 for my $key (keys %info) {
1060 next unless $info{$key};
1061 my $u = Unicode
::String
::latin1
($info{$key});
1062 $info{$key} = $u->utf8;
1065 } elsif ($ver == 1) {
1067 $@
= "No ID3v1 tag found";
1072 ($v2, $v2h) = _get_v2tag
($fh);
1074 unless ($v1 || $v2) {
1076 $@
= "No ID3 tag found";
1080 if (($ver == 0 || $ver == 2) && $v2) {
1081 if ($raw_v2 == 1 && $ver == 2) {
1083 $info{TAGVERSION
} = $v2h->{version
};
1085 my $hash = $raw_v2 == 2 ?
{ map { ($_, $_) } keys %v2_tag_names } : \
%v2_to_v1_names;
1086 for my $id (keys %$hash) {
1087 if (exists $v2->{$id}) {
1088 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
1089 $info{$hash->{$id}} = $mp3_genres[$1];
1091 my $data1 = $v2->{$id};
1093 # this is tricky ... if this is an arrayref, we want
1094 # to only return one, so we pick the first one. but
1095 # if it is a comment, we pick the first one where the
1096 # first charcter after the language is NULL and not an
1097 # additional sub-comment, because that is most likely
1098 # to be the user-supplied comment
1100 if (ref $data1 && !$raw_v2) {
1101 if ($id =~ /^COMM?$/) {
1102 my($newdata) = grep /^(....\000)/, @
{$data1};
1103 $data1 = $newdata || $data1->[0];
1105 $data1 = $data1->[0];
1109 $data1 = [ $data1 ] if ! ref $data1;
1111 for my $data (@
$data1) {
1112 $data =~ s/^(.)//; # strip first char (text encoding)
1115 if ($id =~ /^COM[M ]?$/) {
1116 $data =~ s/^(?:...)//; # strip language
1117 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
1123 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
1124 my $u = Unicode
::String
::utf16
($data);
1126 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
1127 } elsif ($encoding eq "\000") {
1128 my $u = Unicode
::String
::latin1
($data);
1133 if ($raw_v2 == 2 && $desc) {
1134 $data = { $desc => $data };
1137 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
1138 if (ref $info{$hash->{$id}} eq 'ARRAY') {
1139 push @
{$info{$hash->{$id}}}, $data;
1141 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
1144 $info{$hash->{$id}} = $data;
1150 if ($ver == 0 && $info{TAGVERSION
}) {
1151 $info{TAGVERSION
} .= ' / ' . $v2h->{version
};
1153 $info{TAGVERSION
} = $v2h->{version
};
1158 unless ($raw_v2 && $ver == 2) {
1159 foreach my $key (keys %info) {
1160 if (defined $info{$key}) {
1161 $info{$key} =~ s/\000+.*//g;
1162 $info{$key} =~ s/\s+$//;
1166 for (@v1_tag_names) {
1167 $info{$_} = '' unless defined $info{$_};
1171 if (keys %info && exists $info{GENRE
} && ! defined $info{GENRE
}) {
1177 return keys %info ?
{%info} : undef;
1182 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
1185 $v2 = _get_v2head
($fh) or return;
1186 if ($v2->{major_version
} < 2) {
1187 warn "This is $v2->{version}; " .
1188 "ID3v2 versions older than ID3v2.2.0 not supported\n"
1193 if ($v2->{major_version
} == 2) {
1203 read $fh, my($bytes), $hlen;
1204 return unless $bytes =~ /^([A-Z0-9]{$num})/
1205 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes
1206 my($id, $size) = ($1, $hlen);
1207 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num);
1208 for my $i (0 .. ($num - 1)) {
1209 $size += $bytes[$i] * 256 ** $i;
1214 $off = $v2->{ext_header_size
} + 10;
1216 while ($off < $v2->{tag_size
}) {
1217 my($id, $size) = &$myseek or last;
1218 seek $fh, $off + $hlen, 0;
1219 read $fh, my($bytes), $size - $hlen;
1220 if (exists $h->{$id}) {
1221 if (ref $h->{$id} eq 'ARRAY') {
1222 push @
{$h->{$id}}, $bytes;
1224 $h->{$id} = [$h->{$id}, $bytes];
1238 =item get_mp3info (FILE)
1240 Returns hash reference containing file information for MP3 file.
1241 This data cannot be changed. Returned data:
1243 VERSION MPEG audio version (1, 2, 2.5)
1244 LAYER MPEG layer description (1, 2, 3)
1245 STEREO boolean for audio is in stereo
1247 VBR boolean for variable bitrate
1248 BITRATE bitrate in kbps (average for VBR files)
1249 FREQUENCY frequency in kHz
1250 SIZE bytes in audio stream
1255 MS leftover milliseconds
1258 COPYRIGHT boolean for audio is copyrighted
1259 PADDING boolean for MP3 frames are padded
1260 MODE channel mode (0 = stereo, 1 = joint stereo,
1261 2 = dual channel, 3 = single channel)
1262 FRAMES approximate number of frames
1263 FRAME_LENGTH approximate length of a frame
1264 VBR_SCALE VBR scale from VBR header
1266 On error, returns nothing and sets C<$@>.
1272 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
1274 if (not (defined $file && $file ne '')) {
1275 $@
= "No file specified";
1280 $@
= "File is empty";
1284 if (ref $file) { # filehandle passed
1288 if (not open $fh, "< $file\0") {
1289 $@
= "Can't open $file: $!";
1306 if (my $id3v2 = _get_v2head
($fh)) {
1307 $tot += $off += $id3v2->{tag_size
};
1312 $h = _get_head
($byte);
1313 until (_is_mp3
($h)) {
1316 $h = _get_head
($byte);
1317 if ($off > $tot && !$try_harder) {
1319 $@
= "Couldn't find MP3 header (perhaps set " .
1320 '$MP3::Info::try_harder and retry)';
1325 my $vbr = _get_vbr
($fh, $h, \
$off);
1327 $h->{headersize
}=$off; # data size prepending the actual mp3 data
1332 $off += 128 if <$fh> =~ /^TAG/ ?
1 : 0;
1336 $h->{size
} = $eof - $off;
1338 return _get_info
($h, $vbr);
1345 $i->{VERSION
} = $h->{IDR
} == 2 ?
2 : $h->{IDR
} == 3 ?
1 :
1346 $h->{IDR
} == 0 ?
2.5 : 0;
1347 $i->{LAYER
} = 4 - $h->{layer
};
1348 $i->{VBR
} = defined $vbr ?
1 : 0;
1350 $i->{COPYRIGHT
} = $h->{copyright
} ?
1 : 0;
1351 $i->{PADDING
} = $h->{padding_bit
} ?
1 : 0;
1352 $i->{STEREO
} = $h->{mode
} == 3 ?
0 : 1;
1353 $i->{MODE
} = $h->{mode
};
1355 $i->{SIZE
} = $vbr && $vbr->{bytes
} ?
$vbr->{bytes
} : $h->{size
};
1357 my $mfs = $h->{fs
} / ($h->{ID
} ?
144000 : 72000);
1358 $i->{FRAMES
} = int($vbr && $vbr->{frames
}
1360 : $i->{SIZE
} / $h->{bitrate} / $mfs
1364 $i->{VBR_SCALE
} = $vbr->{scale
} if $vbr->{scale
};
1365 $h->{bitrate
} = $i->{SIZE
} / $i->{FRAMES
} * $mfs;
1366 if (not $h->{bitrate
}) {
1367 $@
= "Couldn't determine VBR bitrate";
1372 $h->{'length'} = ($i->{SIZE
} * 8) / $h->{bitrate} / 10;
1373 $i->{SECS
} = $h->{'length'} / 100;
1374 $i->{MM
} = int $i->{SECS
} / 60;
1375 $i->{SS
} = int $i->{SECS
} % 60;
1376 $i->{MS
} = (($i->{SECS
} - ($i->{MM
} * 60) - $i->{SS
}) * 1000);
1377 # $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS});
1378 # int($i->{MS} / 100 * 75); # is this right?
1379 $i->{TIME
} = sprintf "%.2d:%.2d", @
{$i}{'MM', 'SS'};
1381 $i->{BITRATE
} = int $h->{bitrate
};
1382 # should we just return if ! FRAMES?
1383 $i->{FRAME_LENGTH
} = int($h->{size
} / $i->{FRAMES
}) if $i->{FRAMES
};
1384 $i->{FREQUENCY
} = $frequency_tbl[3 * $h->{IDR
} + $h->{sampling_freq
}];
1386 $i->{headersize
} = $h->{headersize
};
1395 $bytes = _unpack_head
($byte);
1396 @
$h{qw(IDR ID layer protection_bit
1397 bitrate_index sampling_freq padding_bit private_bit
1398 mode mode_extension copyright original
1399 emphasis version_index bytes)} = (
1400 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1,
1401 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1,
1402 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1,
1403 $bytes&3, ($bytes>>19)&3, $bytes
1406 $h->{bitrate
} = $t_bitrate[$h->{ID
}][3 - $h->{layer
}][$h->{bitrate_index
}];
1407 $h->{fs
} = $t_sampling_freq[$h->{IDR
}][$h->{sampling_freq
}];
1413 my $h = $_[0] or return undef;
1414 return ! ( # all below must be false
1415 $h->{bitrate_index
} == 0
1417 $h->{version_index
} == 1
1419 ($h->{bytes
} & 0xFFE00000) != 0xFFE00000
1425 $h->{bitrate_index
} == 15
1429 $h->{sampling_freq
} == 3
1433 !$h->{bitrate_index
}
1435 ($h->{bytes
} & 0xFFFF0000) == 0xFFFE0000
1437 ($h->{ID
} == 1 && $h->{layer
} == 3 && $h->{protection_bit
} == 1)
1439 ($h->{mode_extension
} != 0 && $h->{mode
} != 1)
1444 my($fh, $h, $roff) = @_;
1445 my($off, $bytes, @bytes, $myseek, %vbr);
1448 @_ = (); # closure confused if we don't do this
1453 read $fh, $bytes, $n;
1459 if ($h->{ID
}) { # MPEG1
1460 $off += $h->{mode
} == 3 ?
17 : 32;
1462 $off += $h->{mode
} == 3 ?
9 : 17;
1466 return unless $bytes eq 'Xing';
1469 $vbr{flags
} = _unpack_head
($bytes);
1471 if ($vbr{flags
} & 1) {
1473 $vbr{frames
} = _unpack_head
($bytes);
1476 if ($vbr{flags
} & 2) {
1478 $vbr{bytes
} = _unpack_head
($bytes);
1481 if ($vbr{flags
} & 4) {
1483 # Not used right now ...
1484 # $vbr{toc} = _unpack_head($bytes);
1487 if ($vbr{flags
} & 8) { # (quality ind., 0=best 100=worst)
1489 $vbr{scale
} = _unpack_head
($bytes);
1499 my $fh = $_[0] or return;
1500 my($h, $bytes, @bytes);
1502 # check first three bytes for 'ID3'
1504 read $fh, $bytes, 3;
1505 return unless $bytes eq 'ID3';
1508 read $fh, $bytes, 2;
1509 $h->{version
} = sprintf "ID3v2.%d.%d",
1510 @
$h{qw
[major_version minor_version
]} =
1511 unpack 'c2', $bytes;
1514 read $fh, $bytes, 1;
1515 if ($h->{major_version
} == 2) {
1516 @
$h{qw
[unsync compression
]} =
1517 (unpack 'b8', $bytes)[7, 6];
1518 $h->{ext_header
} = 0;
1519 $h->{experimental
} = 0;
1521 @
$h{qw
[unsync ext_header experimental
]} =
1522 (unpack 'b8', $bytes)[7, 6, 5];
1525 # get ID3v2 tag length from bytes 7-10
1526 $h->{tag_size
} = 10; # include ID3v2 header size
1527 read $fh, $bytes, 4;
1528 @bytes = reverse unpack 'C4', $bytes;
1529 foreach my $i (0 .. 3) {
1530 # whoaaaaaa nellllllyyyyyy!
1531 $h->{tag_size
} += $bytes[$i] * 128 ** $i;
1534 # get extended header size
1535 $h->{ext_header_size
} = 0;
1536 if ($h->{ext_header
}) {
1537 $h->{ext_header_size
} += 10;
1538 read $fh, $bytes, 4;
1539 @bytes = reverse unpack 'C4', $bytes;
1541 $h->{ext_header_size
} += $bytes[$i] * 256 ** $i;
1549 unpack('l', pack('L', unpack('N', $_[0])));
1553 my($file, $fh) = @_;
1554 unless (ref $file) { # filehandle not passed
1555 close $fh or warn "Problem closing '$file': $!";
1608 'Instrumental Rock',
1612 'Techno-Industrial',
1701 'Christian Gangsta Rap',
1705 'Contemporary Christian',
1716 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
1717 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
1718 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
1720 [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
1721 [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
1722 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
1725 @t_sampling_freq = (
1726 [11025, 12000, 8000],
1727 [undef, undef, undef], # reserved
1728 [22050, 24000, 16000],
1729 [44100, 48000, 32000]
1732 @frequency_tbl = map { $_ ?
eval "${_}e-3" : 0 }
1733 map { @
$_ } @t_sampling_freq;
1735 @mp3_info_fields = qw(
1757 (TITLE
=> 30, ARTIST
=> 30, ALBUM
=> 30, COMMENT
=> 30, YEAR
=> 4);
1759 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1768 'TRK' => 'TRACKNUM',
1769 'TCO' => 'GENRE', # not clean mapping, but ...
1775 'COMM' => 'COMMENT',
1776 'TRCK' => 'TRACKNUM',
1782 'BUF' => 'Recommended buffer size',
1783 'CNT' => 'Play counter',
1784 'COM' => 'Comments',
1785 'CRA' => 'Audio encryption',
1786 'CRM' => 'Encrypted meta frame',
1787 'ETC' => 'Event timing codes',
1788 'EQU' => 'Equalization',
1789 'GEO' => 'General encapsulated object',
1790 'IPL' => 'Involved people list',
1791 'LNK' => 'Linked information',
1792 'MCI' => 'Music CD Identifier',
1793 'MLL' => 'MPEG location lookup table',
1794 'PIC' => 'Attached picture',
1795 'POP' => 'Popularimeter',
1797 'RVA' => 'Relative volume adjustment',
1798 'SLT' => 'Synchronized lyric/text',
1799 'STC' => 'Synced tempo codes',
1800 'TAL' => 'Album/Movie/Show title',
1801 'TBP' => 'BPM (Beats Per Minute)',
1802 'TCM' => 'Composer',
1803 'TCO' => 'Content type',
1804 'TCR' => 'Copyright message',
1806 'TDY' => 'Playlist delay',
1807 'TEN' => 'Encoded by',
1808 'TFT' => 'File type',
1810 'TKE' => 'Initial key',
1811 'TLA' => 'Language(s)',
1813 'TMT' => 'Media type',
1814 'TOA' => 'Original artist(s)/performer(s)',
1815 'TOF' => 'Original filename',
1816 'TOL' => 'Original Lyricist(s)/text writer(s)',
1817 'TOR' => 'Original release year',
1818 'TOT' => 'Original album/Movie/Show title',
1819 'TP1' => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
1820 'TP2' => 'Band/Orchestra/Accompaniment',
1821 'TP3' => 'Conductor/Performer refinement',
1822 'TP4' => 'Interpreted, remixed, or otherwise modified by',
1823 'TPA' => 'Part of a set',
1824 'TPB' => 'Publisher',
1825 'TRC' => 'ISRC (International Standard Recording Code)',
1826 'TRD' => 'Recording dates',
1827 'TRK' => 'Track number/Position in set',
1829 'TSS' => 'Software/hardware and settings used for encoding',
1830 'TT1' => 'Content group description',
1831 'TT2' => 'Title/Songname/Content description',
1832 'TT3' => 'Subtitle/Description refinement',
1833 'TXT' => 'Lyricist/text writer',
1834 'TXX' => 'User defined text information frame',
1836 'UFI' => 'Unique file identifier',
1837 'ULT' => 'Unsychronized lyric/text transcription',
1838 'WAF' => 'Official audio file webpage',
1839 'WAR' => 'Official artist/performer webpage',
1840 'WAS' => 'Official audio source webpage',
1841 'WCM' => 'Commercial information',
1842 'WCP' => 'Copyright/Legal information',
1843 'WPB' => 'Publishers official webpage',
1844 'WXX' => 'User defined URL link frame',
1847 'AENC' => 'Audio encryption',
1848 'APIC' => 'Attached picture',
1849 'COMM' => 'Comments',
1850 'COMR' => 'Commercial frame',
1851 'ENCR' => 'Encryption method registration',
1852 'EQUA' => 'Equalization',
1853 'ETCO' => 'Event timing codes',
1854 'GEOB' => 'General encapsulated object',
1855 'GRID' => 'Group identification registration',
1856 'IPLS' => 'Involved people list',
1857 'LINK' => 'Linked information',
1858 'MCDI' => 'Music CD identifier',
1859 'MLLT' => 'MPEG location lookup table',
1860 'OWNE' => 'Ownership frame',
1861 'PCNT' => 'Play counter',
1862 'POPM' => 'Popularimeter',
1863 'POSS' => 'Position synchronisation frame',
1864 'PRIV' => 'Private frame',
1865 'RBUF' => 'Recommended buffer size',
1866 'RVAD' => 'Relative volume adjustment',
1868 'SYLT' => 'Synchronized lyric/text',
1869 'SYTC' => 'Synchronized tempo codes',
1870 'TALB' => 'Album/Movie/Show title',
1871 'TBPM' => 'BPM (beats per minute)',
1872 'TCOM' => 'Composer',
1873 'TCON' => 'Content type',
1874 'TCOP' => 'Copyright message',
1876 'TDLY' => 'Playlist delay',
1877 'TENC' => 'Encoded by',
1878 'TEXT' => 'Lyricist/Text writer',
1879 'TFLT' => 'File type',
1881 'TIT1' => 'Content group description',
1882 'TIT2' => 'Title/songname/content description',
1883 'TIT3' => 'Subtitle/Description refinement',
1884 'TKEY' => 'Initial key',
1885 'TLAN' => 'Language(s)',
1887 'TMED' => 'Media type',
1888 'TOAL' => 'Original album/movie/show title',
1889 'TOFN' => 'Original filename',
1890 'TOLY' => 'Original lyricist(s)/text writer(s)',
1891 'TOPE' => 'Original artist(s)/performer(s)',
1892 'TORY' => 'Original release year',
1893 'TOWN' => 'File owner/licensee',
1894 'TPE1' => 'Lead performer(s)/Soloist(s)',
1895 'TPE2' => 'Band/orchestra/accompaniment',
1896 'TPE3' => 'Conductor/performer refinement',
1897 'TPE4' => 'Interpreted, remixed, or otherwise modified by',
1898 'TPOS' => 'Part of a set',
1899 'TPUB' => 'Publisher',
1900 'TRCK' => 'Track number/Position in set',
1901 'TRDA' => 'Recording dates',
1902 'TRSN' => 'Internet radio station name',
1903 'TRSO' => 'Internet radio station owner',
1905 'TSRC' => 'ISRC (international standard recording code)',
1906 'TSSE' => 'Software/Hardware and settings used for encoding',
1907 'TXXX' => 'User defined text information frame',
1909 'UFID' => 'Unique file identifier',
1910 'USER' => 'Terms of use',
1911 'USLT' => 'Unsychronized lyric/text transcription',
1912 'WCOM' => 'Commercial information',
1913 'WCOP' => 'Copyright/Legal information',
1914 'WOAF' => 'Official audio file webpage',
1915 'WOAR' => 'Official artist/performer webpage',
1916 'WOAS' => 'Official audio source webpage',
1917 'WORS' => 'Official internet radio station homepage',
1918 'WPAY' => 'Payment',
1919 'WPUB' => 'Publishers official webpage',
1920 'WXXX' => 'User defined URL link frame',
1922 # v2.4 additional tags
1923 # note that we don't restrict tags from 2.3 or 2.4,
1924 'ASPI' => 'Audio seek point index',
1925 'EQU2' => 'Equalisation (2)',
1926 'RVA2' => 'Relative volume adjustment (2)',
1927 'SEEK' => 'Seek frame',
1928 'SIGN' => 'Signature frame',
1929 'TDEN' => 'Encoding time',
1930 'TDOR' => 'Original release time',
1931 'TDRC' => 'Recording time',
1932 'TDRL' => 'Release time',
1933 'TDTG' => 'Tagging time',
1934 'TIPL' => 'Involved people list',
1935 'TMCL' => 'Musician credits list',
1937 'TPRO' => 'Produced notice',
1938 'TSOA' => 'Album sort order',
1939 'TSOP' => 'Performer sort order',
1940 'TSOT' => 'Title sort order',
1941 'TSST' => 'Set subtitle',
1944 'COM ' => 'Broken iTunes comments',
1956 =head1 TROUBLESHOOTING
1958 If you find a bug, please send me a patch (see the project page in L<"SEE ALSO">).
1959 If you cannot figure out why it does not work for you, please put the MP3 file in
1960 a place where I can get it (preferably via FTP, or HTTP, or .Mac iDisk) and send me
1961 mail regarding where I can get the file, with a detailed description of the problem.
1963 If I download the file, after debugging the problem I will not keep the MP3 file
1964 if it is not legal for me to have it. Just let me know if it is legal for me to
1974 Still need to do more for reading tags, such as using Compress::Zlib to decompress
1975 compressed tags. But until I see this in use more, I won't bother. If something
1976 does not work properly with reading, follow the instructions above for
1979 ID3v2 I<writing> is coming soon.
1981 =item Get data from scalar
1983 Instead of passing a file spec or filehandle, pass the
1984 data itself. Would take some work, converting the seeks, etc.
1988 Do something with padding bit.
1992 Test suite could use a bit of an overhaul and update. Patches very welcome.
1998 Revamp getset.t. Test all the various get_mp3tag args.
2010 Test error handling, check more for missing files, bad MP3s, etc.
2016 Right now, only Xing VBR is supported.
2023 Edward Allen E<lt>allenej@c51844-a.spokn1.wa.home.comE<gt>,
2024 Vittorio Bertola E<lt>v.bertola@vitaminic.comE<gt>,
2025 Michael Blakeley E<lt>mike@blakeley.comE<gt>,
2026 Per Bolmstedt E<lt>tomten@kol14.comE<gt>,
2027 Tony Bowden E<lt>tony@tmtm.comE<gt>,
2028 Tom Brown E<lt>thecap@usa.netE<gt>,
2029 Sergio Camarena E<lt>scamarena@users.sourceforge.netE<gt>,
2030 Chris Dawson E<lt>cdawson@webiphany.comE<gt>,
2031 Luke Drumm E<lt>lukedrumm@mypad.comE<gt>,
2032 Kyle Farrell E<lt>kyle@cantametrix.comE<gt>,
2033 Jeffrey Friedl E<lt>jfriedl@yahoo.comE<gt>,
2034 brian d foy E<lt>comdog@panix.comE<gt>,
2035 Ben Gertzfield E<lt>che@debian.orgE<gt>,
2036 Brian Goodwin E<lt>brian@fuddmain.comE<gt>,
2037 Todd Hanneken E<lt>thanneken@hds.harvard.eduE<gt>,
2038 Todd Harris E<lt>harris@cshl.orgE<gt>,
2039 Woodrow Hill E<lt>asim@mindspring.comE<gt>,
2040 Kee Hinckley E<lt>nazgul@somewhere.comE<gt>,
2041 Roman Hodek E<lt>Roman.Hodek@informatik.uni-erlangen.deE<gt>,
2042 Peter Kovacs E<lt>kovacsp@egr.uri.eduE<gt>,
2044 Peter Marschall E<lt>peter.marschall@mayn.deE<gt>,
2045 Trond Michelsen E<lt>mike@crusaders.noE<gt>,
2046 Dave O'Neill E<lt>dave@nexus.carleton.caE<gt>,
2047 Christoph Oberauer E<lt>christoph.oberauer@sbg.ac.atE<gt>,
2048 Jake Palmer E<lt>jake.palmer@db.comE<gt>,
2049 Andrew Phillips E<lt>asp@wasteland.orgE<gt>,
2050 David Reuteler E<lt>reuteler@visi.comE<gt>,
2051 John Ruttenberg E<lt>rutt@chezrutt.comE<gt>,
2052 Matthew Sachs E<lt>matthewg@zevils.comE<gt>,
2053 E<lt>scfc_de@users.sf.netE<gt>,
2054 Hermann Schwaerzler E<lt>Hermann.Schwaerzler@uibk.ac.atE<gt>,
2055 Chris Sidi E<lt>sidi@angband.orgE<gt>,
2056 Roland Steinbach E<lt>roland@support-system.comE<gt>,
2057 Stuart E<lt>schneis@users.sourceforge.netE<gt>,
2058 Jeffery Sumler E<lt>jsumler@mediaone.netE<gt>,
2059 Predrag Supurovic E<lt>mpgtools@dv.co.yuE<gt>,
2060 Bogdan Surdu E<lt>tim@go.roE<gt>,
2061 E<lt>tim@tim-landscheidt.deE<gt>,
2062 Pass F. B. Travis E<lt>pftravis@bellsouth.netE<gt>,
2063 Tobias Wagener E<lt>tobias@wagener.nuE<gt>,
2064 Ronan Waide E<lt>waider@stepstone.ieE<gt>,
2065 Andy Waite E<lt>andy@mailroute.comE<gt>,
2066 Ken Williams E<lt>ken@forum.swarthmore.eduE<gt>,
2067 Meng Weng Wong E<lt>mengwong@pobox.comE<gt>.
2070 =head1 AUTHOR AND COPYRIGHT
2072 Chris Nandor E<lt>pudge@pobox.comE<gt>, http://pudge.net/
2074 Copyright (c) 1998-2003 Chris Nandor. All rights reserved. This program is
2075 free software; you can redistribute it and/or modify it under the terms
2076 of the Artistic License, distributed with Perl.
2083 =item MP3::Info Project Page
2085 http://projects.pudge.net/
2089 http://www.zevils.com/linux/mp3tools/
2093 http://www.dv.co.yu/mpgscript/mpgtools.htm
2094 http://www.dv.co.yu/mpgscript/mpeghdr.htm
2098 http://www.dtek.chalmers.se/~d2linjo/mp3/mp3tool.html
2104 =item Xing Variable Bitrate
2106 http://www.xingtech.com/support/partner_developer/mp3/vbr_sdk/
2110 http://rupert.informatik.uni-stuttgart.de/~mutschml/MP3ext/
2114 http://www.xmms.org/
2121 v1.02, Sunday, March 2, 2003