File Coverage

blib/lib/MPEG/ID3v2Tag.pm
Criterion Covered Total %
statement 321 523 61.3
branch 77 170 45.2
condition 20 66 30.3
subroutine 65 109 59.6
pod 11 18 61.1
total 494 886 55.7


line stmt bran cond sub pod time code
1 2     2   2310 use strict;
  2         5  
  2         121  
2              
3             # This module may be copied under the same terms as perl itself.
4              
5             # This is a module for reading and writing ID3v2 tags.
6             #
7             # see the pod documentation at the bottom for details.
8              
9             package MPEG::ID3v2Tag;
10              
11 2     2   11 use vars qw($VERSION);
  2         4  
  2         114  
12             $VERSION = "0.39";
13              
14 2     2   19 use Carp;
  2         3  
  2         11184  
15              
16             # Constructor.
17             sub new {
18 3     3 1 1019 my ($package) = @_;
19              
20 3         14 my $self = {
21             FRAMES => [],
22             MAJORVER => 3,
23             MINORVER => 0,
24             };
25              
26 3         19 bless $self, $package;
27             }
28              
29             ####
30             # Return all the frame objects from the tag. Either an array or arrayref,
31             # depending on call context.
32             #
33             # If an arrayref is passed in, it will replace the current frame list.
34             ####
35             sub frames {
36 6     6 1 25 my ( $self, $newframes ) = @_;
37              
38 6 50       16 if ($newframes) {
39 0 0       0 croak "must pass an arrayref to frames" if ref($newframes) !~ 'ARRAY';
40 0         0 $self->{FRAMES} = $newframes;
41             }
42              
43 6 100       14 if (wantarray) {
44 3         5 return @{ $self->{FRAMES} };
  3         25  
45             }
46             else {
47 3         11 return $self->{FRAMES};
48             }
49             }
50              
51             ####
52             # Delete frame by frame id.
53             # If n is provided, deletes frame[n] of that particular frameid
54             # If n is not provided, deletes all frames with that frame id
55             ####
56             sub del_frame {
57 0     0 0 0 my ( $self, $frameid, $frameocc ) = @_;
58              
59 0         0 my $i = 0;
60 0         0 my @newframes;
61              
62 0 0       0 if ( defined $frameocc ) {
63 0 0 0     0 @newframes = grep {
64 0         0 $_->frameid() ne $frameid
65             or (
66             $_->frameid() eq $frameid
67             and $i++ != $frameocc
68             )
69             } $self->frames();
70             }
71             else {
72 0         0 @newframes = grep { $_->frameid() ne $frameid }
  0         0  
73             $self->frames();
74             }
75              
76 0         0 $self->frames( \@newframes );
77             }
78              
79             sub set_frame {
80 0     0 0 0 my $self = shift;
81 0         0 my $frameid = shift;
82              
83 0         0 $self->del_frame($frameid);
84 0         0 $self->add_frame($frameid, @_);
85             }
86              
87             ####
88             # Return the entire tag as a binary string.
89             ####
90             sub as_string {
91 3     3 1 292 my ($self) = @_;
92              
93 3         9 my $body = $self->data_as_string();
94              
95 3 50 33     69 if ( $self->flag_unsynchronization && $body =~ /\xff$/ ) {
96 0 0       0 $self->set_padding_size(256) if !$self->{PADDING_SIZE};
97             }
98              
99 3 100       63 if ( $self->flag_extended_header() ) {
100 1         6 $body = pack(
101             "NCCN", 6, # ext-header-size
102             0, # no flags (I don't support CRC)
103             $self->{PADDING_SIZE}
104             ) . $body;
105             }
106              
107 3 50       58 if ( $self->flag_unsynchronization ) {
108 0         0 $body = unsynchronize($body);
109             }
110              
111 3 0 33     21 if (exists($self->{ORIGINAL_SIZE}) && $self->{PADDING_SIZE} && !$self->{MANUAL_PADDING}) {
      33        
112             # attempt to preserve the original size of the tag by
113             # adjusting the padding size.
114 0         0 my $padlen = $self->{ORIGINAL_SIZE} - length($body);
115              
116 0 0       0 if ( $padlen >= 0 ) {
117 0         0 $self->set_padding_size($padlen);
118             }
119             }
120              
121 3 100       9 if ( $self->{PADDING_SIZE} ) {
122 1         5 $body .= "\0" x $self->{PADDING_SIZE};
123             }
124              
125 3         5 my $size = length($body);
126              
127 3         67 my $flags = ( ( !!$self->flag_unsynchronization() ) << 7 )
128             | ( ( !!$self->flag_extended_header() ) << 6 )
129             | ( ( !!$self->flag_experimental() ) << 5 );
130              
131 3         10 return "ID3" . pack( "CCCN", 3, 0, $flags, MungeSize($size) ) . $body;
132             }
133              
134             ####
135             # Set the amount of nul-padding to add at the end of the tag, in bytes.
136             ####
137             sub set_padding_size {
138 1     1 1 841 my ( $self, $size ) = @_;
139              
140 1         2 $self->{PADDING_SIZE} = $size;
141 1 50       33 $self->flag_extended_header(1) if ($size);
142              
143             # remember that the user set the padding, so we don't try to maintain
144             # the same tag size in as_string()
145 1         2 $self->{MANUAL_PADDING} = 1;
146             }
147              
148             ####
149             # Perform "unsynchronization" on the data. This takes things that
150             # look like mpeg syncs, 11111111 111xxxxx, and stuffs a zero
151             # in between the bytes. In addition, it stuffs a zero after every
152             # ff 00 combination.
153             #
154             # Note that this is A.F.U., because the standard doesn't say to stuff
155             # an extra zero in 11111111 00000000 111xxxxx. It says to stuff it
156             # after 11111111 00000000. That's broken. Seems to be what id3lib
157             # does also, though.
158             #
159             ####
160             sub unsynchronize {
161 0     0 0 0 my ($data) = @_;
162              
163             # zero stuff after ff 00
164 0         0 $data =~ s/\xff\0/\xff\0\0/g;
165              
166             # zero stuff between 11111111 111xxxxx
167 0         0 $data =~ s/\xff(?=[\xe0-\xff])/\xff\x00/g;
168              
169 0         0 return $data;
170             }
171              
172             ####
173             # Reverse the unsyncrhonization process.
174             ####
175             sub un_unsynchronize {
176 0     0 0 0 my ($data) = @_;
177              
178 0         0 $data =~ s/\xff\x00([\xe0-\xff])/\xff$1/g;
179 0         0 $data =~ s/\xff\x00\x00/\xff\x00/g;
180              
181 0         0 return $data;
182             }
183              
184             ####
185             # Return all the formatted frames as a big binary string.
186             ####
187             sub data_as_string {
188 3     3 0 5 my $self = shift;
189              
190 3         5 my $data = "";
191              
192 3         4 for my $frame (@{ $self->frames() }) {
  3         7  
193 10         37 $data .= $frame->as_string();
194             }
195              
196 3         17 return $data;
197             }
198              
199             ####
200             # Add a new frame's data to the id3 tag.
201             # You can either call this with an MPEG::ID3Frame object, or you can
202             # call it with a four-letter frame ID code plus arguments.
203             # If you send the frame code, add_frame will go looking for
204             # a new() method in the package MPEG::ID3Frame:: and call
205             # it with the specified arguments.
206             #
207             # Returns the frame that was added.
208             ####
209             sub add_frame {
210 22     22 1 79 my ( $self, $frame, @args ) = @_;
211              
212 22 100 66     82 if ( length($frame) == 4 && @args ) {
213             # they passed us a frame id and constructor arguments.
214             # Construct an object of the appropriate type and make it format
215             # its data.
216              
217 6         10 my $frameid = $frame;
218 6         11 my $package = "MPEG::ID3Frame::$frameid";
219              
220 6 50       71 if ( !$package->can("new") ) {
221 0         0 croak "Frame type $frameid is not implemented";
222             }
223              
224 6         9 eval { $frame = $package->new(@args) };
  6         19  
225 6 50       17 if ($@) {
226 0         0 chomp $@; # trailing newline.
227 0         0 $@ =~ s/ at.*$//; # at file line#
228 0         0 croak $@ ;
229             }
230             }
231              
232 22 50       138 if ( !$frame->isa("MPEG::ID3Frame") ) {
233 0         0 croak "strange arguments to append_frame()";
234             }
235              
236 22         33 push @{ $self->{FRAMES} }, $frame;
  22         47  
237              
238 22         112 return $frame;
239             }
240              
241             ####
242             # Find a frame by frame id.
243             # In list context, returns all the matching frames.
244             # In scalar context, returns just the first match.
245             ####
246             sub get_frame {
247 0     0 1 0 my ( $self, $frameid ) = @_;
248              
249 0         0 my @frames = grep { $_->frameid() eq $frameid } $self->frames();
  0         0  
250              
251 0 0       0 if (wantarray) {
252 0         0 return @frames;
253             }
254             else {
255 0         0 return $frames[0];
256             }
257             }
258              
259             # create a bunch of flag routines
260             for my $flag (qw(unsynchronization extended_header experimental)) {
261 6 100   6 1 10 eval <
  6 100   13 1 14  
  3 100   15 1 7  
  6         18  
  13         20  
  13         29  
  4         32  
  13         99  
  15         24  
  15         33  
  3         11  
  15         106  
262             sub flag_$flag
263             {
264             my \$self = shift ;
265             if (\@_) {
266             # there was a parameter
267             \$self->{FLAGS}{"$flag"} = \$_[0] ;
268             }
269              
270             return \$self->{FLAGS}{"$flag"} ;
271             }
272             EOT
273             die $@ if $@;
274             }
275              
276             ####
277             # Given an open filehandle, parse out the ID3 tag, if any.
278             # Constructs a new tag object (it's a static method).
279             ####
280             sub parse {
281 3     3 1 2416 my ( $package, $fh ) = @_;
282              
283 3         7 my $tag = {};
284 3         3 my $str;
285              
286 3         6 my ( $header, $data, $place );
287              
288 3 50       10 if ( ( ref $fh ) eq 'GLOB' ) {
289 3         55 my $readlen = read( $fh, $header, 10 );
290 3 50       11 croak "$!" if !defined $readlen;
291 3 50       11 if ($readlen < 10) {
292 0         0 carp "Read less than 10 bytes";
293 0         0 return undef;
294             }
295             }
296             else { ##not a filehandle. asume its a scalar
297 0         0 $place = index( $fh, "ID3" ); ##the real start of the ID3 Tag!!
298 0 0       0 if ($place < 0) {
299 0         0 carp "'ID3' not found in header";
300 0         0 return undef;
301             }
302 0         0 $header = substr( $fh, $place, 10 );
303             }
304              
305 3         4 my ( $id3, $flags, $totalsize );
306              
307 3         27 ( $id3, $tag->{MAJORVER}, $tag->{MINORVER}, $flags, $totalsize )
308             = unpack( "a3CCCN", $header );
309              
310 3         11 $totalsize = UnMungeSize($totalsize);
311 3         6 $tag->{ORIGINAL_SIZE} = $totalsize;
312              
313 3 50       12 if ($id3 ne 'ID3') {
314 0         0 carp "Header does not begin with 'ID3'";
315 0         0 return undef;
316             }
317 3 50       9 if ($tag->{MAJORVER} < 3) {
318 0         0 carp "ID3 tag version is 2.$tag->{MAJORVER}.$tag->{MINORVER}, less than 2.3.0";
319 0         0 return undef;
320             }
321              
322 3         6 bless $tag, $package;
323              
324 3         86 $tag->flag_unsynchronization( ( $flags >> 7 ) & 1 );
325 3         69 $tag->flag_extended_header( ( $flags >> 6 ) & 1 );
326 3         69 $tag->flag_experimental( ( $flags >> 5 ) & 1 );
327              
328 3 50       9 if ( ( ref $fh ) eq 'GLOB' ) {
329 3         5 my $len = 0;
330              
331 3         10 while ( $len < $totalsize ) {
332 3         1581 my $readlen = read( $fh, $data, $totalsize - $len, $len );
333 3 50       11 croak "$!" if !defined $readlen;
334 3 50       7 last if $readlen == 0;
335 3         9 $len += $readlen;
336             }
337             }
338             else {
339             # easier if not a filehandle
340 0         0 $data = substr( $fh, $place + 10, $totalsize );
341             }
342              
343             # now we have all the tag data, minus the main header, in $data.
344 3 50       76 $data = un_unsynchronize($data) if $tag->flag_unsynchronization();
345              
346             # if there's an extended header, peel it off the front of the data
347             # and parse it.
348 3 100       60 if ( $tag->flag_extended_header() ) {
349             # peel off the header size
350 1         5 $str = substr( $data, 0, 4, "" );
351 1         2 my $extheader_size = unpack( "N", $str );
352              
353             # peel the header
354 1         3 my $extheader = substr( $data, 0, $extheader_size, "" );
355 1         4 $str = substr( $extheader, 0, 6, "" );
356              
357             # two bytes of flags, then four byte padding size.
358 1         12 ( $flags, undef, $tag->{PADDING_SIZE} ) = unpack( "CCN", $str );
359              
360             # at this point, anything left in the extended header is stuff
361             # I don't know what to do with, including maybe a CRC value that I'm
362             # ignoring.
363              
364             # If there is any padding, strip it off the end of the data
365             # and throw it away (it's supposed to be all nuls).
366              
367 1 50       26 if ( $tag->{PADDING_SIZE} ) {
368 0         0 substr( $data, -$tag->{PADDING_SIZE}, $tag->{PADDING_SIZE}, "" );
369             }
370             }
371              
372             # Now data contains just the frames. If it's id3v2.3 it won't have
373             # padding, but if it's id3v2.4, it might. Parse until the data is
374             # empty or all padding.
375              
376 3   66     23 while ( $data ne '' and $data !~ /^\0+$/ ) {
377 15         144 my $frame = MPEG::ID3Frame->parse( \$data, $tag );
378 15         38 $tag->add_frame($frame);
379             }
380              
381             # done!
382              
383 3         81 return $tag;
384             }
385              
386             sub UnMungeSize {
387 9     9 0 12 my ($size) = @_;
388 9         9 my $newsize = 0;
389              
390 9         8 my $pos;
391              
392 9         24 for ( $pos = 0; $pos < 4; $pos++ ) {
393 36         36 my $mask = 0xff << ( $pos * 8 );
394              
395 36         37 my $val = ( $size & $mask ) >> ( $pos * 8 );
396              
397 36         63 $newsize |= $val << ( $pos * 7 );
398             }
399              
400 9         13 return $newsize;
401             }
402              
403             # Takes an integer and returns an ID3 size field
404             sub MungeSize {
405 3     3 0 5 my ($size) = @_;
406              
407 3         3 my $newsize = 0;
408 3         5 my $pos;
409              
410 3         9 for ( $pos = 0; $pos < 4; $pos++ ) {
411 12         22 my $val = ( $size >> ( $pos * 7 ) ) & 0x7f;
412 12         24 $newsize |= $val << ( $pos * 8 );
413             }
414              
415 3         46 return $newsize;
416             }
417              
418             sub dump {
419 0     0 1 0 my $self = shift;
420              
421 0         0 print "----Frames:\n";
422              
423 0         0 for my $frame ( $self->frames() ) {
424 0         0 $frame->dump();
425             }
426             }
427              
428             ###############################################################################
429             # MPEG::ID3Frame
430             #
431             # This is a base class from which other classes need to be derived.
432             # To implement a particular frame type, such as UFID, create a new
433             # class MPEG::ID3Frame::UFID (or whatever), and follow the directions
434             # in the pod.
435             #
436             # See the pod for what this class can do.
437             #
438             ##############################################################################
439             package MPEG::ID3Frame;
440 2     2   17 use Carp;
  2         3  
  2         1271  
