line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MP3::TAG::ID3v2; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
26
|
|
4
|
1
|
|
|
1
|
|
4
|
use MP3::TAG::ID3v1; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
15
|
|
5
|
1
|
|
|
1
|
|
6528
|
use Compress::Zlib; |
|
1
|
|
|
|
|
107819
|
|
|
1
|
|
|
|
|
270
|
|
6
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
9
|
use vars qw /%format %long_names/; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
4579
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=pod |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
MP3::TAG::ID3v2 - Read / Write ID3v2.3 tags from MP3 audio files |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 SYNOPSIS |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
MP3::TAG::ID3v2 is designed to be called from the MP3::Tag module. |
18
|
|
|
|
|
|
|
It then returns a ID3v2-tag-object, which can be used in a users |
19
|
|
|
|
|
|
|
program. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
$id3v2 = MP3::TAG::ID3v2->new($mp3obj); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
C<$mp3obj> is a object from MP3::Tag. See according documentation. |
24
|
|
|
|
|
|
|
C<$tag> is undef when no tag is found in the C<$mp3obj>. |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
* Reading a tag |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
@frameIDs = $id3v2->getFrameIDS; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
foreach my $frame (@frameIDs) { |
31
|
|
|
|
|
|
|
my ($info, $name) = $id3v2->getFrame($frame); |
32
|
|
|
|
|
|
|
if (ref $info) { |
33
|
|
|
|
|
|
|
print "$name ($frame):\n"; |
34
|
|
|
|
|
|
|
while(my ($key,$val)=each %$info) { |
35
|
|
|
|
|
|
|
print " * $key => $val\n"; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
} else { |
38
|
|
|
|
|
|
|
print "$name: $info\n"; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
* Changing / Writing a tag |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
$id3v2->add_frame("TIT2", "Title of the song"); |
45
|
|
|
|
|
|
|
$id3v2->change_frame("TALB","Greatest Album"); |
46
|
|
|
|
|
|
|
$id3v2->remove_frame("TLAN"); |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
$id3v2->write_tag(); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
* Get information about supported frames |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
%tags = $id3v2->supported_frames(); |
53
|
|
|
|
|
|
|
while (($fname, $longname) = each %tags) { |
54
|
|
|
|
|
|
|
print "$fname $longname: ", |
55
|
|
|
|
|
|
|
join(", ", @{$id3v2->what_data($fname)}), "\n"; |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head1 AUTHOR |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Thomas Geffert, thg@users.sourceforge.net |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=head1 DESCRIPTION |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=over 4 |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=item new() |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
$tag = new($mp3obj); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
C needs as parameter a mp3obj, as created by C (see documentation |
71
|
|
|
|
|
|
|
of MP3::Tag). |
72
|
|
|
|
|
|
|
C tries to find a ID3v2 tag in the mp3obj. If it does not find a tag it returns undef. |
73
|
|
|
|
|
|
|
Otherwise it reads the tag header, as well an extended header, if available. It reads the |
74
|
|
|
|
|
|
|
rest of the tag in a buffer, does unsynchronizing if neccessary, and returns a ID3v2-object. |
75
|
|
|
|
|
|
|
At this moment only ID3v2.3 is supported. Any extended header with CRC data is ignored, so |
76
|
|
|
|
|
|
|
not CRC check is done at the moment. |
77
|
|
|
|
|
|
|
The ID3v2-object can then be used to extract information from the tag. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub new { |
82
|
2
|
|
|
2
|
1
|
3
|
my $class = shift; |
83
|
2
|
|
|
|
|
3
|
my $mp3obj = shift; |
84
|
2
|
|
|
|
|
3
|
my $create = shift; |
85
|
2
|
|
|
|
|
4
|
my $self={mp3=>$mp3obj}; |
86
|
2
|
|
|
|
|
4
|
my $header=0; my @size; |
|
2
|
|
|
|
|
3
|
|
87
|
2
|
|
|
|
|
5
|
bless $self, $class; |
88
|
|
|
|
|
|
|
|
89
|
2
|
|
|
|
|
7
|
$mp3obj->seek(0,0); |
90
|
2
|
|
|
|
|
7
|
$mp3obj->read(\$header, 10); |
91
|
2
|
|
|
|
|
8
|
$self->{frame_start}=0; |
92
|
|
|
|
|
|
|
|
93
|
2
|
50
|
|
|
|
6
|
if ($self->read_header($header)) { |
94
|
2
|
50
|
33
|
|
|
12
|
if (defined $create && $create) { |
95
|
0
|
|
|
|
|
0
|
$self->{tag_data} = ''; |
96
|
0
|
|
|
|
|
0
|
$self->{data_size} = 0; |
97
|
|
|
|
|
|
|
} else { |
98
|
2
|
|
|
|
|
7
|
$mp3obj->read(\$self->{tag_data}, $self->{tagsize}); |
99
|
2
|
|
|
|
|
5
|
$self->{data_size} = $self->{tagsize}; |
100
|
|
|
|
|
|
|
# un-unsynchronize |
101
|
2
|
50
|
|
|
|
5
|
if ($self->{flags}->{unsync}) { |
102
|
2
|
|
|
|
|
6
|
my $hits= $self->{tag_data} =~ s/\xFF\x00/\xFF/gs; |
103
|
2
|
|
|
|
|
3
|
$self->{data_size} -= $hits; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
# read the ext header if it exists |
106
|
2
|
50
|
|
|
|
6
|
if ($self->{flags}->{extheader}) { |
107
|
0
|
0
|
|
|
|
0
|
unless ($self->read_ext_header(substr ($self->{tag_data}, 0, 14))) { |
108
|
0
|
|
|
|
|
0
|
return undef; # ext header not supported |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
} |
112
|
2
|
|
|
|
|
7
|
return $self; |
113
|
|
|
|
|
|
|
} else { |
114
|
0
|
0
|
0
|
|
|
0
|
if (defined $create && $create) { |
115
|
0
|
|
|
|
|
0
|
$self->{tag_data}=''; |
116
|
0
|
|
|
|
|
0
|
$self->{tagsize} = -10; |
117
|
0
|
|
|
|
|
0
|
$self->{data_size} = 0; |
118
|
0
|
|
|
|
|
0
|
return $self; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
0
|
|
|
|
|
0
|
return undef; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=pod |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=item getFrameIDs() |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
@frameIDs = $tag->getFrameIDs; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
getFrameIDs loops through all frames, which exist in the tag. It returns a |
131
|
|
|
|
|
|
|
list of all available Frame IDs. These are 4-character-codes (short names), |
132
|
|
|
|
|
|
|
the internal names of the frames. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
You can use this list to iterate over all frames to get their data, or to |
135
|
|
|
|
|
|
|
check if a specific frame is included in the tag. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
If there are multiple occurences of a frame in one tag, the first frame is |
138
|
|
|
|
|
|
|
returned with its normal short name, following frames of this type get a |
139
|
|
|
|
|
|
|
'00', '01', '02', ... appended to this name. These expanded names can then |
140
|
|
|
|
|
|
|
used with C to get the information of these frames. |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=cut |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub getFrameIDs { |
145
|
2
|
|
|
2
|
1
|
3
|
my $self=shift; |
146
|
2
|
50
|
|
|
|
7
|
return if exists $self->{frameIDs}; |
147
|
2
|
|
|
|
|
3
|
my $pos=$self->{frame_start}; |
148
|
2
|
50
|
|
|
|
6
|
if ($self->{flags}->{extheader}) { |
149
|
0
|
|
|
|
|
0
|
warn "getFrameIDs: possible wrong IDs because of unsupported extended header\n"; |
150
|
|
|
|
|
|
|
} |
151
|
2
|
|
|
|
|
9
|
my $buf; |
152
|
2
|
|
|
|
|
6
|
while ($pos+10 < $self->{data_size}) { |
153
|
9
|
|
|
|
|
13
|
$buf = substr ($self->{tag_data}, $pos, 10); |
154
|
9
|
|
|
|
|
59
|
my ($ID, $size, $flags) = unpack("a4Nn", $buf); |
155
|
9
|
50
|
|
|
|
19
|
if ($size>255) { |
156
|
|
|
|
|
|
|
# Size>255 means at least 2 bytes are used for size. |
157
|
|
|
|
|
|
|
# Some programs use (incorectly) also for this size |
158
|
|
|
|
|
|
|
# the format of the tag size. Trying do detect that here |
159
|
0
|
0
|
0
|
|
|
0
|
if ($pos+10+$size> $self->{data_size} || |
160
|
|
|
|
|
|
|
!exists $long_names{substr ($self->{tag_data}, $pos+$size,4)}) { |
161
|
|
|
|
|
|
|
# wrong size or last frame |
162
|
0
|
|
|
|
|
0
|
my $fsize=0; |
163
|
0
|
|
|
|
|
0
|
foreach (unpack("x4C4", $buf)) { |
164
|
0
|
|
|
|
|
0
|
$fsize = ($fsize << 7) + $_; |
165
|
|
|
|
|
|
|
} |
166
|
0
|
0
|
0
|
|
|
0
|
if ($pos+20+$fsize<$self->{data_size} && |
167
|
|
|
|
|
|
|
exists $long_names{substr ($self->{tag_data}, $pos+10+$fsize,4)}) { |
168
|
0
|
|
|
|
|
0
|
warn "Probably wrong size format found in frame $ID. Trying to correct it\n"; |
169
|
|
|
|
|
|
|
#probably false size format detected, using corrected size |
170
|
0
|
|
|
|
|
0
|
$size = $fsize; |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
} |
174
|
9
|
100
|
|
|
|
14
|
if ($ID ne "\000\000\000\000") { |
175
|
7
|
100
|
|
|
|
19
|
if (exists $self->{frames}->{$ID}) { |
176
|
2
|
|
|
|
|
3
|
$ID .= '01'; |
177
|
2
|
|
|
|
|
5
|
while (exists $self->{frames}->{$ID}) { |
178
|
0
|
|
|
|
|
0
|
$ID++; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
7
|
|
|
|
|
6
|
printf ("%s @ %s for %s (%x)\n", $ID, $pos, $size, $flags) if 1==0; |
182
|
7
|
|
|
|
|
23
|
$self->{frames}->{$ID} = {start=>$pos+10, size=>$size, flags=>$flags}; |
183
|
7
|
|
|
|
|
20
|
$pos += $size+10; |
184
|
|
|
|
|
|
|
} else { # Padding reached, cut tag data here |
185
|
2
|
|
|
|
|
2
|
last; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
# cut off padding |
189
|
2
|
|
|
|
|
6
|
$self->{tag_data}=substr $self->{tag_data}, 0, $pos; |
190
|
|
|
|
|
|
|
|
191
|
2
|
|
|
|
|
4
|
$self->{frameIDs} =1; |
192
|
2
|
|
|
|
|
2
|
return keys %{$self->{frames}}; |
|
2
|
|
|
|
|
5
|
|
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=pod |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item getFrame() |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
($info, $name) = getFrame($ID); |
200
|
|
|
|
|
|
|
($info, $name) = getFrame($ID, 'raw'); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
getFrame gets the contents of a specific frame, which must be specified by the |
203
|
|
|
|
|
|
|
4-character-ID (aka short name). You can use C to get the IDs of |
204
|
|
|
|
|
|
|
the tag, or use IDs which you hope to find in the tag. If the ID is not found, |
205
|
|
|
|
|
|
|
getFrame returns (undef, undef). |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Otherwise it extracts the contents of the frame. Frames in ID3v2 tags can be |
208
|
|
|
|
|
|
|
very small, or complex and huge. That is the reason, that getFrame returns |
209
|
|
|
|
|
|
|
the frame data in two ways, depending on the tag. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
If it is a simple tag, with only one piece of data, this date is returned |
212
|
|
|
|
|
|
|
directly as ($info, $name), where $info is the text string, and $name is the |
213
|
|
|
|
|
|
|
long (english) name of the frame. |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
If the frame consist of different pieces of data, $info is a hash reference, |
216
|
|
|
|
|
|
|
$name is again the long name of the frame. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
The hash, to which $info points, contains key/value pairs, where the key is |
219
|
|
|
|
|
|
|
always the name of the data, and the value is the data itself. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
If the name starts with a underscore (as eg '_code'), the data is probably |
222
|
|
|
|
|
|
|
binary data and not printable. If the name starts without an underscore, |
223
|
|
|
|
|
|
|
it should be a text string and printable. |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
If there exists a second parameter like raw, the whole frame data is returned, |
226
|
|
|
|
|
|
|
but not the frame header. If the data was stored compressed, it is also in |
227
|
|
|
|
|
|
|
raw mode uncompressed before it is returned. Then $info contains a string |
228
|
|
|
|
|
|
|
with all data (which might be binary), and $name against the long frame name. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
! Encrypted frames are not supported yet ! |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
! Some frames are not supported yet, but the most common ones are supported ! |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=cut |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub getFrame { |
237
|
3
|
|
|
3
|
1
|
9
|
my ($self, $fname, $raw)=@_; |
238
|
3
|
100
|
|
|
|
11
|
$self->getFrameIDs() unless exists $self->{frameIDs}; |
239
|
3
|
50
|
|
|
|
7
|
return undef unless exists $self->{frames}->{$fname}; |
240
|
3
|
|
|
|
|
5
|
my $frame=$self->{frames}->{$fname}; |
241
|
3
|
|
|
|
|
6
|
my $frame_flags = check_flags($frame->{flags},$fname); |
242
|
3
|
|
|
|
|
5
|
$fname = substr ($fname, 0 ,4); |
243
|
3
|
|
|
|
|
3
|
my $start_offset=0; |
244
|
3
|
50
|
|
|
|
9
|
if ($frame_flags->{encryption}) { |
245
|
0
|
|
|
|
|
0
|
warn "Frame $fname: encryption not supported yet\n" ; |
246
|
0
|
|
|
|
|
0
|
return undef; |
247
|
|
|
|
|
|
|
} |
248
|
3
|
50
|
|
|
|
5
|
if ($frame_flags->{groupid}) { |
249
|
|
|
|
|
|
|
# groupid is ignored at the moment |
250
|
0
|
|
|
|
|
0
|
$start_offset=1; |
251
|
|
|
|
|
|
|
} |
252
|
3
|
|
|
|
|
9
|
my $data = substr($self->{tag_data}, $frame->{start}+$start_offset, $frame->{size}-$start_offset); |
253
|
3
|
50
|
|
|
|
9
|
if ($frame_flags->{compression}) { |
254
|
0
|
|
|
|
|
0
|
my $usize=unpack("N", $data); |
255
|
0
|
|
|
|
|
0
|
$data = uncompress(substr ($data, 4)); |
256
|
0
|
0
|
|
|
|
0
|
warn "$fname: Wrong size of uncompressed data\n" if $usize=!length($data); |
257
|
|
|
|
|
|
|
} |
258
|
3
|
50
|
|
|
|
6
|
return ($data, $long_names{$fname}) if defined $raw; |
259
|
|
|
|
|
|
|
|
260
|
3
|
|
|
|
|
6
|
my $format = getformat($fname); |
261
|
3
|
|
|
|
|
3
|
my $result; |
262
|
3
|
50
|
|
|
|
9
|
$result = extract_data($data, $format) if defined $format; |
263
|
3
|
100
|
66
|
|
|
17
|
if (scalar keys %$result ==1 && exists $result->{Text}) { |
264
|
2
|
|
|
|
|
5
|
$result= $result->{Text}; |
265
|
|
|
|
|
|
|
} |
266
|
3
|
50
|
|
|
|
7
|
if (wantarray) { |
267
|
0
|
|
|
|
|
0
|
return ($result, $long_names{$fname}); |
268
|
|
|
|
|
|
|
} else { |
269
|
3
|
|
|
|
|
18
|
return $result; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=pod |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=item write_tag() |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
$id3v2->write_tag; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
Saves a frame to the file. It tries to update the file in place, |
280
|
|
|
|
|
|
|
when the space of the old tag is big enoug for the new tag. |
281
|
|
|
|
|
|
|
Otherwise it creates a temp file (i.e. copies the whole mp3 file) |
282
|
|
|
|
|
|
|
and renames/moves it to the original file name. |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
An extended header with CRC checksum is not supported yet. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
At the moment the tag is automatically unsynchronized. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=cut |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
sub write_tag { |
291
|
2
|
|
|
2
|
1
|
5
|
my $self = shift; |
292
|
2
|
|
|
|
|
3
|
my $n = chr(0); |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# perhaps search for first mp3 data frame to check if tag size is not |
295
|
|
|
|
|
|
|
# too big and will override the mp3 data |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# unsync ? global option should be good |
298
|
|
|
|
|
|
|
# unsync only if MPEG 2 layer I, II and III or MPEG 2.5 files. |
299
|
|
|
|
|
|
|
# do it twice to do correct unsnyc if several FF are following eachother |
300
|
2
|
|
|
|
|
5
|
$self->{tag_data} =~ s/\xFF([\x00\xE0-\xFF])/\xFF\x00$1/gos; |
301
|
2
|
|
|
|
|
5
|
$self->{tag_data} =~ s/\xFF([\xE0-\xFF])/\xFF\x00$1/gos; |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
#ext header are not supported yet |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
#convert size to header format specific size |
306
|
2
|
|
|
|
|
6
|
my $size = unpack('B32', pack ('N', $self->{tagsize})); |
307
|
2
|
|
|
|
|
11
|
substr ($size, -$_, 0) = '0' for (qw/28 21 14 7/); |
308
|
2
|
|
|
|
|
6
|
$size= pack('B32', substr ($size, -32)); |
309
|
|
|
|
|
|
|
|
310
|
2
|
|
|
|
|
3
|
my $flags = chr(128); # unsync |
311
|
2
|
|
|
|
|
2
|
my $header = 'ID3' . chr(3) . chr(0); |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# actually write the tag |
314
|
|
|
|
|
|
|
|
315
|
2
|
|
|
|
|
4
|
my $mp3obj = $self->{mp3}; |
316
|
|
|
|
|
|
|
|
317
|
2
|
50
|
|
|
|
4
|
if (length ($self->{tag_data}) <= $self->{tagsize}) { |
318
|
|
|
|
|
|
|
# new tag can be writte in space of old tag |
319
|
2
|
|
|
|
|
5
|
$mp3obj->close; |
320
|
2
|
50
|
|
|
|
6
|
if ($mp3obj->open("+<")) { |
321
|
2
|
|
|
|
|
7
|
$mp3obj->seek(0,0); |
322
|
2
|
|
|
|
|
5
|
$mp3obj->write($header); |
323
|
2
|
|
|
|
|
7
|
$mp3obj->write($flags); |
324
|
2
|
|
|
|
|
6
|
$mp3obj->write($size); |
325
|
2
|
|
|
|
|
7
|
$mp3obj->write($self->{tag_data}); |
326
|
2
|
|
|
|
|
11
|
$mp3obj->write($n x ($self->{tagsize} - length ($self->{tag_data}))); |
327
|
|
|
|
|
|
|
} else { |
328
|
0
|
|
|
|
|
0
|
warn "Couldn't write tag!"; |
329
|
0
|
|
|
|
|
0
|
return undef; |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} else { |
332
|
0
|
|
|
|
|
0
|
my $tempfile = '/tmp/tmp.mp3'; #BETTER: try first to use same dir |
333
|
0
|
0
|
|
|
|
0
|
if (open (NEW, ">$tempfile")) { |
334
|
0
|
|
|
|
|
0
|
my $padding = 256; # BETTER: calculate padding depending on mp3 size to |
335
|
|
|
|
|
|
|
# fit to 4k cluster size |
336
|
0
|
|
|
|
|
0
|
my $size = unpack('B32', pack ('N', length($self->{tag_data})+$padding)); |
337
|
0
|
|
|
|
|
0
|
substr ($size, -$_, 0) = '0' for (qw/28 21 14 7/); |
338
|
0
|
|
|
|
|
0
|
$size= pack('B32', substr ($size, -32)); |
339
|
0
|
|
|
|
|
0
|
print NEW $header, $flags, $size, $self->{tag_data}, $n x $padding; |
340
|
0
|
|
|
|
|
0
|
my $buf; |
341
|
0
|
|
|
|
|
0
|
$mp3obj->seek($self->{tagsize}+10,0); |
342
|
0
|
|
|
|
|
0
|
while ($mp3obj->read(\$buf,16384)) { |
343
|
0
|
|
|
|
|
0
|
print NEW $buf; |
344
|
|
|
|
|
|
|
} |
345
|
0
|
|
|
|
|
0
|
close NEW; |
346
|
0
|
|
|
|
|
0
|
$mp3obj->close; |
347
|
0
|
0
|
|
|
|
0
|
system("mv",$tempfile,$mp3obj->{filename}) |
348
|
|
|
|
|
|
|
unless rename $tempfile, $mp3obj->{filename}; |
349
|
|
|
|
|
|
|
} else { |
350
|
0
|
|
|
|
|
0
|
warn "Couldn't write tag!"; |
351
|
0
|
|
|
|
|
0
|
return undef; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
} |
354
|
2
|
|
|
|
|
95
|
return 1; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=pod |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
=item remove_tag() |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
$id3v2->remove_tag(); |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
Removes the whole tag from the file by copying the whole |
364
|
|
|
|
|
|
|
mp3-file to a temp-file and renaming/moving that to the |
365
|
|
|
|
|
|
|
original filename. |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
=cut |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub remove_tag { |
370
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
371
|
0
|
|
|
|
|
0
|
my $mp3obj = $self->{mp3}; |
372
|
0
|
|
|
|
|
0
|
my $tempfile = '/tmp/tmp.mp3'; #BETTER: try first to use same dir |
373
|
0
|
0
|
|
|
|
0
|
if (open (NEW, ">$tempfile")) { |
374
|
0
|
|
|
|
|
0
|
my $buf; |
375
|
0
|
|
|
|
|
0
|
$mp3obj->seek($self->{tagsize}+10,0); |
376
|
0
|
|
|
|
|
0
|
while ($mp3obj->read(\$buf,16384)) { |
377
|
0
|
|
|
|
|
0
|
print NEW $buf; |
378
|
|
|
|
|
|
|
} |
379
|
0
|
|
|
|
|
0
|
close NEW; |
380
|
0
|
|
|
|
|
0
|
$mp3obj->close; |
381
|
0
|
0
|
|
|
|
0
|
system("mv",$tempfile,$mp3obj->{filename}) |
382
|
|
|
|
|
|
|
unless rename $tempfile, $mp3obj->{filename}; |
383
|
|
|
|
|
|
|
} else { |
384
|
0
|
|
|
|
|
0
|
warn "Couldn't write temp file\n"; |
385
|
0
|
|
|
|
|
0
|
return undef; |
386
|
|
|
|
|
|
|
} |
387
|
0
|
|
|
|
|
0
|
return 1; |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=pod |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
=item add_frame() |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
$id3v2->add_frame($fname, @data); |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Add a new frame, identified by the short name $fname. |
397
|
|
|
|
|
|
|
The $data must consist from so much elements, as described |
398
|
|
|
|
|
|
|
in the ID3v2.3 standard. If there is need to give an encoding |
399
|
|
|
|
|
|
|
parameter and you would like standard ascii encoding, you |
400
|
|
|
|
|
|
|
can omit the parameter or set it to 0. Any other encoding |
401
|
|
|
|
|
|
|
is not supported yet, and thus ignored. |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Examples: |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
add_frame("TIT2", 0, "Abba"); # both the same, but |
406
|
|
|
|
|
|
|
add_frame("TIT2", "Abba"); # this one with implicit encoding |
407
|
|
|
|
|
|
|
add_frame("COMM", "ENG", "Short text", "This is a comment"); |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=cut |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub add_frame { |
412
|
1
|
|
|
1
|
1
|
3
|
my ($self, $fname, @data) = @_; |
413
|
1
|
50
|
|
|
|
5
|
$self->getFrameIDs() unless exists $self->{frameIDs}; |
414
|
1
|
|
|
|
|
2
|
my $format = getformat($fname); |
415
|
1
|
50
|
|
|
|
3
|
return undef unless defined $format; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
#prepare the data |
418
|
1
|
|
|
|
|
2
|
my $args = $#$format; |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# encoding is not used yet |
421
|
1
|
|
|
|
|
1
|
my $encoding=0; |
422
|
1
|
50
|
33
|
|
|
7
|
my $defenc=1 if (($#data == ($args - 1)) && ($format->[0]->{name} eq "_encoding")); |
423
|
1
|
50
|
33
|
|
|
6
|
return 0 unless $#data == $args || defined $defenc; |
424
|
|
|
|
|
|
|
|
425
|
1
|
|
|
|
|
1
|
my $datastring=""; |
426
|
1
|
|
|
|
|
2
|
foreach my $fs (@$format) { |
427
|
2
|
100
|
|
|
|
5
|
if ($fs->{name} eq "_encoding") { |
428
|
1
|
50
|
|
|
|
3
|
$encoding = shift @data unless $defenc; |
429
|
1
|
50
|
|
|
|
3
|
warn "Encoding of text not supported yet\n" if $encoding!=0; |
430
|
1
|
|
|
|
|
1
|
$encoding = 0; # other values are not used yet, so let's not write them in a tag |
431
|
1
|
|
|
|
|
10
|
$datastring .= chr($encoding); |
432
|
1
|
|
|
|
|
2
|
next; |
433
|
|
|
|
|
|
|
} |
434
|
1
|
|
|
|
|
2
|
my $d = shift @data; |
435
|
1
|
50
|
|
|
|
7
|
if ($fs->{len}>0) { |
|
|
50
|
|
|
|
|
|
436
|
0
|
|
|
|
|
0
|
$d = substr $d, 0, $fs->{len}; |
437
|
|
|
|
|
|
|
}elsif ($fs->{len}==0) { |
438
|
0
|
|
|
|
|
0
|
$d .= chr(0); |
439
|
|
|
|
|
|
|
} |
440
|
1
|
|
|
|
|
2
|
$datastring .= $d; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
#encrypt or compress data if this is wanted |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
#prepare header |
445
|
1
|
|
|
|
|
2
|
my $flags = 0; |
446
|
1
|
|
|
|
|
4
|
my $header = $fname . pack("Nn", length ($datastring), $flags); |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
#add frame to tag_data |
449
|
1
|
|
|
|
|
2
|
my $pos =length($self->{tag_data})+1; |
450
|
1
|
|
|
|
|
2
|
$self->{tag_data} .= $header . $datastring; |
451
|
|
|
|
|
|
|
|
452
|
1
|
50
|
|
|
|
3
|
if (exists $self->{frames}->{$fname}) { |
453
|
0
|
|
|
|
|
0
|
$fname .= '01'; |
454
|
0
|
|
|
|
|
0
|
while (exists $self->{frames}->{$fname}) { |
455
|
0
|
|
|
|
|
0
|
$fname++; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
1
|
|
|
|
|
1
|
printf ("%s @ %s for %s (%x)\n", $fname, $pos, length($datastring), $flags) if 0==1; |
459
|
1
|
|
|
|
|
5
|
$self->{frames}->{$fname} = {start=>$pos+10, size=>length($datastring), flags=>$flags}; |
460
|
|
|
|
|
|
|
|
461
|
1
|
|
|
|
|
11
|
return 1; |
462
|
|
|
|
|
|
|
} |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=pod |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=item change_frame() |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
$id3v2->change_frame($fname, @data); |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
Change an existing frame, which is identified by its |
471
|
|
|
|
|
|
|
short name $fname. @data must be same as in add_frame; |
472
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
=cut |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub change_frame { |
476
|
0
|
|
|
0
|
1
|
0
|
my ($self, $fname, @data) = @_; |
477
|
0
|
0
|
|
|
|
0
|
$self->getFrameIDs() unless exists $self->{frameIDs}; |
478
|
0
|
0
|
|
|
|
0
|
return undef unless exists $self->{frames}->{$fname}; |
479
|
|
|
|
|
|
|
|
480
|
0
|
|
|
|
|
0
|
$self->remove_frame($fname); |
481
|
0
|
|
|
|
|
0
|
$self->add_frame($fname, @data); |
482
|
|
|
|
|
|
|
|
483
|
0
|
|
|
|
|
0
|
return 0; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
=pod |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=item remove_frame() |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
$id3v2->remove_frame($fname); |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Remove an existing frame. $fname is the short name of a frame, |
493
|
|
|
|
|
|
|
eg as returned by C. |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
=cut |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub remove_frame { |
498
|
1
|
|
|
1
|
1
|
7
|
my ($self, $fname) = @_; |
499
|
1
|
50
|
|
|
|
6
|
$self->getFrameIDs() unless exists $self->{frameIDs}; |
500
|
1
|
50
|
|
|
|
4
|
return undef unless exists $self->{frames}->{$fname}; |
501
|
1
|
|
|
|
|
3
|
my $start = $self->{frames}->{$fname}->{start}-10; |
502
|
1
|
|
|
|
|
3
|
my $size = $self->{frames}->{$fname}->{size}+10; |
503
|
1
|
|
|
|
|
2
|
substr ($self->{tag_data}, $start, $size) = ""; |
504
|
1
|
|
|
|
|
4
|
delete $self->{frames}->{$fname}; |
505
|
1
|
|
|
|
|
2
|
foreach (keys %{$self->{frames}}) { |
|
1
|
|
|
|
|
5
|
|
506
|
3
|
50
|
|
|
|
9
|
$self->{frames}->{$_}->{start} -= $size |
507
|
|
|
|
|
|
|
if ($self->{frames}->{$_}->{start}>$start); |
508
|
|
|
|
|
|
|
} |
509
|
1
|
|
|
|
|
3
|
return 1; |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=pod |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=item supported_frames() |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
%frames = $id3v2->supported_frames(); |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
Returns a hash with all supported frames. The keys of the |
519
|
|
|
|
|
|
|
hash are the short names of the supported frames, the |
520
|
|
|
|
|
|
|
according values are the long (english) names of the frames. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=cut |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
sub supported_frames { |
525
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
526
|
|
|
|
|
|
|
|
527
|
0
|
|
|
|
|
0
|
my (%tags, $fname, $lname); |
528
|
0
|
|
|
|
|
0
|
while ( ($fname, $lname) = each %long_names) { |
529
|
0
|
0
|
|
|
|
0
|
$tags{$fname} = $lname if getformat($fname, "quiet"); |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
0
|
|
|
|
|
0
|
return \%tags; |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
=pod |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
=item what_data() |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
@data = $id3v2->what_data($fname); |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
Returns for a frame the needed data fields to write this tag. |
542
|
|
|
|
|
|
|
At this moment only the internal field names are returned, |
543
|
|
|
|
|
|
|
without any additional information about the data format of |
544
|
|
|
|
|
|
|
this field. Names beginning with an underscore (normally '_data') |
545
|
|
|
|
|
|
|
can contain binary data. |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
This will change hopefully later on... |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=cut |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub what_data{ |
552
|
0
|
|
|
0
|
1
|
0
|
my $self=shift; |
553
|
0
|
|
|
|
|
0
|
my $format = getformat(shift, "quiet"); |
554
|
0
|
0
|
|
|
|
0
|
return unless defined $format; |
555
|
0
|
|
|
|
|
0
|
my @data; |
556
|
|
|
|
|
|
|
|
557
|
0
|
|
|
|
|
0
|
foreach (@$format) { |
558
|
0
|
|
|
|
|
0
|
push @data, $_->{name}; |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
0
|
|
|
|
|
0
|
return \@data; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
################## |
565
|
|
|
|
|
|
|
## |
566
|
|
|
|
|
|
|
## internal subs |
567
|
|
|
|
|
|
|
## |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
# This sub tries to read the header of an ID3v2 tag and checks for the right header |
570
|
|
|
|
|
|
|
# identification for the tag. It reads the version number of the tag, the tag size |
571
|
|
|
|
|
|
|
# and the flags. |
572
|
|
|
|
|
|
|
# Returns true if it finds a ID3v2.3 header, false otherwise. |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
sub read_header { |
575
|
2
|
|
|
2
|
0
|
9
|
my ($self, $header) = @_; |
576
|
2
|
|
|
|
|
3
|
my %params; |
577
|
|
|
|
|
|
|
|
578
|
2
|
50
|
|
|
|
6
|
if (substr ($header,0,3) eq "ID3") { |
579
|
|
|
|
|
|
|
# extract the header data |
580
|
2
|
|
|
|
|
7
|
my ($version, $subversion, $pflags) = unpack ("x3CCC", $header); |
581
|
|
|
|
|
|
|
# check the version |
582
|
2
|
50
|
33
|
|
|
12
|
if ($version != 3 || $subversion != 0) { |
583
|
0
|
|
|
|
|
0
|
warn "Unknown ID3v2-Tag version: V$version.$subversion\n"; |
584
|
0
|
|
|
|
|
0
|
return 0; |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
# get the tag size |
587
|
2
|
|
|
|
|
3
|
my $size=0; |
588
|
2
|
|
|
|
|
4
|
foreach (unpack("x6C4", $header)) { |
589
|
8
|
|
|
|
|
10
|
$size = ($size << 7) + $_; |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
# check the flags |
592
|
2
|
|
|
|
|
3
|
my $flags={}; |
593
|
2
|
|
|
|
|
3
|
my $unknownFlag=0; |
594
|
2
|
|
|
|
|
3
|
my $i=0; |
595
|
2
|
|
|
|
|
13
|
foreach (split (//, unpack('b8',pack('v',$pflags)))) { |
596
|
16
|
100
|
|
|
|
22
|
if ($_) { |
597
|
2
|
50
|
|
|
|
4
|
if ($i==7) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
598
|
2
|
|
|
|
|
5
|
$flags->{unsync}=1; |
599
|
|
|
|
|
|
|
} elsif ($i==6) { |
600
|
0
|
|
|
|
|
0
|
$flags->{extheader}=1; |
601
|
|
|
|
|
|
|
} elsif ($i==5) { |
602
|
0
|
|
|
|
|
0
|
$flags->{experimental}=1; |
603
|
0
|
|
|
|
|
0
|
warn "Flag: Experimental not supported\n But trying to read the tag...\n"; |
604
|
|
|
|
|
|
|
} else { |
605
|
0
|
|
|
|
|
0
|
$unknownFlag = 1; |
606
|
0
|
|
|
|
|
0
|
warn "Unsupported flag: Bit $i set in Header-Flags\n"; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
} |
609
|
16
|
|
|
|
|
13
|
$i++; |
610
|
|
|
|
|
|
|
} |
611
|
2
|
50
|
|
|
|
6
|
return 0 if $unknownFlag; |
612
|
2
|
|
|
|
|
6
|
$self->{version} = "V$version.$subversion"; |
613
|
2
|
|
|
|
|
4
|
$self->{tagsize} = $size; |
614
|
2
|
|
|
|
|
3
|
$self->{flags} = $flags; |
615
|
2
|
|
|
|
|
7
|
return 1; |
616
|
|
|
|
|
|
|
} |
617
|
0
|
|
|
|
|
0
|
return 0; # no ID3v2-Tag found |
618
|
|
|
|
|
|
|
} |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
# Reads the extended header and adapts the internal counter for the start of the |
621
|
|
|
|
|
|
|
# frame data. Ignores the rest of the ext. header (as CRC data). |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub read_ext_header { |
624
|
0
|
|
|
0
|
0
|
0
|
my ($self, $ext_header) = @_; |
625
|
|
|
|
|
|
|
# flags, padding and crc ignored at this time |
626
|
0
|
|
|
|
|
0
|
my $size = unpack("N", $ext_header); |
627
|
0
|
|
|
|
|
0
|
$self->{frame_start} += $size+4; # 4 bytes extra for the size |
628
|
0
|
|
|
|
|
0
|
return 1; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
# Main sub for getting data from a frame. |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
sub extract_data { |
635
|
3
|
|
|
3
|
0
|
4
|
my ($data, $format) = @_; |
636
|
3
|
|
|
|
|
3
|
my ($rule, $found,$encoding, $result); |
637
|
|
|
|
|
|
|
|
638
|
3
|
|
|
|
|
6
|
foreach $rule (@$format) { |
639
|
8
|
|
|
|
|
8
|
$encoding=0; |
640
|
|
|
|
|
|
|
# get the data |
641
|
8
|
100
|
|
|
|
20
|
if ( $rule->{len} == 0 ) { |
|
|
100
|
|
|
|
|
|
642
|
1
|
50
|
33
|
|
|
9
|
if (exists $rule->{encoded} && $encoding !=0) { |
643
|
0
|
|
|
|
|
0
|
($found, $data) = split /\x00\x00/, $data, 2; |
644
|
|
|
|
|
|
|
} else { |
645
|
1
|
|
|
|
|
4
|
($found, $data) = split /\x00/, $data, 2; |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
} elsif ($rule->{len} == -1) { |
648
|
3
|
|
|
|
|
7
|
($found, $data) = ($data, ""); |
649
|
|
|
|
|
|
|
} else { |
650
|
4
|
|
|
|
|
7
|
$found = substr $data, 0,$rule->{len}; |
651
|
4
|
|
|
|
|
36
|
substr ($data, 0,$rule->{len}) = ''; |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# was data found? |
655
|
8
|
50
|
33
|
|
|
37
|
unless (defined $found && $found ne "") { |
656
|
0
|
|
|
|
|
0
|
$found = ""; |
657
|
0
|
0
|
|
|
|
0
|
$found = $rule->{default} if exists $rule->{default}; |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
# work with data |
660
|
8
|
100
|
|
|
|
13
|
if ($rule->{name} eq "_encoding") { |
661
|
3
|
|
|
|
|
7
|
$encoding=unpack ("C", $found); |
662
|
|
|
|
|
|
|
} else { |
663
|
5
|
50
|
66
|
|
|
19
|
if (exists $rule->{encoded} && $encoding != 0) { |
664
|
|
|
|
|
|
|
# decode data |
665
|
0
|
|
|
|
|
0
|
warn "Encoding not supported yet: found in $rule->{name}\n"; |
666
|
0
|
|
|
|
|
0
|
next; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
5
|
50
|
|
|
|
12
|
$found = $rule->{func}->($found) if (exists $rule->{func}); |
670
|
|
|
|
|
|
|
|
671
|
5
|
50
|
|
|
|
9
|
unless (exists $rule->{data}) { |
672
|
5
|
|
|
|
|
7
|
$found =~ s/[\x00]+$//; # some progs pad text fields with \x00 |
673
|
5
|
|
|
|
|
6
|
$found =~ s![\x00]! / !g; # some progs use \x00 inside a text string to seperate text strings |
674
|
5
|
|
|
|
|
9
|
$found =~ s/ +$//; # no trailing spaces after the text |
675
|
|
|
|
|
|
|
} |
676
|
|
|
|
|
|
|
|
677
|
5
|
50
|
|
|
|
15
|
if (exists $rule->{re2}) { |
678
|
0
|
|
|
|
|
0
|
while (my ($pat, $rep) = each %{$rule->{re2}}) { |
|
0
|
|
|
|
|
0
|
|
679
|
0
|
|
|
|
|
0
|
$found =~ s/$pat/$rep/gis; |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
# store data |
683
|
5
|
|
|
|
|
16
|
$result->{$rule->{name}}=$found; |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
} |
686
|
3
|
|
|
|
|
6
|
return $result; |
687
|
|
|
|
|
|
|
} |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
#Searches for a format string for a specified frame. format strings exist for |
690
|
|
|
|
|
|
|
#specific frames, or also for a group of frames. Specific format strings have |
691
|
|
|
|
|
|
|
#precedence over general ones. |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
sub getformat { |
694
|
4
|
|
|
4
|
0
|
5
|
my $fname = shift; |
695
|
|
|
|
|
|
|
# to be quiet if called from supported_frames or what_data |
696
|
4
|
|
|
|
|
4
|
my $quiet = shift; |
697
|
4
|
|
|
|
|
5
|
my $fnamecopy = $fname; |
698
|
4
|
|
|
|
|
8
|
while ($fname ne "") { |
699
|
13
|
100
|
|
|
|
36
|
return $format{$fname} if exists $format{$fname}; |
700
|
9
|
|
|
|
|
13
|
substr ($fname, -1) =""; #delete last char |
701
|
|
|
|
|
|
|
} |
702
|
0
|
0
|
|
|
|
0
|
warn "Unknown Frame-Format found: $fnamecopy\n" unless defined $quiet; |
703
|
0
|
|
|
|
|
0
|
return undef; |
704
|
|
|
|
|
|
|
} |
705
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
#Reads the flags of a frame, and returns a hash with all flags as keys, and |
707
|
|
|
|
|
|
|
#0/1 as value for unset/set. |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
sub check_flags { |
710
|
|
|
|
|
|
|
# how to detect unknown flags? |
711
|
3
|
|
|
3
|
0
|
4
|
my ($flags,$fname)=@_; |
712
|
3
|
|
|
|
|
3
|
my %flags; |
713
|
3
|
|
|
|
|
42
|
my @flags = split (//, reverse unpack('b16',pack('v',$flags))); |
714
|
3
|
|
|
|
|
7
|
$flags{tag_preserv}= $flags[0]; |
715
|
3
|
|
|
|
|
6
|
$flags{file_preserv}= $flags[1]; |
716
|
3
|
|
|
|
|
5
|
$flags{read_only}= $flags[2]; |
717
|
3
|
|
|
|
|
4
|
$flags{compression}= $flags[8]; |
718
|
3
|
|
|
|
|
4
|
$flags{encryption}= $flags[9]; |
719
|
3
|
|
|
|
|
4
|
$flags{groupid}= $flags[10]; |
720
|
3
|
|
|
|
|
12
|
return \%flags; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
0
|
|
|
0
|
|
|
sub DESTROY { |
724
|
|
|
|
|
|
|
} |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
################################## |
727
|
|
|
|
|
|
|
# |
728
|
|
|
|
|
|
|
# How to store frame formats? |
729
|
|
|
|
|
|
|
# |
730
|
|
|
|
|
|
|
# format{fname}->[i]->{xxx} |
731
|
|
|
|
|
|
|
# |
732
|
|
|
|
|
|
|
# i - von 0 - ... in der Reihenfolge, in der die bestandteile des frames ausgelesen werden sollen |
733
|
|
|
|
|
|
|
# |
734
|
|
|
|
|
|
|
# xxx = * {len}=s reg expr pattern - gibt an, wieviele zeichen gelesen werden sollen |
735
|
|
|
|
|
|
|
# spezialfälle: 0 = lesen bis \x00 |
736
|
|
|
|
|
|
|
# -1 = alle restlichen zeichen lesen |
737
|
|
|
|
|
|
|
# * {name}=s - name unter der bestandteil an benutzer zurueckgegeben wird, aber: |
738
|
|
|
|
|
|
|
# name = encoding - wird nicht zurueckgegeben, sondern setzt encoding fuer diesen frame |
739
|
|
|
|
|
|
|
# * {encoded}=1 - das ergebnis laut encoding codieren |
740
|
|
|
|
|
|
|
# * {func}=s - reference auf funktion. diese funktion erhält den gefunden Wert als Argument, und |
741
|
|
|
|
|
|
|
# einen neuen Wert zurückgeben |
742
|
|
|
|
|
|
|
# * {re2}=s - hash, das reg expr enthaelt, die vor rueckgabe an benutzer angewendet werden (anwendung |
743
|
|
|
|
|
|
|
# nachdem {func} aufgerufen wurde) |
744
|
|
|
|
|
|
|
# |
745
|
|
|
|
|
|
|
# * {data}=1 - gibt an, dass binary data im feld enthalten sein kann (auch nachdem evtl. func aufgerufen wurde) |
746
|
|
|
|
|
|
|
# |
747
|
|
|
|
|
|
|
# * {default}=s - default fuer feld, falls feld leer oder nicht gefunden |
748
|
|
|
|
|
|
|
# |
749
|
|
|
|
|
|
|
# TCON example: |
750
|
|
|
|
|
|
|
# |
751
|
|
|
|
|
|
|
# $format{TCON}->[0]->{len} = 1 |
752
|
|
|
|
|
|
|
# ->{name} = 'encoding' |
753
|
|
|
|
|
|
|
# ->{data} = 1 |
754
|
|
|
|
|
|
|
# ->[1]->{len} = -1 |
755
|
|
|
|
|
|
|
# ->{name} = 'text' |
756
|
|
|
|
|
|
|
# ->{func} = \&TCON |
757
|
|
|
|
|
|
|
# ->{re2} = {'\(RX\)'=>'Remix', '\(CR\)'=>'Cover'} |
758
|
|
|
|
|
|
|
# |
759
|
|
|
|
|
|
|
# Fragen / Ideen |
760
|
|
|
|
|
|
|
# |
761
|
|
|
|
|
|
|
# * Tags duerfen mehrfach vorkommen. Wie werden die einzelnen verschiedenen Tags getrennt voneinander |
762
|
|
|
|
|
|
|
# gespeichert? $result->{COMM}->[i]=... falls mehrere, sonst string direkt in $result->{COMM} speichern |
763
|
|
|
|
|
|
|
# |
764
|
|
|
|
|
|
|
# * Frame size faelschlicherweise im Tag size format gespeichert? kann das automatisch erkannt werden? |
765
|
|
|
|
|
|
|
# |
766
|
|
|
|
|
|
|
############################ |
767
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
sub toNumber { |
769
|
0
|
|
|
0
|
0
|
|
return unpack ("C", shift); |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
sub APIC { |
773
|
0
|
|
|
0
|
0
|
|
my $byte = shift; |
774
|
0
|
|
|
|
|
|
my $index = unpack ("C", $byte); |
775
|
0
|
|
|
|
|
|
my @pictypes = ("Other", "32x32 pixels 'file icon' (PNG only)", "Other file icon", |
776
|
|
|
|
|
|
|
"Cover (front)", "Cover (back)", "Leaflet page", |
777
|
|
|
|
|
|
|
"Media (e.g. lable side of CD)", "Lead artist/lead performer/soloist", |
778
|
|
|
|
|
|
|
"Artist/performer", "Conductor", "Band/Orchestra", "Composer", |
779
|
|
|
|
|
|
|
"Lyricist/text writer", "Recording Location", "During recording", |
780
|
|
|
|
|
|
|
"During performance", "Movie/video screen capture", |
781
|
|
|
|
|
|
|
"A bright coloured fish", "Illustration", "Band/artist logotype", |
782
|
|
|
|
|
|
|
"Publisher/Studio logotype"); |
783
|
0
|
0
|
|
|
|
|
return "" if $index > $#pictypes; |
784
|
0
|
|
|
|
|
|
return $pictypes[$index]; |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
sub TCON { |
788
|
0
|
|
|
0
|
0
|
|
my $data = shift; |
789
|
0
|
0
|
|
|
|
|
if ($data =~ /\((\d+)\)/) { |
790
|
0
|
|
|
|
|
|
$data =~ s/\((\d+)\)/MP3::TAG::ID3v1::genres($1)/e; |
|
0
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
} |
792
|
0
|
|
|
|
|
|
return $data; |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
sub TFLT { |
796
|
0
|
|
|
0
|
0
|
|
my $text = shift; |
797
|
0
|
0
|
|
|
|
|
return "" if $text eq ""; |
798
|
0
|
|
|
|
|
|
$text =~ s/MPG/MPEG Audio/; |
799
|
0
|
|
|
|
|
|
$text =~ s/VQF/Transform-domain Weighted Interleave Vector Quantization/; |
800
|
0
|
|
|
|
|
|
$text =~ s/PCM/Pulse Code Modulated audio/; |
801
|
0
|
|
|
|
|
|
$text =~ s/AAC/Advanced audio compression/; |
802
|
0
|
0
|
|
|
|
|
unless ($text =~ s!/1!MPEG 1/2 layer I!) { |
803
|
0
|
0
|
|
|
|
|
unless ($text =~ s!/2!MPEG 1/2 layer II!) { |
804
|
0
|
0
|
|
|
|
|
unless ($text =~ s!/3!MPEG 1/2 layer III!) { |
805
|
0
|
|
|
|
|
|
$text =~ s!/2\.5!MPEG 2.5!; |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
} |
808
|
|
|
|
|
|
|
} |
809
|
0
|
|
|
|
|
|
return $text; |
810
|
|
|
|
|
|
|
} |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
sub TMED { |
813
|
0
|
|
|
0
|
0
|
|
my $text = shift; |
814
|
0
|
0
|
|
|
|
|
return "" if $text eq ""; |
815
|
0
|
0
|
|
|
|
|
if ($text =~ /(?
|
816
|
0
|
|
|
|
|
|
my $found = $1; |
817
|
0
|
0
|
0
|
|
|
|
if ($found =~ s!DIG!Other digital Media! || |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
818
|
|
|
|
|
|
|
$found =~ /DAT/ || |
819
|
|
|
|
|
|
|
$found =~ /DCC/ || |
820
|
|
|
|
|
|
|
$found =~ /DVD/ || |
821
|
|
|
|
|
|
|
$found =~ s!MD!MiniDisc! || |
822
|
|
|
|
|
|
|
$found =~ s!LD!Laserdisc!) { |
823
|
0
|
|
|
|
|
|
$found =~ s!/A!, Analog Transfer from Audio!; |
824
|
|
|
|
|
|
|
} |
825
|
|
|
|
|
|
|
elsif ($found =~ /CD/) { |
826
|
0
|
|
|
|
|
|
$found =~ s!/DD!, DDD!; |
827
|
0
|
|
|
|
|
|
$found =~ s!/AD!, ADD!; |
828
|
0
|
|
|
|
|
|
$found =~ s!/AA!, AAD!; |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
elsif ($found =~ s!ANA!Other analog Media!) { |
831
|
0
|
|
|
|
|
|
$found =~ s!/WAC!, Wax cylinder!; |
832
|
0
|
|
|
|
|
|
$found =~ s!/8CA!, 8-track tape cassette!; |
833
|
|
|
|
|
|
|
} |
834
|
|
|
|
|
|
|
elsif ($found =~ s!TT!Turntable records!) { |
835
|
0
|
|
|
|
|
|
$found =~ s!/33!, 33.33 rpm!; |
836
|
0
|
|
|
|
|
|
$found =~ s!/45!, 45 rpm!; |
837
|
0
|
|
|
|
|
|
$found =~ s!/71!, 71.29 rpm!; |
838
|
0
|
|
|
|
|
|
$found =~ s!/76!, 76.59 rpm!; |
839
|
0
|
|
|
|
|
|
$found =~ s!/78!, 78.26 rpm!; |
840
|
0
|
|
|
|
|
|
$found =~ s!/80!, 80 rpm!; |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
elsif ($found =~ s!TV!Television! || |
843
|
|
|
|
|
|
|
$found =~ s!VID!Video! || |
844
|
|
|
|
|
|
|
$found =~ s!RAD!Radio!) { |
845
|
0
|
|
|
|
|
|
$found =~ s!/!, !; |
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
elsif ($found =~ s!TEL!Telephone!) { |
848
|
0
|
|
|
|
|
|
TEL Telephone |
849
|
|
|
|
|
|
|
$found =~ s!/I!, ISDN!; |
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
elsif ($found =~ s!REE!Reel! || |
852
|
|
|
|
|
|
|
$found =~ s!MC!MC (normal cassette)!) { |
853
|
0
|
|
|
|
|
|
$found =~ s!/4!, 4.75 cm/s (normal speed for a two sided cassette)!; |
854
|
0
|
|
|
|
|
|
$found =~ s!/9!, 9.5 cm/s!; |
855
|
0
|
|
|
|
|
|
$found =~ s!/19!, 19 cm/s!; |
856
|
0
|
|
|
|
|
|
$found =~ s!/38!, 38 cm/s!; |
857
|
0
|
|
|
|
|
|
$found =~ s!/76!, 76 cm/s!; |
858
|
0
|
|
|
|
|
|
$found =~ s!/I!, Type I cassette (ferric/normal)!; |
859
|
0
|
|
|
|
|
|
$found =~ s!/II!, Type II cassette (chrome)!; |
860
|
0
|
|
|
|
|
|
$found =~ s!/III!, Type III cassette (ferric chrome)!; |
861
|
0
|
|
|
|
|
|
$found =~ s!/IV!, Type IV cassette (metal)!; |
862
|
|
|
|
|
|
|
} |
863
|
0
|
|
|
|
|
|
$text =~ s/(?
|
864
|
|
|
|
|
|
|
} |
865
|
0
|
|
|
|
|
|
$text =~ s/\(\(/\(/g; |
866
|
0
|
|
|
|
|
|
$text =~ s/ / /g; |
867
|
|
|
|
|
|
|
|
868
|
0
|
|
|
|
|
|
return $text; |
869
|
|
|
|
|
|
|
} |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
BEGIN { |
872
|
1
|
|
|
1
|
|
5
|
my $encoding ={len=>1, name=>"_encoding", data=>1}; |
873
|
1
|
|
|
|
|
10
|
my $text_enc ={len=>-1, name=>"Text", encoded=>1}; |
874
|
1
|
|
|
|
|
3
|
my $text ={len=>-1, name=>"Text"}; |
875
|
1
|
|
|
|
|
2
|
my $description ={len=>0, name=>"Description", encoded=>1}; |
876
|
1
|
|
|
|
|
2
|
my $url ={len=>-1, name=>"URL"}; |
877
|
1
|
|
|
|
|
3
|
my $data ={len=>-1, name=>"_Data", data=>1}; |
878
|
1
|
|
|
|
|
2
|
my $language ={len=>3, name=>"Language"}; |
879
|
|
|
|
|
|
|
|
880
|
1
|
|
|
|
|
68
|
%format = ( |
881
|
|
|
|
|
|
|
#AENC => [], |
882
|
|
|
|
|
|
|
APIC => [$encoding, {len=>0, name=>"MIME type"}, |
883
|
|
|
|
|
|
|
{len=>1, name=>"Picture Type", func=>\&APIC}, $description, $data], |
884
|
|
|
|
|
|
|
COMM => [$encoding, $language, {name=>"short", len=>0, encoding=>1}, $text_enc], |
885
|
|
|
|
|
|
|
#COMR => [], |
886
|
|
|
|
|
|
|
ENCR => [{len=>0, name=>"Owner ID"}, {len=>0, name=>"Method symbol"}, |
887
|
|
|
|
|
|
|
$data], |
888
|
|
|
|
|
|
|
#EQUA => [], |
889
|
|
|
|
|
|
|
#ETCO => [], |
890
|
|
|
|
|
|
|
GEOB => [$encoding, {len=>0, name=>"MIME type"}, |
891
|
|
|
|
|
|
|
{len=>0, name=>"Filename"}, $description, $data], |
892
|
|
|
|
|
|
|
#GRID => [], |
893
|
|
|
|
|
|
|
IPLS => [$encoding, $text_enc], |
894
|
|
|
|
|
|
|
LINK => [{len=>3, name=>"_ID"}, {len=>0, name=>"URL"}, $text], |
895
|
|
|
|
|
|
|
MCDI => [$data], |
896
|
|
|
|
|
|
|
#MLLT => [], |
897
|
|
|
|
|
|
|
OWNE => [$encoding, {len=>0, name=>"Price payed"}, |
898
|
|
|
|
|
|
|
{len=>0, name=>"Date of purchase"}, $text], |
899
|
|
|
|
|
|
|
PCNT => [{len=>-1, name=>"Text", func=>\&toNumber}], |
900
|
|
|
|
|
|
|
POPM => [{len=>0, name=>"URL"},{len=>1, name=>"Rating", func=>\&toNumber}, $data], |
901
|
|
|
|
|
|
|
#POSS => [], |
902
|
|
|
|
|
|
|
PRIV => [{len=>0, name=>"Text"}, $data], |
903
|
|
|
|
|
|
|
#RBUF => [], |
904
|
|
|
|
|
|
|
#SYCT => [], |
905
|
|
|
|
|
|
|
#SYLT => [], |
906
|
|
|
|
|
|
|
T => [$encoding, $text_enc], |
907
|
|
|
|
|
|
|
TCON => [$encoding, {%$text_enc, func=>\&TCON, re2=>{'\(RX\)'=>'Remix', '\(CR\)'=>'Cover'}}], |
908
|
|
|
|
|
|
|
TCOP => [$encoding, {%$text_enc, re2 => {'^'=>'(C) '}}], |
909
|
|
|
|
|
|
|
TFLT => [$encoding, {%$text_enc, func=>\&TFLT}], |
910
|
|
|
|
|
|
|
TMED => [$encoding, {%$text_enc, func=>\&TMED}], |
911
|
|
|
|
|
|
|
TXXX => [$encoding, $description, $text], |
912
|
|
|
|
|
|
|
USER => [$encoding, $language, $text], |
913
|
|
|
|
|
|
|
USLT => [$encoding, $language, $description, $text], |
914
|
|
|
|
|
|
|
#RVAD => [], |
915
|
|
|
|
|
|
|
#RVRB => [], |
916
|
|
|
|
|
|
|
W => [$url], |
917
|
|
|
|
|
|
|
WXXX => [$encoding, $description, $url], |
918
|
|
|
|
|
|
|
UFID => [{%$description, name=>"Text"}, $data], |
919
|
|
|
|
|
|
|
); |
920
|
|
|
|
|
|
|
|
921
|
1
|
|
|
|
|
81
|
%long_names = ( |
922
|
|
|
|
|
|
|
AENC => "Audio encryption", |
923
|
|
|
|
|
|
|
APIC => "Attached picture", |
924
|
|
|
|
|
|
|
COMM => "Comments", |
925
|
|
|
|
|
|
|
COMR => "Commercial frame", |
926
|
|
|
|
|
|
|
ENCR => "Encryption method registration", |
927
|
|
|
|
|
|
|
EQUA => "Equalization", |
928
|
|
|
|
|
|
|
ETCO => "Event timing codes", |
929
|
|
|
|
|
|
|
GEOB => "General encapsulated object", |
930
|
|
|
|
|
|
|
GRID => "Group identification registration", |
931
|
|
|
|
|
|
|
IPLS => "Involved people list", |
932
|
|
|
|
|
|
|
LINK => "Linked information", |
933
|
|
|
|
|
|
|
MCDI => "Music CD identifier", |
934
|
|
|
|
|
|
|
MLLT => "MPEG location lookup table", |
935
|
|
|
|
|
|
|
OWNE => "Ownership frame", |
936
|
|
|
|
|
|
|
PRIV => "Private frame", |
937
|
|
|
|
|
|
|
PCNT => "Play counter", |
938
|
|
|
|
|
|
|
POPM => "Popularimeter", |
939
|
|
|
|
|
|
|
POSS => "Position synchronisation frame", |
940
|
|
|
|
|
|
|
RBUF => "Recommended buffer size", |
941
|
|
|
|
|
|
|
RVAD => "Relative volume adjustment", |
942
|
|
|
|
|
|
|
RVRB => "Reverb", |
943
|
|
|
|
|
|
|
SYLT => "Synchronized lyric/text", |
944
|
|
|
|
|
|
|
SYTC => "Synchronized tempo codes", |
945
|
|
|
|
|
|
|
TALB => "Album/Movie/Show title", |
946
|
|
|
|
|
|
|
TBPM => "BPM (beats per minute)", |
947
|
|
|
|
|
|
|
TCOM => "Composer", |
948
|
|
|
|
|
|
|
TCON => "Content type", |
949
|
|
|
|
|
|
|
TCOP => "Copyright message", |
950
|
|
|
|
|
|
|
TDAT => "Date", |
951
|
|
|
|
|
|
|
TDLY => "Playlist delay", |
952
|
|
|
|
|
|
|
TENC => "Encoded by", |
953
|
|
|
|
|
|
|
TEXT => "Lyricist/Text writer", |
954
|
|
|
|
|
|
|
TFLT => "File type", |
955
|
|
|
|
|
|
|
TIME => "Time", |
956
|
|
|
|
|
|
|
TIT1 => "Content group description", |
957
|
|
|
|
|
|
|
TIT2 => "Title/songname/content description", |
958
|
|
|
|
|
|
|
TIT3 => "Subtitle/Description refinement", |
959
|
|
|
|
|
|
|
TKEY => "Initial key", |
960
|
|
|
|
|
|
|
TLAN => "Language(s)", |
961
|
|
|
|
|
|
|
TLEN => "Length", |
962
|
|
|
|
|
|
|
TMED => "Media type", |
963
|
|
|
|
|
|
|
TOAL => "Original album/movie/show title", |
964
|
|
|
|
|
|
|
TOFN => "Original filename", |
965
|
|
|
|
|
|
|
TOLY => "Original lyricist(s)/text writer(s)", |
966
|
|
|
|
|
|
|
TOPE => "Original artist(s)/performer(s)", |
967
|
|
|
|
|
|
|
TORY => "Original release year", |
968
|
|
|
|
|
|
|
TOWN => "File owner/licensee", |
969
|
|
|
|
|
|
|
TPE1 => "Lead performer(s)/Soloist(s)", |
970
|
|
|
|
|
|
|
TPE2 => "Band/orchestra/accompaniment", |
971
|
|
|
|
|
|
|
TPE3 => "Conductor/performer refinement", |
972
|
|
|
|
|
|
|
TPE4 => "Interpreted, remixed, or otherwise modified by", |
973
|
|
|
|
|
|
|
TPOS => "Part of a set", |
974
|
|
|
|
|
|
|
TPUB => "Publisher", |
975
|
|
|
|
|
|
|
TRCK => "Track number/Position in set", |
976
|
|
|
|
|
|
|
TRDA => "Recording dates", |
977
|
|
|
|
|
|
|
TRSN => "Internet radio station name", |
978
|
|
|
|
|
|
|
TRSO => "Internet radio station owner", |
979
|
|
|
|
|
|
|
TSIZ => "Size", |
980
|
|
|
|
|
|
|
TSRC => "ISRC (international standard recording code)", |
981
|
|
|
|
|
|
|
TSSE => "Software/Hardware and settings used for encoding", |
982
|
|
|
|
|
|
|
TYER => "Year", |
983
|
|
|
|
|
|
|
TXXX => "User defined text information frame", |
984
|
|
|
|
|
|
|
UFID => "Unique file identifier", |
985
|
|
|
|
|
|
|
USER => "Terms of use", |
986
|
|
|
|
|
|
|
USLT => "Unsychronized lyric/text transcription", |
987
|
|
|
|
|
|
|
WCOM => "Commercial information", |
988
|
|
|
|
|
|
|
WCOP => "Copyright/Legal information", |
989
|
|
|
|
|
|
|
WOAF => "Official audio file webpage", |
990
|
|
|
|
|
|
|
WOAR => "Official artist/performer webpage", |
991
|
|
|
|
|
|
|
WOAS => "Official audio source webpage", |
992
|
|
|
|
|
|
|
WORS => "Official internet radio station homepage", |
993
|
|
|
|
|
|
|
WPAY => "Payment", |
994
|
|
|
|
|
|
|
WPUB => "Publishers official webpage", |
995
|
|
|
|
|
|
|
WXXX => "User defined URL link frame", |
996
|
|
|
|
|
|
|
); |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
=pod |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
=head1 SEE ALSO |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
MP3::Tag, MP3::TAG::ID3v1 |
1004
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
ID3v2 standard - http://www.id3.org |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
=cut |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
1; |