| 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; |