441              
442             # DO NOT WRITE A new() METHOD FOR THIS CLASS!
443              
444             ####
445             # This is just a placeholder. In derived classes this would return
446             # the four-letter id code for the frame type.
447             ####
448             sub frameid {
449 4     4   390 my $self = shift;
450 4 50       23 return $self->{FRAMEID} if ( $self->{FROM_PARSER} );
451 0         0 confess "Must get frameid() from a derived class";
452             }
453              
454             ####
455             # Format the entire frame as a string ready for output to the file.
456             ####
457             sub as_string {
458 11     11   174 my $self = shift;
459 11         38 my $data = $self->data_as_string();
460              
461 11 50 33     273 if ( $self->flag_encryption || $self->flag_grouping_identity ) {
462             # these extend the headers.
463 0         0 carp "unsupported flag used, header will be wrong.";
464             }
465              
466             # The musicmatch id3lib doesn't munge frame header sizes.
467             # That seems to be correct.
468             # Only the ID3 header itself needs to take care to avoid false
469             # syncs, because the body is handled by the unsync. scheme.
470              
471 11 50 33     264 if ( $self->flag_compression && !exists $self->{UNSUPPORTED_BODY} ) {
472             # require Compress::Zlib here so the module stays functional
473             # without it.
474              
475 0         0 require Compress::Zlib;
476 0         0 Compress::Zlib->import( "compress", "uncompress" );
477              
478 0         0 my $compressed_data = compress($data);
479              
480 0 0       0 croak "Error in Compress::Zlib::compress"
481             if !defined $compressed_data;
482              
483 0         0 return $self->frameid()
484             . pack( "N", 4 + length($compressed_data) ) # 4 is for "N" length $data
485             . $self->flags_as_string()
486             . pack( "N", length($data) )
487             . $compressed_data;
488             }
489             else {
490 11         35 return $self->frameid()
491             . pack( "N", length($data) )
492             . $self->flags_as_string
493             . $data;
494             }
495             }
496              
497             ####
498             # Return the data as a binary string.
499             # This private method must be overridden in a derived class.
500             ####
501             sub data_as_string {
502 0     0   0 my $self = shift;
503              
504 0 0 0     0 if ( $self->{FROM_PARSER} && exists $self->{UNSUPPORTED_BODY} ) {
505 0         0 return $self->{UNSUPPORTED_BODY};
506             }
507              
508 0         0 confess "Must get data_as_string from a derived class";
509             }
510              
511             ####
512             # Return true iff the parsing module was able to fully parse this
513             # object so the data is useful.
514             #
515             # It could return false for any number of reasons, generally either
516             # an unsupported frame or an unsupported feature like compression
517             # or encryption.
518             ####
519             sub fully_parsed {
520 0     0   0 my $self = shift;
521 0         0 return !exists( $self->{UNSUPPORTED_BODY} );
522             }
523              
524             # create flag get/set subroutines for supported frame flags.
525             my @flags = qw(
526             tag_alter file_alter read_only compression encryption
527             grouping_identity
528             );
529              
530             for my $flag (@flags) {
531 52 100   52   75 eval <<"EOT" ;
  52 100   52   76  
  15 100   26   38  
  37 100   52   318  
  52 100   26   74  
  52 100   26   92  
  15         37  
  37         909  
  26         87  
  26         46  
  15         44  
  11         255  
  52         74  
  52         84  
  15         37  
  37         162  
  26         40  
  26         41  
  15         42  
  11         29  
  26         37  
  26         46  
  15         52  
  11         251  
532             sub flag_$flag {
533             my (\$self, \$value) = \@_ ;
534             if (defined \$value) {
535             \$self->{FLAGS}{"$flag"} = \$value ;
536             } else {
537             return \$self->{FLAGS}{"$flag"} ;
538             }
539             }
540             EOT
541             die $@ if $@;
542             }
543              
544             ####
545             # Some tags require a default of 1 for the file_alter flag.
546             # This function gets exported as flag_file_alter to those packages in
547             # the for loop below this.
548             ####
549             sub flag_file_alter_default_1 {
550 0     0   0 my ( $self, $value ) = @_;
551              
552 0 0       0 if ( defined $value ) {
553 0         0 $self->{FLAGS}{"flag_file_alter"} = $value;
554             }
555             else {
556 0 0       0 if ( exists $self->{FLAGS}{"flag_file_alter"} ) {
557 0         0 return $self->{FLAGS}{"flag_file_alter"};
558             }
559             else {
560 0         0 return 1;
561             }
562             }
563             }
564              
565             # According to section 3.3.2 of the informal v2.3.0 spec, these frames
566             # need to default to discarded-if-file-altered = 1, so override the
567             # flag_file_alter method for those frames to the right default.
568             my @frameids = qw(
569             AENC ETCO EQUA MLLT POSS SYLT SYTC RVAD TENC TLEN TSIZ
570             );
571              
572             for my $frameid (@frameids) {
573             # perl magic to stick a reference to a subroutine into a package's
574             # symbol table as a different subroutine name. Scary stuff, but
575             # better than evaling in 11 new copies of the same function,
576             # and safer than another layer of inheritance.
577 2     2   11 no strict 'refs';
  2         4  
  2         1502  
578              
579             *{"MPEG::ID3Frame::${frameid}::flag_file_alter"}
580             = \&flag_file_alter_default_1;
581             }
582              
583             ####
584             # Return the header flags as a binary string.
585             # Private method.
586             ####
587             sub flags_as_string {
588 11     11   14 my $self = shift;
589              
590 11         18 my ( $byte0, $byte1 );
591              
592 11         236 $byte0 = ( $self->flag_tag_alter() << 7 )
593             | ( $self->flag_file_alter() << 6 )
594             | ( $self->flag_read_only() << 5 );
595 11         227 $byte1 = ( $self->flag_compression() << 7 )
596             | ( $self->flag_encryption() << 6 )
597             | ( $self->flag_grouping_identity() << 5 );
598 11         67 return pack( "CC", $byte0, $byte1 );
599             }
600              
601             ####
602             # Static method/ctor to parse the frame from the front of a string
603             # and return an appropriate MPEG::ID3Frame::* object.
604             # A reference to the binary string is passed in, and the data
605             # is peeled from the front of that string.
606             #
607             # Actually calls MPEG::ID3Frame::::parse_data to do the
608             # frame data parsing. Headers here, data there.
609             ####
610             sub parse {
611 15     15   22 my ( $package, $dataref, $tag ) = @_;
612 15         30 my $self = { FROM_PARSER => 1 };
613 15         16 my ( $body, $original_body );
614 0         0 my $tmp;
615 15         24 bless $self, $package;
616              
617 15         27 my $header = substr( $$dataref, 0, 10, "" );
618 15         45 my ( $frameid, $size, $flags0, $flags1 ) = unpack( "a4NCC", $header );
619 15         38 $self->{FRAMEID} = $frameid;
620              
621 15 100 66     62 if ( defined $tag and $tag->{MAJORVER} == 4 ) {
622 6         11 $size = MPEG::ID3v2Tag::UnMungeSize($size);
623             }
624              
625 15         328 $self->flag_tag_alter( ( $flags0 >> 7 ) & 1 );
626 15         311 $self->flag_file_alter( ( $flags0 >> 6 ) & 1 );
627 15         380 $self->flag_read_only( ( $flags0 >> 5 ) & 1 );
628              
629 15         328 $self->flag_compression( ( $flags1 >> 7 ) & 1 );
630 15         310 $self->flag_encryption( ( $flags1 >> 6 ) & 1 );
631 15         304 $self->flag_grouping_identity( ( $flags1 >> 5 ) & 1 );
632              
633 15         1461 $original_body = $body = substr( $$dataref, 0, $size, "" );
634              
635 15 50 33     315 if ( $self->flag_encryption() || $self->flag_grouping_identity() ) {
636             # we don't know how to parse this field because we don't support
637             # encryption. We will still return the frame, and
638             # it will still be output ok. We just can't do anything with the
639             # data contained.
640              
641 0         0 $self->{UNSUPPORTED_BODY} = $original_body;
642 0         0 $self->{UNSUPPORTED_REASON} = "Unsupported flag";
643 0         0 return $self;
644             }
645              
646 15 50       292 if ( $self->flag_compression() ) {
647 0         0 eval {
648 0         0 require Compress::Zlib;
649 0         0 Compress::Zlib->import( "compress", "uncompress" );
650             };
651 0 0       0 if ($@) {
652 0         0 $self->{UNSUPPORTED_BODY} = $original_body;
653 0         0 $self->{UNSUPPORTED_REASON} = $@;
654 0         0 return $self;
655             }
656              
657             # four bytes after header are actually uncompressed data size.
658 0         0 $tmp = substr( $body, 0, 4, "" );
659 0         0 my $uc_size = unpack( "N", $tmp );
660              
661 0         0 $body = uncompress($body);
662 0 0 0     0 if ( !defined $body || length($body) != $uc_size ) {
663 0         0 $self->{UNSUPPORTED_BODY} = $original_body;
664 0 0       0 if ( defined $body ) {
665 0         0 $self->{UNSUPPORTED_REASON} = "compress size mismatch";
666             }
667             else {
668 0         0 $self->{UNSUPPORTED_REASON} = "zlib compress error";
669             }
670 0         0 return $self;
671             }
672             }
673              
674             # now we've stripped away all the headers and we can attempt to
675             # parse the body.
676              
677             # Look in MPEG::ID3Frame:: for a parse_data method.
678             # if there is one, rebless this frame object into that package
679             # and call the method.
680              
681 15         25 my $frame_package = "MPEG::ID3Frame::$frameid";
682 15 100       149 if ( $frame_package->can("parse_data") ) {
683 11         19 bless $self, $frame_package;
684 11         29 $self->parse_data($body);
685              
686 11 50       31 if ( $self->{UNSUPPORTED_BODY} ) {
687             # the frame's parse_data set up an UNSUPPORTED_BODY,
688             # which means they got some kind of error and they couldn't
689             # complete the parse.
690              
691             # That means they really aren't a MPEG::ID3Frame::$frameid,
692             # they're a generic frame, so re-rebless them back to
693             # the generic frame package.
694 0         0 bless $self, $package;
695              
696             # and they wouldn't know if we had passed them something
697             # that was originally compressed, so we'll use the body
698             # we started with rather than the one they gave us back.
699             # so it'll still be compressed on output.
700 0         0 $self->{UNSUPPORTED_BODY} = $original_body;
701              
702             # but we'll keep their reason, whatever that was.
703             }
704              
705             }
706             else {
707 4         7 $self->{UNSUPPORTED_BODY} = $original_body;
708 4         12 $self->{UNSUPPORTED_REASON} = "No MPEG::ID3Frame::$frameid parse_data method";
709             }
710              
711 15         33 return $self;
712             }
713              
714             sub dump {
715 0     0   0 my $self = shift;
716              
717 0 0       0 if ( $self->{UNSUPPORTED_BODY} ) {
718 0         0 my $reason = $self->{UNSUPPORTED_REASON};
719 0         0 print $self->frameid(), " (unparsed: $reason)\n";
720             }
721             else {
722 0         0 print $self->frameid(), " (no dump method)\n";
723             }
724             }
725              
726             ###############################################################################
727             # MPEG::ID3Frame::Text
728             # MPEG::ID3Frame::TALB
729             # MPEG::ID3Frame::TBPM
730             # MPEG::ID3Frame::TCOM
731             # ... and so on for all T??? frame types.
732             #
733             # This class is derived from MPEG::ID3Frame, and from this is derived all the
734             # MPEG::ID3Frame::T??? fields. It is not useful on its own.
735             #
736             # This section of the file will also create derived classes for all the
737             # Text types (TALB, TBPM, etc). Note that TEXT is not the same as Text.
738             ###############################################################################
739             package MPEG::ID3Frame::Text;
740 2     2   12 use Carp;
  2         5  
  2         129  
