File Coverage

blib/lib/Video/Info/MPEG.pm
Criterion Covered Total %
statement 176 252 69.8
branch 42 72 58.3
condition 9 14 64.2
subroutine 24 29 82.7
pod 2 17 11.7
total 253 384 65.8


line stmt bran cond sub pod time code
1             ##------------------------------------------------------------------------
2             ## Package: MPEG.pm
3             ## Author: Benjamin R. Ginter, Allen Day
4             ## Notice: Copyright (c) 2001 Benjamin R. Ginter, Allen Day
5             ## Purpose: Extract information about MPEG files.
6             ## Comments: None
7             ## CVS: $Id: MPEG.pm,v 1.7 2002/11/13 01:05:17 allenday Exp $
8             ##------------------------------------------------------------------------
9              
10             package Video::Info::MPEG;
11              
12 5     5   43526 use strict;
  5         14  
  5         201  
13 5     5   3644 use IO::File;
  5         48440  
  5         897  
14             #use Video::Info;
15 5     5   2567 use Video::Info::Magic;
  5         14  
  5         2338  
16 5     5   3851 use Video::Info::MPEG::Constants;
  5         16  
  5         1513  
17 5     5   3200 use Video::Info::MPEG::Audio;
  5         18  
  5         163  
18 5     5   4752 use Video::Info::MPEG::Video;
  5         16  
  5         174  
19 5     5   3233 use Video::Info::MPEG::System;
  5         19  
  5         231  
20              
21             #use base qw(Video::Info);
22              
23 5     5   53 use constant DEBUG => 0;
  5         10  
  5         631  
24              
25             use Class::MakeMethods::Emulator::MethodMaker
26 5         81 get_set => [
27             'type',
28             'copyright',
29             'comments',
30            
31             'astreams', #no. of audio streams. can this clash with achans?
32             #this has special behavior, method is below
33             'acodec', #audio codec
34             'acodecraw', #audio codec (numeric)
35             'arate', #audio bitrate
36             'achans', #no. of audio channels. can this clash with astreams?
37             'afrequency',
38            
39             'vstreams', #no. of video streams
40             'vcodec', #video codec
41             'vrate', #video bitrate
42             #this has special behavior, method is below
43             #'vframes', #no. of video frames
44            
45             'fps', #video frames/second
46             'scale', #quoeth transcode: if(scale!=0) AVI->fps = (double)rate/(double)scale;
47             'duration', #duration of video, in seconds
48            
49             'width', #frame width
50             'height', #frame height
51            
52             'aspect_raw', #how to handle this? 16:9 scalar, or 16/9 float?
53             'aspect', #not sure what this is. from MPEG
54            
55             '_handle', #filehandle to bitstream
56            
57             'offset',
58             'last_offset',
59            
60             'header_size',
61            
62             'filesize',
63             'filename',
64             'audio_system_header',
65             'video_system_header',
66             'version',
67             'context',
68            
69             'minutes',
70             'MMSS',
71             'title',
72             'author',
73             'description',
74             'rating',
75             'packets',
76             ],
77             new_with_init => 'new',
78 5     5   4258 ;
  5         56278  
