summaryrefslogtreecommitdiff
path: root/tools/songdb.pl
diff options
context:
space:
mode:
authorBjörn Stenberg <bjorn@haxx.se>2005-01-17 13:23:23 +0000
committerBjörn Stenberg <bjorn@haxx.se>2005-01-17 13:23:23 +0000
commit225116e27634bd6cd7bd686d6f79f1089d08bcab (patch)
tree22c616cbea25d6e7fb9dcdf0a13d1cca53d56ed5 /tools/songdb.pl
parenta50cc0bd343e393705174cb9fb1662b599da1a4c (diff)
downloadrockbox-225116e27634bd6cd7bd686d6f79f1089d08bcab.tar.gz
rockbox-225116e27634bd6cd7bd686d6f79f1089d08bcab.zip
Added MP3::Info.pm inside script to make it standalone.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@5579 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'tools/songdb.pl')
-rwxr-xr-xtools/songdb.pl1582
1 files changed, 1571 insertions, 11 deletions
diff --git a/tools/songdb.pl b/tools/songdb.pl
index 1b1eddee13..0abb014228 100755
--- a/tools/songdb.pl
+++ b/tools/songdb.pl
@@ -1,17 +1,14 @@
1#!/usr/bin/perl 1#!/usr/bin/perl
2 2#
3# Very sparse docs:
4# http://search.cpan.org/~cnandor/MP3-Info-1.02/Info.pm
5
6# MP3::Info is installed on debian using package 'libmp3-info-perl'
7
8# Rockbox song database docs: 3# Rockbox song database docs:
9# http://www.rockbox.org/twiki/bin/view/Main/TagDatabase 4# http://www.rockbox.org/twiki/bin/view/Main/TagDatabase
10 5#
11use MP3::Info; 6# MP3::Info by Chris Nandor is included verbatim in this script to make
7# it runnable standalone on removable drives. See below.
8#
12 9
13my $db = "rockbox.id3db"; 10my $db = "rockbox.id3db";
14my $dir = "."; 11my $dir;
15my $strip; 12my $strip;
16my $verbose; 13my $verbose;
17my $help; 14my $help;
@@ -53,8 +50,8 @@ my %filename;
53my $dbver = 1; 50my $dbver = 1;
54 51
55if(! -d $dir or $help) { 52if(! -d $dir or $help) {
56 print "'$dir' is not a directory\n" if (! -d $dir); 53 print "'$dir' is not a directory\n" if ($dir ne "" and ! -d $dir);
57 print "songdb [--db <file>] [--path <dir>] [--strip <path>] [--verbose] [--help]\n"; 54 print "songdb --path <dir> [--db <file>] [--strip <path>] [--verbose] [--help]\n";
58 exit; 55 exit;
59} 56}
60 57
@@ -428,3 +425,1566 @@ if ($db) {
428 425
429 close(DB); 426 close(DB);
430} 427}
428
429###
430### Here follows module MP3::Info Copyright (c) 1998-2004 Chris Nandor
431### Modified by Björn Stenberg to remove use of external libraries
432###
433
434our(
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
440);
441
442@ISA = 'Exporter';
443@EXPORT = qw(
444 set_mp3tag get_mp3tag get_mp3info remove_mp3tag
445 use_winamp_genres
446);
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]
452);
453
454# $Id$
455($REVISION) = ' $Revision$ ' =~ /\$Revision:\s+([^\s]+)/;
456$VERSION = '1.02';
457
458=pod
459
460=head1 NAME
461
462MP3::Info - Manipulate / fetch info from MP3 audio files
463
464=head1 SYNOPSIS
465
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');
472
473 my $tag = get_mp3tag($file) or die "No TAG info";
474 $tag->{GENRE} = 'rock';
475 set_mp3tag($file, $tag);
476
477 my $info = get_mp3info($file);
478 printf "$file length is %d:%d\n", $info->{MM}, $info->{SS};
479
480=cut
481
482{
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;
487
488 # do it again for winamp genres
489 $c = -1;
490 %winamp_genres = map {($_, ++$c, lc, $c)} @winamp_genres;
491}
492
493=pod
494
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;
499
500
501=head1 DESCRIPTION
502
503=over 4
504
505=item $mp3 = MP3::Info-E<gt>new(FILE)
506
507OOP interface to the rest of the module. The same keys
508available via get_mp3info and get_mp3tag are available
509via the returned object (using upper case or lower case;
510but note that all-caps "VERSION" will return the module
511version, not the MP3 version).
512
513Passing a value to one of the methods will set the value
514for that tag in the MP3 file, if applicable.
515
516=cut
517
518sub new {
519 my($pack, $file) = @_;
520
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
526 );
527
528 @self{@mp3_info_fields, @v1_tag_names, 'file'} = (
529 @{$info}{@mp3_info_fields},
530 @{$tags}{@v1_tag_names},
531 $file
532 );
533
534 return bless \%self, $pack;
535}
536
537sub 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;
543}
544
545sub AUTOLOAD {
546 my($self) = @_;
547 (my $name = uc $AUTOLOAD) =~ s/^.*://;
548
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]);
555 }
556 return $_[0]->{$name};
557 }
558 : sub {
559 return $_[0]->{$name}
560 };
561
562 no strict 'refs';
563 *{$AUTOLOAD} = $sub;
564 goto &$AUTOLOAD;
565
566 } else {
567 warn(sprintf "No method '$name' available in package %s.",
568 __PACKAGE__);
569 }
570}
571
572sub DESTROY {
573
574}
575
576
577=item use_mp3_utf8([STATUS])
578
579Tells MP3::Info to (or not) return TAG info in UTF-8.
580TRUE is 1, FALSE is 0. Default is FALSE.
581
582Will only be able to it on if Unicode::String is available. ID3v2
583tags will be converted to UTF-8 according to the encoding specified
584in each tag; ID3v1 tags will be assumed Latin-1 and converted
585to UTF-8.
586
587Function returns status (TRUE/FALSE). If no argument is supplied,
588or an unaccepted argument is supplied, function merely returns status.
589
590This function is not exported by default, but may be exported
591with the C<:utf8> or C<:all> export tag.
592
593=cut
594
595my $unicode_module = eval { require Unicode::String };
596my $UNICODE = 0;
597
598sub use_mp3_utf8 {
599 my($val) = @_;
600 if ($val == 1) {
601 $UNICODE = 1 if $unicode_module;
602 } elsif ($val == 0) {
603 $UNICODE = 0;
604 }
605 return $UNICODE;
606}
607
608=pod
609
610=item use_winamp_genres()
611
612Puts WinAmp genres into C<@mp3_genres> and C<%mp3_genres>
613(adds 68 additional genres to the default list of 80).
614This is a separate function because these are non-standard
615genres, but they are included because they are widely used.
616
617You can import the data structures with one of:
618
619 use MP3::Info qw(:genres);
620 use MP3::Info qw(:DEFAULT :genres);
621 use MP3::Info qw(:all);
622
623=cut
624
625sub use_winamp_genres {
626 %mp3_genres = %winamp_genres;
627 @mp3_genres = @winamp_genres;
628 return 1;
629}
630
631=pod
632
633=item remove_mp3tag (FILE [, VERSION, BUFFER])
634
635Can remove ID3v1 or ID3v2 tags. VERSION should be C<1> for ID3v1,
636C<2> for ID3v2, and C<ALL> for both.
637
638For ID3v1, removes last 128 bytes from file if those last 128 bytes begin
639with the text 'TAG'. File will be 128 bytes shorter.
640
641For ID3v2, removes ID3v2 tag. Because an ID3v2 tag is at the
642beginning of the file, we rewrite the file after removing the tag data.
643The buffer for rewriting the file is 4MB. BUFFER (in bytes) ca
644change the buffer size.
645
646Returns the number of bytes removed, or -1 if no tag removed,
647or undef if there is an error.
648
649=cut
650
651sub remove_mp3tag {
652 my($file, $version, $buf) = @_;
653 my($fh, $return);
654
655 $buf ||= 4096*1024; # the bigger the faster
656 $version ||= 1;
657
658 if (not (defined $file && $file ne '')) {
659 $@ = "No file specified";
660 return undef;
661 }
662
663 if (not -s $file) {
664 $@ = "File is empty";
665 return undef;
666 }
667
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;
675 }
676 }
677
678 binmode $fh;
679
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;
686 }
687 }
688
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};
696
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;
703 }
704
705 truncate $fh, $eof - $h->{tag_size}
706 or warn "Can't truncate '$file': $!";
707 $return += $h->{tag_size};
708 }
709 }
710
711 _close($file, $fh);
712
713 return $return || -1;
714}
715
716
717=pod
718
719=item set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
720
721=item set_mp3tag (FILE, $HASHREF)
722
723Adds/changes tag information in an MP3 audio file. Will clobber
724any existing information in file.
725
726Fields are TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE. All fields have
727a 30-byte limit, except for YEAR, which has a four-byte limit, and GENRE,
728which is one byte in the file. The GENRE passed in the function is a
729case-insensitive text string representing a genre found in C<@mp3_genres>.
730
731Will accept either a list of values, or a hashref of the type
732returned by C<get_mp3tag>.
733
734If TRACKNUM is present (for ID3v1.1), then the COMMENT field can only be
73528 bytes.
736
737ID3v2 support may come eventually. Note that if you set a tag on a file
738with ID3v2, the set tag will be for ID3v1[.1] only, and if you call
739C<get_mp3_tag> on the file, it will show you the (unchanged) ID3v2 tags,
740unless you specify ID3v1.
741
742=cut
743
744sub 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;
748
749 # set each to '' if undef
750 for ($title, $artist, $album, $year, $comment, $tracknum, $genre,
751 (@info{@v1_tag_names}))
752 {$_ = defined() ? $_ : ''}
753
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);
760
761 # put data from hashref into hashref if hashref is passed
762 } elsif ($ref eq 'HASH') {
763 %info = %$title;
764
765 # return otherwise
766 } else {
767 warn(<<'EOT');
768Usage: set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM])
769 set_mp3tag (FILE, $HASHREF)
770EOT
771 return undef;
772 }
773
774 if (not (defined $file && $file ne '')) {
775 $@ = "No file specified";
776 return undef;
777 }
778
779 if (not -s $file) {
780 $@ = "File is empty";
781 return undef;
782 }
783
784 # comment field length 28 if ID3v1.1
785 $v1_tag_fields{COMMENT} = 28 if $info{TRACKNUM};
786
787
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}";
796 }
797 }
798
799 if ($info{GENRE}) {
800 warn "Genre `$info{GENRE}' does not exist\n"
801 unless exists $mp3_genres{$info{GENRE}};
802 }
803 }
804
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} = '';
812 }
813 }
814
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;
822 }
823 }
824
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;
830
831 # get genre value
832 $info{GENRE} = $info{GENRE} && exists $mp3_genres{$info{GENRE}} ?
833 $mp3_genres{$info{GENRE}} : 255; # some default genre
834
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]};
841 }
842
843 select $oldfh;
844
845 _close($file, $fh);
846
847 return 1;
848}
849
850=pod
851
852=item get_mp3tag (FILE [, VERSION, RAW_V2])
853
854Returns hash reference containing tag information in MP3 file. The keys
855returned are the same as those supplied for C<set_mp3tag>, except in the
856case of RAW_V2 being set.
857
858If VERSION is C<1>, the information is taken from the ID3v1 tag (if present).
859If VERSION is C<2>, the information is taken from the ID3v2 tag (if present).
860If VERSION is not supplied, or is false, the ID3v1 tag is read if present, and
861then, if present, the ID3v2 tag information will override any existing ID3v1
862tag info.
863
864If RAW_V2 is C<1>, the raw ID3v2 tag data is returned, without any manipulation
865of text encoding. The key name is the same as the frame ID (ID to name mappings
866are in the global %v2_tag_names).
867
868If RAW_V2 is C<2>, the ID3v2 tag data is returned, manipulating for Unicode if
869necessary, etc. It also takes multiple values for a given key (such as comments)
870and puts them in an arrayref.
871
872If the ID3v2 version is older than ID3v2.2.0 or newer than ID3v2.4.0, it will
873not be read.
874
875Strings returned will be in Latin-1, unless UTF-8 is specified (L<use_mp3_utf8>),
876(unless RAW_V2 is C<1>).
877
878Also returns a TAGVERSION key, containing the ID3 version used for the returned
879data (if TAGVERSION argument is C<0>, may contain two versions).
880
881=cut
882
883sub 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;
888
889 if (not (defined $file && $file ne '')) {
890 $@ = "No file specified";
891 return undef;
892 }
893
894 if (not -s $file) {
895 $@ = "File is empty";
896 return undef;
897 }
898
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;
906 }
907 }
908
909 binmode $fh;
910
911 if ($ver < 2) {
912 seek $fh, -128, 2;
913 while(defined(my $line = <$fh>)) { $tag .= $line }
914
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';
928 }
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;
934 }
935 }
936 } elsif ($ver == 1) {
937 _close($file, $fh);
938 $@ = "No ID3v1 tag found";
939 return undef;
940 }
941 }
942
943 ($v2, $v2h) = _get_v2tag($fh);
944
945 unless ($v1 || $v2) {
946 _close($file, $fh);
947 $@ = "No ID3 tag found";
948 return undef;
949 }
950
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};
963
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];
977 }
978 }
979
980 $data1 = [ $data1 ] if ! ref $data1;
981
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;
991 }
992
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;
1001 }
1002 }
1003
1004 if ($raw_v2 == 2 && $desc) {
1005 $data = { $desc => $data };
1006 }
1007
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 ];
1013 }
1014 } else {
1015 $info{$hash->{$id}} = $data;
1016 }
1017 }
1018 }
1019 }
1020 }
1021 if ($ver == 0 && $info{TAGVERSION}) {
1022 $info{TAGVERSION} .= ' / ' . $v2h->{version};
1023 } else {
1024 $info{TAGVERSION} = $v2h->{version};
1025 }
1026 }
1027 }
1028
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+$//;
1034 }
1035 }
1036
1037 for (@v1_tag_names) {
1038 $info{$_} = '' unless defined $info{$_};
1039 }
1040 }
1041
1042 if (keys %info && exists $info{GENRE} && ! defined $info{GENRE}) {
1043 $info{GENRE} = '';
1044 }
1045
1046 _close($file, $fh);
1047
1048 return keys %info ? {%info} : undef;
1049}
1050
1051sub _get_v2tag {
1052 my($fh) = @_;
1053 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
1054 $h = {};
1055
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;
1062 }
1063
1064 if ($v2->{major_version} == 2) {
1065 $hlen = 6;
1066 $num = 3;
1067 } else {
1068 $hlen = 10;
1069 $num = 4;
1070 }
1071
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;
1081 }
1082 return($id, $size);
1083 };
1084
1085 $off = $v2->{ext_header_size} + 10;
1086
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];
1096 }
1097 } else {
1098 $h->{$id} = $bytes;
1099 }
1100 $off += $size;
1101 }
1102
1103 return($h, $v2);
1104}
1105
1106
1107=pod
1108
1109=item get_mp3info (FILE)
1110
1111Returns hash reference containing file information for MP3 file.
1112This data cannot be changed. Returned data:
1113
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
1117
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
1122
1123 SECS total seconds
1124 MM minutes
1125 SS leftover seconds
1126 MS leftover milliseconds
1127 TIME time in MM:SS
1128
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
1136
1137On error, returns nothing and sets C<$@>.
1138
1139=cut
1140
1141sub get_mp3info {
1142 my($file) = @_;
1143 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
1144
1145 if (not (defined $file && $file ne '')) {
1146 $@ = "No file specified";
1147 return undef;
1148 }
1149
1150 if (not -s $file) {
1151 $@ = "File is empty";
1152 return undef;
1153 }
1154
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;
1162 }
1163 }
1164
1165 $off = 0;
1166 $tot = 4096;
1167
1168 $myseek = sub {
1169 seek $fh, $off, 0;
1170 read $fh, $byte, 4;
1171 };
1172
1173 binmode $fh;
1174 &$myseek;
1175
1176 if ($off == 0) {
1177 if (my $id3v2 = _get_v2head($fh)) {
1178 $tot += $off += $id3v2->{tag_size};
1179 &$myseek;
1180 }
1181 }
1182
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;
1193 }
1194 }
1195
1196 my $vbr = _get_vbr($fh, $h, \$off);
1197
1198 seek $fh, 0, 2;
1199 $eof = tell $fh;
1200 seek $fh, -128, 2;
1201 $off += 128 if <$fh> =~ /^TAG/ ? 1 : 0;
1202
1203 _close($file, $fh);
1204
1205 $h->{size} = $eof - $off;
1206
1207 return _get_info($h, $vbr);
1208}
1209
1210sub _get_info {
1211 my($h, $vbr) = @_;
1212 my $i;
1213
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;
1218
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};
1223
1224 $i->{SIZE} = $vbr && $vbr->{bytes} ? $vbr->{bytes} : $h->{size};
1225
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
1230 );
1231
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;
1238 }
1239 }
1240
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'};
1249
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}];
1254
1255 return $i;
1256}
1257
1258sub _get_head {
1259 my($byte) = @_;
1260 my($bytes, $h);
1261
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
1271 );
1272
1273 $h->{bitrate} = $t_bitrate[$h->{ID}][3 - $h->{layer}][$h->{bitrate_index}];
1274 $h->{fs} = $t_sampling_freq[$h->{IDR}][$h->{sampling_freq}];
1275
1276 return $h;
1277}
1278
1279sub _is_mp3 {
1280 my $h = $_[0] or return undef;
1281 return ! ( # all below must be false
1282 $h->{bitrate_index} == 0
1283 ||
1284 $h->{version_index} == 1
1285 ||
1286 ($h->{bytes} & 0xFFE00000) != 0xFFE00000
1287 ||
1288 !$h->{fs}
1289 ||
1290 !$h->{bitrate}
1291 ||
1292 $h->{bitrate_index} == 15
1293 ||
1294 !$h->{layer}
1295 ||
1296 $h->{sampling_freq} == 3
1297 ||
1298 $h->{emphasis} == 2
1299 ||
1300 !$h->{bitrate_index}
1301 ||
1302 ($h->{bytes} & 0xFFFF0000) == 0xFFFE0000
1303 ||
1304 ($h->{ID} == 1 && $h->{layer} == 3 && $h->{protection_bit} == 1)
1305 ||
1306 ($h->{mode_extension} != 0 && $h->{mode} != 1)
1307 );
1308}
1309
1310sub _get_vbr {
1311 my($fh, $h, $roff) = @_;
1312 my($off, $bytes, @bytes, $myseek, %vbr);
1313
1314 $off = $$roff;
1315 @_ = (); # closure confused if we don't do this
1316
1317 $myseek = sub {
1318 my $n = $_[0] || 4;
1319 seek $fh, $off, 0;
1320 read $fh, $bytes, $n;
1321 $off += $n;
1322 };
1323
1324 $off += 4;
1325
1326 if ($h->{ID}) { # MPEG1
1327 $off += $h->{mode} == 3 ? 17 : 32;
1328 } else { # MPEG2
1329 $off += $h->{mode} == 3 ? 9 : 17;
1330 }
1331
1332 &$myseek;
1333 return unless $bytes eq 'Xing';
1334
1335 &$myseek;
1336 $vbr{flags} = _unpack_head($bytes);
1337
1338 if ($vbr{flags} & 1) {
1339 &$myseek;
1340 $vbr{frames} = _unpack_head($bytes);
1341 }
1342
1343 if ($vbr{flags} & 2) {
1344 &$myseek;
1345 $vbr{bytes} = _unpack_head($bytes);
1346 }
1347
1348 if ($vbr{flags} & 4) {
1349 $myseek->(100);
1350# Not used right now ...
1351# $vbr{toc} = _unpack_head($bytes);
1352 }
1353
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;
1359 }
1360
1361 $$roff = $off;
1362 return \%vbr;
1363}
1364
1365sub _get_v2head {
1366 my $fh = $_[0] or return;
1367 my($h, $bytes, @bytes);
1368
1369 # check first three bytes for 'ID3'
1370 seek $fh, 0, 0;
1371 read $fh, $bytes, 3;
1372 return unless $bytes eq 'ID3';
1373
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;
1379
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];
1390 }
1391
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;
1399 }
1400
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;
1409 }
1410 }
1411
1412 return $h;
1413}
1414
1415sub _unpack_head {
1416 unpack('l', pack('L', unpack('N', $_[0])));
1417}
1418
1419sub _close {
1420 my($file, $fh) = @_;
1421 unless (ref $file) { # filehandle not passed
1422 close $fh or warn "Problem closing '$file': $!";
1423 }
1424}
1425
1426BEGIN {
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',
1508 );
1509
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',
1580 );
1581
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]
1586 ],[
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]
1590 ]);
1591
1592 @t_sampling_freq = (
1593 [11025, 12000, 8000],
1594 [undef, undef, undef], # reserved
1595 [22050, 24000, 16000],
1596 [44100, 48000, 32000]
1597 );
1598
1599 @frequency_tbl = map { $_ ? eval "${_}e-3" : 0 }
1600 map { @$_ } @t_sampling_freq;
1601
1602 @mp3_info_fields = qw(
1603 VERSION
1604 LAYER
1605 STEREO
1606 VBR
1607 BITRATE
1608 FREQUENCY
1609 SIZE
1610 SECS
1611 MM
1612 SS
1613 MS
1614 TIME
1615 COPYRIGHT
1616 PADDING
1617 MODE
1618 FRAMES
1619 FRAME_LENGTH
1620 VBR_SCALE
1621 );
1622
1623 %v1_tag_fields =
1624 (TITLE => 30, ARTIST => 30, ALBUM => 30, COMMENT => 30, YEAR => 4);
1625
1626 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1627
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',
1645 );
1646
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',
1712
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',
1788
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',
1809
1810 # grrrrrrr
1811 'COM ' => 'Broken iTunes comments',
1812 );
1813}
1814
18151;
1816
1817__END__
1818
1819=pod
1820
1821=back
1822
1823=head1 TROUBLESHOOTING
1824
1825If you find a bug, please send me a patch (see the project page in L<"SEE ALSO">).
1826If you cannot figure out why it does not work for you, please put the MP3 file in
1827a place where I can get it (preferably via FTP, or HTTP, or .Mac iDisk) and send me
1828mail regarding where I can get the file, with a detailed description of the problem.
1829
1830If I download the file, after debugging the problem I will not keep the MP3 file
1831if it is not legal for me to have it. Just let me know if it is legal for me to
1832keep it or not.
1833
1834
1835=head1 TODO
1836
1837=over 4
1838
1839=item ID3v2 Support
1840
1841Still need to do more for reading tags, such as using Compress::Zlib to decompress
1842compressed tags. But until I see this in use more, I won't bother. If something
1843does not work properly with reading, follow the instructions above for
1844troubleshooting.
1845
1846ID3v2 I<writing> is coming soon.
1847
1848=item Get data from scalar
1849
1850Instead of passing a file spec or filehandle, pass the
1851data itself. Would take some work, converting the seeks, etc.
1852
1853=item Padding bit ?
1854
1855Do something with padding bit.
1856
1857=item Test suite
1858
1859Test suite could use a bit of an overhaul and update. Patches very welcome.
1860
1861=over 4
1862
1863=item *
1864
1865Revamp getset.t. Test all the various get_mp3tag args.
1866
1867=item *
1868
1869Test Unicode.
1870
1871=item *
1872
1873Test OOP API.
1874
1875=item *
1876
1877Test error handling, check more for missing files, bad MP3s, etc.
1878
1879=back
1880
1881=item Other VBR
1882
1883Right now, only Xing VBR is supported.
1884
1885=back
1886
1887
1888=head1 THANKS
1889
1890Edward Allen E<lt>allenej@c51844-a.spokn1.wa.home.comE<gt>,
1891Vittorio Bertola E<lt>v.bertola@vitaminic.comE<gt>,
1892Michael Blakeley E<lt>mike@blakeley.comE<gt>,
1893Per Bolmstedt E<lt>tomten@kol14.comE<gt>,
1894Tony Bowden E<lt>tony@tmtm.comE<gt>,
1895Tom Brown E<lt>thecap@usa.netE<gt>,
1896Sergio Camarena E<lt>scamarena@users.sourceforge.netE<gt>,
1897Chris Dawson E<lt>cdawson@webiphany.comE<gt>,
1898Luke Drumm E<lt>lukedrumm@mypad.comE<gt>,
1899Kyle Farrell E<lt>kyle@cantametrix.comE<gt>,
1900Jeffrey Friedl E<lt>jfriedl@yahoo.comE<gt>,
1901brian d foy E<lt>comdog@panix.comE<gt>,
1902Ben Gertzfield E<lt>che@debian.orgE<gt>,
1903Brian Goodwin E<lt>brian@fuddmain.comE<gt>,
1904Todd Hanneken E<lt>thanneken@hds.harvard.eduE<gt>,
1905Todd Harris E<lt>harris@cshl.orgE<gt>,
1906Woodrow Hill E<lt>asim@mindspring.comE<gt>,
1907Kee Hinckley E<lt>nazgul@somewhere.comE<gt>,
1908Roman Hodek E<lt>Roman.Hodek@informatik.uni-erlangen.deE<gt>,
1909Peter Kovacs E<lt>kovacsp@egr.uri.eduE<gt>,
1910Johann Lindvall,
1911Peter Marschall E<lt>peter.marschall@mayn.deE<gt>,
1912Trond Michelsen E<lt>mike@crusaders.noE<gt>,
1913Dave O'Neill E<lt>dave@nexus.carleton.caE<gt>,
1914Christoph Oberauer E<lt>christoph.oberauer@sbg.ac.atE<gt>,
1915Jake Palmer E<lt>jake.palmer@db.comE<gt>,
1916Andrew Phillips E<lt>asp@wasteland.orgE<gt>,
1917David Reuteler E<lt>reuteler@visi.comE<gt>,
1918John Ruttenberg E<lt>rutt@chezrutt.comE<gt>,
1919Matthew Sachs E<lt>matthewg@zevils.comE<gt>,
1920E<lt>scfc_de@users.sf.netE<gt>,
1921Hermann Schwaerzler E<lt>Hermann.Schwaerzler@uibk.ac.atE<gt>,
1922Chris Sidi E<lt>sidi@angband.orgE<gt>,
1923Roland Steinbach E<lt>roland@support-system.comE<gt>,
1924Stuart E<lt>schneis@users.sourceforge.netE<gt>,
1925Jeffery Sumler E<lt>jsumler@mediaone.netE<gt>,
1926Predrag Supurovic E<lt>mpgtools@dv.co.yuE<gt>,
1927Bogdan Surdu E<lt>tim@go.roE<gt>,
1928E<lt>tim@tim-landscheidt.deE<gt>,
1929Pass F. B. Travis E<lt>pftravis@bellsouth.netE<gt>,
1930Tobias Wagener E<lt>tobias@wagener.nuE<gt>,
1931Ronan Waide E<lt>waider@stepstone.ieE<gt>,
1932Andy Waite E<lt>andy@mailroute.comE<gt>,
1933Ken Williams E<lt>ken@forum.swarthmore.eduE<gt>,
1934Meng Weng Wong E<lt>mengwong@pobox.comE<gt>.
1935
1936
1937=head1 AUTHOR AND COPYRIGHT
1938
1939Chris Nandor E<lt>pudge@pobox.comE<gt>, http://pudge.net/
1940
1941Copyright (c) 1998-2003 Chris Nandor. All rights reserved. This program is
1942free software; you can redistribute it and/or modify it under the terms
1943of the Artistic License, distributed with Perl.
1944
1945
1946=head1 SEE ALSO
1947
1948=over 4
1949
1950=item MP3::Info Project Page
1951
1952 http://projects.pudge.net/
1953
1954=item mp3tools
1955
1956 http://www.zevils.com/linux/mp3tools/
1957
1958=item mpgtools
1959
1960 http://www.dv.co.yu/mpgscript/mpgtools.htm
1961 http://www.dv.co.yu/mpgscript/mpeghdr.htm
1962
1963=item mp3tool
1964
1965 http://www.dtek.chalmers.se/~d2linjo/mp3/mp3tool.html
1966
1967=item ID3v2
1968
1969 http://www.id3.org/
1970
1971=item Xing Variable Bitrate
1972
1973 http://www.xingtech.com/support/partner_developer/mp3/vbr_sdk/
1974
1975=item MP3Ext
1976
1977 http://rupert.informatik.uni-stuttgart.de/~mutschml/MP3ext/
1978
1979=item Xmms
1980
1981 http://www.xmms.org/
1982
1983
1984=back
1985
1986=head1 VERSION
1987
1988v1.02, Sunday, March 2, 2003
1989
1990=cut