summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2005-01-17 14:26:36 +0000
committerDaniel Stenberg <daniel@haxx.se>2005-01-17 14:26:36 +0000
commite24a528499f992a8a8987432d0fcb3e595f1206f (patch)
treef3eb1226f1c67596a871bed6a27d245b6c4f4ec7
parent7396d99027410feda4661f860df90509783b50c5 (diff)
downloadrockbox-e24a528499f992a8a8987432d0fcb3e595f1206f.tar.gz
rockbox-e24a528499f992a8a8987432d0fcb3e595f1206f.zip
re-indented to look normal ;-)
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@5582 a1c6a512-1295-4272-9138-f99709370657
-rwxr-xr-xtools/songdb.pl860
1 files changed, 430 insertions, 430 deletions
diff --git a/tools/songdb.pl b/tools/songdb.pl
index bfea05314d..64a973e7d9 100755
--- a/tools/songdb.pl
+++ b/tools/songdb.pl
@@ -885,226 +885,226 @@ data (if TAGVERSION argument is C<0>, may contain two versions).
885=cut 885=cut
886 886
887sub get_mp3tag { 887sub get_mp3tag {
888 my($file, $ver, $raw_v2) = @_; 888 my($file, $ver, $raw_v2) = @_;
889 my($tag, $v1, $v2, $v2h, %info, @array, $fh); 889 my($tag, $v1, $v2, $v2h, %info, @array, $fh);
890 $raw_v2 ||= 0; 890 $raw_v2 ||= 0;
891 $ver = !$ver ? 0 : ($ver == 2 || $ver == 1) ? $ver : 0; 891 $ver = !$ver ? 0 : ($ver == 2 || $ver == 1) ? $ver : 0;
892 892
893 if (not (defined $file && $file ne '')) { 893 if (not (defined $file && $file ne '')) {
894 $@ = "No file specified"; 894 $@ = "No file specified";
895 return undef; 895 return undef;
896 } 896 }
897
898 if (not -s $file) {
899 $@ = "File is empty";
900 return undef;
901 }
902
903 if (ref $file) { # filehandle passed
904 $fh = $file;
905 } else {
906 $fh = gensym;
907 if (not open $fh, "< $file\0") {
908 $@ = "Can't open $file: $!";
909 return undef;
910 }
911 }
912 897
913 binmode $fh; 898 if (not -s $file) {
899 $@ = "File is empty";
900 return undef;
901 }
914 902
915 if ($ver < 2) { 903 if (ref $file) { # filehandle passed
916 seek $fh, -128, 2; 904 $fh = $file;
917 while(defined(my $line = <$fh>)) { $tag .= $line } 905 } else {
918 906 $fh = gensym;
919 if ($tag =~ /^TAG/) { 907 if (not open $fh, "< $file\0") {
920 $v1 = 1; 908 $@ = "Can't open $file: $!";
921 if (substr($tag, -3, 2) =~ /\000[^\000]/) { 909 return undef;
922 (undef, @info{@v1_tag_names}) = 910 }
923 (unpack('a3a30a30a30a4a28', $tag), 911 }
924 ord(substr($tag, -2, 1)),
925 $mp3_genres[ord(substr $tag, -1)]);
926 $info{TAGVERSION} = 'ID3v1.1';
927 } else {
928 (undef, @info{@v1_tag_names[0..4, 6]}) =
929 (unpack('a3a30a30a30a4a30', $tag),
930 $mp3_genres[ord(substr $tag, -1)]);
931 $info{TAGVERSION} = 'ID3v1';
932 }
933 if ($UNICODE) {
934 for my $key (keys %info) {
935 next unless $info{$key};
936 my $u = Unicode::String::latin1($info{$key});
937 $info{$key} = $u->utf8;
938 }
939 }
940 } elsif ($ver == 1) {
941 _close($file, $fh);
942 $@ = "No ID3v1 tag found";
943 return undef;
944 }
945 }
946 912
947 ($v2, $v2h) = _get_v2tag($fh); 913 binmode $fh;
914
915 if ($ver < 2) {
916 seek $fh, -128, 2;
917 while(defined(my $line = <$fh>)) { $tag .= $line }
918
919 if ($tag =~ /^TAG/) {
920 $v1 = 1;
921 if (substr($tag, -3, 2) =~ /\000[^\000]/) {
922 (undef, @info{@v1_tag_names}) =
923 (unpack('a3a30a30a30a4a28', $tag),
924 ord(substr($tag, -2, 1)),
925 $mp3_genres[ord(substr $tag, -1)]);
926 $info{TAGVERSION} = 'ID3v1.1';
927 } else {
928 (undef, @info{@v1_tag_names[0..4, 6]}) =
929 (unpack('a3a30a30a30a4a30', $tag),
930 $mp3_genres[ord(substr $tag, -1)]);
931 $info{TAGVERSION} = 'ID3v1';
932 }
933 if ($UNICODE) {
934 for my $key (keys %info) {
935 next unless $info{$key};
936 my $u = Unicode::String::latin1($info{$key});
937 $info{$key} = $u->utf8;
938 }
939 }
940 } elsif ($ver == 1) {
941 _close($file, $fh);
942 $@ = "No ID3v1 tag found";
943 return undef;
944 }
945 }
948 946
949 unless ($v1 || $v2) { 947 ($v2, $v2h) = _get_v2tag($fh);
950 _close($file, $fh);
951 $@ = "No ID3 tag found";
952 return undef;
953 }
954 948
955 if (($ver == 0 || $ver == 2) && $v2) { 949 unless ($v1 || $v2) {
956 if ($raw_v2 == 1 && $ver == 2) { 950 _close($file, $fh);
957 %info = %$v2; 951 $@ = "No ID3 tag found";
958 $info{TAGVERSION} = $v2h->{version}; 952 return undef;
959 } else { 953 }
960 my $hash = $raw_v2 == 2 ? { map { ($_, $_) } keys %v2_tag_names } : \%v2_to_v1_names;
961 for my $id (keys %$hash) {
962 if (exists $v2->{$id}) {
963 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
964 $info{$hash->{$id}} = $mp3_genres[$1];
965 } else {
966 my $data1 = $v2->{$id};
967
968 # this is tricky ... if this is an arrayref,
969 # we want to only return one, so we pick the
970 # first one. but if it is a comment, we pick
971 # the first one where the first charcter after
972 # the language is NULL and not an additional
973 # sub-comment, because that is most likely to be
974 # the user-supplied comment
975 if (ref $data1 && !$raw_v2) {
976 if ($id =~ /^COMM?$/) {
977 my($newdata) = grep /^(....\000)/, @{$data1};
978 $data1 = $newdata || $data1->[0];
979 } else {
980 $data1 = $data1->[0];
981 }
982 }
983
984 $data1 = [ $data1 ] if ! ref $data1;
985
986 for my $data (@$data1) {
987 $data =~ s/^(.)//; # strip first char (text encoding)
988 my $encoding = $1;
989 my $desc;
990 if ($id =~ /^COM[M ]?$/) {
991 $data =~ s/^(?:...)//; # strip language
992 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
993 # for sub-comment
994 $desc = $1;
995 }
996
997 if ($UNICODE) {
998 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
999 my $u = Unicode::String::utf16($data);
1000 $data = $u->utf8;
1001 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
1002 } elsif ($encoding eq "\000") {
1003 my $u = Unicode::String::latin1($data);
1004 $data = $u->utf8;
1005 }
1006 }
1007
1008 if ($raw_v2 == 2 && $desc) {
1009 $data = { $desc => $data };
1010 }
1011
1012 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
1013 if (ref $info{$hash->{$id}} eq 'ARRAY') {
1014 push @{$info{$hash->{$id}}}, $data;
1015 } else {
1016 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
1017 }
1018 } else {
1019 $info{$hash->{$id}} = $data;
1020 }
1021 }
1022 }
1023 }
1024 }
1025 if ($ver == 0 && $info{TAGVERSION}) {
1026 $info{TAGVERSION} .= ' / ' . $v2h->{version};
1027 } else {
1028 $info{TAGVERSION} = $v2h->{version};
1029 }
1030 }
1031 }
1032 954
1033 unless ($raw_v2 && $ver == 2) { 955 if (($ver == 0 || $ver == 2) && $v2) {
1034 foreach my $key (keys %info) { 956 if ($raw_v2 == 1 && $ver == 2) {
1035 if (defined $info{$key}) { 957 %info = %$v2;
1036 $info{$key} =~ s/\000+.*//g; 958 $info{TAGVERSION} = $v2h->{version};
1037 $info{$key} =~ s/\s+$//; 959 } else {
1038 } 960 my $hash = $raw_v2 == 2 ? { map { ($_, $_) } keys %v2_tag_names } : \%v2_to_v1_names;
1039 } 961 for my $id (keys %$hash) {
962 if (exists $v2->{$id}) {
963 if ($id =~ /^TCON?$/ && $v2->{$id} =~ /^.?\((\d+)\)/) {
964 $info{$hash->{$id}} = $mp3_genres[$1];
965 } else {
966 my $data1 = $v2->{$id};
967
968 # this is tricky ... if this is an arrayref, we want
969 # to only return one, so we pick the first one. but
970 # if it is a comment, we pick the first one where the
971 # first charcter after the language is NULL and not an
972 # additional sub-comment, because that is most likely
973 # to be the user-supplied comment
974
975 if (ref $data1 && !$raw_v2) {
976 if ($id =~ /^COMM?$/) {
977 my($newdata) = grep /^(....\000)/, @{$data1};
978 $data1 = $newdata || $data1->[0];
979 } else {
980 $data1 = $data1->[0];
981 }
982 }
983
984 $data1 = [ $data1 ] if ! ref $data1;
985
986 for my $data (@$data1) {
987 $data =~ s/^(.)//; # strip first char (text encoding)
988 my $encoding = $1;
989 my $desc;
990 if ($id =~ /^COM[M ]?$/) {
991 $data =~ s/^(?:...)//; # strip language
992 $data =~ s/^(.*?)\000+//; # strip up to first NULL(s),
993 # for sub-comment
994 $desc = $1;
995 }
996
997 if ($UNICODE) {
998 if ($encoding eq "\001" || $encoding eq "\002") { # UTF-16, UTF-16BE
999 my $u = Unicode::String::utf16($data);
1000 $data = $u->utf8;
1001 $data =~ s/^\xEF\xBB\xBF//; # strip BOM
1002 } elsif ($encoding eq "\000") {
1003 my $u = Unicode::String::latin1($data);
1004 $data = $u->utf8;
1005 }
1006 }
1007
1008 if ($raw_v2 == 2 && $desc) {
1009 $data = { $desc => $data };
1010 }
1011
1012 if ($raw_v2 == 2 && exists $info{$hash->{$id}}) {
1013 if (ref $info{$hash->{$id}} eq 'ARRAY') {
1014 push @{$info{$hash->{$id}}}, $data;
1015 } else {
1016 $info{$hash->{$id}} = [ $info{$hash->{$id}}, $data ];
1017 }
1018 } else {
1019 $info{$hash->{$id}} = $data;
1020 }
1021 }
1022 }
1023 }
1024 }
1025 if ($ver == 0 && $info{TAGVERSION}) {
1026 $info{TAGVERSION} .= ' / ' . $v2h->{version};
1027 } else {
1028 $info{TAGVERSION} = $v2h->{version};
1029 }
1030 }
1031 }
1040 1032
1041 for (@v1_tag_names) { 1033 unless ($raw_v2 && $ver == 2) {
1042 $info{$_} = '' unless defined $info{$_}; 1034 foreach my $key (keys %info) {
1043 } 1035 if (defined $info{$key}) {
1044 } 1036 $info{$key} =~ s/\000+.*//g;
1037 $info{$key} =~ s/\s+$//;
1038 }
1039 }
1040
1041 for (@v1_tag_names) {
1042 $info{$_} = '' unless defined $info{$_};
1043 }
1044 }
1045 1045
1046 if (keys %info && exists $info{GENRE} && ! defined $info{GENRE}) { 1046 if (keys %info && exists $info{GENRE} && ! defined $info{GENRE}) {
1047 $info{GENRE} = ''; 1047 $info{GENRE} = '';
1048 } 1048 }
1049 1049
1050 _close($file, $fh); 1050 _close($file, $fh);
1051 1051
1052 return keys %info ? {%info} : undef; 1052 return keys %info ? {%info} : undef;
1053} 1053}
1054 1054
1055sub _get_v2tag { 1055sub _get_v2tag {
1056 my($fh) = @_; 1056 my($fh) = @_;
1057 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num); 1057 my($off, $myseek, $myseek_22, $myseek_23, $v2, $h, $hlen, $num);
1058 $h = {}; 1058 $h = {};
1059 1059
1060 $v2 = _get_v2head($fh) or return; 1060 $v2 = _get_v2head($fh) or return;
1061 if ($v2->{major_version} < 2) { 1061 if ($v2->{major_version} < 2) {
1062 warn "This is $v2->{version}; " . 1062 warn "This is $v2->{version}; " .
1063 "ID3v2 versions older than ID3v2.2.0 not supported\n" 1063 "ID3v2 versions older than ID3v2.2.0 not supported\n"
1064 if $^W; 1064 if $^W;
1065 return; 1065 return;
1066 } 1066 }
1067 1067
1068 if ($v2->{major_version} == 2) { 1068 if ($v2->{major_version} == 2) {
1069 $hlen = 6; 1069 $hlen = 6;
1070 $num = 3; 1070 $num = 3;
1071 } else { 1071 } else {
1072 $hlen = 10; 1072 $hlen = 10;
1073 $num = 4; 1073 $num = 4;
1074 } 1074 }
1075 1075
1076 $myseek = sub { 1076 $myseek = sub {
1077 seek $fh, $off, 0; 1077 seek $fh, $off, 0;
1078 read $fh, my($bytes), $hlen; 1078 read $fh, my($bytes), $hlen;
1079 return unless $bytes =~ /^([A-Z0-9]{$num})/ 1079 return unless $bytes =~ /^([A-Z0-9]{$num})/
1080 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes 1080 || ($num == 4 && $bytes =~ /^(COM )/); # stupid iTunes
1081 my($id, $size) = ($1, $hlen); 1081 my($id, $size) = ($1, $hlen);
1082 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num); 1082 my @bytes = reverse unpack "C$num", substr($bytes, $num, $num);
1083 for my $i (0 .. ($num - 1)) { 1083 for my $i (0 .. ($num - 1)) {
1084 $size += $bytes[$i] * 256 ** $i; 1084 $size += $bytes[$i] * 256 ** $i;
1085 } 1085 }
1086 return($id, $size); 1086 return($id, $size);
1087 }; 1087 };
1088 1088
1089 $off = $v2->{ext_header_size} + 10; 1089 $off = $v2->{ext_header_size} + 10;
1090 1090
1091 while ($off < $v2->{tag_size}) { 1091 while ($off < $v2->{tag_size}) {
1092 my($id, $size) = &$myseek or last; 1092 my($id, $size) = &$myseek or last;
1093 seek $fh, $off + $hlen, 0; 1093 seek $fh, $off + $hlen, 0;
1094 read $fh, my($bytes), $size - $hlen; 1094 read $fh, my($bytes), $size - $hlen;
1095 if (exists $h->{$id}) { 1095 if (exists $h->{$id}) {
1096 if (ref $h->{$id} eq 'ARRAY') { 1096 if (ref $h->{$id} eq 'ARRAY') {
1097 push @{$h->{$id}}, $bytes; 1097 push @{$h->{$id}}, $bytes;
1098 } else { 1098 } else {
1099 $h->{$id} = [$h->{$id}, $bytes]; 1099 $h->{$id} = [$h->{$id}, $bytes];
1100 } 1100 }
1101 } else { 1101 } else {
1102 $h->{$id} = $bytes; 1102 $h->{$id} = $bytes;
1103 } 1103 }
1104 $off += $size; 1104 $off += $size;
1105 } 1105 }
1106 1106
1107 return($h, $v2); 1107 return($h, $v2);
1108} 1108}
1109 1109
1110 1110
@@ -1143,292 +1143,292 @@ On error, returns nothing and sets C<$@>.
1143=cut 1143=cut
1144 1144
1145sub get_mp3info { 1145sub get_mp3info {
1146 my($file) = @_; 1146 my($file) = @_;
1147 my($off, $myseek, $byte, $eof, $h, $tot, $fh); 1147 my($off, $myseek, $byte, $eof, $h, $tot, $fh);
1148 1148
1149 if (not (defined $file && $file ne '')) { 1149 if (not (defined $file && $file ne '')) {
1150 $@ = "No file specified"; 1150 $@ = "No file specified";
1151 return undef; 1151 return undef;
1152 } 1152 }
1153 1153
1154 if (not -s $file) { 1154 if (not -s $file) {
1155 $@ = "File is empty"; 1155 $@ = "File is empty";
1156 return undef; 1156 return undef;
1157 } 1157 }
1158 1158
1159 if (ref $file) { # filehandle passed 1159 if (ref $file) { # filehandle passed
1160 $fh = $file; 1160 $fh = $file;
1161 } else { 1161 } else {
1162 $fh = gensym; 1162 $fh = gensym;
1163 if (not open $fh, "< $file\0") { 1163 if (not open $fh, "< $file\0") {
1164 $@ = "Can't open $file: $!"; 1164 $@ = "Can't open $file: $!";
1165 return undef; 1165 return undef;
1166 } 1166 }
1167 } 1167 }
1168 1168
1169 $off = 0; 1169 $off = 0;
1170 $tot = 4096; 1170 $tot = 4096;
1171 1171
1172 $myseek = sub { 1172 $myseek = sub {
1173 seek $fh, $off, 0; 1173 seek $fh, $off, 0;
1174 read $fh, $byte, 4; 1174 read $fh, $byte, 4;
1175 }; 1175 };
1176 1176
1177 binmode $fh; 1177 binmode $fh;
1178 &$myseek; 1178 &$myseek;
1179 1179
1180 if ($off == 0) { 1180 if ($off == 0) {
1181 if (my $id3v2 = _get_v2head($fh)) { 1181 if (my $id3v2 = _get_v2head($fh)) {
1182 $tot += $off += $id3v2->{tag_size}; 1182 $tot += $off += $id3v2->{tag_size};
1183 &$myseek; 1183 &$myseek;
1184 } 1184 }
1185 } 1185 }
1186 1186
1187 $h = _get_head($byte); 1187 $h = _get_head($byte);
1188 until (_is_mp3($h)) { 1188 until (_is_mp3($h)) {
1189 $off++; 1189 $off++;
1190 &$myseek; 1190 &$myseek;
1191 $h = _get_head($byte); 1191 $h = _get_head($byte);
1192 if ($off > $tot && !$try_harder) { 1192 if ($off > $tot && !$try_harder) {
1193 _close($file, $fh); 1193 _close($file, $fh);
1194 $@ = "Couldn't find MP3 header (perhaps set " . 1194 $@ = "Couldn't find MP3 header (perhaps set " .
1195 '$MP3::Info::try_harder and retry)'; 1195 '$MP3::Info::try_harder and retry)';
1196 return undef; 1196 return undef;
1197 } 1197 }
1198 } 1198 }
1199 1199
1200 my $vbr = _get_vbr($fh, $h, \$off); 1200 my $vbr = _get_vbr($fh, $h, \$off);
1201 1201
1202 seek $fh, 0, 2; 1202 seek $fh, 0, 2;
1203 $eof = tell $fh; 1203 $eof = tell $fh;
1204 seek $fh, -128, 2; 1204 seek $fh, -128, 2;
1205 $off += 128 if <$fh> =~ /^TAG/ ? 1 : 0; 1205 $off += 128 if <$fh> =~ /^TAG/ ? 1 : 0;
1206 1206
1207 _close($file, $fh); 1207 _close($file, $fh);
1208 1208
1209 $h->{size} = $eof - $off; 1209 $h->{size} = $eof - $off;
1210 1210
1211 return _get_info($h, $vbr); 1211 return _get_info($h, $vbr);
1212} 1212}
1213 1213
1214sub _get_info { 1214sub _get_info {
1215 my($h, $vbr) = @_; 1215 my($h, $vbr) = @_;
1216 my $i; 1216 my $i;
1217 1217
1218 $i->{VERSION} = $h->{IDR} == 2 ? 2 : $h->{IDR} == 3 ? 1 : 1218 $i->{VERSION} = $h->{IDR} == 2 ? 2 : $h->{IDR} == 3 ? 1 :
1219 $h->{IDR} == 0 ? 2.5 : 0; 1219 $h->{IDR} == 0 ? 2.5 : 0;
1220 $i->{LAYER} = 4 - $h->{layer}; 1220 $i->{LAYER} = 4 - $h->{layer};
1221 $i->{VBR} = defined $vbr ? 1 : 0; 1221 $i->{VBR} = defined $vbr ? 1 : 0;
1222 1222
1223 $i->{COPYRIGHT} = $h->{copyright} ? 1 : 0; 1223 $i->{COPYRIGHT} = $h->{copyright} ? 1 : 0;
1224 $i->{PADDING} = $h->{padding_bit} ? 1 : 0; 1224 $i->{PADDING} = $h->{padding_bit} ? 1 : 0;
1225 $i->{STEREO} = $h->{mode} == 3 ? 0 : 1; 1225 $i->{STEREO} = $h->{mode} == 3 ? 0 : 1;
1226 $i->{MODE} = $h->{mode}; 1226 $i->{MODE} = $h->{mode};
1227 1227
1228 $i->{SIZE} = $vbr && $vbr->{bytes} ? $vbr->{bytes} : $h->{size}; 1228 $i->{SIZE} = $vbr && $vbr->{bytes} ? $vbr->{bytes} : $h->{size};
1229 1229
1230 my $mfs = $h->{fs} / ($h->{ID} ? 144000 : 72000); 1230 my $mfs = $h->{fs} / ($h->{ID} ? 144000 : 72000);
1231 $i->{FRAMES} = int($vbr && $vbr->{frames} 1231 $i->{FRAMES} = int($vbr && $vbr->{frames}
1232 ? $vbr->{frames} 1232 ? $vbr->{frames}
1233 : $i->{SIZE} / $h->{bitrate} / $mfs 1233 : $i->{SIZE} / $h->{bitrate} / $mfs
1234 ); 1234 );
1235 1235
1236 if ($vbr) { 1236 if ($vbr) {
1237 $i->{VBR_SCALE} = $vbr->{scale} if $vbr->{scale}; 1237 $i->{VBR_SCALE} = $vbr->{scale} if $vbr->{scale};
1238 $h->{bitrate} = $i->{SIZE} / $i->{FRAMES} * $mfs; 1238 $h->{bitrate} = $i->{SIZE} / $i->{FRAMES} * $mfs;
1239 if (not $h->{bitrate}) { 1239 if (not $h->{bitrate}) {
1240 $@ = "Couldn't determine VBR bitrate"; 1240 $@ = "Couldn't determine VBR bitrate";
1241 return undef; 1241 return undef;
1242 } 1242 }
1243 } 1243 }
1244 1244
1245 $h->{'length'} = ($i->{SIZE} * 8) / $h->{bitrate} / 10; 1245 $h->{'length'} = ($i->{SIZE} * 8) / $h->{bitrate} / 10;
1246 $i->{SECS} = $h->{'length'} / 100; 1246 $i->{SECS} = $h->{'length'} / 100;
1247 $i->{MM} = int $i->{SECS} / 60; 1247 $i->{MM} = int $i->{SECS} / 60;
1248 $i->{SS} = int $i->{SECS} % 60; 1248 $i->{SS} = int $i->{SECS} % 60;
1249 $i->{MS} = (($i->{SECS} - ($i->{MM} * 60) - $i->{SS}) * 1000); 1249 $i->{MS} = (($i->{SECS} - ($i->{MM} * 60) - $i->{SS}) * 1000);
1250# $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS}); 1250# $i->{LF} = ($i->{MS} / 1000) * ($i->{FRAMES} / $i->{SECS});
1251# int($i->{MS} / 100 * 75); # is this right? 1251# int($i->{MS} / 100 * 75); # is this right?
1252 $i->{TIME} = sprintf "%.2d:%.2d", @{$i}{'MM', 'SS'}; 1252 $i->{TIME} = sprintf "%.2d:%.2d", @{$i}{'MM', 'SS'};
1253 1253
1254 $i->{BITRATE} = int $h->{bitrate}; 1254 $i->{BITRATE} = int $h->{bitrate};
1255 # should we just return if ! FRAMES? 1255 # should we just return if ! FRAMES?
1256 $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES}; 1256 $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES};
1257 $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}]; 1257 $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}];
1258 1258
1259 return $i; 1259 return $i;
1260} 1260}
1261 1261
1262sub _get_head { 1262sub _get_head {
1263 my($byte) = @_; 1263 my($byte) = @_;
1264 my($bytes, $h); 1264 my($bytes, $h);
1265 1265
1266 $bytes = _unpack_head($byte); 1266 $bytes = _unpack_head($byte);
1267 @$h{qw(IDR ID layer protection_bit 1267 @$h{qw(IDR ID layer protection_bit
1268 bitrate_index sampling_freq padding_bit private_bit 1268 bitrate_index sampling_freq padding_bit private_bit
1269 mode mode_extension copyright original 1269 mode mode_extension copyright original
1270 emphasis version_index bytes)} = ( 1270 emphasis version_index bytes)} = (
1271 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1, 1271 ($bytes>>19)&3, ($bytes>>19)&1, ($bytes>>17)&3, ($bytes>>16)&1,
1272 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1, 1272 ($bytes>>12)&15, ($bytes>>10)&3, ($bytes>>9)&1, ($bytes>>8)&1,
1273 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1, 1273 ($bytes>>6)&3, ($bytes>>4)&3, ($bytes>>3)&1, ($bytes>>2)&1,
1274 $bytes&3, ($bytes>>19)&3, $bytes 1274 $bytes&3, ($bytes>>19)&3, $bytes
1275 ); 1275 );
1276 1276
1277 $h->{bitrate} = $t_bitrate[$h->{ID}][3 - $h->{layer}][$h->{bitrate_index}]; 1277 $h->{bitrate} = $t_bitrate[$h->{ID}][3 - $h->{layer}][$h->{bitrate_index}];
1278 $h->{fs} = $t_sampling_freq[$h->{IDR}][$h->{sampling_freq}]; 1278 $h->{fs} = $t_sampling_freq[$h->{IDR}][$h->{sampling_freq}];
1279 1279
1280 return $h; 1280 return $h;
1281} 1281}
1282 1282
1283sub _is_mp3 { 1283sub _is_mp3 {
1284 my $h = $_[0] or return undef; 1284 my $h = $_[0] or return undef;
1285 return ! ( # all below must be false 1285 return ! ( # all below must be false
1286 $h->{bitrate_index} == 0 1286 $h->{bitrate_index} == 0
1287 || 1287 ||
1288 $h->{version_index} == 1 1288 $h->{version_index} == 1
1289 || 1289 ||
1290 ($h->{bytes} & 0xFFE00000) != 0xFFE00000 1290 ($h->{bytes} & 0xFFE00000) != 0xFFE00000
1291 || 1291 ||
1292 !$h->{fs} 1292 !$h->{fs}
1293 || 1293 ||
1294 !$h->{bitrate} 1294 !$h->{bitrate}
1295 || 1295 ||
1296 $h->{bitrate_index} == 15 1296 $h->{bitrate_index} == 15
1297 || 1297 ||
1298 !$h->{layer} 1298 !$h->{layer}
1299 || 1299 ||
1300 $h->{sampling_freq} == 3 1300 $h->{sampling_freq} == 3
1301 || 1301 ||
1302 $h->{emphasis} == 2 1302 $h->{emphasis} == 2
1303 || 1303 ||
1304 !$h->{bitrate_index} 1304 !$h->{bitrate_index}
1305 || 1305 ||
1306 ($h->{bytes} & 0xFFFF0000) == 0xFFFE0000 1306 ($h->{bytes} & 0xFFFF0000) == 0xFFFE0000
1307 || 1307 ||
1308 ($h->{ID} == 1 && $h->{layer} == 3 && $h->{protection_bit} == 1) 1308 ($h->{ID} == 1 && $h->{layer} == 3 && $h->{protection_bit} == 1)
1309 || 1309 ||
1310 ($h->{mode_extension} != 0 && $h->{mode} != 1) 1310 ($h->{mode_extension} != 0 && $h->{mode} != 1)
1311 ); 1311 );
1312} 1312}
1313 1313
1314sub _get_vbr { 1314sub _get_vbr {
1315 my($fh, $h, $roff) = @_; 1315 my($fh, $h, $roff) = @_;
1316 my($off, $bytes, @bytes, $myseek, %vbr); 1316 my($off, $bytes, @bytes, $myseek, %vbr);
1317 1317
1318 $off = $$roff; 1318 $off = $$roff;
1319 @_ = (); # closure confused if we don't do this 1319 @_ = (); # closure confused if we don't do this
1320 1320
1321 $myseek = sub { 1321 $myseek = sub {
1322 my $n = $_[0] || 4; 1322 my $n = $_[0] || 4;
1323 seek $fh, $off, 0; 1323 seek $fh, $off, 0;
1324 read $fh, $bytes, $n; 1324 read $fh, $bytes, $n;
1325 $off += $n; 1325 $off += $n;
1326 }; 1326 };
1327 1327
1328 $off += 4; 1328 $off += 4;
1329 1329
1330 if ($h->{ID}) { # MPEG1 1330 if ($h->{ID}) { # MPEG1
1331 $off += $h->{mode} == 3 ? 17 : 32; 1331 $off += $h->{mode} == 3 ? 17 : 32;
1332 } else { # MPEG2 1332 } else { # MPEG2
1333 $off += $h->{mode} == 3 ? 9 : 17; 1333 $off += $h->{mode} == 3 ? 9 : 17;
1334 } 1334 }
1335 1335
1336 &$myseek; 1336 &$myseek;
1337 return unless $bytes eq 'Xing'; 1337 return unless $bytes eq 'Xing';
1338 1338
1339 &$myseek; 1339 &$myseek;
1340 $vbr{flags} = _unpack_head($bytes); 1340 $vbr{flags} = _unpack_head($bytes);
1341 1341
1342 if ($vbr{flags} & 1) { 1342 if ($vbr{flags} & 1) {
1343 &$myseek; 1343 &$myseek;
1344 $vbr{frames} = _unpack_head($bytes); 1344 $vbr{frames} = _unpack_head($bytes);
1345 } 1345 }
1346 1346
1347 if ($vbr{flags} & 2) { 1347 if ($vbr{flags} & 2) {
1348 &$myseek; 1348 &$myseek;
1349 $vbr{bytes} = _unpack_head($bytes); 1349 $vbr{bytes} = _unpack_head($bytes);
1350 } 1350 }
1351 1351
1352 if ($vbr{flags} & 4) { 1352 if ($vbr{flags} & 4) {
1353 $myseek->(100); 1353 $myseek->(100);
1354# Not used right now ... 1354# Not used right now ...
1355# $vbr{toc} = _unpack_head($bytes); 1355# $vbr{toc} = _unpack_head($bytes);
1356 } 1356 }
1357 1357
1358 if ($vbr{flags} & 8) { # (quality ind., 0=best 100=worst) 1358 if ($vbr{flags} & 8) { # (quality ind., 0=best 100=worst)
1359 &$myseek; 1359 &$myseek;
1360 $vbr{scale} = _unpack_head($bytes); 1360 $vbr{scale} = _unpack_head($bytes);
1361 } else { 1361 } else {
1362 $vbr{scale} = -1; 1362 $vbr{scale} = -1;
1363 } 1363 }
1364 1364
1365 $$roff = $off; 1365 $$roff = $off;
1366 return \%vbr; 1366 return \%vbr;
1367} 1367}
1368 1368
1369sub _get_v2head { 1369sub _get_v2head {
1370 my $fh = $_[0] or return; 1370 my $fh = $_[0] or return;
1371 my($h, $bytes, @bytes); 1371 my($h, $bytes, @bytes);
1372 1372
1373 # check first three bytes for 'ID3' 1373 # check first three bytes for 'ID3'
1374 seek $fh, 0, 0; 1374 seek $fh, 0, 0;
1375 read $fh, $bytes, 3; 1375 read $fh, $bytes, 3;
1376 return unless $bytes eq 'ID3'; 1376 return unless $bytes eq 'ID3';
1377 1377
1378 # get version 1378 # get version
1379 read $fh, $bytes, 2; 1379 read $fh, $bytes, 2;
1380 $h->{version} = sprintf "ID3v2.%d.%d", 1380 $h->{version} = sprintf "ID3v2.%d.%d",
1381 @$h{qw[major_version minor_version]} = 1381 @$h{qw[major_version minor_version]} =
1382 unpack 'c2', $bytes; 1382 unpack 'c2', $bytes;
1383 1383
1384 # get flags 1384 # get flags
1385 read $fh, $bytes, 1; 1385 read $fh, $bytes, 1;
1386 if ($h->{major_version} == 2) { 1386 if ($h->{major_version} == 2) {
1387 @$h{qw[unsync compression]} = 1387 @$h{qw[unsync compression]} =
1388 (unpack 'b8', $bytes)[7, 6]; 1388 (unpack 'b8', $bytes)[7, 6];
1389 $h->{ext_header} = 0; 1389 $h->{ext_header} = 0;
1390 $h->{experimental} = 0; 1390 $h->{experimental} = 0;
1391 } else { 1391 } else {
1392 @$h{qw[unsync ext_header experimental]} = 1392 @$h{qw[unsync ext_header experimental]} =
1393 (unpack 'b8', $bytes)[7, 6, 5]; 1393 (unpack 'b8', $bytes)[7, 6, 5];
1394 } 1394 }
1395 1395
1396 # get ID3v2 tag length from bytes 7-10 1396 # get ID3v2 tag length from bytes 7-10
1397 $h->{tag_size} = 10; # include ID3v2 header size 1397 $h->{tag_size} = 10; # include ID3v2 header size
1398 read $fh, $bytes, 4; 1398 read $fh, $bytes, 4;
1399 @bytes = reverse unpack 'C4', $bytes; 1399 @bytes = reverse unpack 'C4', $bytes;
1400 foreach my $i (0 .. 3) { 1400 foreach my $i (0 .. 3) {
1401 # whoaaaaaa nellllllyyyyyy! 1401 # whoaaaaaa nellllllyyyyyy!
1402 $h->{tag_size} += $bytes[$i] * 128 ** $i; 1402 $h->{tag_size} += $bytes[$i] * 128 ** $i;
1403 } 1403 }
1404 1404
1405 # get extended header size 1405 # get extended header size
1406 $h->{ext_header_size} = 0; 1406 $h->{ext_header_size} = 0;
1407 if ($h->{ext_header}) { 1407 if ($h->{ext_header}) {
1408 $h->{ext_header_size} += 10; 1408 $h->{ext_header_size} += 10;
1409 read $fh, $bytes, 4; 1409 read $fh, $bytes, 4;
1410 @bytes = reverse unpack 'C4', $bytes; 1410 @bytes = reverse unpack 'C4', $bytes;
1411 for my $i (0..3) { 1411 for my $i (0..3) {
1412 $h->{ext_header_size} += $bytes[$i] * 256 ** $i; 1412 $h->{ext_header_size} += $bytes[$i] * 256 ** $i;
1413 } 1413 }
1414 } 1414 }
1415 1415
1416 return $h; 1416 return $h;
1417} 1417}
1418 1418
1419sub _unpack_head { 1419sub _unpack_head {
1420 unpack('l', pack('L', unpack('N', $_[0]))); 1420 unpack('l', pack('L', unpack('N', $_[0])));
1421} 1421}
1422 1422
1423sub _close { 1423sub _close {
1424 my($file, $fh) = @_; 1424 my($file, $fh) = @_;
1425 unless (ref $file) { # filehandle not passed 1425 unless (ref $file) { # filehandle not passed
1426 close $fh or warn "Problem closing '$file': $!"; 1426 close $fh or warn "Problem closing '$file': $!";
1427 } 1427 }
1428} 1428}
1429 1429
1430BEGIN { 1430BEGIN {
1431 @mp3_genres = ( 1431 @mp3_genres = (
1432 'Blues', 1432 'Blues',
1433 'Classic Rock', 1433 'Classic Rock',
1434 'Country', 1434 'Country',
@@ -1583,7 +1583,7 @@ BEGIN {
1583 'Synthpop', 1583 'Synthpop',
1584 ); 1584 );
1585 1585
1586 @t_bitrate = ([ 1586 @t_bitrate = ([
1587 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256], 1587 [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256],
1588 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160], 1588 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160],
1589 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160] 1589 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
@@ -1593,17 +1593,17 @@ BEGIN {
1593 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] 1593 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
1594 ]); 1594 ]);
1595 1595
1596 @t_sampling_freq = ( 1596 @t_sampling_freq = (
1597 [11025, 12000, 8000], 1597 [11025, 12000, 8000],
1598 [undef, undef, undef], # reserved 1598 [undef, undef, undef], # reserved
1599 [22050, 24000, 16000], 1599 [22050, 24000, 16000],
1600 [44100, 48000, 32000] 1600 [44100, 48000, 32000]
1601 ); 1601 );
1602 1602
1603 @frequency_tbl = map { $_ ? eval "${_}e-3" : 0 } 1603 @frequency_tbl = map { $_ ? eval "${_}e-3" : 0 }
1604 map { @$_ } @t_sampling_freq; 1604 map { @$_ } @t_sampling_freq;
1605 1605
1606 @mp3_info_fields = qw( 1606 @mp3_info_fields = qw(
1607 VERSION 1607 VERSION
1608 LAYER 1608 LAYER
1609 STEREO 1609 STEREO
@@ -1624,12 +1624,12 @@ BEGIN {
1624 VBR_SCALE 1624 VBR_SCALE
1625 ); 1625 );
1626 1626
1627 %v1_tag_fields = 1627 %v1_tag_fields =
1628 (TITLE => 30, ARTIST => 30, ALBUM => 30, COMMENT => 30, YEAR => 4); 1628 (TITLE => 30, ARTIST => 30, ALBUM => 30, COMMENT => 30, YEAR => 4);
1629 1629
1630 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE); 1630 @v1_tag_names = qw(TITLE ARTIST ALBUM YEAR COMMENT TRACKNUM GENRE);
1631 1631
1632 %v2_to_v1_names = ( 1632 %v2_to_v1_names = (
1633 # v2.2 tags 1633 # v2.2 tags
1634 'TT2' => 'TITLE', 1634 'TT2' => 'TITLE',
1635 'TP1' => 'ARTIST', 1635 'TP1' => 'ARTIST',
@@ -1648,7 +1648,7 @@ BEGIN {
1648 'TCON' => 'GENRE', 1648 'TCON' => 'GENRE',
1649 ); 1649 );
1650 1650
1651 %v2_tag_names = ( 1651 %v2_tag_names = (
1652 # v2.2 tags 1652 # v2.2 tags
1653 'BUF' => 'Recommended buffer size', 1653 'BUF' => 'Recommended buffer size',
1654 'CNT' => 'Play counter', 1654 'CNT' => 'Play counter',