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   23 use strict;
  6         8  
  6         174  
5 6     6   20 use warnings;
  6         7  
  6         121  
6 6     6   21 use Carp;
  6         7  
  6         363  
7             our $VERSION = '0.54';
8              
9 6     6   25 use Scalar::Util 'weaken';
  6         10  
  6         224  
10              
11 6     6   3535 use Tree::Simple 'use_weak_refs';
  6         14390  
  6         57  
12 6     6   2974 use Tree::Simple::Visitor;
  6         4349  
  6         187  
13 6     6   3205 use Tree::Simple::View::HTML;
  6         43168  
  6         12941  
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 5 my ($buf) = @_;
109 1         3 my ( $high32bits, $low32bits ) = unpack( "NN", $buf );
110 1         6 return ( $high32bits * ( 2**32 ) ) + $low32bits;
111             }
112              
113             # begin class methods
114              
115             sub new {
116 1203     1203 1 3012 my ( $class, %args ) = @_;
117 1203         1267 my $self = \%args;
118 1203         2192 bless( $self, $class );
119 1203         2489 $self->{node} = Tree::Simple->new($self);
120 1203 100       27945 if( ref $self->{parent} ) {
121 1188         2668 $self->{parent}->addChild( $self->{node} );
122 1188         82493 weaken $self->{node};
123 1188         1873 weaken $self->{parent};
124             }
125             else {
126 15         35 $self->{parent} = 0;
127             }
128 1203 50       2581 if( ref $self->{rbuf} ) {
129 1203         1719 weaken $self->{rbuf};
130 1203 100       3119 $self->read_buffer( $self->{read_buffer_position} )
131             if exists $self->{read_buffer_position};
132             }
133 1203         2333 return $self;
134             }
135              
136             sub DESTROY {
137 1218     1218   4968 my($self) = @_;
138 1218         1336 delete $self->{parent};
139 1218         1180 delete $self->{rbuf};
140 1218 100       5159 return unless ref $self->{node};
141 15         57 my @kids = $self->{node}->getAllChildren();
142 15         157 foreach my $child (@kids) {
143 196 50       328 next unless ref $child;
144 196         306 my $val = $child->getNodeValue();
145 196 50 33     1459 $val->DESTROY
      33        
146             if ref $val
147             and ref $val->{parent}
148             and $val->{parent} eq $self;
149             }
150 15 50       96 $self->{node}->DESTROY if ref $self->{node};
151 15         82 delete $self->{node};
152             }
153              
154 0     0 1 0 sub parent { return shift->{parent} }
155              
156 7449     7449 1 35201 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 1191 my ( $self, $starting ) = @_;
162 1128         1337 $self->{start} = $starting;
163 1128         1249 $self->{offset} = 8;
164 1128         5177 ( $self->{size}, $self->{type} ) = unpack 'Na4',
165 1128         979 substr( ${ $self->{rbuf} }, $starting, 8 );
166 1128 100       2222 if ( $self->{size} == 1 ) {
167 1         8 $self->{size} =
168 1         2 int64fromN( substr( ${ $self->{rbuf} }, $starting + 8, 8 ) );
169 1         4 $self->{offset} = 16;
170             }
171 1128         1352 return $self->{size};
172             }
173              
174             sub type {
175 535237     535237 1 434231 my ( $self, $newtype ) = @_;
176 535237 50       682777 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 535237         2055386 return $self->{type};
181             }
182              
183             sub start {
184 907     907 1 894 my ( $self, $newstart ) = @_;
185 907 50       1394 $self->{start} = $newstart if defined $newstart;
186 907         2253 return $self->{start};
187             }
188              
189             sub size {
190 3675     3675 1 3109 my ( $self, $newsize ) = @_;
191 3675 100       5308 if ( defined $newsize ) {
192 380 50 33     755 return $self->BigResize($newsize)
193             if $newsize >= 2**32
194             and $self->{size} >= 2**32;
195 380 50 33     687 return $self->toBigSize($newsize)
196             if $newsize >= 2**32
197             and $self->{size} < 2**32;
198 380 50 33     743 return $self->toRegularSize($newsize)
199             if $self->{size} >= 2**32
200             and $newsize < 2**32;
201 380         319 $self->{size} = $newsize;
202 380         309 substr( ${ $self->{rbuf} }, $self->{start}, 4, pack( 'N', $newsize ) );
  380         793  
203             }
204 3675         9098 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 580 my ( $self, $o ) = @_;
245 710 0 0     993 $self->{offset} = $o if defined($o) and ( $o == 8 or $o == 16 );
      33        
246 710         1642 return $self->{offset};
247             }
248              
249             sub data {
250 1178     1178 1 1502 my ( $self, $newdata ) = @_;
251 1178 100       2042 if ( defined $newdata ) {
252 1         2 my $newsize = ( length $newdata ) + 8;
253 1         2 my $diff = $newsize - $self->{size};
254 1         4 $self->resizeContainers($diff);
255 1         166 substr(
256 1         1 ${ $self->{rbuf} },
257             $self->{start} + $self->{offset},
258             $self->{size} - $self->{offset}, $newdata
259             );
260 1         3 $self->size($newsize);
261 1         3 $self->redoStarts( $diff, $self->{start} );
262             }
263 1178         6852 return substr(
264 1178         1108 ${ $self->{rbuf} },
265             $self->{start} + $self->{offset},
266             $self->{size} - $self->{offset}
267             );
268             }
269              
270             sub root {
271 3748     3748 1 4104 my ($self) = @_;
272 3748 100       5833 return $self->node if $self->node->isRoot();
273 469 50       2340 return unless ref $self->{parent};
274 469         720 return $self->{parent}->getNodeValue()->root();
275             }
276              
277             sub getAllRelatives {
278 3279     3279 1 3021 my ($self) = @_;
279 3279         8325 my $visitor = Tree::Simple::Visitor->new();
280 3279         48807 $self->root()->accept($visitor);
281 3279         2691993 my @a = $visitor->getResults;
282 3279         63858 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 303 my ( $self, $diff ) = @_;
303 305 100 66     1182 if ( $self->{parent} and ref $self->{parent} ) {
304 304         592 my $container = $self->{parent}->getNodeValue();
305 304 50       975 if ( $container->{type} ne 'file' ) {
306 304         381 $container->size( $container->size + $diff );
307 304 100       822 $container->resizeContainers($diff)
308             unless $container->{type} eq 'moov';
309             }
310             }
311             }
312              
313             sub redoStarts {
314 91     91 1 115 my ( $self, $diff, $pivot ) = @_;
315 91         91 foreach my $atom ( @{ $self->getAllRelatives() } ) {
  91         138  
316 6628 100 100     14018 $atom->{start} += $diff
317             if $atom->{start} >= $pivot
318             and $atom != $self;
319             }
320             }
321              
322             sub selfDelete {
323 15     15 1 25 my ($self) = @_;
324 15         32 $self->resizeContainers( -$self->size );
325 15         16 substr( ${ $self->{rbuf} }, $self->start, $self->size, '' );
  15         40  
326 15         32 $self->redoStarts( -$self->size, $self->{start} );
327 15 50       61 return unless ref $self->{parent};
328 15         61 $self->{parent}->removeChild( $self->{node} );
329 15         3559 delete $self->{parent};
330 15         61 return 1;
331             }
332              
333             sub insertNew {
334 75     75 1 126 my ( $self, $type, $data, $before ) = @_;
335 75         121 my $node = $self->{node};
336 75         269 my $atom = new Audio::M4P::Atom( parent => $node, rbuf => $self->{rbuf} );
337 75         92 my $after_atom;
338 75 50 33     218 if ( $before and ( $after_atom = $self->Contained($before) ) ) {
339 0         0 $atom->{start} = $after_atom->{start};
340             }
341 75         180 else { $atom->{start} = $self->{start} + $self->{size}; }
342 75         107 $atom->{offset} = 8;
343 75         160 $atom->{size} = 8 + length $data;
344 75         113 $atom->{type} = $type;
345 75         214 $atom->redoStarts( $atom->{size}, $atom->{start} );
346 75 100       687 my $buf = pack( 'Na4', $atom->{size}, $type ? $type : 'junk' ) . $data;
347 75         90 substr( ${ $self->{rbuf} }, $atom->{start}, 0, $buf );
  75         16091  
348 75         317 $self->size( $self->{size} + $atom->{size} );
349 75         183 $self->resizeContainers( $atom->{size} );
350 75         198 return $atom;
351             }
352              
353             sub insertNewMetaData {
354 31     31 1 66 my ( $self, $type, $data, $before ) = @_;
355 31         105 my $wrapper = $self->insertNew( $type, '', $before );
356 31 100       351 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         227 $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 2 my ( $self, $data, $type ) = @_;
368 1 50       6 $type = 13 unless $type;
369 1 50       3 my $covr = $self->Contained('covr') or croak "No covr atom in this atom";
370 1         30 $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 979     979 1 1585 my ( $self, $type ) = @_;
383 979         1404 my $node = $self->{node};
384 979         2251 my @kids = $node->getAllChildren();
385 979         4211 my @results;
386 979         1605 foreach my $child (@kids) {
387 1507         2513 my $val = $child->getNodeValue();
388 1507 100 100     11922 push @results, $val if $val->{type} and $val->{type} =~ /$type/i;
389             }
390 979 100       2064 return @results if wantarray;
391 957 100       1956 return unless scalar @results > 0;
392 917         2840 return $results[0];
393             }
394              
395             sub isContainer {
396 1060     1060 1 995 my ($self) = @_;
397 1060         2284 return $container_atom_types{ $self->{type} };
398             }
399              
400             sub ParentAtom {
401 4     4 1 8 my ($self) = @_;
402 4 50       16 return unless ref $self->{parent};
403 4         16 return $self->{parent}->getNodeValue();
404             }
405              
406             sub DirectChildren {
407 5     5 1 12 my ( $self, $type ) = @_;
408 5         21 my @kids = $self->Contained($type);
409 5         9 my @results;
410 5         14 foreach my $a (@kids) {
411 4 50       12 push @results, $a if $a->ParentAtom() eq $self;
412             }
413 5 50       62 return @results if wantarray;
414 5 100       84 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;