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";
22 if($ARGV[0] eq "--db") {
27 elsif($ARGV[0] eq "--path") {
32 elsif($ARGV[0] eq "--strip") {
37 elsif($ARGV[0] eq "--add") {
42 elsif($ARGV[0] eq "--verbose") {
46 elsif($ARGV[0] eq "--dirisalbum") {
50 elsif($ARGV[0] eq "--dirisalbumname") {
54 elsif($ARGV[0] eq "--help" or ($ARGV[0] eq "-h")) {
75 if(! -d
$dir or $help) {
76 print "'$dir' is not a directory\n" if ($dir ne "" and ! -d
$dir);
77 print "songdb --path <dir> [--dirisalbum] [--dirisalbumname] [--db <file>] [--strip <path>] [--add <path>] [--verbose] [--help]\n";
85 my $ogg = vorbiscomm
->new($fn);
89 # Convert this format into the same format used by the id3 parser hash
91 foreach my $k ($ogg->comment_tags())
93 foreach my $cmmt ($ogg->comment($k))
96 if($k =~ /^artist$/i) {
99 elsif($k =~ /^album$/i) {
102 $hash{$n}=$cmmt if($n);
113 my $ogg = vorbiscomm
->new($fn);
117 return $ogg->{'INFO'};
120 # return ALL directory entries in the given dir
124 $dir =~ s
|/$|| if ($dir ne "/");
126 if (opendir(DIR, $dir)) {
127 my @all = readdir(DIR);
132 warn "can
't opendir $dir: $!\n";
137 my ($dir, @files) = @_;
140 if( (/\.mp[23]$/i || /\.ogg$/i) && -f "$dir/$_" ) {
148 my ($dir, @files) = @_;
152 if( -d "$dir/$_" && ($_ !~ /^\.(|\.)$/)) {
159 # CRC32 32KB of data (use less if there isn't
32KB available
)
162 my ($filename, $index) = @_;
166 if(!open(FILE
, "<$filename")) {
167 print "failed to open \"$filename\" $!\n";
171 # read $data from index $index to $buffer from the file, may return fewer
172 # bytes when dealing with a very small file.
174 # TODO: make sure we don't include a trailer with metadata when doing this.
176 my $nread = sysread FILE
, $buffer, $len, $index;
181 ( # CRC32 lookup table for polynomial 0x04C11DB7
182 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B,
183 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
184 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7,
185 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75,
186 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3,
187 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039,
188 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF,
189 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D,
190 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB,
191 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1,
192 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0,
193 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072,
194 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4,
195 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE,
196 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08,
197 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA,
198 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC,
199 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6,
200 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050,
201 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2,
202 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34,
203 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637,
204 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1,
205 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53,
206 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5,
207 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF,
208 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9,
209 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B,
210 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD,
211 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7,
212 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71,
213 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3,
214 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2,
215 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8,
216 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E,
217 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC,
218 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A,
219 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0,
220 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676,
221 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4,
222 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662,
223 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668,
224 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4
227 my $crc = 0xffffffff;
228 for ($i = 0; $i < $nread; $i++) {
229 # get the numeric for the byte of the $i index
230 $buf = ord(substr($buffer, $i, 1));
232 $crc = ($crc << 8) ^ $crc_table[(($crc >> 24) ^ $buf) & 0xFF];
234 # printf("%08x\n", $crc);
246 if($file =~ /\.ogg$/i) {
247 $hash = get_oggtag
($file);
249 $info = get_ogginfo
($file);
251 $hash->{FILECRC
} = crc32
($file, $info->{audio_offset
});
254 $hash = get_mp3tag
($file);
256 $info = get_mp3info
($file);
258 $hash->{FILECRC
} = crc32
($file, $info->{headersize
});
261 return $hash; # a hash reference
271 # getdir() returns all entries in the given dir
272 my @a = getdir
($dir);
274 # extractmp3 filters out only the mp3 files from all given entries
275 my @m = extractmp3
($dir, @a);
281 my $id3 = singlefile
("$dir/$f");
291 # don't index songs without tags
293 if (not defined $$id3{'ARTIST'} and
294 not defined $$id3{'ALBUM'} and
295 not defined $$id3{'TITLE'})
300 #printf "Artist: %s\n", $id3->{'ARTIST'};
301 my $path = "$dir/$f";
302 if ($strip ne "" and $path =~ /^$strip(.*)/) {
307 $path = $add . $path;
310 # Only use one case-variation of each album/artist
311 if (exists($lcalbums{lc($$id3{'ALBUM'})})) {
312 # if another album with different case exists
313 # use that case (store it in $$id3{'ALBUM'}
314 $$id3{'ALBUM'} = $lcalbums{lc($$id3{'ALBUM'})};
317 # else create a new entry in the hash
318 $lcalbums{lc($$id3{'ALBUM'})} = $$id3{'ALBUM'};
321 if (exists($lcartists{lc($$id3{'ARTIST'})})) {
322 $$id3{'ARTIST'} = $lcartists{lc($$id3{'ARTIST'})};
325 $lcartists{lc($$id3{'ARTIST'})} = $$id3{'ARTIST'};
328 $entries{$path}= $id3;
329 $artists{$id3->{'ARTIST'}}++ if($id3->{'ARTIST'});
330 $genres{$id3->{'GENRE'}}++ if($id3->{'GENRE'});
331 $years{$id3->{'YEAR'}}++ if($id3->{'YEAR'});
334 $$id3{'ARTIST'} = "<no artist tag>" if ($$id3{'ARTIST'} eq "");
335 # Fall back on the directory name (not full path dirname),
338 if($dir2albumname{$dir} eq "") {
339 $dir2albumname{$dir} = $$id3{'ALBUM'};
341 elsif($dir2albumname{$dir} ne $$id3{'ALBUM'}) {
342 $dir2albumname{$dir} = (split m
[/], $dir)[-1];
346 if ($dirisalbumname) {
347 $$id3{'ALBUM'} = (split m
[/], $dir)[-1] if ($$id3{'ALBUM'} eq "");
349 $$id3{'ALBUM'} = "<no album tag>" if ($$id3{'ALBUM'} eq "");
350 # fall back on basename of the file if no title tag.
352 ($base = $f) =~ s/\.\w+$//;
353 $$id3{'TITLE'} = $base if ($$id3{'TITLE'} eq "");
355 # Append dirname, to handle multi-artist albums
359 $albumid=$$id3{'DIR'};
362 $albumid= $id3->{'ALBUM'}."___".$$id3{'DIR'};
364 #printf "album id: %s\n", $albumid;
366 # if($id3->{'ALBUM'}."___".$id3->{'DIR'} ne "<no album tag>___<no artist tag>") {
367 my $num = ++$albums{$albumid};
368 if($num > $maxsongperalbum) {
369 $maxsongperalbum = $num;
370 $longestalbum = $albumid;
372 $album2songs{$albumid}{$$id3{TITLE
}} = $id3;
374 $artist2albums{$$id3{ARTIST
}}{$$id3{DIR
}} = $id3;
377 $artist2albums{$$id3{ARTIST
}}{$$id3{ALBUM
}} = $id3;
382 if($dirisalbum and $dir2albumname{$dir} eq "") {
383 $dir2albumname{$dir} = (split m
[/], $dir)[-1];
384 printf "%s\n", $dir2albumname{$dir};
387 # extractdirs filters out only subdirectories from all given entries
388 my @d = extractdirs
($dir, @a);
400 print "File name table\n" if ($verbose);
401 for(sort keys %entries) {
402 printf(" %s\n", $_) if ($verbose);
404 if($l > $maxfilelen) {
406 $longestfilename = $_;
409 $maxfilelen++; # include zero termination byte
410 while($maxfilelen&3) {
416 print "\nSong title table\n" if ($verbose);
418 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
419 printf(" %s\n", $entries{$_}->{'TITLE'} ) if ($verbose);
420 my $l = length($entries{$_}->{'TITLE'});
421 if($l > $maxsonglen) {
423 $longestsong = $entries{$_}->{'TITLE'};
426 $maxsonglen++; # include zero termination byte
427 while($maxsonglen&3) {
431 my $maxartistlen = 0;
432 print "\nArtist table\n" if ($verbose);
435 for(sort {uc($a) cmp uc($b)} keys %artists) {
436 printf(" %s: %d\n", $_, $i) if ($verbose);
437 $artistcount{$_}=$i++;
439 if($l > $maxartistlen) {
444 $l = scalar keys %{$artist2albums{$_}};
445 if ($l > $maxalbumsperartist) {
446 $maxalbumsperartist = $l;
447 $longestartistalbum = $_;
450 $maxartistlen++; # include zero termination byte
451 while($maxartistlen&3) {
455 print "\nGenre table\n" if ($verbose);
456 for(sort keys %genres) {
458 if($l > $maxgenrelen) {
460 $longestgenrename = $_;
464 $maxgenrelen++; #include zero termination byte
465 while($maxgenrelen&3) {
471 print "\nYear table\n";
472 for(sort keys %years) {
477 print "\nAlbum table\n" if ($verbose);
483 @albumssort = sort {uc($dir2albumname{$a}) cmp uc($dir2albumname{$b})} keys %albums;
486 @albumssort = sort {uc($a) cmp uc($b)} keys %albums;
489 my @moo=split(/___/, $_);
490 printf(" %s\n", $moo[0]) if ($verbose);
491 $albumcount{$_} = $i++;
494 $l = length($dir2albumname{$_});
497 $l = length($moo[0]);
499 if($l > $maxalbumlen) {
502 $longestalbumname = $dir2albumname{$_};
505 $longestalbumname = $moo[0];
509 $maxalbumlen++; # include zero termination byte
510 while($maxalbumlen&3) {
519 # print "int: $num\n";
521 print DB
pack "n", $num;
527 # print "int: $num\n";
529 print DB
pack "N", $num;
532 if (!scalar keys %entries) {
533 print "No songs found. Did you specify the right --path ?\n";
534 print "Use the --help parameter to see all options.\n";
539 my $songentrysize = $maxsonglen + 12 + $maxgenrelen+ 12;
540 my $albumentrysize = $maxalbumlen + 4 + $maxsongperalbum*4;
541 my $artistentrysize = $maxartistlen + $maxalbumsperartist*4;
542 my $fileentrysize = $maxfilelen + 12;
544 printf "Number of artists : %d\n", scalar keys %artists;
545 printf "Number of albums : %d\n", scalar keys %albums;
546 printf "Number of songs / files : %d\n", scalar keys %entries;
547 print "Max artist length : $maxartistlen ($longestartist)\n";
548 print "Max album length : $maxalbumlen ($longestalbumname)\n";
549 print "Max song length : $maxsonglen ($longestsong)\n";
550 print "Max songs per album : $maxsongperalbum ($longestalbum)\n";
551 print "Max albums per artist: $maxalbumsperartist ($longestartistalbum)\n";
552 print "Max genre length : $maxgenrelen ($longestgenrename)\n";
553 print "Max file length : $maxfilelen ($longestfilename)\n";
554 print "Database version: $dbver\n" if ($verbose);
555 print "Song Entry Size : $songentrysize ($maxsonglen + 12 + $maxgenrelen + 4)\n" if ($verbose);
556 print "Album Entry Size: $albumentrysize ($maxalbumlen + 4 + $maxsongperalbum * 4)\n" if ($verbose);
557 print "Artist Entry Size: $artistentrysize ($maxartistlen + $maxalbumsperartist * 4)\n" if ($verbose);
558 print "File Entry Size: $fileentrysize ($maxfilelen + 12)\n" if ($verbose);
561 open(DB
, ">$db") || die "couldn't make $db";
563 printf DB
"RDB%c", $dbver;
565 $pathindex = 68; # paths always start at index 68
567 $artistindex = $pathindex;
569 # set total size of song title table
570 $sc = scalar(keys %entries) * $songentrysize;
571 my $ac = scalar(keys %albums) * $albumentrysize;
572 my $arc = scalar(keys %artists) * $artistentrysize;
573 $albumindex = $artistindex + $arc; # arc is size of all artists
574 $songindex = $albumindex + $ac; # ac is size of all albums
575 my $fileindex = $songindex + $sc; # sc is size of all songs
577 dumpint
($artistindex); # file position index of artist table
578 dumpint
($albumindex); # file position index of album table
579 dumpint
($songindex); # file position index of song table
580 dumpint
($fileindex); # file position index of file table
581 dumpint
(scalar(keys %artists)); # number of artists
582 dumpint
(scalar(keys %albums)); # number of albums
583 dumpint
(scalar(keys %entries)); # number of songs
584 dumpint
(scalar(keys %entries)); # number of files
585 dumpint
($maxartistlen); # length of artist name field
586 dumpint
($maxalbumlen); # length of album name field
587 dumpint
($maxsonglen); # length of song name field
588 dumpint
($maxgenrelen); #length of genre field
589 dumpint
($maxfilelen); # length of file field
590 dumpint
($maxsongperalbum); # number of entries in songs-per-album array
591 dumpint
($maxalbumsperartist); # number of entries in albums-per-artist array
592 dumpint
(-1); # rundb dirty
594 #### TABLE of artists ###
596 # pointers to albums of artist1
598 for (sort {uc($a) cmp uc($b)} keys %artists) {
600 my $str = $_."\x00" x
($maxartistlen - length($_));
603 for (sort keys %{$artist2albums{$artist}}) {
604 my $id3 = $artist2albums{$artist}{$_};
607 $a = $albumcount{"$$id3{'DIR'}"} * $albumentrysize;
610 $a = $albumcount{"$$id3{'ALBUM'}___$$id3{'DIR'}"} * $albumentrysize;
612 dumpint
($a + $albumindex);
615 for (scalar keys %{$artist2albums{$artist}} .. $maxalbumsperartist-1) {
616 print DB
"\x00\x00\x00\x00";
621 ### Build song offset info.
622 my $offset = $songindex;
623 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
624 my $id3 = $entries{$_};
625 $$id3{'songoffset'} = $offset;
626 $offset += $songentrysize;
630 #### TABLE of albums ###
632 # pointers to artists of album1
633 # pointers to songs on album1
637 my @moo=split(/___/, $_);
641 $t = $dir2albumname{$albumid};
646 $str = $t."\x00" x
($maxalbumlen - length($t));
649 my @songlist = keys %{$album2songs{$albumid}};
650 my $id3 = $album2songs{$albumid}{$songlist[0]};
652 #printf "(d) albumid: %s artist: %s\n",$albumid, $id3->{'ARTIST'};
654 my $aoffset = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
655 dumpint
($aoffset + $artistindex); # pointer to artist of this album
657 if (defined $id3->{'TRACKNUM'}) {
659 $album2songs{$albumid}{$a}->{'TRACKNUM'} <=>
660 $album2songs{$albumid}{$b}->{'TRACKNUM'}
664 @songlist = sort @songlist;
668 my $id3 = $album2songs{$albumid}{$_};
669 dumpint
($$id3{'songoffset'});
672 for (scalar keys %{$album2songs{$albumid}} .. $maxsongperalbum-1) {
673 print DB
"\x00\x00\x00\x00";
677 #### Build filename offset info
680 for $f (sort {uc($a) cmp uc($b)} keys %entries) {
681 $filenamepos{$f}= $l;
682 $l += $fileentrysize;
685 #### TABLE of songs ###
687 # pointer to artist of song1
688 # pointer to album of song1
689 # pointer to filename of song1
691 for(sort {uc($entries{$a}->{'TITLE'}) cmp uc($entries{$b}->{'TITLE'})} keys %entries) {
693 my $id3 = $entries{$f};
694 my $t = $id3->{'TITLE'};
695 my $g = $id3->{'GENRE'};
696 my $str = $t."\x00" x
($maxsonglen- length($t));
698 print DB
$str; # title
699 $str = $g."\x00" x
($maxgenrelen - length($g));
701 my $a = $artistcount{$id3->{'ARTIST'}} * $artistentrysize;
702 dumpint
($a + $artistindex); # pointer to artist of this song
705 $a = $albumcount{"$$id3{DIR}"} * $albumentrysize;
708 $a = $albumcount{"$$id3{ALBUM}___$$id3{DIR}"} * $albumentrysize;
710 dumpint
($a + $albumindex); # pointer to album of this song
712 # pointer to filename of this song
713 dumpint
($filenamepos{$f});
714 print DB
$str; #genre
716 dumpshort
($id3->{'YEAR'});
718 dumpshort
($id3->{'TRACKNUM'});
722 #### TABLE of file names ###
725 for $f (sort {uc($a) cmp uc($b)} %entries) {
726 my $str = $f."\x00" x
($maxfilelen- length($f));
727 my $id3 = $entries{$f};
729 #print STDERR "CRC: ".."\n";
730 dumpint
($id3->{'FILECRC'}); # CRC32 of the song data
731 dumpint
($id3->{'songoffset'}); # offset to song data
732 dumpint
(-1); # offset to rundb data. always set to -1. this is updated by rockbox code on the player.
739 ### Here follows module MP3::Info Copyright (c) 1998-2004 Chris Nandor
740 ### Modified by Björn Stenberg to remove use of external libraries
744 @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION, $REVISION,
745 @mp3_genres, %mp3_genres, @winamp_genres, %winamp_genres, $try_harder,
746 @t_bitrate, @t_sampling_freq, @frequency_tbl, %v1_tag_fields,
747 @v1_tag_names, %v2_tag_names, %v2_to_v1_names, $AUTOLOAD,
753 set_mp3tag get_mp3tag get_mp3info remove_mp3tag
756 @EXPORT_OK = qw(@mp3_genres %mp3_genres use_mp3_utf8);
758 genres => [qw(@mp3_genres %mp3_genres)],
759 utf8 => [qw(use_mp3_utf8)],
760 all
=> [@EXPORT, @EXPORT_OK]
764 ($REVISION) = ' $Revision$ ' =~ /\$Revision:\s+([^\s]+)/;
771 MP3::Info - Manipulate / fetch info from MP3 audio files
777 my $file = 'Pearls_Before_Swine.mp3';
778 set_mp3tag($file, 'Pearls Before Swine', q"77's",
779 'Sticks and Stones', '1990',
780 q"(c) 1990 77's LTD.", 'rock & roll');
782 my $tag = get_mp3tag($file) or die "No TAG info";
783 $tag->{GENRE} = 'rock';
784 set_mp3tag($file, $tag);
786 my $info = get_mp3info($file);
787 printf "$file length is %d:%d\n", $info->{MM}, $info->{SS};
793 # set all lower-case and regular-cased versions of genres as keys
794 # with index as value of each key
795 %mp3_genres = map {($_, ++$c, lc, $c)} @mp3_genres;
797 # do it again for winamp genres
799 %winamp_genres = map {($_, ++$c, lc, $c)} @winamp_genres;
804 my $mp3 = new MP3::Info $file;
805 $mp3->title('Perls Before Swine');
806 printf "$file length is %s, title is %s\n",
807 $mp3->time, $mp3->title;
814 =item $mp3 = MP3::Info-E<gt>new(FILE)
816 OOP interface to the rest of the module. The same keys
817 available via get_mp3info and get_mp3tag are available
818 via the returned object (using upper case or lower case;
819 but note that all-caps "VERSION" will return the module
820 version, not the MP3 version).
822 Passing a value to one of the methods will set the value
823 for that tag in the MP3 file, if applicable.
828 my($pack, $file) = @_;
830 my $info = get_mp3info
($file) or return undef;
831 my $tags = get_mp3tag
($file) || { map { ($_ => undef) } @v1_tag_names };
837 @self{@mp3_info_fields, @v1_tag_names, 'file'} = (
838 @
{$info}{@mp3_info_fields},
839 @
{$tags}{@v1_tag_names},
843 return bless \
%self, $pack;
848 return $self->SUPER::can
(@_) unless ref $self;
850 return sub { $self->$name(@_) } if exists $self->{$name};
856 (my $name = uc $AUTOLOAD) =~ s/^.*://;
858 if (exists $self->{$name}) {
859 my $sub = exists $v1_tag_fields{$name}
862 $_[0]->{$name} = $_[1];
863 set_mp3tag
($_[0]->{FILE
}, $_[0]);
865 return $_[0]->{$name};
868 return $_[0]->{$name}
875 warn(sprintf "No method '$name' available in package %s.",
885 =item use_mp3_utf8([STATUS])
887 Tells MP3::Info to (or not) return TAG info in UTF-8.
888 TRUE is 1, FALSE is 0. Default is FALSE.
890 Will only be able to it on if Unicode::String is available. ID3v2
891 tags will be converted to UTF-8 according to the encoding specified
892 in each tag; ID3v1 tags will be assumed Latin-1 and converted
895 Function returns status (TRUE/FALSE). If no argument is supplied,
896 or an unaccepted argument is supplied, function merely returns status.
898 This function is not exported by default, but may be exported
899 with the C<:utf8> or C<:all> export tag.
903 my $unicode_module = eval { require Unicode
::String
};
909 $UNICODE = 1 if $unicode_module;
910 } elsif ($val == 0) {
918 =item use_winamp_genres()
920 Puts WinAmp genres into C<@mp3_genres> and C<%mp3_genres>
921 (adds 68 additional genres to the default list of 80).
922 This is a separate function because these are non-standard
923 genres, but they are included because they are widely used.
925 You can import the data structures with one of:
927 use MP3::Info qw(:genres);
928 use MP3::Info qw(:DEFAULT :genres);
929 use MP3::Info qw(:all);
933 sub use_winamp_genres
{
934 %mp3_genres = %winamp_genres;
935 @mp3_genres = @winamp_genres;
943 =item get_mp3tag (FILE [, VERSION, RAW_V2])
945 Returns hash reference containing tag information in MP3 file. The keys
946 returned are the same as those supplied for C<set_mp3tag>, except in the
947 case of RAW_V2 being set.
949 If VERSION is C<1>, the information is taken from the ID3v1 tag (if present).
950 If VERSION is C<2>, the information is taken from the ID3v2 tag (if present).
951 If VERSION is not supplied, or is false, the ID3v1 tag is read if present, and
952 then, if present, the ID3v2 tag information will override any existing ID3v1
955 If RAW_V2 is C<1>, the raw ID3v2 tag data is returned, without any manipulation
956 of text encoding. The key name is the same as the frame ID (ID to name mappings
957 are in the global %v2_tag_names).
959 If RAW_V2 is C<2>, the ID3v2 tag data is returned, manipulating for Unicode if
960 necessary, etc. It also takes multiple values for a given key (such as comments)
961 and puts them in an arrayref.
963 If the ID3v2 version is older than ID3v2.2.0 or newer than ID3v2.4.0, it will
966 Strings returned will be in Latin-1, unless UTF-8 is specified (L<use_mp3_utf8>),
967 (unless RAW_V2 is C<1>).
969 Also returns a TAGVERSION key, containing the ID3 version used for the returned
970 data (if TAGVERSION argument is C<0>, may contain two versions).
975 my($file, $ver, $raw_v2) = @_;
976 my($tag, $v1, $v2, $v2h, %info, @array, $fh);
978 $ver = !$ver ?
0 : ($ver == 2 || $ver == 1) ?
$ver : 0;
980 if (not (defined $file && $file ne '')) {
981 $@
= "No file specified";
986 $@
= "File is empty";
990 if (ref $file) { # filehandle passed
994 if (not open $fh, "< $file\0") {
995 $@
= "Can't open $file: $!";
1004 while(defined(my $line = <$fh>)) { $tag .= $line }
1006 if ($tag =~ /^TAG/) {
1008 if (substr($tag, -3, 2) =~ /\000[^\000]/) {
1009 (undef, @info{@v1_tag_names}) =
1010 (unpack('a3a30a30a30a4a28', $tag),
1011 ord(substr($tag, -2, 1)),
1012 $mp3_genres[ord(substr $tag, -1)]);
1013 $info{TAGVERSION
} = 'ID3v1.1';
1015 (undef, @info{@v1_tag_names[0..4, 6]}) =
1016 (unpack('a3a30a30a30a4a30', $tag),
1017 $mp3_genres[ord(substr $tag, -1)]);
1018 $info{TAGVERSION
} = 'ID3v1';
1021 for my $key (keys %info) {
1022 next unless $info{$key};
1023 my $u = Unicode
::String
::latin1
($info{$key});
1024 $info{$key} = $u->utf8;
1027 } elsif ($ver == 1) {
1029 $@
= "No ID3v1 tag found";
1034 ($v2, $v2h) = _get_v2tag
($fh);
1036 unless ($v1 || $v2) {
1038 $@
= "No ID3 tag found";
1042 if (($ver == 0 || $ver == 2) && $v2) {
1043 if ($raw_v2 == 1 && $ver == 2) {
1045 $info{TAGVERSION
} = $v2h->{version
};
1047 my $hash = $raw_v2 == 2 ?
{ map { ($_, $_) } keys %v2_tag_names } : \
%v2_to_v1_names;
1048 for my $id (keys %$hash) {
1049 if (exists $v2->{$id}) {
1050 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
1051 $info{$hash->{$id}} = $mp3_genres[$1];
1053 my $data1 = $v2->{$id};
1055 # this is tricky ... if this is an arrayref, we want
1056 # to only return one, so we pick the first one. but
1057 # if it is a comment, we pick the first one where the
1058 # first charcter after the language is NULL and not an
1059 # additional sub-comment, because that is most likely
1060 # to be the user-supplied comment
1062 if (ref $data1 && !$raw_v2) {
1063 if ($id =~ /^COMM?$/) {
1064 my($newdata) = grep /^(....\000)/, @
{$data1};
1065 $data1 = $newdata || $data1->[0];
1067 $data1 = $data1->[0];
1071 $data1 = [ $data1 ] if ! ref $data1;
1073 for my $data (@
$data1) {
1074 $data =~ s/^(.)//; # strip first char (text encoding)
1077 if ($id =~ /^COM[M ]?$/) {
1078 $data =~ s/^(?:...)//; # strip language
1079 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
1085 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
1086 my $u = Unicode
::String
::utf16
($data);
1088 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
1089 } elsif ($encoding eq "\000") {
1090 my $u = Unicode
::String
::latin1
($data);
1095 if ($raw_v2 == 2 && $desc) {
1096 $data = { $desc => $data };
1099 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
1100 if (ref $info{$hash->{$id}} eq 'ARRAY') {
1101 push @
{$info{$hash->{$id}}}, $data;
1103 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
1106 $info{$hash->{$id}} = $data;
1112 if ($ver == 0 && $info{TAGVERSION
}) {
1113 $info{TAGVERSION
} .= ' / ' . $v2h->{version
};
1115 $info{TAGVERSION
} = $v2h->{version
};
1120 unless ($raw_v2 && $ver == 2) {
1121 foreach my $key (keys %info) {
1122 if (defined $info{$key}) {
1123 $info{$key} =~ s/\000+.*//g;
1124 $info{$key} =~ s/\s+$//;
1128 for (@v1_tag_names) {
1129 $info{$_} = '' unless defined $info{$_};
1133 if (keys %info && exists $info{GENRE
} && ! defined $info{GENRE
}) {
1139 return keys %info ?
{%info} : undef;
1144 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
1147 $v2 = _get_v2head
($fh) or return;
1148 if ($v2->{major_version
} < 2) {
1149 warn "This is $v2->{version}; " .
1150 "ID3v2 versions older than ID3v2.2.0 not supported\n"
1155 if ($v2->{major_version
} == 2) {
1165 read $fh, my($bytes), $hlen;
1166 return unless $bytes =~ /^([A-Z0-9]{$num})/
1167 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes
1168 my($id, $size) = ($1, $hlen);
1169 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num);
1170 for my $i (0 .. ($num - 1)) {
1171 $size += $bytes[$i] * 256 ** $i;
1176 $off = $v2->{ext_header_size
} + 10;
1178 while ($off < $v2->{tag_size
}) {
1179 my($id, $size) = &$myseek or last;
1180 seek $fh, $off + $hlen, 0;
1181 read $fh, my($bytes), $size - $hlen;
1182 if (exists $h->{$id}) {
1183 if (ref $h->{$id} eq 'ARRAY') {
1184 push @
{$h->{$id}}, $bytes;
1186 $h->{$id} = [$h->{$id}, $bytes];
1200 =item get_mp3info (FILE)
1202 Returns hash reference containing file information for MP3 file.
1203 This data cannot be changed. Returned data:
1205 VERSION MPEG audio version (1, 2, 2.5)
1206 LAYER MPEG layer description (1, 2, 3)
1207 STEREO boolean for audio is in stereo
1209 VBR boolean for variable bitrate
1210 BITRATE bitrate in kbps (average for VBR files)
1211 FREQUENCY frequency in kHz
1212 SIZE bytes in audio stream
1217 MS leftover milliseconds
1220 COPYRIGHT boolean for audio is copyrighted
1221 PADDING boolean for MP3 frames are padded
1222 MODE channel mode (0 = stereo, 1 = joint stereo,
1223 2 = dual channel, 3 = single channel)
1224 FRAMES approximate number of frames
1225 FRAME_LENGTH approximate length of a frame
1226 VBR_SCALE VBR scale from VBR header
1228 On error, returns nothing and sets C<$@>.
1234 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
1236 if (not (defined $file && $file ne '')) {
1237 $@
= "No file specified";
1242 $@
= "File is empty";
1246 if (ref $file) { # filehandle passed
1250 if (not open $fh, "< $file\0") {
1251 $@
= "Can't open $file: $!";
1268 if (my $id3v2 = _get_v2head
($fh)) {
1269 $tot += $off += $id3v2->{tag_size
};
1274 $h = _get_head
($byte);
1275 until (_is_mp3
($h)) {
1278 $h = _get_head
($byte);
1279 if ($off > $tot && !$try_harder) {
1281 $@
= "Couldn't find MP3 header (perhaps set " .
1282 '$MP3::Info::try_harder and retry)';
1287 my $vbr = _get_vbr
($fh, $h, \
$off);
1289 $h->{headersize
}=$off; # data size prepending the actual mp3 data
1294 $off += 128 if <$fh> =~ /^TAG/ ?
1 : 0;
1298 $h->{size
} = $eof - $off;
1300 return _get_info
($h, $vbr);
1307 $i->{VERSION
} = $h->{IDR
} == 2 ?
2 : $h->{IDR
} == 3 ?
1 :
1308 $h->{IDR
} == 0 ?
2.5 : 0;
1309 $i->{LAYER
} = 4 - $h->{layer
};
1310 $i->{VBR
} = defined $vbr ?
1 : 0;
1312 $i->{COPYRIGHT
} = $h->{copyright
} ?
1 : 0;
1313 $i->{PADDING
} = $h->{padding_bit
} ?
1 : 0;
1314 $i->{STEREO
} = $h->{mode
} == 3 ?
0 : 1;
1315 $i->{MODE
} = $h->{mode
};
1317 $i->{SIZE
} = $vbr && $vbr->{bytes
} ?
$vbr->{bytes
} : $h->{size
};
1319 my $mfs = $h->{fs
} / ($h->{ID
} ?
144000 : 72000);
1320 $i->{FRAMES
} = int($vbr && $vbr->{frames
}
1322 : $i->{SIZE
} / $h->{bitrate} / $mfs
1326 $i->{VBR_SCALE
} = $vbr->{scale
} if $vbr->{scale
};
1327 $h->{bitrate
} = $i->{SIZE
} / $i->{FRAMES
} * $mfs;
1328 if (not $h->{bitrate
}) {
1329 $@
= "Couldn't determine VBR bitrate";
1334 $h->{'length'} = ($i->{SIZE
} * 8) / $h->{bitrate} / 10;
1335 $i->{SECS
} = $h->{'length'} / 100;
1336 $i->{MM
} = int $i->{SECS
} / 60;
1337 $i->{SS
} = int $i->{SECS
} % 60;
1338 $i->{MS
} = (($i->{SECS
} - ($i->{MM
} * 60) - $i->{SS
}) * 1000);
1339 # $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS});
1340 # int($i->{MS} / 100 * 75); # is this right?
1341 $i->{TIME
} = sprintf "%.2d:%.2d", @
{$i}{'MM', 'SS'};
1343 $i->{BITRATE
} = int $h->{bitrate
};
1344 # should we just return if ! FRAMES?
1345 $i->{FRAME_LENGTH
} = int($h->{size
} / $i->{FRAMES
}) if $i->{FRAMES
};
1346 $i->{FREQUENCY
} = $frequency_tbl[3 * $h->{IDR
} + $h->{sampling_freq
}];
1348 $i->{headersize
} = $h->{headersize
};
1357 $bytes = _unpack_head
($byte);
1358 @
$h{qw(IDR ID layer protection_bit
1359 bitrate_index sampling_freq padding_bit private_bit
1360 mode mode_extension copyright original
1361 emphasis version_index bytes)} = (
1362 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1,
1363 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1,
1364 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1,
1365 $bytes&3, ($bytes>>19)&3, $bytes
1368 $h->{bitrate
} = $t_bitrate[$h->{ID
}][3 - $h->{layer
}][$h->{bitrate_index
}];
1369 $h->{fs
} = $t_sampling_freq[$h->{IDR
}][$h->{sampling_freq
}];
1375 my $h = $_[0] or return undef;
1376 return ! ( # all below must be false
1377 $h->{bitrate_index
} == 0
1379 $h->{version_index
} == 1
1381 ($h->{bytes
} & 0xFFE00000) != 0xFFE00000
1387 $h->{bitrate_index
} == 15
1391 $h->{sampling_freq
} == 3
1395 !$h->{bitrate_index
}
1397 ($h->{bytes
} & 0xFFFF0000) == 0xFFFE0000
1399 ($h->{ID
} == 1 && $h->{layer
} == 3 && $h->{protection_bit
} == 1)
1401 ($h->{mode_extension
} != 0 && $h->{mode
} != 1)
1406 my($fh, $h, $roff) = @_;
1407 my($off, $bytes, @bytes, $myseek, %vbr);
1410 @_ = (); # closure confused if we don't do this
1415 read $fh, $bytes, $n;
1421 if ($h->{ID
}) { # MPEG1
1422 $off += $h->{mode
} == 3 ?
17 : 32;
1424 $off += $h->{mode
} == 3 ?
9 : 17;
1428 return unless $bytes eq 'Xing';
1431 $vbr{flags
} = _unpack_head
($bytes);
1433 if ($vbr{flags
} & 1) {
1435 $vbr{frames
} = _unpack_head
($bytes);
1438 if ($vbr{flags
} & 2) {
1440 $vbr{bytes
} = _unpack_head
($bytes);
1443 if ($vbr{flags
} & 4) {
1445 # Not used right now ...
1446 # $vbr{toc} = _unpack_head($bytes);
1449 if ($vbr{flags
} & 8) { # (quality ind., 0=best 100=worst)
1451 $vbr{scale
} = _unpack_head
($bytes);
1461 my $fh = $_[0] or return;
1462 my($h, $bytes, @bytes);
1464 # check first three bytes for 'ID3'
1466 read $fh, $bytes, 3;
1467 return unless $bytes eq 'ID3';
1470 read $fh, $bytes, 2;
1471 $h->{version
} = sprintf "ID3v2.%d.%d",
1472 @
$h{qw
[major_version minor_version
]} =
1473 unpack 'c2', $bytes;
1476 read $fh, $bytes, 1;
1477 if ($h->{major_version
} == 2) {
1478 @
$h{qw
[unsync compression
]} =
1479 (unpack 'b8', $bytes)[7, 6];
1480 $h->{ext_header
} = 0;
1481 $h->{experimental
} = 0;
1483 @
$h{qw
[unsync ext_header experimental
]} =
1484 (unpack 'b8', $bytes)[7, 6, 5];
1487 # get ID3v2 tag length from bytes 7-10
1488 $h->{tag_size
} = 10; # include ID3v2 header size
1489 read $fh, $bytes, 4;
1490 @bytes = reverse unpack 'C4', $bytes;
1491 foreach my $i (0 .. 3) {
1492 # whoaaaaaa nellllllyyyyyy!
1493 $h->{tag_size
} += $bytes[$i] * 128 ** $i;
1496 # get extended header size
1497 $h->{ext_header_size
} = 0;
1498 if ($h->{ext_header
}) {
1499 $h->{ext_header_size
} += 10;
1500 read $fh, $bytes, 4;
1501 @bytes = reverse unpack 'C4', $bytes;
1503 $h->{ext_header_size
} += $bytes[$i] * 256 ** $i;
1511 unpack('l', pack('L', unpack('N', $_[0])));
1515 my($file, $fh) = @_;
1516 unless (ref $file) { # filehandle not passed
1517 close $fh or warn "Problem closing '$file': $!";
1570 'Instrumental Rock',
1574 'Techno-Industrial',
1663 'Christian Gangsta Rap',
1667 'Contemporary Christian',
1678 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
1679 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
1680 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
1682 [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448],
1683 [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384],
1684 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
1687 @t_sampling_freq = (
1688 [11025, 12000, 8000],
1689 [undef, undef, undef], # reserved
1690 [22050, 24000, 16000],
1691 [44100, 48000, 32000]
1694 @frequency_tbl = map { $_ ?
eval "${_}e-3" : 0 }
1695 map { @
$_ } @t_sampling_freq;
1697 @mp3_info_fields = qw(
1719 (TITLE
=> 30, ARTIST
=> 30, ALBUM
=> 30, COMMENT
=> 30, YEAR
=> 4);
1721 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1730 'TRK' => 'TRACKNUM',
1731 'TCO' => 'GENRE', # not clean mapping, but ...
1737 'COMM' => 'COMMENT',
1738 'TRCK' => 'TRACKNUM',
1744 'BUF' => 'Recommended buffer size',
1745 'CNT' => 'Play counter',
1746 'COM' => 'Comments',
1747 'CRA' => 'Audio encryption',
1748 'CRM' => 'Encrypted meta frame',
1749 'ETC' => 'Event timing codes',
1750 'EQU' => 'Equalization',
1751 'GEO' => 'General encapsulated object',
1752 'IPL' => 'Involved people list',
1753 'LNK' => 'Linked information',
1754 'MCI' => 'Music CD Identifier',
1755 'MLL' => 'MPEG location lookup table',
1756 'PIC' => 'Attached picture',
1757 'POP' => 'Popularimeter',
1759 'RVA' => 'Relative volume adjustment',
1760 'SLT' => 'Synchronized lyric/text',
1761 'STC' => 'Synced tempo codes',
1762 'TAL' => 'Album/Movie/Show title',
1763 'TBP' => 'BPM (Beats Per Minute)',
1764 'TCM' => 'Composer',
1765 'TCO' => 'Content type',
1766 'TCR' => 'Copyright message',
1768 'TDY' => 'Playlist delay',
1769 'TEN' => 'Encoded by',
1770 'TFT' => 'File type',
1772 'TKE' => 'Initial key',
1773 'TLA' => 'Language(s)',
1775 'TMT' => 'Media type',
1776 'TOA' => 'Original artist(s)/performer(s)',
1777 'TOF' => 'Original filename',
1778 'TOL' => 'Original Lyricist(s)/text writer(s)',
1779 'TOR' => 'Original release year',
1780 'TOT' => 'Original album/Movie/Show title',
1781 'TP1' => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
1782 'TP2' => 'Band/Orchestra/Accompaniment',
1783 'TP3' => 'Conductor/Performer refinement',
1784 'TP4' => 'Interpreted, remixed, or otherwise modified by',
1785 'TPA' => 'Part of a set',
1786 'TPB' => 'Publisher',
1787 'TRC' => 'ISRC (International Standard Recording Code)',
1788 'TRD' => 'Recording dates',
1789 'TRK' => 'Track number/Position in set',
1791 'TSS' => 'Software/hardware and settings used for encoding',
1792 'TT1' => 'Content group description',
1793 'TT2' => 'Title/Songname/Content description',
1794 'TT3' => 'Subtitle/Description refinement',
1795 'TXT' => 'Lyricist/text writer',
1796 'TXX' => 'User defined text information frame',
1798 'UFI' => 'Unique file identifier',
1799 'ULT' => 'Unsychronized lyric/text transcription',
1800 'WAF' => 'Official audio file webpage',
1801 'WAR' => 'Official artist/performer webpage',
1802 'WAS' => 'Official audio source webpage',
1803 'WCM' => 'Commercial information',
1804 'WCP' => 'Copyright/Legal information',
1805 'WPB' => 'Publishers official webpage',
1806 'WXX' => 'User defined URL link frame',
1809 'AENC' => 'Audio encryption',
1810 'APIC' => 'Attached picture',
1811 'COMM' => 'Comments',
1812 'COMR' => 'Commercial frame',
1813 'ENCR' => 'Encryption method registration',
1814 'EQUA' => 'Equalization',
1815 'ETCO' => 'Event timing codes',
1816 'GEOB' => 'General encapsulated object',
1817 'GRID' => 'Group identification registration',
1818 'IPLS' => 'Involved people list',
1819 'LINK' => 'Linked information',
1820 'MCDI' => 'Music CD identifier',
1821 'MLLT' => 'MPEG location lookup table',
1822 'OWNE' => 'Ownership frame',
1823 'PCNT' => 'Play counter',
1824 'POPM' => 'Popularimeter',
1825 'POSS' => 'Position synchronisation frame',
1826 'PRIV' => 'Private frame',
1827 'RBUF' => 'Recommended buffer size',
1828 'RVAD' => 'Relative volume adjustment',
1830 'SYLT' => 'Synchronized lyric/text',
1831 'SYTC' => 'Synchronized tempo codes',
1832 'TALB' => 'Album/Movie/Show title',
1833 'TBPM' => 'BPM (beats per minute)',
1834 'TCOM' => 'Composer',
1835 'TCON' => 'Content type',
1836 'TCOP' => 'Copyright message',
1838 'TDLY' => 'Playlist delay',
1839 'TENC' => 'Encoded by',
1840 'TEXT' => 'Lyricist/Text writer',
1841 'TFLT' => 'File type',
1843 'TIT1' => 'Content group description',
1844 'TIT2' => 'Title/songname/content description',
1845 'TIT3' => 'Subtitle/Description refinement',
1846 'TKEY' => 'Initial key',
1847 'TLAN' => 'Language(s)',
1849 'TMED' => 'Media type',
1850 'TOAL' => 'Original album/movie/show title',
1851 'TOFN' => 'Original filename',
1852 'TOLY' => 'Original lyricist(s)/text writer(s)',
1853 'TOPE' => 'Original artist(s)/performer(s)',
1854 'TORY' => 'Original release year',
1855 'TOWN' => 'File owner/licensee',
1856 'TPE1' => 'Lead performer(s)/Soloist(s)',
1857 'TPE2' => 'Band/orchestra/accompaniment',
1858 'TPE3' => 'Conductor/performer refinement',
1859 'TPE4' => 'Interpreted, remixed, or otherwise modified by',
1860 'TPOS' => 'Part of a set',
1861 'TPUB' => 'Publisher',
1862 'TRCK' => 'Track number/Position in set',
1863 'TRDA' => 'Recording dates',
1864 'TRSN' => 'Internet radio station name',
1865 'TRSO' => 'Internet radio station owner',
1867 'TSRC' => 'ISRC (international standard recording code)',
1868 'TSSE' => 'Software/Hardware and settings used for encoding',
1869 'TXXX' => 'User defined text information frame',
1871 'UFID' => 'Unique file identifier',
1872 'USER' => 'Terms of use',
1873 'USLT' => 'Unsychronized lyric/text transcription',
1874 'WCOM' => 'Commercial information',
1875 'WCOP' => 'Copyright/Legal information',
1876 'WOAF' => 'Official audio file webpage',
1877 'WOAR' => 'Official artist/performer webpage',
1878 'WOAS' => 'Official audio source webpage',
1879 'WORS' => 'Official internet radio station homepage',
1880 'WPAY' => 'Payment',
1881 'WPUB' => 'Publishers official webpage',
1882 'WXXX' => 'User defined URL link frame',
1884 # v2.4 additional tags
1885 # note that we don't restrict tags from 2.3 or 2.4,
1886 'ASPI' => 'Audio seek point index',
1887 'EQU2' => 'Equalisation (2)',
1888 'RVA2' => 'Relative volume adjustment (2)',
1889 'SEEK' => 'Seek frame',
1890 'SIGN' => 'Signature frame',
1891 'TDEN' => 'Encoding time',
1892 'TDOR' => 'Original release time',
1893 'TDRC' => 'Recording time',
1894 'TDRL' => 'Release time',
1895 'TDTG' => 'Tagging time',
1896 'TIPL' => 'Involved people list',
1897 'TMCL' => 'Musician credits list',
1899 'TPRO' => 'Produced notice',
1900 'TSOA' => 'Album sort order',
1901 'TSOP' => 'Performer sort order',
1902 'TSOT' => 'Title sort order',
1903 'TSST' => 'Set subtitle',
1906 'COM ' => 'Broken iTunes comments',
1918 =head1 TROUBLESHOOTING
1920 If you find a bug, please send me a patch (see the project page in L<"SEE ALSO">).
1921 If you cannot figure out why it does not work for you, please put the MP3 file in
1922 a place where I can get it (preferably via FTP, or HTTP, or .Mac iDisk) and send me
1923 mail regarding where I can get the file, with a detailed description of the problem.
1925 If I download the file, after debugging the problem I will not keep the MP3 file
1926 if it is not legal for me to have it. Just let me know if it is legal for me to
1936 Still need to do more for reading tags, such as using Compress::Zlib to decompress
1937 compressed tags. But until I see this in use more, I won't bother. If something
1938 does not work properly with reading, follow the instructions above for
1941 ID3v2 I<writing> is coming soon.
1943 =item Get data from scalar
1945 Instead of passing a file spec or filehandle, pass the
1946 data itself. Would take some work, converting the seeks, etc.
1950 Do something with padding bit.
1954 Test suite could use a bit of an overhaul and update. Patches very welcome.
1960 Revamp getset.t. Test all the various get_mp3tag args.
1972 Test error handling, check more for missing files, bad MP3s, etc.
1978 Right now, only Xing VBR is supported.
1985 Edward Allen E<lt>allenej@c51844-a.spokn1.wa.home.comE<gt>,
1986 Vittorio Bertola E<lt>v.bertola@vitaminic.comE<gt>,
1987 Michael Blakeley E<lt>mike@blakeley.comE<gt>,
1988 Per Bolmstedt E<lt>tomten@kol14.comE<gt>,
1989 Tony Bowden E<lt>tony@tmtm.comE<gt>,
1990 Tom Brown E<lt>thecap@usa.netE<gt>,
1991 Sergio Camarena E<lt>scamarena@users.sourceforge.netE<gt>,
1992 Chris Dawson E<lt>cdawson@webiphany.comE<gt>,
1993 Luke Drumm E<lt>lukedrumm@mypad.comE<gt>,
1994 Kyle Farrell E<lt>kyle@cantametrix.comE<gt>,
1995 Jeffrey Friedl E<lt>jfriedl@yahoo.comE<gt>,
1996 brian d foy E<lt>comdog@panix.comE<gt>,
1997 Ben Gertzfield E<lt>che@debian.orgE<gt>,
1998 Brian Goodwin E<lt>brian@fuddmain.comE<gt>,
1999 Todd Hanneken E<lt>thanneken@hds.harvard.eduE<gt>,
2000 Todd Harris E<lt>harris@cshl.orgE<gt>,
2001 Woodrow Hill E<lt>asim@mindspring.comE<gt>,
2002 Kee Hinckley E<lt>nazgul@somewhere.comE<gt>,
2003 Roman Hodek E<lt>Roman.Hodek@informatik.uni-erlangen.deE<gt>,
2004 Peter Kovacs E<lt>kovacsp@egr.uri.eduE<gt>,
2006 Peter Marschall E<lt>peter.marschall@mayn.deE<gt>,
2007 Trond Michelsen E<lt>mike@crusaders.noE<gt>,
2008 Dave O'Neill E<lt>dave@nexus.carleton.caE<gt>,
2009 Christoph Oberauer E<lt>christoph.oberauer@sbg.ac.atE<gt>,
2010 Jake Palmer E<lt>jake.palmer@db.comE<gt>,
2011 Andrew Phillips E<lt>asp@wasteland.orgE<gt>,
2012 David Reuteler E<lt>reuteler@visi.comE<gt>,
2013 John Ruttenberg E<lt>rutt@chezrutt.comE<gt>,
2014 Matthew Sachs E<lt>matthewg@zevils.comE<gt>,
2015 E<lt>scfc_de@users.sf.netE<gt>,
2016 Hermann Schwaerzler E<lt>Hermann.Schwaerzler@uibk.ac.atE<gt>,
2017 Chris Sidi E<lt>sidi@angband.orgE<gt>,
2018 Roland Steinbach E<lt>roland@support-system.comE<gt>,
2019 Stuart E<lt>schneis@users.sourceforge.netE<gt>,
2020 Jeffery Sumler E<lt>jsumler@mediaone.netE<gt>,
2021 Predrag Supurovic E<lt>mpgtools@dv.co.yuE<gt>,
2022 Bogdan Surdu E<lt>tim@go.roE<gt>,
2023 E<lt>tim@tim-landscheidt.deE<gt>,
2024 Pass F. B. Travis E<lt>pftravis@bellsouth.netE<gt>,
2025 Tobias Wagener E<lt>tobias@wagener.nuE<gt>,
2026 Ronan Waide E<lt>waider@stepstone.ieE<gt>,
2027 Andy Waite E<lt>andy@mailroute.comE<gt>,
2028 Ken Williams E<lt>ken@forum.swarthmore.eduE<gt>,
2029 Meng Weng Wong E<lt>mengwong@pobox.comE<gt>.
2032 =head1 AUTHOR AND COPYRIGHT
2034 Chris Nandor E<lt>pudge@pobox.comE<gt>, http://pudge.net/
2036 Copyright (c) 1998-2003 Chris Nandor. All rights reserved. This program is
2037 free software; you can redistribute it and/or modify it under the terms
2038 of the Artistic License, distributed with Perl.
2045 =item MP3::Info Project Page
2047 http://projects.pudge.net/
2051 http://www.zevils.com/linux/mp3tools/
2055 http://www.dv.co.yu/mpgscript/mpgtools.htm
2056 http://www.dv.co.yu/mpgscript/mpeghdr.htm
2060 http://www.dtek.chalmers.se/~d2linjo/mp3/mp3tool.html
2066 =item Xing Variable Bitrate
2068 http://www.xingtech.com/support/partner_developer/mp3/vbr_sdk/
2072 http://rupert.informatik.uni-stuttgart.de/~mutschml/MP3ext/
2076 http://www.xmms.org/
2083 v1.02, Sunday, March 2, 2003