741              
742 2     2   11 use vars '@ISA';
  2         4  
  2         1521  
743              
744             @ISA = qw(MPEG::ID3Frame);
745              
746             sub new {
747 5     5   15 my $package = shift;
748 5         8 my $self;
749 5 50       13 if ( @_ == 1 ) {
    0          
750 5         17 $self = {
751             ENCODING => 0,
752             DATA => $_[0]
753             };
754             }
755             elsif ( @_ == 2 ) {
756 0         0 $self = {
757             ENCODING => $_[0],
758             DATA => $_[1]
759             };
760             }
761             else {
762 0         0 croak "Wrong # arguments to MPEG::ID3Frame::Text::new\n";
763             }
764 5         15 bless $self, $package;
765             }
766              
767             ####
768             # Given the body portion of a text-type tag, parse out the encoding and data
769             # portions.
770             ####
771             sub parse_data {
772 8     8   10 my ( $self, $data ) = @_;
773              
774             # ($self->{ENCODING}, $self->{DATA}) = unpack("CZ*", $data) ;
775 8         34 $self->{ENCODING} = unpack( "C", substr( $data, 0, 1 ) );
776              
777 8 100       20 if ( $self->{ENCODING} == 0 ) {
    50          
    0          
    0          
778 2         9 $self->{DATA} = unpack( "Z*", substr( $data, 1 ) );
779             }
780             elsif ( $self->{ENCODING} == 1 ) { ##with BOM
781             ######## a really dirty hack to change the UNICODE to normal ISO-8859-1 this will of course
782             ######## destroy the real unicode. so no need to write a UNICODE back to file
783 6         7 my @text_as_list;
784 6         13 $self->{BOM} = unpack( "n", substr( $data, 1, 2 ) );
785 6 50       11 if ( $self->{BOM} == 0xfeff ) {
786 0         0 @text_as_list = unpack( "n*", substr( $data, 3 ) );
787             }
788             else {
789 6         27 @text_as_list = unpack( "v*", substr( $data, 3 ) );
790             }
791 6         19 $self->{DATA} = pack( "C*", @text_as_list );
792 6         12 $self->{ENCODING} = 0; ## now
793             }
794             elsif ( $self->{ENCODING} == 2 ) { #no BOM here ##never tested
795             ######## a really dirty hack to change the UNICODE to normal ISO-8859-1 this will of course
796             ######## destroy the real unicode. so no need to write a UNICODE back to file
797              
798             ## I hope this n is working for ENCODING type 2. else change to back to v like type 1
799 0         0 my @text_as_list;
800 0         0 @text_as_list = unpack( "v*", substr( $data, 1 ) );
801 0         0 $self->{DATA} = pack( "C*", @text_as_list );
802 0         0 $self->{ENCODING} = 0; ## now
803             }
804             elsif ( $self->{ENCODING} == 3 ) { ##never tested
805 0         0 my @text_as_list;
806 0         0 @text_as_list = unpack( "U*", substr( $data, 1 ) );
807 0         0 $self->{DATA} = pack( "C*", @text_as_list );
808 0         0 $self->{ENCODING} = 0; ## now
809             }
810             }
811              
812 0     0   0 sub encoding { return $_[0]->{ENCODING} }
813 2     2   8 sub text { return $_[0]->{DATA} }
814              
815             ####
816             # Override for MPEG::ID3Frame::frameid(). returns the frame id, the
817             # four-letter word identifying the frame type.
818             ####
819             sub frameid {
820 27     27   446 my ($self) = @_;
821 27         40 my $frameid = ref $self;
822 27         101 $frameid =~ s/^.*:://;
823 27 50 33     124 if ( $frameid eq 'Text' || $frameid eq 'Url' ) {
824 0         0 confess "Must get frameid() from a derived class";
825             }
826 27         131 return $frameid;
827             }
828              
829             ####
830             # Return the data portion of this frame, formatted as a binary string.
831             ####
832             sub data_as_string {
833 6     6   8 my ($self) = @_;
834              
835             # zero for encoding=latin-1, and the rest is just the text, nul-terminated.
836 6         35 return pack( "CZ*", $self->{ENCODING}, $self->{DATA} );
837             }
838              
839             sub dump {
840 0     0   0 my $self = shift;
841 0         0 print $self->frameid(), " (enc=", $self->encoding, ") ", $self->text, "\n";
842             }
843              
844             # automatically derive a ton of classes from this one.
845             my @derived_frameids = qw(
846             TALB TBPM TCOM TCON TCOP TDAT TDLY TENC TEXT TFLT TIME TIT1 TIT2
847             TIT3 TKEY TLAN TLEN TMED TOAL TOFN TOLY TOPE TORY TOWN TPE1 TPE2
848             TPE3 TPE4 TPOS TPUB TRCK TRDA TRSN TRSO TSIZ TSRC TSSE TYER
849             );
850              
851             my $evalstr;
852             for my $frameid (@derived_frameids) {
853             $evalstr .= <
854             \@MPEG::ID3Frame\::$frameid\::ISA = qw(MPEG::ID3Frame::Text) ;
855             EOT
856             }
857              
858             eval $evalstr;
859             die $@ if $@;
860             undef $evalstr;
861              
862             ###############################################################################
863             # MPEG::ID3Frame::Url
864             # MPEG::ID3Frame::WCOM
865             # MPEG::ID3Frame::WCOP
866             # MPEG::ID3Frame::WOAF
867             # ... and so on for all W??? frame types.
868             #
869             # This class is derived from MPEG::ID3Frame, and from this is derived all the
870             # MPEG::ID3Frame::W??? fields. It is not useful on its own.
871             #
872             # It steals its new and frameid() methods from ::Text. That's kinda
873             # sloppy; they should both derive from some class.
874             #
875             # This section of the file will also create derived classes for all the
876             # URL types (WCOM, WCOP, etc).
877             ###############################################################################
878             package MPEG::ID3Frame::Url;
879 2     2   17 use Carp;
  2         4  
  2         126  
