File Coverage

blib/lib/Audio/M4P/QuickTime.pm
Criterion Covered Total %
statement 385 501 76.8
branch 177 284 62.3
condition 42 82 51.2
subroutine 43 62 69.3
pod 56 56 100.0
total 703 985 71.3


line stmt bran cond sub pod time code
1             package Audio::M4P::QuickTime;
2              
3             require 5.008;
4 6     6   49272 use strict;
  6         6  
  6         197  
5 6     6   25 use warnings;
  6         5  
  6         134  
6 6     6   21 use Carp;
  6         6  
  6         413  
7 6     6   30 use Scalar::Util 'weaken';
  6         7  
  6         610  
8             our $VERSION = '0.57';
9              
10 6     6   2307 use Audio::M4P::Atom;
  6         13  
  6         37432  
11              
12             #-------------- useful hashes and arrays ------------------------------------#
13              
14             our %meta_info_types = (
15             aART => 1, # album artist
16             aaid => 1, # album artist
17             '©alb' => 1, # album
18             akid => 1, # ? alternate id ?
19             apid => 1, # apple id
20             '©ART' => 1, # artist (performing)
21             atid => 1, # apple itunes id ?
22             catg => 1, # category
23             '©cmt' => 1, # comment field
24             '©com' => 1, # composer
25             covr => 1, # cover art
26             cpil => 1, # 1 if compilation => 0 if not ?
27             cprt => 1, # copyrighted material purchaser ?
28             '©day' => 1, # date of release--often just the year
29             disk => 1, # CD set number: cut is from, disk [field 1] of [field 2]
30             geid => 1, # iTMS store ID ?
31             gnre => 1, # genre
32             '©grp' => 1, # group(?)
33             '©lyr' => 1, # lyrics
34             '©nam' => 1, # title of track
35             pcst => 1, # podcast flag
36             pgap => 1, # iTunes gapless playback ?
37             pinf => 1, # iTunes 7.2+ purchaser info ?
38             plid => 1, # purchase id ?
39             purd => 1, # iTunes 6+ purchase date
40             purl => 1, # program URL
41             rtng => 1, # rating (integer)
42             sfid => 1, # ? itms ID info
43             sign => 1, # ? file hash signature
44             stik => 1, # movie type: 0x1 default, 0x5 bookmarkable, 0x6 music video,
45             # 0xA TV show, ?? 0x2 newsreel, 0xE ringtone
46             tmpo => 1, # tempo (beats per minute)
47             '©too' => 1, # encoder
48             trkn => 1, # two fields: [field 1] track num. of [field 2] total tracks
49             tves => 1, # TV show episode
50             tvsh => 1, # TV show
51             '©wrt' => 1, # composer
52             '----' => 1, # itunes specific info
53             );
54              
55             our %utf8_atoms = (
56             aART => 1, # album artist
57             aaid => 1, # album artist
58             '©alb' => 1, # album
59             '©ART' => 1, # artist (performing)
60             catg => 1, # category
61             '©cmt' => 1, # comment field
62             '©com' => 1, # composer
63             cprt => 1, # copyrighted material purchaser ?
64             '©day' => 1, # date of release--often just the year
65             '©grp' => 1, # group(?)
66             '©lyr' => 1, # lyrics
67             '©nam' => 1, # title of track
68             purl => 1, # program URL
69             '©too' => 1, # encoder
70             tves => 1, # TV show episode
71             tvsh => 1, # TV show
72             '©wrt' => 1, # composer
73             );
74              
75             our %tag_types = (
76             AAID => 'aaid',
77             AAID => 'aART',
78             ALB => '©alb',
79             ALBUM => '©alb',
80             ARTIST => '©ART',
81             CMT => '©cmt',
82             COMMENT => '©cmt',
83             COM => '©com',
84             CPIL => 'cpil',
85             CPRT => 'cprt',
86             DAY => '©day',
87             DISK => 'disk',
88             GENRE => 'gnre',
89             GNRE => 'gnre',
90             GRP => '©grp',
91             NAM => '©nam',
92             RTNG => 'rtng',
93             SONG => '©nam',
94             TITLE => '©nam',
95             TMPO => 'tmpo',
96             TOO => '©too',
97             TRACKNUM => 'trkn',
98             TRKN => 'trkn',
99             WRT => '©wrt',
100             COVR => 'covr',
101             LYRICS => '©lyr',
102             GENRE_ => '©gen',
103             YEAR => '©day',
104             );
105              
106             our %alternate_tag_types = (
107             GENRE => 'GNRE',
108             NAM => 'TITLE',
109             ARTIST => 'ART',
110             ALBUM => 'ALB',
111             YEAR => 'DAY',
112             COMMENT => 'CMT',
113             TRKN => 'TRACKNUM',
114             SONG => 'TITLE',
115             );
116              
117             our @m4p_not_m4a_atom_types = qw( sinf cnID apID atID plID geID akID ---- );
118             our @apple_user_id_atoms = qw( pinf apID cnID atID plID geID sfID akID purd ownr );
119              
120             our %iTMS_dict_meta_types = (
121             copyright => 'cprt',
122             comments => '©cmt',
123             songName => '©nam',
124             genre => 'gnre',
125             playlistArtistName => '©ART',
126             genreID => '©gen',
127             composerName => '©wrt',
128             playlistName => '©alb',
129             year => '©day',
130             trackNumber => 'trkn',
131             trackCount => 'trkn',
132             discNumber => 'disk',
133             discCount => 'disk',
134             artworkURL => 'covr',
135             );
136              
137             our @genre_strings = (
138             "Blues", "Classic Rock",
139             "Country", "Dance",
140             "Disco", "Funk",
141             "Grunge", "Hip-Hop",
142             "Jazz", "Metal",
143             "New Age", "Oldies",
144             "Other", "Pop",
145             "R&B", "Rap",
146             "Reggae", "Rock",
147             "Techno", "Industrial",
148             "Alternative", "Ska",
149             "Death Metal", "Pranks",
150             "Soundtrack", "Euro-Techno",
151             "Ambient", "Trip-Hop",
152             "Vocal", "Jazz+Funk",
153             "Fusion", "Trance",
154             "Classical", "Instrumental",
155             "Acid", "House",
156             "Game", "Sound Clip",
157             "Gospel", "Noise",
158             "AlternRock", "Bass",
159             "Soul", "Punk",
160             "Space", "Meditative",
161             "Instrumental Pop", "Instrumental Rock",
162             "Ethnic", "Gothic",
163             "Darkwave", "Techno-Industrial",
164             "Electronic", "Pop-Folk",
165             "Eurodance", "Dream",
166             "Southern Rock", "Comedy",
167             "Cult", "Gangsta",
168             "Top 40", "Christian Rap",
169             "Pop/Funk", "Jungle",
170             "Native American", "Cabaret",
171             "New Wave", "Psychadelic",
172             "Rave", "Showtunes",
173             "Trailer", "Lo-Fi",
174             "Tribal", "Acid Punk",
175             "Acid Jazz", "Polka",
176             "Retro", "Musical",
177             "Rock & Roll", "Hard Rock",
178             "Folk", "Folk/Rock",
179             "National Folk", "Swing",
180             "Fast-Fusion", "BeBop",
181             "Latin", "Revival",
182             "Celtic", "Bluegrass",
183             "Avantgarde", "Gothic Rock",
184             "Progressive Rock", "Psychedelic Rock",
185             "Symphonic Rock", "Slow Rock",
186             "Big Band", "Chorus",
187             "Easy Listening", "Acoustic",
188             "Humour", "Speech",
189             "Chanson", "Opera",
190             "Chamber Music", "Sonata",
191             "Symphony", "Booty Bass",
192             "Primus", "Porn Groove",
193             "Satire", "Slow Jam",
194             "Club", "Tango",
195             "Samba", "Folklore",
196             "Ballad", "Power Ballad",
197             "Rhythmic Soul", "Freestyle",
198             "Duet", "Punk Rock",
199             "Drum Solo", "A capella",
200             "Euro-House", "Dance Hall",
201             "Goa", "Drum & Bass",
202             "Club House", "Hardcore",
203             "Terror", "Indie",
204             "BritPop", "NegerPunk",
205             "Polsk Punk", "Beat",
206             "Christian Gangsta", "Heavy Metal",
207             "Black Metal", "Crossover",
208             "Contemporary C", "Christian Rock",
209             "Merengue", "Salsa",
210             "Thrash Metal", "Anime",
211             "JPop", "SynthPop",
212             "INVALID_GENRE"
213             );
214              
215             our %genre_text_to_genre_numbers;
216             my $genre_num = 1;
217             foreach my $genre (@genre_strings) {
218             $genre_text_to_genre_numbers{$genre} = $genre_num;
219             ++$genre_num;
220             }
221             our %genre_numbers_to_genre_text = reverse %genre_text_to_genre_numbers;
222              
223             our %asset_3GP_types = (
224             ALBUM => 'albm', # album title and track number for the media
225             ARTIST => 'perf', # performer or artist
226             COM => 'auth', # author/composer of the media
227             COMMENT => 'dscp', # caption or description for the media
228             COPYRIGHT => 'cprt', # notice about organisation holding copyright
229             GENRE => 'gnre', # genre (category and style) of the media
230             RTNG => 'rtng', # media rating
231             TITLE => 'titl', # title for the media
232             YEAR => 'yrrc', # recording year for the media
233              
234             # these exist in 3GP but not really in iTMS meta data
235             CLASS => 'clsf', # classification of the media
236             KEYWORDS => 'kywd', # media keywords
237             LOCATION => 'loci', # location information
238             );
239              
240             our $default_asset_3GP_lang = 'eng';
241              
242             #------------------- object methods -----------------------------------------#
243              
244             sub new {
245 18     18 1 2886 my ( $class, %args ) = @_;
246 18         37 my $self = {};
247 18         46 bless( $self, $class );
248 18         58 $self->{meta} = {};
249 18         68 foreach my $k (qw( DEBUG DEBUGDUMPFILE file)) {
250 54 100       149 $self->{$k} = $args{$k} if exists $args{$k};
251             }
252 18 50       121 $self->{DEBUG} = 0 unless exists $self->{DEBUG};
253 18 100       64 if ( exists $self->{file} ) {
254 15         60 $self->ReadFile( $self->{file} );
255 15         58 $self->ParseBuffer();
256             }
257 18         100 return $self;
258             }
259            
260             sub DESTROY {
261 18     18   1007 my($self) = @_;
262 18 100       152 if( ref $self->{root} ) {
263 15         61 $self->{root}->DESTROY;
264             }
265             }
266            
267             sub ReadFile {
268 15     15 1 30 my ( $self, $infile ) = @_;
269 15 50       494 open( my $infh, '<', $infile ) or croak "Cannot open input $infile: $!";
270 15         41 binmode $infh;
271 15 50       3488 read( $infh, $self->{buffer}, -s $infile ) or croak "Bad file read: $!";
272 15         148 close $infh;
273 15         108 $self->{meta}->{filesize} = length $self->{buffer};
274             }
275              
276             sub ParseBuffer {
277 15     15 1 22 my ($self) = @_;
278 15         36 $self->{atom_count} = 0;
279 15         130 $self->{root} = new Audio::M4P::Atom(
280             rbuf => \$self->{buffer},
281             type => 'file',
282             size => length $self->{buffer},
283             read_buffer_position => 0,
284             offset => 8,
285             parent => 0,
286             );
287 15         53 weaken $self->{root};
288 15         31 my $fsize = length $self->{buffer};
289 15 50       43 print "Buffer size is $fsize\n" if $self->{DEBUG};
290 15         50 $self->ParseMP4Container( $self->{root}->node, 0, $fsize );
291 15 50       51 print "Found $self->{atom_count} atoms.\n" if $self->{DEBUG};
292 15 50       70 $self->DumpTree( $self->{DEBUGDUMPFILE} ) if $self->{DEBUG} > 1;
293             }
294              
295             sub WriteFile {
296 9     9 1 40 my ( $self, $outfile ) = @_;
297 9 50       119402 open( my $outfh, '>', $outfile ) or croak "Cannot open output $outfile: $!";
298 9         37 binmode $outfh;
299 9         7169 my $retval = print $outfh $self->{buffer};
300 9         291 close $outfh;
301 9         97 return $retval;
302             }
303              
304             sub ParseMP4Container {
305 422     422 1 475 my ( $self, $parent, $posit, $end_posit ) = @_;
306 422 50       741 my $pAtom = $parent->getNodeValue() or croak "Cannot get atom from node";
307 422 50       1692 $posit = $pAtom->start + $pAtom->offset unless defined $posit;
308 422 50       629 $end_posit = $pAtom->start + $pAtom->size unless $end_posit;
309 422         707 while ( $posit < $end_posit ) {
310 1113         2712 my $atom = new Audio::M4P::Atom(
311             parent => $parent,
312             rbuf => \$self->{buffer},
313             read_buffer_position => $posit
314             );
315 1113 50       2028 print $atom->type, " at $posit size ", $atom->size, "\n"
316             if $self->{DEBUG};
317 1113 100       2129 last unless $atom->size > 7; # sanity check
318 1112         1174 $self->{atom_count}++;
319 1112 100       1835 if ( $atom->type =~ /stsd/i ) { $self->ParseStsd($atom) }
  25 100       70  
    50          
    100          
    100          
320 14         42 elsif ( $atom->type =~ /mp4a/i ) { $self->ParseMp4a($atom) }
321 0         0 elsif ( $atom->type =~ /drms/i ) { $self->ParseDrms($atom) }
322 13         55 elsif ( $atom->type =~ /meta/i ) { $self->ParseMeta($atom) }
323             elsif ( $atom->isContainer() ) {
324 355         681 $self->ParseMP4Container(
325             $atom->node,
326             $posit + $atom->offset,
327             $posit + $atom->size - $atom->offset
328             );
329             }
330             else {
331 705 50       1178 print( "done with noncontainer atom of atom of type ",
332             $atom->type, "\n" )
333             if $self->{DEBUG};
334             }
335 1112         2230 $posit += $atom->size;
336             }
337             }
338              
339             sub ParseStsd {
340 25     25 1 32 my ( $self, $stsd ) = @_;
341 25         58 $self->ParseMP4Container(
342             $stsd->node,
343             $stsd->start + 16,
344             $stsd->start + $stsd->size - 16
345             );
346             }
347              
348             sub ParseMp4a {
349 14     14 1 20 my ( $self, $mp4a ) = @_;
350 14         43 $self->ParseMP4Container(
351             $mp4a->node,
352             $mp4a->start + 36,
353             $mp4a->start + $mp4a->size - 36
354             );
355             }
356             sub ParseDrms {
357 0     0 1 0 my ( $self, $drms ) = @_;
358 0         0 $self->ParseMP4Container(
359             $drms->node,
360             $drms->start + 36,
361             $drms->start + $drms->size - 36
362             );
363 0         0 $self->{userID} = unpack 'N*', $self->FindAtomData('user');
364 0         0 my $key = $self->FindAtomData('key ');
365 0 0       0 $self->{keyID} = unpack 'N', $key if $key;
366 0         0 $self->{priv} = $self->FindAtomData('priv');
367 0         0 my $name = $self->FindAtomData('name');
368 0         0 $self->{name} = substr( $name, 0, index( $name, "\0" ) );
369 0         0 $self->{iviv} = $self->FindAtomData('iviv');
370 0 0       0 print "userID ", $self->{userID}, " keyID ", $self->{keyID}, "\n"
371             if $self->{DEBUG};
372             }
373              
374             sub ParseMeta {
375 13     13 1 25 my ( $self, $meta ) = @_;
376 13         51 $self->ParseMP4Container(
377             $meta->node,
378             $meta->start + 12,
379             $meta->start + $meta->size - 12
380             );
381 13         154 foreach my $type ( keys %meta_info_types ) {
382 481 100       922 my $atom = $self->FindAtom($type) or next;
383 109         326 my $adata =
384             substr( $self->{buffer}, $atom->start + 24, $atom->size - 24 );
385 109 100       304 next if length $adata > 300;
386 101 100       419 if ( $type eq 'disk' ) {
    100          
    50          
387 5         19 my ( $field1, $field2 ) = unpack "nn",
388             substr( $self->{buffer}, $atom->start + $atom->size - 4, 4 );
389 5         19 $adata = "Disk $field1 of $field2";
390             }
391             elsif ( $type eq 'trkn' ) {
392 9         42 my ( $fld1, $fld2 ) = unpack "nn",
393             substr( $self->{buffer}, $atom->start + $atom->size - 6, 4 );
394 9         40 $adata = "Track $fld1 of $fld2";
395             }
396             elsif ( $type eq 'genre' ) {
397 0         0 $adata = unpack 'n',
398             substr( $self->{buffer}, $atom->start + $atom->size - 2, 2 );
399             }
400 101 50       435 $self->{meta}->{$type} = $adata unless length $adata > 300;
401             }
402 13 50       122 print $self->MetaInfo() if $self->{DEBUG};
403             }
404              
405             sub AtomList {
406 3188     3188 1 3073 my ($self) = @_;
407 3188         7922 return $self->{root}->getAllRelatives(); # returns ref to list of atoms
408             }
409              
410             sub FindAtom {
411 3188     3188 1 5418 my ( $self, $type ) = @_;
412 265703 100 66     581998 my @atoms =
413 3188         6008 grep { $type and $_->type and $_->type =~ /$type$/i }
414 3188         2685 @{ $self->AtomList() };
415 3188 100       12566 return @atoms if wantarray;
416 2987 100       14082 return unless scalar @atoms > 0;
417 1333         3995 return $atoms[0];
418             }
419              
420             sub FindAtomData {
421 0     0 1 0 my ( $self, $type ) = @_;
422 0 0       0 my $a = $self->FindAtom($type) or return;
423 0         0 return $a->data;
424             }
425              
426             sub MetaInfo {
427 0     0 1 0 my ($self) = @_;
428 0         0 my $meta_info = '';
429 0         0 my $file_type = $self->GetFtype();
430 0 0       0 $meta_info = "File type is $file_type\n" if $file_type;
431 0         0 while ( my ( $mtype, $mdata ) = each %{ $self->{meta} } ) {
  0         0  
432 0         0 $meta_info .= "Meta type $mtype, meta data $mdata\n";
433             }
434 0         0 return $meta_info;
435             }
436              
437             sub AtomTree {
438 0     0 1 0 my ($self) = @_;
439 0         0 return $self->{root}->AtomTree();
440             }
441              
442             sub DumpTree {
443 0     0 1 0 my ( $self, $outfile ) = @_;
444 0 0 0     0 if ( $outfile and open( my $dumpfh, ">$outfile" ) ) {
445 0         0 print $dumpfh $self->AtomTree();
446 0         0 close $dumpfh;
447             }
448 0         0 else { print $self->AtomTree() }
449             }
450              
451             sub ConvertDrmsToMp4a {
452 0     0 1 0 my ($self) = @_;
453 0         0 my $diff = 0;
454 0 0       0 my $drms = $self->FindAtom('drms') or return;
455 0         0 foreach my $a (@m4p_not_m4a_atom_types) {
456 0 0       0 my @unwanted = $self->FindAtom($a) or next;
457 0         0 foreach my $u (@unwanted) { $diff += $u->size; $u->selfDelete() }
  0         0  
  0         0  
458             }
459 0 0       0 print "Shrunk file by $diff bytes during conversion\n" if $self->{DEBUG};
460 0         0 $self->FixStco( $diff, $drms->start );
461 0         0 $drms->type('mp4a');
462             }
463              
464             sub FixStco {
465 49     49 1 95 my ( $self, $sinf_sz, $change_position ) = @_;
466 49         139 my @stco_atoms = $self->FindAtom('stco');
467 49         144 my @co64_atoms = $self->FindAtom('co64');
468 49         132 my @tfhd_atoms = $self->FindAtom('tfhd');
469              
470             # all Quicktime files should have at least one stco or co64 atom
471 49 50 33     220 croak 'No stco or co64 atom' unless @stco_atoms || @co64_atoms;
472              
473             # if mdat is before change postion will not need to do anything
474 49 50       119 my @mdat = $self->FindAtom('mdat') or return;
475 49         84 my $all_mdat_before = 1;
476 49         110 foreach my $mdt (@mdat) {
477 181 50       369 $all_mdat_before = 0 if $mdt->start > $change_position;
478             }
479 49 50       133 return if $all_mdat_before;
480              
481 49         94 foreach my $stco (@stco_atoms) {
482 1064 50       1555 my @samples =
483 82         243 map { ( $_ > $change_position ) ? $_ - $sinf_sz : $_ }
484             unpack( "N*",
485             substr( $self->{buffer}, $stco->start + 16, $stco->size - 16 ) );
486 82         239 substr(
487             $self->{buffer},
488             $stco->start + 16,
489             $stco->size - 16,
490             pack( 'N*', @samples )
491             );
492             }
493 49         116 foreach my $co64 (@co64_atoms) {
494 1         4 my @samples =
495             unpack( "N*",
496             substr( $self->{buffer}, $co64->start + 16, $co64->size - 16 ) );
497 1         3 my $num_longs = scalar @samples;
498 1         4 for ( my $i = 0 ; $i < $num_longs ; $i += 2 ) {
499 16         11 my $high32bits = $samples[$i];
500 16         12 my $low32bits = $samples[ $i + 1 ];
501 16         14 my $offset64 = ( $high32bits * ( 2**32 ) ) + $low32bits;
502 16 50       19 $offset64 -= $sinf_sz if $offset64 > $change_position;
503 16         13 $samples[ $i + 1 ] = $offset64 % ( 2**32 );
504 16         27 $samples[$i] = int( $offset64 / ( 2**32 ) + 0.0001 );
505             }
506 1         3 substr(
507             $self->{buffer},
508             $co64->start + 16,
509             $co64->size - 16,
510             pack( 'N*', @samples )
511             );
512             }
513 49         232 foreach my $tfhd (@tfhd_atoms) {
514 132         212 my ( $tf_flags, undef, $offset_high32, $offset_low32 ) =
515             unpack( 'NNNN', substr( $self->{buffer}, $tfhd->start + 8, 16 ) );
516              
517             # we only need to adjust if the 1st movie fragment tf_flags bit is set
518 132 50       205 next unless ( ( $tf_flags % 2 ) == 1 );
519            
520 132         106 my $offset64 = ( $offset_high32 * ( 2**32 ) ) + $offset_low32;
521 132 50       168 next if $offset64 < $change_position;
522            
523 132         89 $offset64 -= $sinf_sz;
524 132         119 $offset_high32 = int( $offset64 / ( 2**32 ) + 0.0001 );
525 132         91 $offset_low32 = $offset64 % ( 2**32 );
526 132         200 substr( $self->{buffer}, $tfhd->start + 16,
527             8, pack( 'NN', $offset_high32, $offset_low32 ) );
528             }
529             }
530              
531             sub GetSampleTable {
532 0     0 1 0 my ($self) = @_;
533 0 0       0 my $stsz = $self->FindAtom('stsz') or croak "No stsz table found";
534 0         0 my $sampleCount = unpack 'N',
535             substr( $self->{buffer}, $stsz->start + 16, 4 );
536 0         0 my @samples = unpack 'N*',
537             substr( $self->{buffer}, $stsz->start + 20, $sampleCount * 4 );
538 0 0       0 print "There are $sampleCount samples in stsz atom.\n" if $self->{DEBUG};
539 0         0 return \@samples;
540             }
541              
542             sub DeleteAtom {
543 0     0 1 0 my ( $self, $unwanted ) = @_;
544 0 0       0 my $atom = $self->FindAtom($unwanted) or return;
545 0         0 return $atom->selfDelete();
546             }
547              
548             sub DeleteAtomWithStcoFix {
549 1     1 1 709 my ( $self, $unwanted ) = @_;
550 1 50       3 my $atom = $self->FindAtom($unwanted) or return;
551 1         4 my $siz = $atom->size;
552 1         3 my $pos = $atom->start;
553 1 50       4 $atom->selfDelete() or return;
554 1         4 $self->FixStco( $siz, $pos );
555 1         3 return 1;
556             }
557              
558             sub CleanAppleM4aPersonalData {
559 0     0 1 0 my ( $self, %args ) = @_;
560 0 0 0     0 if( $self->FindAtom("mp4a") or $args{force} ) {
561 0         0 foreach my $atm (@apple_user_id_atoms) {
562 0         0 $self->DeleteAtomWithStcoFix($atm);
563             }
564             }
565 0 0       0 if( $args{zero_free_atoms} ) {
566 0         0 my @free_atoms = $self->FindAtom("free");
567 0         0 foreach my $atm (@free_atoms) {
568 0         0 my $data_size = $atm->{size} - $atm->{offset};
569 0         0 substr( ${ $atm->{rbuf} }, $atm->{start} + $atm->{offset},
  0         0  
570             $data_size, "\x0" x $data_size );
571             }
572             }
573             }
574              
575             sub GetFtype {
576 209     209 1 287 my ($self) = @_;
577 209 50       443 my $atom = $self->FindAtom('ftyp') or return;
578 209         586 my $ftyp = substr( $atom->data, 0, 4 );
579 209         1542 $ftyp =~ s/^(\S+)\s+$/$1/;
580 209         849 return $ftyp;
581             }
582              
583             sub Get3GPInfo {
584 13     13 1 24 my ($self) = @_;
585 13         80 while ( my ( $meta_type, $atom_type ) = each %asset_3GP_types ) {
586 156 50       448 my $atom = $self->FindAtom($atom_type) or next;
587 0         0 my $data =
588             substr( $atom->{buffer}, $atom->start + 14, $atom->size - 14 );
589 0         0 $self->{MP4Info}->{$meta_type} = $data;
590             }
591 13         96 while ( my ( $tag, $alt_tag ) = each %alternate_tag_types ) {
592 104 50       338 $self->{MP4Info}->{$alt_tag} = $self->{MP4Info}->{$tag}
593             if exists $self->{MP4Info}->{$tag};
594             }
595 13         61 my $file_type = $self->GetFtype();
596 13 50       63 $self->{MP4Info}->{FTYP} = $file_type if $file_type;
597 13         54 return $self->{MP4Info};
598             }
599              
600             sub Set3GPInfo {
601 3     3 1 8 my ( $self, $field, $value, $delete_old ) = @_;
602 3         14 my $asset_type = $asset_3GP_types{$field};
603 3 50       10 my $moov = $self->FindAtom('moov') or croak "No moov atom found";
604 3         6 my ( $asset, $udta );
605 3         15 foreach my $typ ( values %asset_3GP_types ) {
606 36 50       102 $asset = $self->FindAtom($typ) or next;
607 0         0 $udta = $asset->GetParent();
608 0 0 0     0 last if $udta and $udta->{type} =~ /udta/i;
609 0         0 $udta = 0;
610             }
611              
612             # if cannot find any asset atoms, look for the udta child of moov
613 3 50       20 $udta = $moov->DirectChildren('udta') unless $udta;
614              
615             # if no direct child of moov udta, make one
616 3 100       9 unless ($udta) {
617 1         4 $moov->insertNew( 'udta', '' );
618 1         5 $self->FixStco( -8, $moov->start );
619 1         6 $udta = $moov->Contained('udta');
620             }
621 3         14 my $entry = $udta->Contained($asset_type);
622 3         6 my $diff = 0;
623 3 50 33     13 if ( $entry and $delete_old ) {
624 0         0 my @unwanted = $udta->Contained($asset_type);
625 0         0 foreach my $u (@unwanted) {
626 0         0 $diff += $u->size;
627 0         0 $u->selfDelete;
628             }
629             }
630              
631             # now we can add the data
632             # we set language code to 'eng'
633 3         5 my $lang = $default_asset_3GP_lang;
634 3         12 my $packed_lang = asset_language_pack_iso_639_2T($lang);
635 3         63 my $data_packet = pack( 'Nn', 0, $packed_lang ) . $value;
636 3         15 my $new_atom = $udta->insertNew( $asset_type, $data_packet );
637 3         10 $diff -= $new_atom->size;
638 3         9 $self->FixStco( $diff, $udta->start );
639 3         22 return $new_atom;
640             }
641              
642             sub GetMetaInfo {
643 85     85 1 1627 my ( $self, $as_text ) = @_;
644              
645             # if we have a 3gp file, dispatch
646 85 100       233 return $self->Get3GPInfo() if $self->GetFtype() =~ /^3g/;
647              
648 72         104 my %meta_tags;
649 72         323 while ( my ( $meta_tag, $type ) = each %tag_types ) {
650 1944         5629 $type =~ s/\W//g;
651 1944 100       3562 my $atm = $self->FindAtom($type) or next;
652 900 100       2440 my $data_atm = $atm->Contained('data') or next;
653 894         1938 my $data = $data_atm->data;
654 894 100       2320 if ( $type eq 'gnre' ) {
    100          
655 92         458 ( undef, undef, $data ) = unpack 'NNn', $data;
656             }
657             elsif ( $type eq 'trkn' ) {
658 78         518 ( undef, undef, undef, $data, $self->{MP4Info}->{TRACKCOUNT} ) =
659             unpack 'NNnnn', $data;
660             }
661             else {
662 724         1927 my $firstchar = unpack( 'C', $data );
663 724 50       1442 if ( $firstchar == 0 ) {
664 724         1631 $data = substr( $data, 8 );
665 724 100       1932 utf8::decode($data) if ($utf8_atoms{$type});
666             }
667             }
668 894         1738 $self->{MP4Info}->{$meta_tag} = $data;
669 894         2611 while ( my ( $tag, $alt_tag ) = each %alternate_tag_types ) {
670 7152 100       24713 $self->{MP4Info}->{$alt_tag} = $self->{MP4Info}->{$tag}
671             if defined $self->{MP4Info}->{$tag};
672             }
673             }
674            
675 72 100       323 if( !$self->{MP4Info}->{GENRE} ) {
676 26         76 my $gen_atom = $self->FindAtom('©gen');
677 26 50       108 if($gen_atom) {
678 0         0 my $genre_txt = substr $gen_atom->data, 16;
679 0         0 my $genre_num = $genre_text_to_genre_numbers{$genre_txt};
680 0         0 $self->{MP4Info}->{GENRE} = $self->{MP4Info}->{GNRE} = $genre_num;
681             }
682             }
683            
684 72 100       187 if ($as_text) {
685             # if as_text, we need to convert the tags to text
686 2 50       11 if ( defined $self->{MP4Info}->{DISK} ) {
687 2         7 ( undef, my $disknum, my $disks ) = unpack 'nnn',
688             $self->{MP4Info}->{DISK};
689 2         9 $self->{MP4Info}->{DISK} = "Disk $disknum of $disks";
690             }
691 2 50       8 if ( defined $self->{MP4Info}->{TRKN} ) {
692 2         8 ( undef, my $tracknum, my $tracks ) = unpack 'nnn',
693             $self->{MP4Info}->{TRKN};
694 2 50 33     8 $self->{MP4Info}->{TRKN} = "Track $tracknum of $tracks"
695             if $tracknum && $tracks;
696             }
697 2 50       7 if ( defined $self->{MP4Info}->{TMPO} ) {
698 2         5 my $tempo = unpack 'n', $self->{MP4Info}->{TMPO};
699 2   50     10 $self->{MP4Info}->{TMPO} = $tempo || "Undefined";
700             }
701 2 50 33     18 if( defined $self->{MP4Info}->{CPRT} &&
702             length( $self->{MP4Info}->{CPRT} ) > 3 ) {
703 2         10 $self->{MP4Info}->{CPRT} = substr( $self->{MP4Info}->{CPRT}, 3 );
704             }
705 2 50       7 if ( defined $self->{MP4Info}->{COVR} ) {
706 2         4 $self->{MP4Info}->{COVR} = "Coverart present";
707             }
708 2 50 33     27 if ( defined $self->{MP4Info}->{GENRE}
      33        
709             and $self->{MP4Info}->{GENRE} =~ /^\d+$/
710             and $self->{MP4Info}->{GENRE} < 128 )
711             {
712 2         7 $self->{MP4Info}->{GENRE} =
713             $genre_strings[ $self->{MP4Info}->{GENRE} - 1 ];
714             }
715             }
716 72         269 $self->{MP4Info}->{FTYP} = $self->GetFtype();
717 72         281 return $self->{MP4Info};
718             }
719              
720             sub GetMP4Info {
721 0     0 1 0 my ($self) = @_;
722 0         0 my $meta = $self->GetMetaInfo();
723 0         0 $meta->{LAYER} = 1;
724 0         0 $meta->{VERSION} = 4;
725 0 0       0 $meta->{COPYRIGHT} = ( exists $meta->{CPRT} ) ? 1 : 0;
726 0         0 my $mdat = $self->FindAtom('mdat');
727 0   0     0 $meta->{SIZE} = $mdat->size || 1;
728 0         0 $meta->{ENCRYPTED} = $self->FindAtom('drms');
729 0         0 my $mvhd_data = $self->FindAtomData('mvhd');
730 0         0 my ( $timescale, $duration );
731              
732 0 0       0 if ($mvhd_data) {
733 0         0 my @mvhd = unpack( 'Ca3NNNNNN', $mvhd_data );
734 0 0       0 if ( $mvhd[0] == 1 ) {
735 0         0 $timescale = $mvhd[6];
736 0         0 $duration = ( $mvhd[7] * ( 2**32 ) + $mvhd[8] ) / $mvhd[6];
737             }
738             else {
739 0         0 $timescale = $mvhd[4];
740 0         0 $duration = $mvhd[5];
741             }
742 0         0 $meta->{SECONDS} = int( $duration / $timescale + 0.5 );
743 0         0 $meta->{MM} = int( $meta->{SECONDS} / 60 );
744 0         0 $meta->{SS} = $meta->{SECONDS} % 60;
745 0         0 $meta->{BITRATE} = int( $meta->{SIZE} / $meta->{SECONDS} + 0.5 );
746             }
747 0         0 return $meta;
748             }
749              
750             sub SetMetaInfo {
751 39     39 1 689 my ( $self, $field, $value, $delete_old, $before, $as_text ) = @_;
752 39         116 $self->GetMetaInfo; # fill default fields like TRACKCOUNT
753              
754             # if we have a 3gp file, dispatch
755 39 100       121 return $self->Set3GPInfo( $self, $field, $value, $delete_old )
756             if $self->GetFtype() =~ /^3g/;
757              
758 36   66     198 my $type = $tag_types{$field} || lc substr( $field, 0, 4 );
759 36   50     92 my $ilst = $self->FindAtom('ilst') || $self->MakeIlstAtom || return;
760 36         68 my $typ = $type;
761 36         131 $typ =~ s/\W//g;
762 36         116 my $entry = $ilst->Contained($typ);
763 36         68 my $diff = 0;
764 36 100 100     154 if ( $entry and $delete_old ) {
765 13         29 my @unwanted = $ilst->Contained($typ);
766 13         26 foreach my $u (@unwanted) {
767 13         51 $diff += $u->size;
768 13         36 $u->selfDelete;
769             }
770             }
771 36 100       106 if ($as_text) {
772 4         72 my %iTMS_meta_atoms = reverse %iTMS_dict_meta_types;
773 4 50       18 if ( $iTMS_meta_atoms{$type} ) {
774 4         7 my %h;
775 4 50 33     65 if ( $type eq 'disk' and $value =~ m/(\d+)\D+(\d+)/ ) {
    100 66        
    50          
776 0         0 $h{discNumber} = $1;
777 0         0 $h{discCount} = $2;
778             }
779             elsif ( $type eq 'trkn' and $value =~ m/(\d+)\D+(\d+)/ ) {
780 3         10 $h{trackNumber} = $1;
781 3         17 $h{trackCount} = $2;
782             }
783             elsif ( $type eq 'gnre' ) {
784 0         0 $value = genre_text_to_genre_num($value);
785             }
786 1         4 else { $h{ $iTMS_meta_atoms{$type} } = $value }
787 4 100       16 $self->FixStco( $diff, $ilst->start ) if $diff;
788 4         18 return $self->iTMS_MetaInfo( \%h, 1 );
789             }
790             }
791 32 100 100     155 if ( $typ eq 'covr' and $ilst->Contained($typ) ) {
792 1         5 $ilst->addMoreArtwork($value);
793 1         2 $diff -= 16;
794             }
795             else {
796 31 100       134 utf8::encode($value) if ($utf8_atoms{$type});
797 31         120 $ilst->insertNewMetaData( $type, $value, $before );
798 31         65 $diff -= 24;
799             }
800 32         54 $diff -= length $value;
801 32         100 $self->FixStco( $diff, $ilst->start );
802             }
803              
804             sub MakeIlstAtom {
805 2     2 1 4 my ($self) = @_;
806 2 50       25 my $moov = $self->FindAtom('moov') or croak "No moov atom found";
807 2         11 my $udta = $moov->DirectChildren('udta');
808              
809             # if no udta under moov, make one under moov
810 2 50       12 unless ($udta) {
811 0         0 $moov->insertNew( 'udta', '' );
812 0         0 $self->FixStco( -8, $moov->start );
813 0         0 $udta = $moov->Contained('udta');
814             }
815              
816             # if we have a meta atom, add an hdlr atom and ilst to it
817             # if we do not have one make one
818 2         4 my ( $meta, $hdlr, $ilst );
819 2         6 $meta = $udta->Contained('meta');
820 2 50       9 unless ($meta) {
821 2         9 $udta->insertNew( 'meta', "\0\0\0\0" );
822 2         8 $self->FixStco( -12, $udta->start );
823 2         8 $meta = $udta->Contained('meta');
824             }
825 2         7 $hdlr = $meta->Contained('hdlr');
826 2 50       6 unless ($hdlr) {
827 2         8 $meta->insertNew( 'hdlr',
828             "\0\0\0\0\0\0\0\0mdirappl\0\0\0\0\0\0\0\0\0" );
829 2         7 $self->FixStco( -33, $meta->start );
830 2         9 $hdlr = $meta->Contained('hdlr');
831             }
832 2         8 $ilst = $meta->Contained('ilst');
833 2 50       7 unless ($ilst) {
834 2         9 $meta->insertNew( 'ilst', '' );
835 2         9 $self->FixStco( -8, $meta->start );
836 2         10 $ilst = $meta->Contained('ilst');
837             }
838 2         19 return $ilst;
839             }
840              
841             sub iTMS_MetaInfo {
842 6     6 1 673 my ( $self, $dict, $keep_old ) = @_;
843 6         8 my ( $key, $type, %info );
844 6 100       26 if ($dict) {
845 5         22 while ( ( $key, $type ) = each %iTMS_dict_meta_types ) {
846 70 100       166 next if $key =~ /Count$/;
847 60 100       171 next unless exists $dict->{$key};
848 14         30 my $data = $dict->{$key};
849 14 100       44 if ( $key eq 'discNumber' ) {
850 1 50       3 my $count = $dict->{discCount} or next;
851 1         6 $data = pack "nnn", 0, $data, $count;
852             }
853 14 100       42 if ( $key eq 'trackNumber' ) {
854 4   50     24 my $count = $dict->{trackCount} || 0;
855 4         22 $data = pack "nnnn", 0, $data, $count, 0;
856             }
857 14 50       33 if ( $key eq 'artworkURL' ) {
858 0         0 eval 'require LWP::Simple; $data = get($data)';
859             }
860 14 100       29 if ( $key eq 'copyright' ) {
861 2         5 $data = "\xE2\x84\x97 " . $data;
862             }
863 14 100       34 if ( $key eq 'genre' ) {
864 1         5 my $gnre = genre_text_to_genre_num($data);
865 1 50       6 $data = pack "n", $gnre unless $gnre eq 'INVALID_GENRE';
866             }
867 14 100       79 $self->SetMetaInfo( $type, $data, $keep_old ? undef: 1,
868             undef, undef );
869             }
870             }
871 6         92 while ( ( $key, $type ) = each %iTMS_dict_meta_types ) {
872 84 100       153 my $meta = $self->FindAtom($type) or next;
873 68         152 my $data = substr( $meta->data, 16 );
874 68 100       227 if ( $type eq 'trkn' ) {
    100          
    100          
875 12         95 ( undef, $info{trackNumber}, $info{trackCount} ) = unpack "nnn",
876             $data;
877             }
878             elsif ( $type eq 'disk' ) {
879 8         59 ( undef, $info{diskNumber}, $info{discCount} ) = unpack "nnn",
880             $data;
881             }
882             elsif ( $type eq 'cprt' ) {
883 4         25 ( undef, $info{'copyright'} ) = split( /\s+/, $data, 2 );
884             }
885 44         195 else { $info{$key} = $data }
886             }
887 6         56 return \%info;
888             }
889              
890              
891             # Get cover art--returns a reference to an array of cover artwork
892             sub GetCoverArt {
893 3     3 1 1021 my ($self) = @_;
894 3 100       11 my @covr = $self->FindAtom('covr') or return;
895 2         3 my @artwork = ();
896 2         70 foreach my $atm (@covr) {
897 2 50       7 my @data_atms = $atm->Contained('data') or next;
898 2         4 foreach my $dt ( @data_atms ) {
899 2         8 push @artwork, substr( $dt->data, 8 );
900             }
901             }
902 2         16 return \@artwork;
903             }
904              
905              
906             # remove all cover art, return number of covers removed
907             # does not remove an empty covr atom (one without cover data)
908             # but otherwise will remove covr atoms as well as cover data
909             sub DeleteAllCoverArt {
910 1     1 1 3 my ($self) = @_;
911 1         1 my $removed = 0;
912 1         3 my @covr = $self->FindAtom('covr');
913 1         3 foreach my $atm (@covr) {
914 1 50       4 my @atoms = $atm->Contained('data') or next;
915 1         4 my $siz = $atm->size;
916 1         4 my $pos = $atm->start;
917 1 50       5 $atm->selfDelete() or next;
918 1         5 $self->FixStco( $siz, $pos );
919 1         5 $removed += scalar @atoms;
920             }
921 1         3 return $removed;
922             }
923              
924             # add a single album cover by EITHER adding one covr atom
925             # OR adding one cover's data to an existing covr atom
926             # takes a argument which should be a compatible graphic format binary
927             # but does NO checks for compatibility with iTunes' cover art display
928             # type OUGHT TO BE 13 for jpeg, 14 for png graphics format,
929             # but method DOES NOT CHECK for type except will default to 13
930             # if no type argument provided
931             sub AddCoverArt {
932 1     1 1 4 my ($self, $art, $type) = @_;
933 1 50       5 $type = 13 unless $type;
934 1         5 my $covr = $self->FindAtom('covr');
935 1 50       3 if( !$covr )
936             {
937 1   50     3 my $ilst = $self->FindAtom('ilst') || $self->MakeIlstAtom || return;
938 1         5 $ilst->insertNew( 'covr', '' );
939 1         5 $self->FixStco( -8, $ilst->start );
940 1 50       3 return unless $covr = $self->FindAtom('covr');
941             }
942 1         23 $covr->insertNew( 'data', pack( 'NN', $type, 0 ) . $art );
943 1         6 $self->FixStco( -16 - length $art, $covr->start );
944 1         4 return 1;
945             }
946              
947             #-----------------------------------------------------------
948              
949             # MP3::Tag analogs, but more fields, and allow setting of tags
950              
951             sub autoinfo {
952 1     1 1 699 my ($self) = @_;
953 1         5 my $tags = $self->GetMetaInfo;
954             return (
955 1         9 $tags->{TITLE}, $tags->{TRKN}, $tags->{ARTIST}, $tags->{ALBUM},
956             $tags->{COMMENT}, $tags->{YEAR}, $tags->{GENRE}
957             );
958             }
959              
960             sub title {
961 6     6 1 17 my ( $self, $new_tag ) = @_;
962 6 100       27 $self->SetMetaInfo( 'TITLE', $new_tag, 1 ) if $new_tag;
963 6         24 my $tags = $self->GetMetaInfo;
964 6   100     54 return $tags->{TITLE} || '';
965             }
966              
967             sub album {
968 8     8 1 720 my ( $self, $new_tag ) = @_;
969 8 100       40 $self->SetMetaInfo( 'ALBUM', $new_tag, 1 ) if $new_tag;
970 8         33 my $tags = $self->GetMetaInfo;
971 8   100     71 return $tags->{ALBUM} || '';
972             }
973              
974             sub artist {
975 5     5 1 14 my ( $self, $new_tag ) = @_;
976 5 100       29 $self->SetMetaInfo( 'ARTIST', $new_tag, 1 ) if $new_tag;
977 5         17 my $tags = $self->GetMetaInfo;
978 5   100     60 return $tags->{ARTIST} || '';
979             }
980              
981             sub comment {
982 5     5 1 17 my ( $self, $new_tag ) = @_;
983 5 100       25 $self->SetMetaInfo( 'COMMENT', $new_tag, 1 ) if $new_tag;
984 5         18 my $tags = $self->GetMetaInfo;
985 5   100     56 return $tags->{COMMENT} || '';
986             }
987              
988             sub year {
989 5     5 1 17 my ( $self, $new_tag ) = @_;
990 5 100       22 $self->SetMetaInfo( 'YEAR', $new_tag, 1 ) if $new_tag;
991 5         18 my $tags = $self->GetMetaInfo;
992 5   100     51 return $tags->{YEAR} || 0;
993             }
994              
995             sub genre {
996 7     7 1 21 my ( $self, $new_tag ) = @_;
997 7 100       34 $self->SetMetaInfo( 'GENRE', pack( "n", $new_tag ), 1 ) if $new_tag;
998 7         26 my $tags = $self->GetMetaInfo;
999 7   100     63 return $tags->{GENRE} || '';
1000             }
1001              
1002             sub genre_as_text {
1003 2     2 1 6 my ( $self, $new_tag ) = @_;
1004 2         4 my ( $i, $genre_num );
1005 2 50       95 if ($new_tag) {
1006 0         0 $self->genre( genre_text_to_genre_num($new_tag) );
1007             }
1008 2         6 return genre_num_to_genre_text( $self->genre );
1009             }
1010              
1011             sub track {
1012 1     1 1 3 my ( $self, $new_trkn ) = @_;
1013 1         3 my $tags = $self->GetMetaInfo(1);
1014 1 50       5 if ($new_trkn) {
1015 0   0     0 my $tcount = $tags->{TRACKCOUNT} || 0;
1016 0         0 $self->SetMetaInfo( 'TRKN', "$new_trkn of $tcount", 1, 0, 1 );
1017 0         0 $tags = $self->GetMetaInfo(1);
1018             }
1019 1   50     11 return $tags->{TRKN} || 0;
1020             }
1021              
1022             sub tracks {
1023 4     4 1 14 my ( $self, $new_trkn, $new_tcount ) = @_;
1024 4 100 66     30 $self->SetMetaInfo( 'TRKN', "$new_trkn of $new_tcount", 1, 0, 1 )
1025             if ( $new_trkn and $new_tcount );
1026 4         25 my $tags = $self->GetMetaInfo;
1027 4   50     19 my $trkn = $tags->{TRKN} || 0;
1028 4   50     15 my $tcount = $tags->{TRACKCOUNT} || 0;
1029 4         18 return ( $trkn, $tcount );
1030             }
1031              
1032             sub total {
1033 1     1 1 2 my ( $self, $new_tcount ) = @_;
1034 1         1 my $tags;
1035 1 50       4 if ($new_tcount) {
1036 0         0 $tags = $self->GetMetaInfo;
1037 0         0 $self->SetMetaInfo( 'TRKN', $tags->{TRKN} . " of $new_tcount", 1, 0,
1038             1 );
1039             }
1040 1         4 $tags = $self->GetMetaInfo;
1041 1   50     10 my $trkn = $tags->{TRKN} || 0;
1042 1   50     5 my $tcount = $tags->{TRACKCOUNT} || 0;
1043 1         15 return $tcount;
1044             }
1045              
1046             sub all_tags {
1047 1     1 1 3 my ( $self, $tags_href ) = @_;
1048 1 50       4 if ( ref $tags_href ) {
1049 1 50       6 $self->title( $tags_href->{title} ) if $tags_href->{title};
1050 1 50       6 $self->artist( $tags_href->{artist} ) if $tags_href->{artist};
1051 1 50       7 $self->album( $tags_href->{album} ) if $tags_href->{album};
1052 1 50       5 $self->comment( $tags_href->{comment} ) if $tags_href->{comment};
1053 1 50       4 $self->genre( $tags_href->{genre} ) if $tags_href->{genre};
1054 1 50       3 $self->year( $tags_href->{year} ) if $tags_href->{year};
1055 1 50       4 $self->track( $tags_href->{track} ) if $tags_href->{track};
1056 1 50       4 $self->total( $tags_href->{total} ) if $tags_href->{total};
1057             }
1058             return {
1059 1         4 title => $self->title(),
1060             artist => $self->artist(),
1061             album => $self->album(),
1062             comment => $self->comment(),
1063             genre => $self->genre(),
1064             year => $self->year(),
1065             track => $self->track(),
1066             total => $self->total(),
1067             };
1068             }
1069              
1070             #------------ other compatibility functions with Audio::TagLib -----------------#
1071              
1072 0     0 1 0 sub setTitle { title(@_) }
1073 0     0 1 0 sub setArtist { artist(@_) }
1074 0     0 1 0 sub setAlbum { album(@_) }
1075 0     0 1 0 sub setComment { comment(@_) }
1076 0     0 1 0 sub setGenre { genre(@_) }
1077 0     0 1 0 sub setTrack { track(@_) }
1078 0     0 1 0 sub setTracks { tracks(@_) }
1079 0     0 1 0 sub setTotal { total(@_) }
1080              
1081             #-------------- non-self helper functions --------------------------#
1082              
1083             sub isMetaDataType {
1084 0     0 1 0 return $meta_info_types{shift};
1085             }
1086              
1087             sub genre_text_to_genre_num {
1088 1     1 1 2 my $text = shift;
1089 1         3 return $genre_text_to_genre_numbers{$text};
1090             }
1091              
1092             sub genre_num_to_genre_text {
1093 2     2 1 5 my $num = shift;
1094 2         21 return $genre_numbers_to_genre_text{$num};
1095             }
1096              
1097             sub asset_language_pack_iso_639_2T {
1098 3     3 1 4 my ($lang3chars) = @_;
1099 9 50       24 my ( $c1, $c2, $c3 ) =
1100 3         14 map { $_ ? ord($_) - 60 : 0 } split( //, $lang3chars );
1101 3         13 return ( $c1 * ( 2**10 ) ) + ( $c2 * ( 2**5 ) ) + $c3;
1102             }
1103              
1104             =head1 NAME
1105              
1106             Audio::M4P::QuickTime -- Perl M4P/MP4/M4a audio / video tools
1107              
1108             =head1 DESCRIPTION
1109              
1110             Perl manipulation of Quicktime Audio files, including protected audio M4P
1111             files. Allows extraction and modification of meta information in Apple
1112             QuickTime AAC/m4a music files.
1113              
1114             =head2 About QuickTime File Structure and Atoms
1115              
1116             M4P is a QuickTime protected audio file format. It is composed of a linear
1117             stream of bytes which are segmented into units called atoms. Some atoms
1118             may be containers for other atoms. iTunes Music Store M4P music files are
1119             Quicktime audio files which are encrypted using a combination of information
1120             in the file's drms atom and information which is commonly stored on the
1121             computer or audio player.
1122              
1123             =head1 SYNOPSIS
1124              
1125             =over 4
1126              
1127             use Audio::M4P::QuickTime;
1128              
1129             my $mp4file = "file.m4p";
1130              
1131             my $qt = new Audio::M4P::QuickTime(file => $mp4file);
1132              
1133             my $tags = $qt->GetMetaInfo;
1134              
1135             print "Artist is $tags->{ARTIST}\n" if $tags->{ARTIST};
1136              
1137             =back
1138              
1139             =head1 METHODS
1140              
1141             =head2 Object Methods
1142              
1143             =over 4
1144              
1145             =item B
1146              
1147             my $qt = Audio::M4P::QuickTime->new;
1148              
1149             $qt = new Audio::M4P::QuickTime(
1150             DEBUG => 2,
1151             DEBUGDUMPFILE => 'quicktime_treedump.html'
1152             );
1153              
1154             $qt = new Audio::M4P::QuickTime(file => 'qt_audio_file.m4p');
1155              
1156             Create a new Audio::M4P::QuickTime object. DEBUG => 1 as argument causes
1157             parse and other information to be printed to stdout during processing.
1158             DEBUG => 2, DEBUGDUMPFILE => "file" causes an HTML tree representation
1159             of the QuickTime file to be emitted to the file given as value to the
1160             argument pair. file => "filename.m4p" causes the named QuickTime file to
1161             be read and parsed during object initialization.
1162              
1163             =item B
1164              
1165             $qt->ReadFile("filename.m4a");
1166              
1167             Read the named file into the QuickTime object buffer.
1168              
1169             =item B
1170              
1171             $qt->ParseBuffer;
1172              
1173             Parse the file that has been read as a QuickTime stream.
1174              
1175             =item B
1176              
1177             $qt->WriteFile("ouput.m4p");
1178              
1179             Write the (possibly modified) file back to the output file argument.
1180              
1181             =item B
1182              
1183             my $hashref = $qt->GetMetaInfo(1);
1184             while(my($tag, $value) = each %{$hashref}) {
1185             print "$tag => $value\n";
1186             }
1187              
1188             Returns a hash reference to meta tag information. Attempts to be compatible
1189             with tag information formats in MP3::Info and MP4::Info. Potential tags are
1190             AAID, ALBUM, ARTIST, COMMENT, COM, CPIL, CPRT, YEAR, DISK, GENRE, GRP, NAM,
1191             RTNG, TMPO, TOO, TRKN, and WRT. Note that, due to preservation of compatibility
1192             with MP3::Info by returning tag info as a hash reference, duplicate entries of
1193             the same tag name, such as multiple comment fields, will not be returned in the hash
1194             reference. An optional second argument, if 1 or true, should convert some
1195             binary fields to text in the tags, for instance
1196             my $hashref = $qt->GetMetaInfo(1);
1197              
1198             =item B
1199              
1200             my $hashref = $qt->GetMP4Info;
1201             while(my($tag, $value) = each %{$hashref}) {
1202             print "$tag => $value\n";
1203             }
1204              
1205             Returns a hash reference to MP3 tag audio information. Attempts to be compatible
1206             with tag information formats in MP3::Info and MP4::Info. Potential tags are
1207             LAYER (1), VERSION (4), SIZE, SECONDS, SS, MM, and BITRATE.
1208              
1209             =item B
1210              
1211             my $comment = "After paying for this music file, I have fair use rights to change it.";
1212              
1213             $qt->SetMetaInfo(COMMENT => $comment);
1214             $qt->SetMetaInfo(GENRE => "Bebop", 1, 'day');
1215              
1216             Set a meta information field. The third argument, if given and true, indicates
1217             that the program should replace all instances of meta data of this type with
1218             the new entry, rather than adding the tag to the existing meta data. The fourth
1219             argument, if given and true, indicated a tag value before which the new tag is
1220             to be placed in the file. The fifth argument indicates the values are in text
1221             form, ie for meta type 'trkn', value is something like 'Track 5 of 11'.
1222              
1223             =item B
1224              
1225             my $hashref = $qt->iTMS_MetaInfo;
1226            
1227             $hashref->{comments} = "A new comment";
1228             $qt->iTMS_MetaInfo($hashref);
1229            
1230             Get or set a meta information field via a hash reference to an Apple iTMS
1231             type dict data structure. Possible fields are copyright, comments,
1232             songName, genre, playlistArtistName, genreID, composerName, playlistName,
1233             year, trackNumber, trackCount, discNumber, discCount, and artworkURL. iTMS
1234             meta data entries may not be compatible with MP3::Info type meta data. An
1235             optional second argument, if true, prevents the method from replacing old meta
1236             information, as in $qt->iTMS_MetaInfo($hashref, 1);
1237              
1238             Note that although this method of manipulating M4P data tags is closest to the
1239             way iTMS and iTunes do metadata, it may be less intuitive for most audio tag
1240             programmers than the MP3::Tag and Audio::TagLib compatible methods below.
1241              
1242             =item B
1243              
1244             my $artwork = $qt->GetCoverArt();
1245             foreach my $pic (@{$artwork}) {
1246             # do stuff with art
1247             }
1248            
1249             Returns a reference to an array of cover artwork. Note: the artwork routines
1250             were suggested and largely contributed by pucklock. (Thanks!)
1251              
1252              
1253             =item B
1254              
1255             $qt->DeleteAllCoverArt;
1256              
1257             Delete all cover art from the file. This removes all data from the covr atom,
1258             if any. Returns the number of cover data atoms deleted.
1259              
1260              
1261             =item B
1262              
1263              
1264             $qt->AddCoverArt( $jpeg_art, 13 ); # $jpeg_art is an iTunes compatible jpeg
1265             $qt->AddCoverArt( $jpeg_art ); # the same as above, defaults to type 13
1266             $qt->AddCoverArt( $png_art, 14 ); # PNG graphics are data type 14
1267              
1268             Add cover artwork to the file. Creates a new covr atom if needed. Returns 1 if
1269             successful, otherwise null.
1270              
1271             The method adds a single album cover by either adding one covr atom or
1272             by adding one cover's data to an existing covr atom. Takes a argument which
1273             should be a compatible graphic format binary, but does NO checks for
1274             compatibility with iTunes' cover art display. The type should be 13
1275             for jpeg, 14 for png graphics format, but defaults to 13.
1276              
1277              
1278             =back
1279              
1280             =head2 MP3::Tag and Audio::TagLib Compatible Functions
1281              
1282             =over 4
1283              
1284             =item B
1285              
1286             my($title, $tracknum, $artist, $album, $comment, $year, $genre) =
1287             $qt->autoinfo;
1288              
1289             Returns an array of tag metadata, similar to the same method in MP3::Tag.
1290              
1291             =item B
1292              
1293             my $album = $qt->album;
1294             $new_album = "My New Album Name";
1295             $qt->album($new_album);
1296              
1297             Get and set title tag data.
1298             Similar to the same method in MP3::TagLib.
1299              
1300             Note this and other tag functions below will usually return the empty
1301             string "" when there is tag data lacking, unless an integer result is expected,
1302             in which case 0 is returned. This is for compatibility with MP3::Tag and
1303             Audio::TagLib's implementation of these methods.
1304              
1305             =item B
1306              
1307             my $artist = $qt->artist;
1308             $new_artist = "My New Artist";
1309             $qt->artist($new_artist);
1310              
1311             Get and set artist tag data.
1312             Similar to the same method in MP3::TagLib.
1313              
1314             =item B
1315              
1316             my $comment = $qt->comment;
1317             $new_comment = "My Comment Goes Here";
1318             $qt->comment($new_comment);
1319              
1320             Get and set comment tag data.
1321             Similar to the same method in MP3::Tag.
1322              
1323             =item B
1324              
1325             my $genre = $qt->genre;
1326             $new_genre = 18;
1327             $qt->genre($new_genre);
1328              
1329             Get and set genre tag data BY NUMBER.
1330              
1331             =item B
1332              
1333             my $text_genre = $qt->genre_as_text;
1334             $new_genre = "Rock";
1335             $qt->genre_as_text($new_genre);
1336              
1337             Get and set genre tag data as text. Note that the given text tag must exist
1338             in the genre database to work. See the "our @genre_strings" object in the
1339             code, which can be imported by the declaration "our @genre_strings;"
1340             in code using the module.
1341              
1342             =item B </td> </tr> <tr> <td class="h" > <a name="1343">1343</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1344">1344</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $title = $qt->title; </td> </tr> <tr> <td class="h" > <a name="1345">1345</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $new_title = "My New One"; </td> </tr> <tr> <td class="h" > <a name="1346">1346</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->title($new_title); </td> </tr> <tr> <td class="h" > <a name="1347">1347</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1348">1348</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Get and set title tag data. </td> </tr> <tr> <td class="h" > <a name="1349">1349</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Similar to the same method in MP3::Tag. </td> </tr> <tr> <td class="h" > <a name="1350">1350</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1351">1351</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<track> </td> </tr> <tr> <td class="h" > <a name="1352">1352</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1353">1353</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $track = $qt->track; </td> </tr> <tr> <td class="h" > <a name="1354">1354</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $new_track = 3; </td> </tr> <tr> <td class="h" > <a name="1355">1355</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->track($new_track); </td> </tr> <tr> <td class="h" > <a name="1356">1356</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1357">1357</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Get or set the track number. </td> </tr> <tr> <td class="h" > <a name="1358">1358</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1359">1359</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<tracks> </td> </tr> <tr> <td class="h" > <a name="1360">1360</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1361">1361</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my ($track, $count) = $qt->tracks; </td> </tr> <tr> <td class="h" > <a name="1362">1362</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $new_track_number = 3; </td> </tr> <tr> <td class="h" > <a name="1363">1363</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $total_tracks_on_CD = 17; </td> </tr> <tr> <td class="h" > <a name="1364">1364</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->tracks($new_track_number, $total_tracks_on_CD); </td> </tr> <tr> <td class="h" > <a name="1365">1365</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1366">1366</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Get or set both the track number and the total tracks on the originating media </td> </tr> <tr> <td class="h" > <a name="1367">1367</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> work. Not actually an MP3::Tag method, but MP4 files, unlike many MP3 files, </td> </tr> <tr> <td class="h" > <a name="1368">1368</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> regularly contain both track number and the total originating CD's track count. </td> </tr> <tr> <td class="h" > <a name="1369">1369</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1370">1370</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<total> </td> </tr> <tr> <td class="h" > <a name="1371">1371</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1372">1372</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $total = $qt->total; </td> </tr> <tr> <td class="h" > <a name="1373">1373</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $new_total = 15; </td> </tr> <tr> <td class="h" > <a name="1374">1374</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->total($new_total); </td> </tr> <tr> <td class="h" > <a name="1375">1375</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1376">1376</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Get or set the track total number. </td> </tr> <tr> <td class="h" > <a name="1377">1377</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1378">1378</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<year> </td> </tr> <tr> <td class="h" > <a name="1379">1379</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1380">1380</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $year = $qt->year; </td> </tr> <tr> <td class="h" > <a name="1381">1381</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $new_year = "My New One"; </td> </tr> <tr> <td class="h" > <a name="1382">1382</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->year($new_year); </td> </tr> <tr> <td class="h" > <a name="1383">1383</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1384">1384</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Get and set year tag data. </td> </tr> <tr> <td class="h" > <a name="1385">1385</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Similar to the same method in MP3::Tag. </td> </tr> <tr> <td class="h" > <a name="1386">1386</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1387">1387</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<all_tags> </td> </tr> <tr> <td class="h" > <a name="1388">1388</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1389">1389</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $tref = $qt->all_tags( album => "My new album", genre => 21 ); </td> </tr> <tr> <td class="h" > <a name="1390">1390</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> print $tref->{artist}; </td> </tr> <tr> <td class="h" > <a name="1391">1391</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1392">1392</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Similar to the Audio::File::Tag B<all> method. Set or get all the above tags. </td> </tr> <tr> <td class="h" > <a name="1393">1393</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> To set the tags pass a hash reference with the names of the tags as keys and </td> </tr> <tr> <td class="h" > <a name="1394">1394</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> the tag values as hash values. Returns a hash reference if no argument is </td> </tr> <tr> <td class="h" > <a name="1395">1395</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> specified. </td> </tr> <tr> <td class="h" > <a name="1396">1396</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1397">1397</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> The following tag names are supported by this method: </td> </tr> <tr> <td class="h" > <a name="1398">1398</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> album </td> </tr> <tr> <td class="h" > <a name="1399">1399</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> artist </td> </tr> <tr> <td class="h" > <a name="1400">1400</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> comment </td> </tr> <tr> <td class="h" > <a name="1401">1401</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> genre ( the integer value genre ) </td> </tr> <tr> <td class="h" > <a name="1402">1402</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> title </td> </tr> <tr> <td class="h" > <a name="1403">1403</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> track </td> </tr> <tr> <td class="h" > <a name="1404">1404</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> total </td> </tr> <tr> <td class="h" > <a name="1405">1405</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1406">1406</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1407">1407</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1408">1408</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head2 Other Audio::TagLib syntactic compatibility </td> </tr> <tr> <td class="h" > <a name="1409">1409</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1410">1410</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1411">1411</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1412">1412</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item The following 'set' methods are equivalent to methods above used with an argument. They are included in this module for Audio::TagLib compatibility: </td> </tr> <tr> <td class="h" > <a name="1413">1413</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1414">1414</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item Method equivalent to </td> </tr> <tr> <td class="h" > <a name="1415">1415</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1416">1416</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ------------------------ </td> </tr> <tr> <td class="h" > <a name="1417">1417</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1418">1418</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setAlbum album </td> </tr> <tr> <td class="h" > <a name="1419">1419</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1420">1420</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setArtist artist </td> </tr> <tr> <td class="h" > <a name="1421">1421</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1422">1422</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setTitle title </td> </tr> <tr> <td class="h" > <a name="1423">1423</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1424">1424</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setComment comment </td> </tr> <tr> <td class="h" > <a name="1425">1425</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1426">1426</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setGenre genre </td> </tr> <tr> <td class="h" > <a name="1427">1427</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1428">1428</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setTrack track </td> </tr> <tr> <td class="h" > <a name="1429">1429</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1430">1430</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setTracks tracks </td> </tr> <tr> <td class="h" > <a name="1431">1431</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1432">1432</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item setTotal total tracks </td> </tr> <tr> <td class="h" > <a name="1433">1433</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1434">1434</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1435">1435</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1436">1436</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head2 Apple m4a personal data removal function </td> </tr> <tr> <td class="h" > <a name="1437">1437</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1438">1438</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1439">1439</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1440">1440</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item B<CleanAppleM4aPersonalData> </td> </tr> <tr> <td class="h" > <a name="1441">1441</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1442">1442</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $file_name = "mp4aIDfile.m4a"; </td> </tr> <tr> <td class="h" > <a name="1443">1443</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $qt = Audio::M4P::QuickTime->new(file => $file_name); </td> </tr> <tr> <td class="h" > <a name="1444">1444</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->CleanAppleM4aPersonalData(); </td> </tr> <tr> <td class="h" > <a name="1445">1445</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->WriteFile('cleaned' . $file_name); </td> </tr> <tr> <td class="h" > <a name="1446">1446</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> </td> </tr> <tr> <td class="h" > <a name="1447">1447</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ...OR... </td> </tr> <tr> <td class="h" > <a name="1448">1448</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1449">1449</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> #!/usr/bin/perl </td> </tr> <tr> <td class="h" > <a name="1450">1450</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1451">1451</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> use Tk; </td> </tr> <tr> <td class="h" > <a name="1452">1452</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> use Cwd; </td> </tr> <tr> <td class="h" > <a name="1453">1453</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> use strict; </td> </tr> <tr> <td class="h" > <a name="1454">1454</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> use warnings; </td> </tr> <tr> <td class="h" > <a name="1455">1455</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> use Audio::M4P::QuickTime; </td> </tr> <tr> <td class="h" > <a name="1456">1456</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1457">1457</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $backup_requested = "yes"; </td> </tr> <tr> <td class="h" > <a name="1458">1458</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1459">1459</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $win = new MainWindow; </td> </tr> <tr> <td class="h" > <a name="1460">1460</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $frm = $win->Frame()->pack; </td> </tr> <tr> <td class="h" > <a name="1461">1461</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $frm->Label( </td> </tr> <tr> <td class="h" > <a name="1462">1462</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -text => "Anonymize Apple iTunes Plus .m4a Files", </td> </tr> <tr> <td class="h" > <a name="1463">1463</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -font => "Garamond 20 bold", </td> </tr> <tr> <td class="h" > <a name="1464">1464</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> )->pack; </td> </tr> <tr> <td class="h" > <a name="1465">1465</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1466">1466</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $do_backup_choice = $frm->Radiobutton( </td> </tr> <tr> <td class="h" > <a name="1467">1467</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -text => "Back Up (append .old.m4a to old files)", </td> </tr> <tr> <td class="h" > <a name="1468">1468</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -value => 'yes', </td> </tr> <tr> <td class="h" > <a name="1469">1469</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -variable => \$backup_requested, </td> </tr> <tr> <td class="h" > <a name="1470">1470</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -font => "Garamond 14 bold", </td> </tr> <tr> <td class="h" > <a name="1471">1471</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> )->pack; </td> </tr> <tr> <td class="h" > <a name="1472">1472</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1473">1473</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $do_no_backup_choice = $frm->Radiobutton( </td> </tr> <tr> <td class="h" > <a name="1474">1474</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -text => "Do Not Back Up (files will be over-written!)", </td> </tr> <tr> <td class="h" > <a name="1475">1475</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -value => 'no', </td> </tr> <tr> <td class="h" > <a name="1476">1476</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -variable => \$backup_requested, </td> </tr> <tr> <td class="h" > <a name="1477">1477</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -font => "Garamond 14 bold", </td> </tr> <tr> <td class="h" > <a name="1478">1478</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> )->pack; </td> </tr> <tr> <td class="h" > <a name="1479">1479</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1480">1480</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $convert_button = $win->Button( </td> </tr> <tr> <td class="h" > <a name="1481">1481</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -text => "Convert Files", </td> </tr> <tr> <td class="h" > <a name="1482">1482</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -command => \&push_button, </td> </tr> <tr> <td class="h" > <a name="1483">1483</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -font => "Garamond 17 bold", </td> </tr> <tr> <td class="h" > <a name="1484">1484</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> )->pack; </td> </tr> <tr> <td class="h" > <a name="1485">1485</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1486">1486</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $exit_button = $win->Button( </td> </tr> <tr> <td class="h" > <a name="1487">1487</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -text => "Exit", </td> </tr> <tr> <td class="h" > <a name="1488">1488</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -command => sub { exit 0 }, </td> </tr> <tr> <td class="h" > <a name="1489">1489</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -font => "Garamond 17 bold", </td> </tr> <tr> <td class="h" > <a name="1490">1490</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> )->pack; </td> </tr> <tr> <td class="h" > <a name="1491">1491</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1492">1492</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> MainLoop; </td> </tr> <tr> <td class="h" > <a name="1493">1493</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1494">1494</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> sub push_button { </td> </tr> <tr> <td class="h" > <a name="1495">1495</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $write_extension = $backup_requested eq 'no' ? '' : '.old.m4a'; </td> </tr> <tr> <td class="h" > <a name="1496">1496</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my @file_list = $win->getOpenFile( </td> </tr> <tr> <td class="h" > <a name="1497">1497</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -defaultextension => ".pl", </td> </tr> <tr> <td class="h" > <a name="1498">1498</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -filetypes => [ [ 'MP4a files', '.m4a', ], [ 'All Files', '*', ], ], </td> </tr> <tr> <td class="h" > <a name="1499">1499</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -initialdir => Cwd::cwd(), </td> </tr> <tr> <td class="h" > <a name="1500">1500</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -initialfile => "getopenfile", </td> </tr> <tr> <td class="h" > <a name="1501">1501</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -title => "Choose Purchased Apple iTunes Plus Files to Anonymize", </td> </tr> <tr> <td class="h" > <a name="1502">1502</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -multiple => 1, </td> </tr> <tr> <td class="h" > <a name="1503">1503</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ); </td> </tr> <tr> <td class="h" > <a name="1504">1504</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1505">1505</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> foreach my $filename (@file_list) { </td> </tr> <tr> <td class="h" > <a name="1506">1506</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> my $qt = Audio::M4P::QuickTime->new( file => $filename ); </td> </tr> <tr> <td class="h" > <a name="1507">1507</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> if ( $qt->FindAtom("mp4a") ) { </td> </tr> <tr> <td class="h" > <a name="1508">1508</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->CleanAppleM4aPersonalData(); </td> </tr> <tr> <td class="h" > <a name="1509">1509</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> rename( $filename, $filename . $write_extension ); </td> </tr> <tr> <td class="h" > <a name="1510">1510</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->WriteFile($filename); </td> </tr> <tr> <td class="h" > <a name="1511">1511</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="1512">1512</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> else { </td> </tr> <tr> <td class="h" > <a name="1513">1513</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $win->messageBox( </td> </tr> <tr> <td class="h" > <a name="1514">1514</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -message => "Error: $filename is not a valid m4a file.", </td> </tr> <tr> <td class="h" > <a name="1515">1515</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -type => 'ok', </td> </tr> <tr> <td class="h" > <a name="1516">1516</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> -icon => 'error' </td> </tr> <tr> <td class="h" > <a name="1517">1517</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> ); </td> </tr> <tr> <td class="h" > <a name="1518">1518</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="1519">1519</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="1520">1520</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> } </td> </tr> <tr> <td class="h" > <a name="1521">1521</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1522">1522</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1523">1523</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Remove personal identifiers from Apple's iTMS .m4a format files. </td> </tr> <tr> <td class="h" > <a name="1524">1524</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1525">1525</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Note: to prevent inadvertent alteration of non-Apple .m4a files, the function </td> </tr> <tr> <td class="h" > <a name="1526">1526</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> requires a m4a atom to be part of the file unless the "force" argument is used, eg. </td> </tr> <tr> <td class="h" > <a name="1527">1527</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1528">1528</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> $qt->CleanAppleM4aPersonalData( force => 1, zero_free_atoms => 1 ); </td> </tr> <tr> <td class="h" > <a name="1529">1529</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> </td> </tr> <tr> <td class="h" > <a name="1530">1530</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Here, the zero_free_atoms => 1 named argument forces all data in free atoms </td> </tr> <tr> <td class="h" > <a name="1531">1531</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> to be nulled out as well. </td> </tr> <tr> <td class="h" > <a name="1532">1532</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1533">1533</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1534">1534</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1535">1535</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1536">1536</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head2 Class Internal Methods and Functions </td> </tr> <tr> <td class="h" > <a name="1537">1537</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1538">1538</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1539">1539</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1540">1540</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item AtomList </td> </tr> <tr> <td class="h" > <a name="1541">1541</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1542">1542</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item AtomTree </td> </tr> <tr> <td class="h" > <a name="1543">1543</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1544">1544</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ConvertDrmsToMp4a </td> </tr> <tr> <td class="h" > <a name="1545">1545</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1546">1546</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item DeleteAtom </td> </tr> <tr> <td class="h" > <a name="1547">1547</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1548">1548</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item DeleteAtomWithStcoFix </td> </tr> <tr> <td class="h" > <a name="1549">1549</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1550">1550</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item DumpTree </td> </tr> <tr> <td class="h" > <a name="1551">1551</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1552">1552</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item FindAtom </td> </tr> <tr> <td class="h" > <a name="1553">1553</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1554">1554</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item FindAtomData </td> </tr> <tr> <td class="h" > <a name="1555">1555</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1556">1556</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item FixStco </td> </tr> <tr> <td class="h" > <a name="1557">1557</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1558">1558</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item GetSampleTable </td> </tr> <tr> <td class="h" > <a name="1559">1559</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1560">1560</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item MakeIlstAtom </td> </tr> <tr> <td class="h" > <a name="1561">1561</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1562">1562</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item MetaInfo </td> </tr> <tr> <td class="h" > <a name="1563">1563</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1564">1564</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ParseDrms </td> </tr> <tr> <td class="h" > <a name="1565">1565</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1566">1566</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ParseMP4Container </td> </tr> <tr> <td class="h" > <a name="1567">1567</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1568">1568</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ParseMeta </td> </tr> <tr> <td class="h" > <a name="1569">1569</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1570">1570</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ParseStsd </td> </tr> <tr> <td class="h" > <a name="1571">1571</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1572">1572</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item ParseMp4a </td> </tr> <tr> <td class="h" > <a name="1573">1573</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1574">1574</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item genre_num_to_genre_text </td> </tr> <tr> <td class="h" > <a name="1575">1575</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1576">1576</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item genre_text_to_genre_num </td> </tr> <tr> <td class="h" > <a name="1577">1577</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1578">1578</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item isMetaDataType </td> </tr> <tr> <td class="h" > <a name="1579">1579</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1580">1580</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item Get3GPInfo </td> </tr> <tr> <td class="h" > <a name="1581">1581</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1582">1582</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item GetFtype </td> </tr> <tr> <td class="h" > <a name="1583">1583</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1584">1584</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item Set3GPInfo </td> </tr> <tr> <td class="h" > <a name="1585">1585</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1586">1586</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item asset_language_pack_iso_639_2T </td> </tr> <tr> <td class="h" > <a name="1587">1587</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1588">1588</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1589">1589</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1590">1590</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 BUGS </td> </tr> <tr> <td class="h" > <a name="1591">1591</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1592">1592</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1593">1593</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1594">1594</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> The Audio::M4P::* code is not re-entrant on a per-file basis, due to recursive </td> </tr> <tr> <td class="h" > <a name="1595">1595</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> changes to containers not being thread-safe. Threaded code using these modules </td> </tr> <tr> <td class="h" > <a name="1596">1596</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> may need to lock down all method calls with a semaphore or other serialization </td> </tr> <tr> <td class="h" > <a name="1597">1597</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> method, unless only one thread is used to modify any given audio file. </td> </tr> <tr> <td class="h" > <a name="1598">1598</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1599">1599</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1600">1600</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1601">1601</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 SEE ALSO WITH THIS MODULE </td> </tr> <tr> <td class="h" > <a name="1602">1602</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1603">1603</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1604">1604</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1605">1605</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item L<Audio::M4P>, L<Audio::M4P::Atom> </td> </tr> <tr> <td class="h" > <a name="1606">1606</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1607">1607</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1608">1608</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1609">1609</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 SEE ALSO </td> </tr> <tr> <td class="h" > <a name="1610">1610</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1611">1611</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1612">1612</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1613">1613</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item L<LWP::UserAgent::iTMS_Client>, L<iTunes::Sid> </td> </tr> <tr> <td class="h" > <a name="1614">1614</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1615">1615</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =item L<MP3::Info>, L<MP4::Info>, L<MP3::Tag>, L<Audio::TagLib>, L<Audio::File::Tag>, L<Mac::iTunes>, L<Net::iTMS>, L<LWP::UserAgent::iTMS_Client> </td> </tr> <tr> <td class="h" > <a name="1616">1616</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1617">1617</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1618">1618</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1619">1619</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 AUTHOR </td> </tr> <tr> <td class="h" > <a name="1620">1620</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1621">1621</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1622">1622</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1623">1623</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> William Herrera B<wherrera@skylightview.com>. </td> </tr> <tr> <td class="h" > <a name="1624">1624</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1625">1625</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1626">1626</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1627">1627</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 SUPPORT </td> </tr> <tr> <td class="h" > <a name="1628">1628</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1629">1629</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1630">1630</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1631">1631</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Questions, feature requests and bug reports should go to </td> </tr> <tr> <td class="h" > <a name="1632">1632</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> <wherrera@skylightview.com>. </td> </tr> <tr> <td class="h" > <a name="1633">1633</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1634">1634</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1635">1635</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1636">1636</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =head1 COPYRIGHT </td> </tr> <tr> <td class="h" > <a name="1637">1637</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1638">1638</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =over 4 </td> </tr> <tr> <td class="h" > <a name="1639">1639</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1640">1640</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> Copyright (c) 2003-2008 William Herrera. All rights reserved. </td> </tr> <tr> <td class="h" > <a name="1641">1641</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> This program is free software; you can redistribute it and/or modify </td> </tr> <tr> <td class="h" > <a name="1642">1642</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> it under the same terms as Perl itself. </td> </tr> <tr> <td class="h" > <a name="1643">1643</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1644">1644</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =back </td> </tr> <tr> <td class="h" > <a name="1645">1645</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1646">1646</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> =cut </td> </tr> <tr> <td class="h" > <a name="1647">1647</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s">   </td> </tr> <tr> <td class="h" > <a name="1648">1648</a> </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td >   </td> <td class="s"> 1; </td> </tr> </table> </body> </html>