File Coverage

blib/lib/MP3/TAG/ID3v2.pm
Criterion Covered Total %
statement 197 355 55.4
branch 58 160 36.2
condition 10 57 17.5
subroutine 15 26 57.6
pod 10 20 50.0
total 290 618 46.9


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;