880              
881 2     2   14 use vars '@ISA';
  2         3  
  2         588  
882              
883             @ISA = qw(MPEG::ID3Frame::Text);
884              
885             ####
886             # Return the data portion of this frame, formatted as a binary string.
887             ####
888             sub data_as_string {
889 2     2   2 my ($self) = @_;
890              
891 2         17 return $self->{DATA} . "\0";
892             }
893              
894             ####
895             # Given the body portion of a text-type tag, parse out the encoding and data
896             # portions.
897             ####
898             sub parse_data {
899 1     1   3 my ( $self, $data ) = @_;
900              
901 1         8 ( $self->{DATA} ) = unpack( "Z*", $data );
902             }
903              
904 1     1   6 sub url { return $_[0]->{DATA} }
905              
906             sub dump {
907 0     0   0 my $self = shift;
908 0         0 print $self->frameid(), " ", $self->url(), "\n";
909             }
910              
911             # automatically derive a bunch of classes from this one.
912             for my $frameid (qw(WCOM WCOP WOAF WOAR WOAS WORS WPAY WPUB)) {
913             $evalstr .= <
914             \@MPEG::ID3Frame\::$frameid\::ISA = qw(MPEG::ID3Frame::Url) ;
915             EOT
916             }
917              
918             #print $evalstr ;
919             eval $evalstr;
920             die $@ if $@;
921             undef $evalstr;
922              
923             ##############################################################################
924             # MPEG::ID3Frame::UFID
925             # Unique file Identifier frame type.
926             ##############################################################################
927             package MPEG::ID3Frame::UFID;
928 2     2   11 use vars '@ISA';
  2         3  
  2         92  
