File Coverage

blib/lib/Audio/M4P/Atom.pm
Criterion Covered Total %
statement 163 213 76.5
branch 53 88 60.2
condition 15 39 38.4
subroutine 29 39 74.3
pod 30 30 100.0
total 290 409 70.9


line stmt bran cond sub pod time code
1             package Audio::M4P::Atom;
2              
3             require 5.006;
4 6     6   30 use strict;
  6         11  
  6         180  
5 6     6   27 use warnings;
  6         9  
  6         215  
6 6     6   146 use Carp;
  6         10  
  6         445  
7             our $VERSION = '0.54';
8              
9 6     6   29 use Scalar::Util 'weaken';
  6         17  
  6         286  
10              
11 6     6   8416 use Tree::Simple 'use_weak_refs';
  6         27981  
  6         74  
12 6     6   7674 use Tree::Simple::Visitor;
  6         6986  
  6         192  
13 6     6   7086 use Tree::Simple::View::HTML;
  6         86772  
  6         37691  
14              
15             # see http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
16             my %container_atom_types = (
17             aaid => 1,
18             akid => 1,
19             '©alb' => 1,
20             apid => 1,
21             aART => 1,
22             '©ART' => 1,
23             atid => 1,
24             clip => 1,
25             '©cmt' => 1,
26             '©com' => 1,
27             covr => 1,
28             cpil => 1,
29             cprt => 1,
30             '©day' => 1,
31             dinf => 1,
32             disk => 1,
33             drms => 1,
34             edts => 1,
35             geid => 1,
36             gnre => 1,
37             '©grp' => 1,
38             hinf => 1,
39             hnti => 1,
40             ilst => 1,
41             matt => 1,
42             mdia => 1,
43             meta => 1,
44             minf => 1,
45             moof => 1,
46             moov => 1,
47             mp4a => 1,
48             '©nam' => 1,
49             pinf => 1,
50             plid => 1,
51             rtng => 1,
52             schi => 1,
53             sinf => 1,
54             stbl => 1,
55             stik => 1,
56             stsd => 1,
57             tmpo => 1,
58             '©too' => 1,
59             traf => 1,
60             trak => 1,
61             trkn => 1,
62             udta => 1,
63             '©wrt' => 1,
64             );
65              
66             my %noncontainer_atom_types = (
67             chtb => 1,
68             ctts => 1,
69             data => 1,
70             esds => 1,
71             free => 1,
72             frma => 1,
73             ftyp => 1,
74             '©gen' => 1,
75             hmhd => 1,
76             iviv => 1,
77             'key ' => 1,
78             mdat => 1,
79             mdhd => 1,
80             mp4s => 1,
81             mpv4 => 1,
82             mvhd => 1,
83             name => 1,
84             priv => 1,
85             rtp => 1,
86             sign => 1,
87             stco => 1,
88             stsc => 1,
89             stp => 1,
90             stts => 1,
91             tfhd => 1,
92             tkhd => 1,
93             tref => 1,
94             trun => 1,
95             user => 1,
96             vmhd => 1,
97             wide => 1,
98             );
99              
100             sub int64toN {
101 0     0 1 0 my ($int64) = @_;
102 0         0 my $high32bits = pack( 'N', int( $int64 / ( 2**32 ) + 0.0001 ) );
103 0         0 my $low32bits = pack( 'N', $int64 % ( 2**32 ) );
104 0         0 return $high32bits . $low32bits;
105             }
106              
107             sub int64fromN {
108 1     1 1 4 my ($buf) = @_;
109 1         5 my ( $high32bits, $low32bits ) = unpack( "NN", $buf );
110 1         5 return ( $high32bits * ( 2**32 ) ) + $low32bits;
111             }
112              
113             # begin class methods
114              
115             sub new {
116 1203     1203 1 5577 my ( $class, %args ) = @_;
117 1203         1627 my $self = \%args;
118 1203         2713 bless( $self, $class );
119 1203         4018 $self->{node} = Tree::Simple->new($self);
120 1203 100       38541 if( ref $self->{parent} ) {
121 1188         12716 $self->{parent}->addChild( $self->{node} );
122 1188         122170 weaken $self->{node};
123 1188         2738 weaken $self->{parent};
124             }
125             else {
126 15         46 $self->{parent} = 0;
127             }
128 1203 50       3336 if( ref $self->{rbuf} ) {
129 1203         3008 weaken $self->{rbuf};
130 1203 100       5588 $self->read_buffer( $self->{read_buffer_position} )
131             if exists $self->{read_buffer_position};
132             }
133 1203         3176 return $self;
134             }
135              
136             sub DESTROY {
137 1218     1218   7569 my($self) = @_;
138 1218         1757 delete $self->{parent};
139 1218         1692 delete $self->{rbuf};
140 1218 100       9712 return unless ref $self->{node};
141 15         70 my @kids = $self->{node}->getAllChildren();
142 15         217 foreach my $child (@kids) {
143 196 50       442 next unless ref $child;
144 196         469 my $val = $child->getNodeValue();
145 196 50 33     2150 $val->DESTROY
      33        
146             if ref $val
147             and ref $val->{parent}
148             and $val->{parent} eq $self;
149             }
150 15 50       109 $self->{node}->DESTROY if ref $self->{node};
151 15         100 delete $self->{node};
152             }
153              
154 0     0 1 0 sub parent { return shift->{parent} }
155              
156 7451     7451 1 58036 sub node { return shift->{node} }
157              
158 0     0 1 0 sub rbuf { return shift->{rbuf} }
159              
160             sub read_buffer {
161 1128     1128 1 1447 my ( $self, $starting ) = @_;
162 1128         2533 $self->{start} = $starting;
163 1128         1839 $self->{offset} = 8;
164 1128         6490 ( $self->{size}, $self->{type} ) = unpack 'Na4',
165 1128         1230 substr( ${ $self->{rbuf} }, $starting, 8 );
166 1128 100       3429 if ( $self->{size} == 1 ) {
167 1         8 $self->{size} =
168 1         3 int64fromN( substr( ${ $self->{rbuf} }, $starting + 8, 8 ) );
169 1         4 $self->{offset} = 16;
170             }
171 1128         1928 return $self->{size};
172             }
173              
174             sub type {
175 535737     535737 1 749988 my ( $self, $newtype ) = @_;
176 535737 50       1103389 if ( defined $newtype ) {
177 0         0 $self->{type} = substr( $newtype, 0, 4 );
178 0         0 substr( ${ $self->{rbuf} }, $self->{start} + 4, 4, $self->{type} );
  0         0  
179             }
180 535737         3812165 return $self->{type};
181             }
182              
183             sub start {
184 907     907 1 2085 my ( $self, $newstart ) = @_;
185 907 50       1974 $self->{start} = $newstart if defined $newstart;
186 907         3676 return $self->{start};
187             }
188              
189             sub size {
190 3675     3675 1 4955 my ( $self, $newsize ) = @_;
191 3675 100       7056 if ( defined $newsize ) {
192 380 50 33     1039 return $self->BigResize($newsize)
193             if $newsize >= 2**32
194             and $self->{size} >= 2**32;
195 380 50 33     926 return $self->toBigSize($newsize)
196             if $newsize >= 2**32
197             and $self->{size} < 2**32;
198 380 50 33     1098 return $self->toRegularSize($newsize)
199             if $self->{size} >= 2**32
200             and $newsize < 2**32;
201 380         727 $self->{size} = $newsize;
202 380         462 substr( ${ $self->{rbuf} }, $self->{start}, 4, pack( 'N', $newsize ) );
  380         4132  
203             }
204 3675         12772 return $self->{size};
205             }
206              
207             sub BigResize {
208 0     0 1 0 my ( $self, $newsize ) = @_;
209 0 0       0 croak "atom size big, but offset not 16" if $self->{offset} != 16;
210 0         0 $self->{size} = $newsize;
211 0         0 substr( ${ $self->{rbuf} }, $self->{start} + 8, 8, int64toN($newsize) );
  0         0  
212 0         0 return $self->{size};
213             }
214              
215             sub toBigSize {
216 0     0 1 0 my ( $self, $newsize ) = @_;
217              
218             # need to add 2 bytes to the data section and reset containers and starts
219 0 0 0     0 return unless $self->{offset} == 8 and $newsize >= 2**32;
220 0         0 $self->{offset} = 16;
221 0         0 $self->{size} = $newsize;
222 0         0 substr( ${ $self->{rbuf} }, $self->{start}, 4, pack( 'N', 1 ) );
  0         0  
223 0         0 substr( ${ $self->{rbuf} }, $self->{start} + 8, 0, int64toN($newsize) );
  0         0  
224 0         0 $self->redoStarts(8);
225 0 0       0 $self->resizeContainers(8) unless $self->{type} eq 'moov';
226 0         0 return $self->{size};
227             }
228              
229             sub toRegularSize {
230 0     0 1 0 my ( $self, $newsize ) = @_;
231              
232             # need to remove 2 bytes from data section and reset containers and starts
233 0 0 0     0 return unless $self->{offset} == 16 and $newsize < 2**32;
234 0         0 $self->{offset} = 8;
235 0         0 $self->{size} = $newsize;
236 0         0 substr( ${ $self->{rbuf} }, $self->{start}, 4, pack( 'N', $newsize ) );
  0         0  
237 0         0 substr( ${ $self->{rbuf} }, $self->{start} + 8, 8, '' );
  0         0  
238 0         0 $self->redoStarts(-8);
239 0 0       0 $self->resizeContainers(-8) unless $self->{type} eq 'moov';
240 0         0 return $self->{size};
241             }
242              
243             sub offset {
244 710     710 1 806 my ( $self, $o ) = @_;
245 710 0 0     1258 $self->{offset} = $o if defined($o) and ( $o == 8 or $o == 16 );
      33        
246 710         2355 return $self->{offset};
247             }
248              
249             sub data {
250 1181     1181 1 2604 my ( $self, $newdata ) = @_;
251 1181 100       2859 if ( defined $newdata ) {
252 1         3 my $newsize = ( length $newdata ) + 8;
253 1         2 my $diff = $newsize - $self->{size};
254 1         6 $self->resizeContainers($diff);
255 1         190 substr(
256 1         2 ${ $self->{rbuf} },
257             $self->{start} + $self->{offset},
258             $self->{size} - $self->{offset}, $newdata
259             );
260 1         4 $self->size($newsize);
261 1         5 $self->redoStarts( $diff, $self->{start} );
262             }
263 1181         12008 return substr(
264 1181         1601 ${ $self->{rbuf} },
265             $self->{start} + $self->{offset},
266             $self->{size} - $self->{offset}
267             );
268             }
269              
270             sub root {
271 3749     3749 1 6769 my ($self) = @_;
272 3749 100       8865 return $self->node if $self->node->isRoot();
273 469 50       3813 return unless ref $self->{parent};
274 469         1233 return $self->{parent}->getNodeValue()->root();
275             }
276              
277             sub getAllRelatives {
278 3280     3280 1 9042 my ($self) = @_;
279 3280         13819 my $visitor = Tree::Simple::Visitor->new();
280 3280         85699 $self->root()->accept($visitor);
281 3280         4560838 my @a = $visitor->getResults;
282 3280         107714 return \@a;
283             }
284              
285             sub AtomTree {
286 0     0 1 0 my ($self) = @_;
287             my $view = Tree::Simple::View::HTML->new(
288             $self->{node},
289             (
290             list_css => "list-style: circle;",
291             list_item_css => "font-family: courier;",
292             node_formatter => sub {
293 0     0   0 my ($tree) = @_;
294 0         0 return " " . $tree->getNodeValue->print() . " ";
295             },
296             )
297 0         0 );
298 0         0 return $view->expandAll();
299             }
300              
301             sub resizeContainers {
302 305     305 1 469 my ( $self, $diff ) = @_;
303 305 100 66     2011 if ( $self->{parent} and ref $self->{parent} ) {
304 304         1515 my $container = $self->{parent}->getNodeValue();
305 304 50       1568 if ( $container->{type} ne 'file' ) {
306 304         641 $container->size( $container->size + $diff );
307 304 100       1407 $container->resizeContainers($diff)
308             unless $container->{type} eq 'moov';
309             }
310             }
311             }
312              
313             sub redoStarts {
314 91     91 1 151 my ( $self, $diff, $pivot ) = @_;
315 91         134 foreach my $atom ( @{ $self->getAllRelatives() } ) {
  91         211  
316 6646 100 100     18308 $atom->{start} += $diff
317             if $atom->{start} >= $pivot
318             and $atom != $self;
319             }
320             }
321              
322             sub selfDelete {
323 15     15 1 32 my ($self) = @_;
324 15         35 $self->resizeContainers( -$self->size );
325 15         117 substr( ${ $self->{rbuf} }, $self->start, $self->size, '' );
  15         79  
326 15         52 $self->redoStarts( -$self->size, $self->{start} );
327 15 50       88 return unless ref $self->{parent};
328 15         79 $self->{parent}->removeChild( $self->{node} );
329 15         4590 delete $self->{parent};
330 15         84 return 1;
331             }
332              
333             sub insertNew {
334 75     75 1 174 my ( $self, $type, $data, $before ) = @_;
335 75         137 my $node = $self->{node};
336 75         379 my $atom = new Audio::M4P::Atom( parent => $node, rbuf => $self->{rbuf} );
337 75         128 my $after_atom;
338 75 50 33     292 if ( $before and ( $after_atom = $self->Contained($before) ) ) {
339 0         0 $atom->{start} = $after_atom->{start};
340             }
341 75         250 else { $atom->{start} = $self->{start} + $self->{size}; }
342 75         145 $atom->{offset} = 8;
343 75         285 $atom->{size} = 8 + length $data;
344 75         172 $atom->{type} = $type;
345 75         304 $atom->redoStarts( $atom->{size}, $atom->{start} );
346 75 100       1192 my $buf = pack( 'Na4', $atom->{size}, $type ? $type : 'junk' ) . $data;
347 75         109 substr( ${ $self->{rbuf} }, $atom->{start}, 0, $buf );
  75         22641  
348 75         411 $self->size( $self->{size} + $atom->{size} );
349 75         284 $self->resizeContainers( $atom->{size} );
350 75         265 return $atom;
351             }
352              
353             sub insertNewMetaData {
354 31     31 1 83 my ( $self, $type, $data, $before ) = @_;
355 31         129 my $wrapper = $self->insertNew( $type, '', $before );
356 31 100       449 my $flag =
    50          
    100          
357             ( $type =~ /gnre|disk|trkn/i ) ? 0
358             : ( $type =~ /rtng/i ) ? 21
359             : ( $type =~ /covr/i ) ? 13
360             : 1;
361 31         351 $wrapper->insertNew( 'data', pack( 'NN', $flag, 0 ) . $data );
362             }
363              
364             sub addMoreArtwork {
365              
366             # add more artwork to a covr atom contained in self
367 1     1 1 4 my ( $self, $data, $type ) = @_;
368 1 50       5 $type = 13 unless $type;
369 1 50       4 my $covr = $self->Contained('covr') or croak "No covr atom in this atom";
370 1         48 $covr->insertNew( 'data', pack( 'NN', $type, 0 ) . $data );
371             }
372              
373             sub Container {
374 0     0 1 0 my ( $self, $container_type ) = @_;
375 0 0       0 return unless ref $self->{parent};
376 0         0 my $parent_atom = $self->{parent}->getNodeValue();
377 0 0       0 return $parent_atom if $parent_atom->{type} =~ /$container_type/i;
378 0         0 return $parent_atom->Container($container_type);
379             }
380              
381             sub Contained {
382 982     982 1 2757 my ( $self, $type ) = @_;
383 982         2292 my $node = $self->{node};
384 982         3264 my @kids = $node->getAllChildren();
385 982         6389 my @results;
386 982         2262 foreach my $child (@kids) {
387 1516         4295 my $val = $child->getNodeValue();
388 1516 100 100     16834 push @results, $val if $val->{type} and $val->{type} =~ /$type/i;
389             }
390 982 100       2616 return @results if wantarray;
391 960 100       2933 return unless scalar @results > 0;
392 920         4152 return $results[0];
393             }
394              
395             sub isContainer {
396 1060     1060 1 1328 my ($self) = @_;
397 1060         3534 return $container_atom_types{ $self->{type} };
398             }
399              
400             sub ParentAtom {
401 4     4 1 6 my ($self) = @_;
402 4 50       24 return unless ref $self->{parent};
403 4         16 return $self->{parent}->getNodeValue();
404             }
405              
406             sub DirectChildren {
407 5     5 1 15 my ( $self, $type ) = @_;
408 5         28 my @kids = $self->Contained($type);
409 5         10 my @results;
410 5         10 foreach my $a (@kids) {
411 4 50       19 push @results, $a if $a->ParentAtom() eq $self;
412             }
413 5 50       79 return @results if wantarray;
414 5 100       22 return unless scalar @results > 0;
415 4         15 return $results[0];
416             }
417              
418             sub print {
419 0     0 1   my ($self) = @_;
420 0           return "Atom "
421             . $self->type . " at "
422             . $self->start
423             . " size "
424             . $self->size
425             . " ends at "
426             . ( $self->start + $self->size );
427             }
428              
429             =head1 NAME
430              
431             Audio::M4P::Atom -- M4P/MP4/M4A QuickTime audio music format atoms
432              
433             =head1 DESCRIPTION
434            
435             M4P is a QuickTime protected audio file format. It is composed of a linear
436             stream of bytes which are segmented into units called atoms. Some atoms
437             may contain other atoms. This module has methods for handling atoms which
438             are delegated by the QuickTime and other modules in the Audio::M4P hierarchy.
439            
440             =head2 Class Internal Functions
441              
442             =over 4
443              
444             =item B
445              
446             =item B
447              
448             =item B
449              
450             =item B
451              
452             =item B
453              
454             =item B
455              
456             =item B
457              
458             =item B
459              
460             =item B
461              
462             =item B
463              
464             =item B
465              
466             =item B
467              
468             =item B
469              
470             =item B
471              
472             =item B
473              
474             =item B
475              
476             =item B
477              
478             =item B
479              
480             =item B
481              
482             =item B
483              
484             =item B
485              
486             =item B
487              
488             =item B
489              
490             =item B
491              
492             =item B
493              
494             =item B
495              
496             =item B
497              
498             =item B
499              
500             =item B
501              
502             =item B
503              
504             =back
505            
506             =head1 AUTHOR
507              
508             William Herrera B.
509              
510             =head1 SUPPORT
511              
512             Questions, feature requests and bug reports should go to
513             .
514              
515              
516             =cut
517              
518             1;