79              
80             #
81             ### Get the file versions in sync with CVS
82             #our $VERSION = do { my @r = (q$Version$ =~ /\d+/g); sprintf " %d."."%02d" x $#r, @r };
83              
84             $| = 1;
85              
86             sub init {
87 6     6 0 1085 my $self = shift;
88              
89 6         188 $self->offset(0);
90 6         237 $self->filesize(0);
91 6         202 $self->audio_system_header(0);
92 6         286 $self->video_system_header(0);
93 6         195 $self->version(1);
94              
95 6         64 $self->init_attributes(@_);
96              
97 6         1082 $self->{audio} = Video::Info::MPEG::Audio->new( -file => $self->filename);
98 6         428 $self->{video} = Video::Info::MPEG::Video->new( -file => $self->filename);
99 6         353 $self->{system} = Video::Info::MPEG::System->new(-file => $self->filename);
100             }
101              
102             sub init_attributes {
103 38     38 0 67 my $self = shift;
104 38         89 my %raw_param = @_;
105 38         52 my %param;
106 38         111 foreach(keys %raw_param){/^-?(.+)/;$param{$1} = $raw_param{$_}};
  36         112  
  36         123  
107              
108 38         83 foreach my $attr (qw(
109             astreams arate achans vstreams vrate fps
110             scale duration width height aspect aspect_raw
111             )
112             ) {
113 456         20429 $self->$attr(0);
114             }
115              
116 38         1262 $self->filename($param{file});
117 38   100     1439 $self->filesize(-s $self->filename || 0);
118 38 100       2933 $self->handle($self->filename) if $self->filename;
119             }
120              
121             sub handle {
122 205459     205459 0 298107 my($self,$file) = @_;
123              
124 205459 100       485243 if(defined $file){
125 56         282 my $fh = new IO::File;
126 56 50       1675 $fh->open($file) or die "couldn't open $file";
127 56         4026 $self->_handle($fh);
128             }
129 205459         6551890 return $self->_handle;
130             }
131              
132             ##------------------------------------------------------------------------
133             ## Extra methods
134             ##
135             ##------------------------------------------------------------------------
136             sub minutes {
137 1     1   198 my $self = shift;
138 1         29 my $seconds = int($self->duration) % 60;
139 1         34 my $minutes = (int($self->duration) - $seconds) / 60;
140 1         13 return $minutes;
141             }
142              
143             sub MMSS {
144 0     0   0 my $self = shift;
145 0         0 my $mm = $self->minutes;
146 0         0 my $ss = int($self->duration) - ($self->minutes * 60);
147              
148 0         0 my $return = sprintf( "%02d:%02d",$mm,$ss );
149             }
150              
151             ##------------------------------------------------------------------------
152             ## probe()
153             ##
154             ## Probe the file for content type
155             ##------------------------------------------------------------------------
156             sub probe {
157 8     8 1 646 print "probe()\n" if DEBUG;
158 8         22 my $self = shift;
159              
160 8 100       33 if ( $self->audio->parse ) {
    100          
    50          
161 2         3 print "MPEG Audio Only\n" if DEBUG;
162 2         9 $self->type($self->audio->type);
163 2         22 $self->acodec($self->audio->acodecraw);
164 2         68 $self->astreams(1); #are you sure? could be multiple audio...
165 2         57 $self->vstreams(0);
166 2         13 $self->arate($self->audio->arate);
167 2         22 $self->achans($self->audio->achans);
168 2         22 $self->acodecraw($self->audio->acodecraw);
169 2         62 $self->acodec(acodec2str($self->acodecraw));
170 2         17 return 1;
171             }
172             elsif ( $self->video->parse ) {
173 3         5 print "MPEG Video Only\n" if DEBUG;
174 3         127 $self->vstreams(1); #are you sure? could be multiple video...
175 3         96 $self->astreams(0);
176 3 100       94 $self->vcodec( 'MPEG1' ) if $self->vcodec eq '';
177 3         94 $self->height($self->video->height);
178 3         38 $self->width($self->video->width);
179 3         39 $self->vrate($self->video->vrate);
180 3         39 $self->fps($self->video->fps);
181 3         37 $self->type($self->video->type);
182 3         48 return 1;
183             }
184             elsif ( $self->system->parse ) {
185 3         5 print "MPEG Audio/Video\n" if DEBUG;
186 3         79 $self->astreams(1); #are you sure? could be multiple video...
187 3         92 $self->vstreams(1); #are you sure? could be multiple video...
188 3         29 $self->type($self->system->video->type);
189 3         43 $self->acodecraw($self->system->audio->acodecraw);
190 3         40 $self->acodec(acodec2str($self->system->audio->acodecraw));
191 3         29 $self->achans($self->system->audio->achans);
192 3         40 $self->arate($self->system->audio->arate);
193 3         36 $self->fps($self->system->video->fps);
194 3         39 $self->height($self->system->video->height);
195 3         38 $self->width($self->system->video->width);
196 3 100       111 $self->vcodec( 'MPEG1' ) if $self->vcodec eq '';
197 3         87 $self->duration($self->system->duration);
198 3         34 $self->vrate($self->system->video->vrate);
199 3         38 $self->vframes($self->system->video->vframes);
200 3         111 $self->comments($self->system->video->comments);
201 3         42 return 1;
202             }
203              
204 0         0 return 0;
205             }
206              
207 18     18 0 326 sub audio { $_[0]->{audio} };
208 39     39 0 231 sub system { $_[0]->{system} };
209 21     21 0 450 sub video { $_[0]->{video} };
210             #sub acodecraw { $_[0]->acodec };
211              
212              
213             ##------------------------------------------------------------------------
214             ## parse_system()
215             ##
216             ## Parse a system stream
217             ##------------------------------------------------------------------------
218             sub parse_system {
219 0     0 0 0 my $self = shift;
220 0         0 my $fh = $self->handle;
221 0         0 my $offset = 0;
222              
223 0         0 my ( $pack_start, $pack_len, $pack_head, $packet_size, $packet_type );
224             # print '-' x 74, "\n", "Parse System\n", '-' x 74, "\n";
225              
226             ## Get the first sequence start code (ssc)
227 0 0       0 if ( !$self->next_start_code( PACK_PKT ) ) {
228 0         0 print "Couldn't find packet start code\n" if DEBUG;
229 0         0 return 0;
230             }
231              
232 0         0 return 1;
233             }
234              
235             ##------------------------------------------------------------------------
236             ## parse_user_data()
237             ##
238             ## Parse user data (usually encoder version, etc.)
239             ##
240             ## TODO: Can we use this for annotating video?
241             ##------------------------------------------------------------------------
242             sub parse_user_data {
243 2     2 0 4 my $self = shift;
244 2         4 my $offset = shift;
245              
246             # print "\n", '-' x 74, "\nParse User Data\n", '-' x 74, "\n";
247              
248 2         6 $self->next_start_code( undef, $offset + 1 );
249              
250 2         5 my $all_printable = 1;
251 2         7 my $size = $self->{last_offset} - $offset - 4;
252              
253 2 50       10 return 0 if $size <= 0;
254              
255 2         11 for ( my $i = $offset + 4; $i < $self->{last_offset}; $i++ ) {
256 146         248 my $char = $self->get_byte( $i );
257 146 50 66     578 if ( $char < 0x20 && $char != 0x0A && $char != 0x0D ) {
      33        
258 0         0 $all_printable = 0;
259 0         0 last;
260             }
261             }
262              
263 2 50       9 if ( $all_printable ) {
264 2         3 my $data;
265              
266 2         17 for ( my $i = 0; $i < $size; $i++ ) {
267 146         309 $data .= chr( $self->get_byte( $offset + 4 + $i ) );
268              
269             }
270 2         27 $self->{userdata} = $data;
271 2         85 $self->comments( $data );
272             # print $data, "\n";
273             }
274 2         21 return 1;
275             }
276              
277             ##------------------------------------------------------------------------
278             ## parse_extension()
279             ##
280             ## Parse extensions to MPEG.. hrm, I need some examples to really test
281             ## this.
282             ##------------------------------------------------------------------------
283             sub parse_extension {
284 0     0 0 0 my $self = shift;
285 0         0 my $offset = ( shift ) + 4;
286            
287 0         0 my $code = $self->get_byte( $offset ) >> 4;
288            
289 0 0       0 if ( $code == 1 ) {
    0          
290 0         0 return $self->parse_seq_ext( $offset );
291             }
292             elsif ( $code == 2 ) {
293 0         0 return $self->parse_seq_display_ext( $offset );
294             }
295             else {
296 0         0 die "Unknown Extension: $code\n";
297             }
298             }
299              
300             ##------------------------------------------------------------------------
301             ## parse_seq_ext()
302             ##
303             ## This stuff gets stored in the hashref $self->{sext}. It will also
304             ## modify width, height, vrate, and fps
305             ##------------------------------------------------------------------------
306             sub parse_seq_ext {
307 0     0 0 0 my $self = shift;
308 0         0 my $offset = shift;
309            
310             ## We are an MPEG-2 file
311 0         0 $self->version( 2 );
312              
313 0         0 my $byte1 = $self->get_byte( $offset + 1 );
314 0         0 my $byte2 = $self->get_byte( $offset + 2 );
315              
316             ## Progressive scan mode?
317 0 0       0 if ( $byte1 & 0x08 ) {
318 0         0 $self->{sext}->{progressive} = 1;
319             }
320            
321             ## Chroma format
322 0         0 $self->{sext}->{chroma_format} = ( $byte1 & 0x06 ) >> 1;
323              
324             ## Width
325 0         0 my $hsize = ( $byte1 & 0x01 ) << 1;
326 0         0 $hsize |= ( $byte2 & 80 ) >> 7;
327 0         0 $hsize <<= 12;
328 0 0       0 return 0 if !$self->{vstreams};
329 0         0 $self->{width} |= $hsize;
330            
331             ## Height
332 0         0 $self->{height} |= ( $byte2 & 0x60 ) << 7;;
333            
334             ## Video Bitrate
335 0         0 my $bitrate = ( $byte2 & 0x1F ) << 7;
336 0         0 $bitrate |= ( $self->get_byte( $offset + 3 ) & 0xFE ) >> 1;
337 0         0 $bitrate <<= 18;
338 0         0 $self->{vrate} |= $bitrate;
339              
340             ## Delay
341 0 0       0 if ( $self->get_byte( $offset + 5 ) & 0x80 ) {
342 0         0 $self->{sext}->{low_delay} = 1;
343             }
344             else {
345 0         0 $self->{sext}->{low_delay} = 0;
346             }
347              
348             ## Frame Rate
349 0         0 my $frate_n = ( $self->get_byte( $offset + 5 ) & 0x60 ) >> 5;
350 0         0 my $frate_d = ( $self->get_byte( $offset + 5 ) & 0x1F );
351            
352 0         0 $frate_n++;
353 0         0 $frate_d++;
354            
355 0         0 $self->{fps} = ( $self->{fps} * $frate_n ) / $frate_d;
356            
357 0         0 return 1;
358             }
359              
360             ##------------------------------------------------------------------------
361             ## parse_seq_display_ext()
362             ##
363             ## man, some specs would be nice
364             ##------------------------------------------------------------------------
365             sub parse_seq_display_ext {
366 0     0 0 0 my $self = shift;
367 0         0 my $offset = shift;
368            
369 0         0 my @codes = ();
370            
371 0         0 for ( 0..4 ) {
372 0         0 push @codes, $self->get_byte( $offset + $_ );
373             }
374              
375 0         0 $self->{dext}->{video_format} = ( $codes[0] & 0x0E ) >> 1;
376            
377 0 0       0 if ( $codes[0] & 0x01 ) {
378 0         0 $self->{dext}->{colour_prim} = $codes[1];
379 0         0 $self->{dext}->{transfer_char} = $codes[2];
380 0         0 $self->{dext}->{matrix_coeff} = $codes[3];
381 0         0 $offset += 3;
382             }
383             else {
384 0         0 $self->{dext}->{color_prim} = 0;
385 0         0 $self->{dext}->{transfer_char} = 0;
386 0         0 $self->{dext}->{matrix_coeff} = 0;
387             }
388              
389 0         0 $self->{dext}->{h_display_size} = $codes[1] << 6;
390 0         0 $self->{dext}->{h_display_size} |= ( $codes[2] & 0xFC ) >> 2;
391            
392 0         0 $self->{dext}->{v_display_size} = ( $codes[2] & 0x01 ) << 13;
393 0         0 $self->{dext}->{v_display_size} |= $codes[3] << 5;
394 0         0 $self->{dext}->{v_display_size} |= ( $codes[4] & 0xF8 ) >> 3;
395              
396 0         0 return 1;
397             }
398              
399             ##------------------------------------------------------------------------
400             ## next_start_code()
401             ##
402             ## Find the next sequence start code
403             ##------------------------------------------------------------------------
404             sub next_start_code {
405 32     32 0 95 my $self = shift;
406 32         46 my $start_code = shift;
407 32         41 my $offset = shift;
408 32   100     125 my $debug = shift || 0;
409              
410 32         73 my $fh = $self->handle;
411              
412             ## huh?
413 32 50       217 $offset = $self->{offset} if !defined $offset;
414 32         51 my $skip = 4;
415 32 100       106 if ( !$offset ) {
416 3 50       11 $skip = 1 if !defined $offset;
417             }
418              
419 32         32 if ( DEBUG ) {
420             print "Bytes Per Iteration: $skip\n";
421             print "Got $start_code $offset $debug\n" if defined $start_code;
422             print "Offsets: $offset $self->{offset}\n";
423             print "Seeking to $offset\n" if $offset != $self->{offset};
424             }
425              
426 32         299 seek $fh, $offset, 0;
427              
428             ## die "CALLER: ", ref( $self ), " OFFSET: $offset\n";
429 32         1995 while ( $offset <= $self->filesize - 4 ) {
430              
431             #print "Grabbing 4 bytes from $offset\n";
432             #my $code = $self->grab( 4, $offset );
433             #my ( $a, $b, $c, $d ) = unpack( 'C4', pack( "N", $code ) );
434              
435 50373         387583 my $a = $self->get_byte( $offset );
436 50373 100       118996 if ( $a != 0x00 ) { $offset++; next; }
  48608         54090  
  48608         1342339  
437            
438 1765         5261 my $b = $self->get_byte( $offset + 1 );
439 1765 100       4153 if ( $b != 0x00 ) { $offset += 2; next; };
  1228         1555  
  1228         33310  
440              
441 537         1446 my $c = $self->get_byte( $offset + 2 );
442 537 100       1379 if ( $c != 0x01 ) { $offset += 3; next; };
  22         36  
  22         592  
443              
444 515         1441 my $d = $self->get_byte( $offset + 3 );
445              
446             # printf "Found 0x%02x @ %d\n", $d, $offset + 3;
447             # if ( $a == 0x00 && $b == 0x00 && $c == 0x01 ) {
448 515 100       1155 if ( defined $start_code ) {
449 498 50       1286 if ( ref( $start_code ) eq 'ARRAY' ) {
450 0         0 foreach my $sc ( @$start_code ) {
451 0 0       0 if ( $sc == $d ) {
452             # print "Got it @ $offset!\n" if DEBUG;
453 0         0 $self->{last_offset} = $offset;
454 0         0 return 1;
455             }
456             }
457             }
458             else {
459 498 100       1187 if ( $d == $start_code ) {
460             # print "Got it @ $offset!\n" if DEBUG;
461 14         35 $self->{last_offset} = $offset;
462 14         99 return 1;
463             }
464             }
465             }
466             else {
467 17         33 $self->{last_offset} = $offset;
468 17         66 return $d;
469             }
470            
471             # printf "Skipping 0x%02x 0x%02x 0x%02x 0x%02x @ offset %d\n", $a, $b, $c, $d, $offset;
472 484         14868 $offset++;
473             }
474            
475            
476 1 50       17 return 0 if defined $start_code;
477            
478 0         0 die "No More Sequence Start Codes Found!\n";
479             }
480              
481             ##------------------------------------------------------------------------
482             ## _last_offset
483             ##
484             ## Return the last_offset from a search
485             ##------------------------------------------------------------------------
486             sub _last_offset {
487 12     12   18 my $self = shift;
488 12         25 return $self->{last_offset};
489             }
490              
491             ##------------------------------------------------------------------------
492             ## grab()
493             ##
494             ## Grab n bytes from current offset
495             ##------------------------------------------------------------------------
496             sub grab {
497 205362     205362 0 256632 my $self = shift;
498 205362   50     480721 my $bytes = shift || 1;
499 205362         223247 my $offset = shift;
500 205362   50     743038 my $debug = shift || 0;
501              
502 205362         227165 my $data;
503 205362 50       456540 my $fh = $self->handle or die "$self: Can't get filehandle: $!\n";
504              
505 205362 50       1867501 $offset = $self->{offset} if !defined $offset;
506              
507             # print "GRAB: $offset $bytes bytes called from ", ref( $self ), "\n";
508              
509             ## Would it be good to cache the bytes we've read to avoid the penalty
510             ## of a seek() and read() at the expense of memory?
511              
512             # print "grab: seeking to $offset to grab $bytes bytes\n";
513 205362 100       531053 if ( tell( $fh ) != $offset ) {
514 1305         28068 seek( $fh, $offset, 0 );
515             }
516            
517 205362         432101 read( $fh, $data, $bytes );
518              
519 205362         237400 my $type;
520              
521 205362 100       391943 if ( $bytes == 1 ) {
    50          
    0          
522 205338         348498 $type = 'C';
523             # return unpack( 'C', $data );
524             }
525             elsif ( $bytes == 2 ) {
526 24         37 $type = 'n';
527             # return unpack( 'n', $data );
528             }
529             elsif ( $bytes == 4 ) {
530 0         0 $type = 'N';
531             # return unpack( 'N', $data );
532             }
533             else {
534 0         0 return $data;
535             }
536              
537 205362         426882 $data = unpack( $type, $data );
538             # if ( defined $START_CODE->{ $data } ) {
539             # print "START CODE: $START_CODE->{ $data }\n";
540             # }
541             # elsif ( defined $STREAM_ID->{$data} ) {
542             # print "STREAM ID: $STREAM_ID->{ $data }\n";
543             # }
544              
545 205362         696382 return $data;
546             }
547              
548             ##------------------------------------------------------------------------
549             ## get_byte()
550             ##
551             ## Return a byte from the specified offset
552             ##------------------------------------------------------------------------
553             sub get_byte {
554 205326     205326 0 303202 my $self = shift;
555 205326         515634 return $self->grab( 1, shift );
556             }
557              
558             ##------------------------------------------------------------------------
559             ## get_header()
560             ##
561             ## Grab the four bytes we need for the header
562             ##------------------------------------------------------------------------
563             sub get_header {
564 23     23 0 44 my $self = shift;
565              
566             ## we only need these four bytes
567             ## should do this differently though :|
568 23         420 return [ $self->get_byte( $self->{offset} ),
569             $self->get_byte( $self->{offset} + 1 ),
570             $self->get_byte( $self->{offset} + 2 ),
571             $self->get_byte( $self->{offset} + 3 ) ];
572             }
573              
574             ##------------------------------------------------------------------------
575             ## vframes()
576             ## this is just calculated given fps and duration. MPEG doesn't contain
577             ## this information in the file directly
578             ##------------------------------------------------------------------------
579             sub vframes {
580 10     10 1 4046 my $self = shift;
581 10 100       273 return int($self->duration * $self->fps) if $self->duration;
582 3         34 return 0;
583             }
584              
585             1;
586              
587             __END__