929             @ISA = qw(MPEG::ID3Frame);
930 2     2   10 use Carp;
  2         5  
  2         357  
931              
932 0     0   0 sub frameid () { return "UFID" }
933              
934             sub new {
935 0     0   0 my ( $package, $owner_id, $id ) = @_;
936 0         0 my $self = {
937             OWNER_ID => $owner_id,
938             ID => $id
939             };
940 0         0 bless $self, $package;
941             }
942              
943             sub data_as_string {
944 0     0   0 my $self = shift;
945              
946 0         0 return $self->{OWNER_ID} . "\0" . $self->{ID};
947             }
948              
949             ##############################################################################
950             # MPEG::ID3Frame::USLT
951             # Unsynchronized lyrics/text transcription frame.
952             ##############################################################################
953             package MPEG::ID3Frame::USLT;
954 2     2   12 use Carp;
  2         4  
  2         115  
955 2     2   9 use vars '@ISA';
  2         4  
  2         100  
956             @ISA = qw(MPEG::ID3Frame);
957 2     2   466 use Carp;
  2         3  
  2         1487  
958              
959 0     0   0 sub frameid () { return "USLT" }
960              
961             sub new {
962 0     0   0 my ( $package, $encoding, $language, $content_descriptor, $lyrics ) = @_;
963 0 0       0 croak "language must be a three letter code" if length($language) != 3;
964 0         0 my $self = {
965             ENCODING => $encoding,
966             LANGUAGE => $language,
967             CONTENT_DESC => $content_descriptor,
968             LYRICS => $lyrics
969             };
970 0         0 bless $self, $package;
971             }
972              
973 0     0   0 sub encoding { return $_[0]->{ENCODING} }
974 0     0   0 sub language { return $_[0]->{LANGUAGE} }
975 0     0   0 sub content_descriptor { return $_[0]->{CONTENT_DESC} }
976 0     0   0 sub lyrics { return $_[0]->{LYRICS} }
977              
978             sub data_as_string {
979 0     0   0 my $self = shift;
980              
981 0         0 return pack( "Ca3Z*", $self->{ENCODING}, $self->{LANGUAGE}, $self->{CONTENT_DESC} . "\0" )
982             . $self->{LYRICS} . "\0";
983             }
984              
985             sub parse_data {
986 0     0   0 my ( $self, $data ) = @_;
987              
988 0         0 my $tmp = substr( $data, 0, 4, "" );
989 0         0 ( $self->{ENCODING}, $self->{LANGUAGE} ) = unpack( "Ca3", $tmp );
990 0         0 ( $self->{CONTENT_DESC}, $self->{LYRICS} ) = ( $data =~ /^(.*?)\x00(.*)\x00/s );
991             }
992              
993             sub dump {
994 0     0   0 my $self = shift;
995 0         0 printf "%s (enc=%d lang=%s desc=%s)\n", $self->frameid(),
996             $self->encoding(), $self->language(), $self->content_descriptor();
997 0         0 my $lyrics = $self->lyrics;
998 0         0 $lyrics =~ s/^/ | /mg;
999 0         0 print $lyrics ;
1000             }
1001              
1002             ##############################################################################
1003             # MPEG::ID3Frame::APIC
1004             # attached picture.
1005             ##############################################################################
1006             package MPEG::ID3Frame::APIC;
1007 2     2   10 use Carp;
  2         2  
  2         100  
