diff options
Diffstat (limited to 'tools/songdb.pl')
-rwxr-xr-x | tools/songdb.pl | 317 |
1 files changed, 95 insertions, 222 deletions
diff --git a/tools/songdb.pl b/tools/songdb.pl index 202ab06401..589267b632 100755 --- a/tools/songdb.pl +++ b/tools/songdb.pl | |||
@@ -84,7 +84,7 @@ sub get_oggtag { | |||
84 | 84 | ||
85 | my $ogg = vorbiscomm->new($fn); | 85 | my $ogg = vorbiscomm->new($fn); |
86 | 86 | ||
87 | $ogg->load; | 87 | my $h= $ogg->load; |
88 | 88 | ||
89 | # Convert this format into the same format used by the id3 parser hash | 89 | # Convert this format into the same format used by the id3 parser hash |
90 | 90 | ||
@@ -100,7 +100,6 @@ sub get_oggtag { | |||
100 | $n = 'ALBUM'; | 100 | $n = 'ALBUM'; |
101 | } | 101 | } |
102 | $hash{$n}=$cmmt if($n); | 102 | $hash{$n}=$cmmt if($n); |
103 | # print $k, '=', $cmmt, "\n"; | ||
104 | } | 103 | } |
105 | } | 104 | } |
106 | 105 | ||
@@ -147,6 +146,88 @@ sub extractdirs { | |||
147 | return @dirs; | 146 | return @dirs; |
148 | } | 147 | } |
149 | 148 | ||
149 | # CRC32 32KB of data (use less if there isn't 32KB available) | ||
150 | |||
151 | sub crc32 { | ||
152 | my ($filename, $index) = @_; | ||
153 | |||
154 | my $len = 32*1024; | ||
155 | |||
156 | if(!open(FILE, "<$filename")) { | ||
157 | print "failed to open \"$filename\" $!\n"; | ||
158 | return -2; | ||
159 | } | ||
160 | |||
161 | # read $data from index $index to $buffer from the file, may return fewer | ||
162 | # bytes when dealing with a very small file. | ||
163 | # | ||
164 | # TODO: make sure we don't include a trailer with metadata when doing this. | ||
165 | # Like a id3v1 tag. | ||
166 | my $nread = sysread FILE, $buffer, $len, $index; | ||
167 | |||
168 | close(FILE); | ||
169 | |||
170 | my @crc_table = | ||
171 | ( # CRC32 lookup table for polynomial 0x04C11DB7 | ||
172 | 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, | ||
173 | 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, | ||
174 | 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, | ||
175 | 0x4593E01E, 0x4152FDA9, 0x5F15ADAC, 0x5BD4B01B, 0x569796C2, 0x52568B75, | ||
176 | 0x6A1936C8, 0x6ED82B7F, 0x639B0DA6, 0x675A1011, 0x791D4014, 0x7DDC5DA3, | ||
177 | 0x709F7B7A, 0x745E66CD, 0x9823B6E0, 0x9CE2AB57, 0x91A18D8E, 0x95609039, | ||
178 | 0x8B27C03C, 0x8FE6DD8B, 0x82A5FB52, 0x8664E6E5, 0xBE2B5B58, 0xBAEA46EF, | ||
179 | 0xB7A96036, 0xB3687D81, 0xAD2F2D84, 0xA9EE3033, 0xA4AD16EA, 0xA06C0B5D, | ||
180 | 0xD4326D90, 0xD0F37027, 0xDDB056FE, 0xD9714B49, 0xC7361B4C, 0xC3F706FB, | ||
181 | 0xCEB42022, 0xCA753D95, 0xF23A8028, 0xF6FB9D9F, 0xFBB8BB46, 0xFF79A6F1, | ||
182 | 0xE13EF6F4, 0xE5FFEB43, 0xE8BCCD9A, 0xEC7DD02D, 0x34867077, 0x30476DC0, | ||
183 | 0x3D044B19, 0x39C556AE, 0x278206AB, 0x23431B1C, 0x2E003DC5, 0x2AC12072, | ||
184 | 0x128E9DCF, 0x164F8078, 0x1B0CA6A1, 0x1FCDBB16, 0x018AEB13, 0x054BF6A4, | ||
185 | 0x0808D07D, 0x0CC9CDCA, 0x7897AB07, 0x7C56B6B0, 0x71159069, 0x75D48DDE, | ||
186 | 0x6B93DDDB, 0x6F52C06C, 0x6211E6B5, 0x66D0FB02, 0x5E9F46BF, 0x5A5E5B08, | ||
187 | 0x571D7DD1, 0x53DC6066, 0x4D9B3063, 0x495A2DD4, 0x44190B0D, 0x40D816BA, | ||
188 | 0xACA5C697, 0xA864DB20, 0xA527FDF9, 0xA1E6E04E, 0xBFA1B04B, 0xBB60ADFC, | ||
189 | 0xB6238B25, 0xB2E29692, 0x8AAD2B2F, 0x8E6C3698, 0x832F1041, 0x87EE0DF6, | ||
190 | 0x99A95DF3, 0x9D684044, 0x902B669D, 0x94EA7B2A, 0xE0B41DE7, 0xE4750050, | ||
191 | 0xE9362689, 0xEDF73B3E, 0xF3B06B3B, 0xF771768C, 0xFA325055, 0xFEF34DE2, | ||
192 | 0xC6BCF05F, 0xC27DEDE8, 0xCF3ECB31, 0xCBFFD686, 0xD5B88683, 0xD1799B34, | ||
193 | 0xDC3ABDED, 0xD8FBA05A, 0x690CE0EE, 0x6DCDFD59, 0x608EDB80, 0x644FC637, | ||
194 | 0x7A089632, 0x7EC98B85, 0x738AAD5C, 0x774BB0EB, 0x4F040D56, 0x4BC510E1, | ||
195 | 0x46863638, 0x42472B8F, 0x5C007B8A, 0x58C1663D, 0x558240E4, 0x51435D53, | ||
196 | 0x251D3B9E, 0x21DC2629, 0x2C9F00F0, 0x285E1D47, 0x36194D42, 0x32D850F5, | ||
197 | 0x3F9B762C, 0x3B5A6B9B, 0x0315D626, 0x07D4CB91, 0x0A97ED48, 0x0E56F0FF, | ||
198 | 0x1011A0FA, 0x14D0BD4D, 0x19939B94, 0x1D528623, 0xF12F560E, 0xF5EE4BB9, | ||
199 | 0xF8AD6D60, 0xFC6C70D7, 0xE22B20D2, 0xE6EA3D65, 0xEBA91BBC, 0xEF68060B, | ||
200 | 0xD727BBB6, 0xD3E6A601, 0xDEA580D8, 0xDA649D6F, 0xC423CD6A, 0xC0E2D0DD, | ||
201 | 0xCDA1F604, 0xC960EBB3, 0xBD3E8D7E, 0xB9FF90C9, 0xB4BCB610, 0xB07DABA7, | ||
202 | 0xAE3AFBA2, 0xAAFBE615, 0xA7B8C0CC, 0xA379DD7B, 0x9B3660C6, 0x9FF77D71, | ||
203 | 0x92B45BA8, 0x9675461F, 0x8832161A, 0x8CF30BAD, 0x81B02D74, 0x857130C3, | ||
204 | 0x5D8A9099, 0x594B8D2E, 0x5408ABF7, 0x50C9B640, 0x4E8EE645, 0x4A4FFBF2, | ||
205 | 0x470CDD2B, 0x43CDC09C, 0x7B827D21, 0x7F436096, 0x7200464F, 0x76C15BF8, | ||
206 | 0x68860BFD, 0x6C47164A, 0x61043093, 0x65C52D24, 0x119B4BE9, 0x155A565E, | ||
207 | 0x18197087, 0x1CD86D30, 0x029F3D35, 0x065E2082, 0x0B1D065B, 0x0FDC1BEC, | ||
208 | 0x3793A651, 0x3352BBE6, 0x3E119D3F, 0x3AD08088, 0x2497D08D, 0x2056CD3A, | ||
209 | 0x2D15EBE3, 0x29D4F654, 0xC5A92679, 0xC1683BCE, 0xCC2B1D17, 0xC8EA00A0, | ||
210 | 0xD6AD50A5, 0xD26C4D12, 0xDF2F6BCB, 0xDBEE767C, 0xE3A1CBC1, 0xE760D676, | ||
211 | 0xEA23F0AF, 0xEEE2ED18, 0xF0A5BD1D, 0xF464A0AA, 0xF9278673, 0xFDE69BC4, | ||
212 | 0x89B8FD09, 0x8D79E0BE, 0x803AC667, 0x84FBDBD0, 0x9ABC8BD5, 0x9E7D9662, | ||
213 | 0x933EB0BB, 0x97FFAD0C, 0xAFB010B1, 0xAB710D06, 0xA6322BDF, 0xA2F33668, | ||
214 | 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 | ||
215 | ); | ||
216 | |||
217 | my $crc = 0xffffffff; | ||
218 | for ($i = 0; $i < $nread; $i++) { | ||
219 | # get the numeric for the byte of the $i index | ||
220 | $buf = ord(substr($buffer, $i, 1)); | ||
221 | |||
222 | $crc = ($crc << 8) ^ $crc_table[(($crc >> 24) ^ $buf) & 0xFF]; | ||
223 | |||
224 | # printf("%08x\n", $crc); | ||
225 | } | ||
226 | |||
227 | return $crc; | ||
228 | |||
229 | } | ||
230 | |||
150 | sub singlefile { | 231 | sub singlefile { |
151 | my ($file) = @_; | 232 | my ($file) = @_; |
152 | my $hash; | 233 | my $hash; |
@@ -156,6 +237,10 @@ sub singlefile { | |||
156 | } | 237 | } |
157 | else { | 238 | else { |
158 | $hash = get_mp3tag($file); | 239 | $hash = get_mp3tag($file); |
240 | |||
241 | my $info = get_mp3info($file); | ||
242 | |||
243 | $hash->{FILECRC} = crc32($file, $info->{headersize}); | ||
159 | } | 244 | } |
160 | 245 | ||
161 | return $hash; # a hash reference | 246 | return $hash; # a hash reference |
@@ -626,9 +711,10 @@ if ($db) { | |||
626 | my $str = $f."\x00" x ($maxfilelen- length($f)); | 711 | my $str = $f."\x00" x ($maxfilelen- length($f)); |
627 | my $id3 = $entries{$f}; | 712 | my $id3 = $entries{$f}; |
628 | print DB $str; | 713 | print DB $str; |
629 | dumpint(0); # TODO: add hashing; 0 for now. | 714 | #print STDERR "CRC: ".."\n"; |
630 | dumpint($id3->{'songoffset'}); | 715 | dumpint($id3->{'FILECRC'}); # CRC32 of the song data |
631 | dumpint(-1); | 716 | dumpint($id3->{'songoffset'}); # offset to song data |
717 | dumpint(-1); # what's this for? | ||
632 | } | 718 | } |
633 | 719 | ||
634 | close(DB); | 720 | close(DB); |
@@ -837,223 +923,6 @@ sub use_winamp_genres { | |||
837 | 923 | ||
838 | =pod | 924 | =pod |
839 | 925 | ||
840 | =item remove_mp3tag (FILE [, VERSION, BUFFER]) | ||
841 | |||
842 | Can remove ID3v1 or ID3v2 tags. VERSION should be C<1> for ID3v1, | ||
843 | C<2> for ID3v2, and C<ALL> for both. | ||
844 | |||
845 | For ID3v1, removes last 128 bytes from file if those last 128 bytes begin | ||
846 | with the text 'TAG'. File will be 128 bytes shorter. | ||
847 | |||
848 | For ID3v2, removes ID3v2 tag. Because an ID3v2 tag is at the | ||
849 | beginning of the file, we rewrite the file after removing the tag data. | ||
850 | The buffer for rewriting the file is 4MB. BUFFER (in bytes) ca | ||
851 | change the buffer size. | ||
852 | |||
853 | Returns the number of bytes removed, or -1 if no tag removed, | ||
854 | or undef if there is an error. | ||
855 | |||
856 | =cut | ||
857 | |||
858 | sub remove_mp3tag { | ||
859 | my($file, $version, $buf) = @_; | ||
860 | my($fh, $return); | ||
861 | |||
862 | $buf ||= 4096*1024; # the bigger the faster | ||
863 | $version ||= 1; | ||
864 | |||
865 | if (not (defined $file && $file ne '')) { | ||
866 | $@ = "No file specified"; | ||
867 | return undef; | ||
868 | } | ||
869 | |||
870 | if (not -s $file) { | ||
871 | $@ = "File is empty"; | ||
872 | return undef; | ||
873 | } | ||
874 | |||
875 | if (ref $file) { # filehandle passed | ||
876 | $fh = $file; | ||
877 | } else { | ||
878 | $fh = gensym; | ||
879 | if (not open $fh, "+< $file\0") { | ||
880 | $@ = "Can't open $file: $!"; | ||
881 | return undef; | ||
882 | } | ||
883 | } | ||
884 | |||
885 | binmode $fh; | ||
886 | |||
887 | if ($version eq 1 || $version eq 'ALL') { | ||
888 | seek $fh, -128, 2; | ||
889 | my $tell = tell $fh; | ||
890 | if (<$fh> =~ /^TAG/) { | ||
891 | truncate $fh, $tell or warn "Can't truncate '$file': $!"; | ||
892 | $return += 128; | ||
893 | } | ||
894 | } | ||
895 | |||
896 | if ($version eq 2 || $version eq 'ALL') { | ||
897 | my $h = _get_v2head($fh); | ||
898 | if ($h) { | ||
899 | local $\; | ||
900 | seek $fh, 0, 2; | ||
901 | my $eof = tell $fh; | ||
902 | my $off = $h->{tag_size}; | ||
903 | |||
904 | while ($off < $eof) { | ||
905 | seek $fh, $off, 0; | ||
906 | read $fh, my($bytes), $buf; | ||
907 | seek $fh, $off - $h->{tag_size}, 0; | ||
908 | print $fh $bytes; | ||
909 | $off += $buf; | ||
910 | } | ||
911 | |||
912 | truncate $fh, $eof - $h->{tag_size} | ||
913 | or warn "Can't truncate '$file': $!"; | ||
914 | $return += $h->{tag_size}; | ||
915 | } | ||
916 | } | ||
917 | |||
918 | _close($file, $fh); | ||
919 | |||
920 | return $return || -1; | ||
921 | } | ||
922 | |||
923 | |||
924 | =pod | ||
925 | |||
926 | =item set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM]) | ||
927 | |||
928 | =item set_mp3tag (FILE, $HASHREF) | ||
929 | |||
930 | Adds/changes tag information in an MP3 audio file. Will clobber | ||
931 | any existing information in file. | ||
932 | |||
933 | Fields are TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE. All fields have | ||
934 | a 30-byte limit, except for YEAR, which has a four-byte limit, and GENRE, | ||
935 | which is one byte in the file. The GENRE passed in the function is a | ||
936 | case-insensitive text string representing a genre found in C<@mp3_genres>. | ||
937 | |||
938 | Will accept either a list of values, or a hashref of the type | ||
939 | returned by C<get_mp3tag>. | ||
940 | |||
941 | If TRACKNUM is present (for ID3v1.1), then the COMMENT field can only be | ||
942 | 28 bytes. | ||
943 | |||
944 | ID3v2 support may come eventually. Note that if you set a tag on a file | ||
945 | with ID3v2, the set tag will be for ID3v1[.1] only, and if you call | ||
946 | C<get_mp3_tag> on the file, it will show you the (unchanged) ID3v2 tags, | ||
947 | unless you specify ID3v1. | ||
948 | |||
949 | =cut | ||
950 | |||
951 | sub set_mp3tag { | ||
952 | my($file, $title, $artist, $album, $year, $comment, $genre, $tracknum) = @_; | ||
953 | my(%info, $oldfh, $ref, $fh); | ||
954 | local %v1_tag_fields = %v1_tag_fields; | ||
955 | |||
956 | # set each to '' if undef | ||
957 | for ($title, $artist, $album, $year, $comment, $tracknum, $genre, | ||
958 | (@info{@v1_tag_names})) | ||
959 | {$_ = defined() ? $_ : ''} | ||
960 | |||
961 | ($ref) = (overload::StrVal($title) =~ /^(?:.*\=)?([^=]*)\((?:[^\(]*)\)$/) | ||
962 | if ref $title; | ||
963 | # populate data to hashref if hashref is not passed | ||
964 | if (!$ref) { | ||
965 | (@info{@v1_tag_names}) = | ||
966 | ($title, $artist, $album, $year, $comment, $tracknum, $genre); | ||
967 | |||
968 | # put data from hashref into hashref if hashref is passed | ||
969 | } elsif ($ref eq 'HASH') { | ||
970 | %info = %$title; | ||
971 | |||
972 | # return otherwise | ||
973 | } else { | ||
974 | warn(<<'EOT'); | ||
975 | Usage: set_mp3tag (FILE, TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE [, TRACKNUM]) | ||
976 | set_mp3tag (FILE, $HASHREF) | ||
977 | EOT | ||
978 | return undef; | ||
979 | } | ||
980 | |||
981 | if (not (defined $file && $file ne '')) { | ||
982 | $@ = "No file specified"; | ||
983 | return undef; | ||
984 | } | ||
985 | |||
986 | if (not -s $file) { | ||
987 | $@ = "File is empty"; | ||
988 | return undef; | ||
989 | } | ||
990 | |||
991 | # comment field length 28 if ID3v1.1 | ||
992 | $v1_tag_fields{COMMENT} = 28 if $info{TRACKNUM}; | ||
993 | |||
994 | |||
995 | # only if -w is on | ||
996 | if ($^W) { | ||
997 | # warn if fields too long | ||
998 | foreach my $field (keys %v1_tag_fields) { | ||
999 | $info{$field} = '' unless defined $info{$field}; | ||
1000 | if (length($info{$field}) > $v1_tag_fields{$field}) { | ||
1001 | warn "Data too long for field $field: truncated to " . | ||
1002 | "$v1_tag_fields{$field}"; | ||
1003 | } | ||
1004 | } | ||
1005 | |||
1006 | if ($info{GENRE}) { | ||
1007 | warn "Genre `$info{GENRE}' does not exist\n" | ||
1008 | unless exists $mp3_genres{$info{GENRE}}; | ||
1009 | } | ||
1010 | } | ||
1011 | |||
1012 | if ($info{TRACKNUM}) { | ||
1013 | $info{TRACKNUM} =~ s/^(\d+)\/(\d+)$/$1/; | ||
1014 | unless ($info{TRACKNUM} =~ /^\d+$/ && | ||
1015 | $info{TRACKNUM} > 0 && $info{TRACKNUM} < 256) { | ||
1016 | warn "Tracknum `$info{TRACKNUM}' must be an integer " . | ||
1017 | "from 1 and 255\n" if $^W; | ||
1018 | $info{TRACKNUM} = ''; | ||
1019 | } | ||
1020 | } | ||
1021 | |||
1022 | if (ref $file) { # filehandle passed | ||
1023 | $fh = $file; | ||
1024 | } else { | ||
1025 | $fh = gensym; | ||
1026 | if (not open $fh, "+< $file\0") { | ||
1027 | $@ = "Can't open $file: $!"; | ||
1028 | return undef; | ||
1029 | } | ||
1030 | } | ||
1031 | |||
1032 | binmode $fh; | ||
1033 | $oldfh = select $fh; | ||
1034 | seek $fh, -128, 2; | ||
1035 | # go to end of file if no tag, beginning of file if tag | ||
1036 | seek $fh, (<$fh> =~ /^TAG/ ? -128 : 0), 2; | ||
1037 | |||
1038 | # get genre value | ||
1039 | $info{GENRE} = $info{GENRE} && exists $mp3_genres{$info{GENRE}} ? | ||
1040 | $mp3_genres{$info{GENRE}} : 255; # some default genre | ||
1041 | |||
1042 | local $\; | ||
1043 | # print TAG to file | ||
1044 | if ($info{TRACKNUM}) { | ||
1045 | print pack "a3a30a30a30a4a28xCC", 'TAG', @info{@v1_tag_names}; | ||
1046 | } else { | ||
1047 | print pack "a3a30a30a30a4a30C", 'TAG', @info{@v1_tag_names[0..4, 6]}; | ||
1048 | } | ||
1049 | |||
1050 | select $oldfh; | ||
1051 | |||
1052 | _close($file, $fh); | ||
1053 | |||
1054 | return 1; | ||
1055 | } | ||
1056 | |||
1057 | =pod | 926 | =pod |
1058 | 927 | ||
1059 | =item get_mp3tag (FILE [, VERSION, RAW_V2]) | 928 | =item get_mp3tag (FILE [, VERSION, RAW_V2]) |
@@ -1402,6 +1271,8 @@ sub get_mp3info { | |||
1402 | 1271 | ||
1403 | my $vbr = _get_vbr($fh, $h, \$off); | 1272 | my $vbr = _get_vbr($fh, $h, \$off); |
1404 | 1273 | ||
1274 | $h->{headersize}=$off; # data size prepending the actual mp3 data | ||
1275 | |||
1405 | seek $fh, 0, 2; | 1276 | seek $fh, 0, 2; |
1406 | $eof = tell $fh; | 1277 | $eof = tell $fh; |
1407 | seek $fh, -128, 2; | 1278 | seek $fh, -128, 2; |
@@ -1459,6 +1330,8 @@ sub _get_info { | |||
1459 | $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES}; | 1330 | $i->{FRAME_LENGTH} = int($h->{size} / $i->{FRAMES}) if $i->{FRAMES}; |
1460 | $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}]; | 1331 | $i->{FREQUENCY} = $frequency_tbl[3 * $h->{IDR} + $h->{sampling_freq}]; |
1461 | 1332 | ||
1333 | $i->{headersize} = $h->{headersize}; | ||
1334 | |||
1462 | return $i; | 1335 | return $i; |
1463 | } | 1336 | } |
1464 | 1337 | ||