diff options
author | Daniel Stenberg <daniel@haxx.se> | 2005-01-17 14:26:36 +0000 |
---|---|---|
committer | Daniel Stenberg <daniel@haxx.se> | 2005-01-17 14:26:36 +0000 |
commit | e24a528499f992a8a8987432d0fcb3e595f1206f (patch) | |
tree | f3eb1226f1c67596a871bed6a27d245b6c4f4ec7 /tools | |
parent | 7396d99027410feda4661f860df90509783b50c5 (diff) | |
download | rockbox-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
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/songdb.pl | 860 |
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 | ||
887 | sub get_mp3tag { | 887 | sub 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 | ||
1055 | sub _get_v2tag { | 1055 | sub _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 | ||
1145 | sub get_mp3info { | 1145 | sub 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 | ||
1214 | sub _get_info { | 1214 | sub _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 | ||
1262 | sub _get_head { | 1262 | sub _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 | ||
1283 | sub _is_mp3 { | 1283 | sub _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 | ||
1314 | sub _get_vbr { | 1314 | sub _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 | ||
1369 | sub _get_v2head { | 1369 | sub _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 | ||
1419 | sub _unpack_head { | 1419 | sub _unpack_head { |
1420 | unpack('l', pack('L', unpack('N', $_[0]))); | 1420 | unpack('l', pack('L', unpack('N', $_[0]))); |
1421 | } | 1421 | } |
1422 | 1422 | ||
1423 | sub _close { | 1423 | sub _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 | ||
1430 | BEGIN { | 1430 | BEGIN { |
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', |