1008 2     2   1978 use IO::File;
  2         22570  
  2         324  
1009 2     2   15 use vars '@ISA';
  2         4  
  2         104  
1010             @ISA = qw(MPEG::ID3Frame);
1011 2     2   10 use Carp;
  2         3  
  2         1467  
1012              
1013 3     3   18 sub frameid () { return "APIC" }
1014              
1015             sub new {
1016 2     2   4 my $package = shift;
1017 2         10 my $self = {
1018             PICTURETYPE => 0,
1019             ENCODING => 0,
1020             DESCRIPTION => " "
1021             };
1022 2         3 my $fh;
1023             my $fname;
1024              
1025 2   66     29 while ( @_ && $_[0] =~ /^-/ ) {
1026 4         7 my $arg = shift;
1027 4 50 66     52 if ( $arg =~ /^-encoding/ ) {
    50 33        
    100 0        
    50          
    50          
    50          
    0          
1028 0         0 $self->{ENCODING} = shift(@_);
1029             }
1030             elsif ( $arg =~ /^-mime/ ) {
1031 0   0     0 $self->{MIMETYPE} = shift(@_)
1032             || croak "bad argument to -mimetype";
1033             }
1034             elsif ( $arg =~ /^-picture_type/ || $arg =~ /-type/ ) {
1035 2         15 $self->{PICTURETYPE} = shift(@_);
1036             }
1037             elsif ( $arg =~ /^-desc/ ) {
1038 0         0 $self->{DESCRIPTION} = shift(@_);
1039             }
1040             elsif ( $arg =~ /^-fh/ ) {
1041 0   0     0 $fh = shift(@_) || croak "bad argument to -fh";
1042             }
1043             elsif ( $arg =~ /^-fn/ || $arg =~ /^-file/ ) {
1044 2   33     11 $fname = shift(@_) || croak "bad argument to $arg";
1045             }
1046             elsif ( $arg =~ /^-data/ || $arg =~ /^-data/ ) {
1047 0   0     0 $self->{DATA} = shift(@_) || croak "bad argument to $arg";
1048             }
1049             else {
1050 0         0 croak "unknown switch $arg";
1051             }
1052             }
1053 2 50       7 croak "bad arguments to APIC" if @_;
1054              
1055 2 50       14 if ( !exists $self->{MIMETYPE} ) {
1056 2 50       6 if ($fname) {
1057 2 50       9 if ( $fname =~ /\.gif$/i ) {
    0          
1058 2         6 $self->{MIMETYPE} = "image/gif";
1059             }
1060             elsif ( $fname =~ /\.jpg/ ) {
1061 0         0 $self->{MIMETYPE} = "image/jpeg";
1062             }
1063             }
1064             }
1065 2 50       8 if ( !exists $self->{MIMETYPE} ) {
1066 0         0 croak "must specify a -mimetype";
1067             }
1068              
1069 2 50       8 if ( !exists $self->{DATA} ) {
1070 2 50       9 if ( !defined $fh ) {
1071 2 50       5 croak "must specify -data, -file, or -fh" if ( !defined $fname );
1072 2   33     18 $fh = IO::File->new("<$fname") || croak "$fname: $!\n";
1073             }
1074 2         281 local $/ = undef; # file slurp mode
1075 2         65 $self->{DATA} = <$fh>;
1076             }
1077              
1078 2         29 bless $self, $package;
1079             }
1080              
1081             sub data_as_string {
1082 3     3   4 my $self = shift;
1083              
1084 3         30 my $data = pack( "CZ*", $self->{ENCODING}, $self->{MIMETYPE} )
1085             . pack( "CZ*", $self->{PICTURETYPE}, $self->{DESCRIPTION} )
1086             . $self->{DATA};
1087              
1088 3         7 return $data;
1089             }
1090              
1091             ##############################################################################
1092             # MPEG::ID3Frame::USER
1093             # terms of use frame.
1094             ##############################################################################
1095             package MPEG::ID3Frame::USER;
1096 2     2   11 use Carp;
  2         4  
  2         108  
1097 2     2   7 use vars '@ISA';
  2         26  
  2         78  
1098             @ISA = qw(MPEG::ID3Frame);
1099 2     2   8 use Carp;
  2         3  
  2         366  
1100              
1101 0     0   0 sub frameid () { return "USER" }
1102              
1103             sub new {
1104 0     0   0 my ( $package, $encoding, $language, $text ) = @_;
1105 0 0       0 croak "language must be a three letter code" if length($language) != 3;
1106 0         0 my $self = {
1107             ENCODING => $encoding,
1108             LANGUAGE => $language,
1109             TEXT => $text
1110             };
1111 0         0 bless $self, $package;
1112             }
1113              
1114             sub data_as_string {
1115 0     0   0 my $self = shift;
1116              
1117 0         0 return pack( "Ca3a*", $self->{ENCODING}, $self->{LANGUAGE}, $self->{TEXT} );
1118             }
1119              
1120             ##############################################################################
1121             # MPEG::ID3Frame::COMM
1122             # comment frame.
1123             ##############################################################################
1124             package MPEG::ID3Frame::COMM;
1125 2     2   9 use Carp;
  2         4  
  2         87  
1126 2     2   8 use vars '@ISA';
  2         6  
  2         85  
1127             @ISA = qw(MPEG::ID3Frame);
1128 2     2   8 use Carp;
  2         22  
  2         856  
1129              
1130 2     2   12 sub frameid () { return "COMM" }
1131              
1132             sub new {
1133 0     0   0 my ( $package, $encoding, $language, $description, $text ) = @_;
1134 0 0       0 croak "language must be a three letter code" if length($language) != 3;
1135 0         0 my $self = {
1136             ENCODING => $encoding,
1137             LANGUAGE => $language,
1138             DESCRIPTION => $description,
1139             TEXT => $text
1140             };
1141 0         0 bless $self, $package;
1142             }
1143              
1144             sub parse_data {
1145 2     2   3 my ( $self, $data ) = @_;
1146              
1147 2         13 ( $self->{ENCODING}, $self->{LANGUAGE} ) = unpack( "Ca3", substr( $data, 0, 4 ) );
1148              
1149 2         7 my $textpos = index( $data, "\0", 4 ) + 1;
1150 2         4 my $desc = substr( $data, 4, $textpos - 5 );
1151 2         3 my $text = substr( $data, $textpos );
1152              
1153 2 50       10 if ( $self->{ENCODING} == 0 ) {
    0          
1154 2         4 $self->{DESCRIPTION} = $desc;
1155 2         4 $self->{TEXT} = $text;
1156             }
1157             elsif ( $self->{ENCODING} == 1 ) { ##with BOM
1158             ######## a really dirty hack to change the UNICODE to normal ISO-8859-1 this will of course
1159             ######## destroy the real unicode. so no need to write a UNICODE back to file
1160 0           my @text_as_list_t;
1161             my @text_as_list_d;
1162              
1163 0           $self->{BOM} = unpack( "n", substr( $data, 1, 2 ) );
1164 0 0         if ( $self->{BOM} == 0xfeff ) {
1165 0           @text_as_list_t = unpack( "n*", substr( $text, 2 ) );
1166 0           @text_as_list_d = unpack( "n*", substr( $desc, 2 ) );
1167             }
1168             else {
1169 0           @text_as_list_t = unpack( "v*", $text );
1170 0           @text_as_list_d = unpack( "v*", $desc );
1171             }
1172 0           $self->{DESCRIPTION} = pack( "C*", @text_as_list_d );
1173 0           $self->{TEXT} = pack( "C*", @text_as_list_t );
1174 0           $self->{ENCODING} = 0; ## now
1175             }
1176              
1177             }
1178              
1179             sub data_as_string {
1180 0     0     my $self = shift;
1181              
1182 0           return pack( "Ca3a*", $self->{ENCODING}, $self->{LANGUAGE}, $self->{DESCRIPTION} . "\0" . $self->{TEXT} );
1183             }
1184              
1185             ##############################################################################
1186             # MPEG::ID3Frame::WXXX
1187             # User defined URL link frame
1188             ##############################################################################
1189             package MPEG::ID3Frame::WXXX;
1190 2     2   10 use Carp;
  2         2  
  2         92  
1191 2     2   8 use vars '@ISA';
  2         3  
  2         82  
1192             @ISA = qw(MPEG::ID3Frame);
1193 2     2   9 use Carp;
  2         7  
  2         580  
1194              
1195 0     0     sub frameid () { return "WXXX" }
1196              
1197             sub new {
1198 0     0     my ( $package, $encoding, $description, $url ) = @_;
1199 0           my $self = {
1200             ENCODING => $encoding,
1201             DESCRIPTION => $description,
1202             URL => $url
1203             };
1204 0           bless $self, $package;
1205             }
1206              
1207             sub parse_data {
1208 0     0     my ( $self, $data ) = @_;
1209 0           my $desc_url;
1210              
1211 0           ( $self->{ENCODING}, $desc_url ) = unpack( "Ca*", $data );
1212 0           ( $self->{DESCRIPTION}, $self->{URL} ) = split( "\0", $desc_url );
1213             }
1214              
1215 0     0     sub encoding { return $_[0]->{ENCODING} }
1216 0     0     sub description { return $_[0]->{DESCRIPTION} }
1217 0     0     sub url { return $_[0]->{URL} }
1218              
1219             sub data_as_string {
1220 0     0     my $self = shift;
1221              
1222 0           return pack( "Ca*", $self->{ENCODING}, $self->{DESCRIPTION} . "\0" . $self->{URL} );
1223             }
1224              
1225             ##############################################################################
1226             # MPEG::ID3Frame::TXXX
1227             # User defined text frame
1228             ##############################################################################
1229             package MPEG::ID3Frame::TXXX;
1230 2     2   9 use Carp;
  2         6  
  2         91  
1231 2     2   8 use vars '@ISA';
  2         4  
  2         80  
1232             @ISA = qw(MPEG::ID3Frame);
1233 2     2   15 use Carp;
  2         2  
  2         639  
1234              
1235 0     0     sub frameid () { return "TXXX" }
1236              
1237             sub new {
1238 0     0     my ( $package, $encoding, $description, $data ) = @_;
1239 0           my $self = {
1240             ENCODING => $encoding,
1241             DESCRIPTION => $description,
1242             DATA => $data
1243             };
1244 0           bless $self, $package;
1245             }
1246              
1247             sub parse_data {
1248 0     0     my ( $self, $data ) = @_;
1249 0           my $desc_data;
1250              
1251 0           ( $self->{ENCODING}, $desc_data ) = unpack( "Ca*", $data );
1252 0           ( $self->{DESCRIPTION}, $self->{DATA} ) = split( "\0", $desc_data );
1253             }
1254              
1255 0     0     sub encoding { return $_[0]->{ENCODING} }
1256 0     0     sub description { return $_[0]->{DESCRIPTION} }
1257 0     0     sub text { return $_[0]->{DATA} }
1258              
1259             sub data_as_string {
1260 0     0     my $self = shift;
1261              
1262 0           return pack( "Ca*", $self->{ENCODING}, $self->{DESCRIPTION} . "\0" . $self->{DATA} );
1263             }
1264              
1265             1;
1266              
1267             __END__