File Coverage

blib/lib/MPEG/Info.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             ##------------------------------------------------------------------------
2             ## Package: Info.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: Info.pm,v 1.10 2002/02/13 08:27:51 synaptic Exp $
8             ##------------------------------------------------------------------------
9              
10             package MPEG::Info;
11              
12             require 5.005_62;
13 1     1   9227 use strict;
  1         3  
  1         37  
14 1     1   6 use warnings;
  1         2  
  1         36  
15 1     1   5 use vars qw($VERSION @ISA);
  1         6  
  1         61  
16              
17 1     1   1485 use Video::Info;
  0            
  0            
18             use Video::Info::Magic;
19              
20             use MPEG::Info::Constants;
21              
22             use MPEG::Info::Audio;
23             use MPEG::Info::Video;
24             #use MPEG::Info::System; ## The next version will use this. Let's release.
25              
26             use constant DEBUG => 0;
27              
28             @ISA = qw( Video::Info );
29              
30             our %FIELDS = ( version => 1,
31             size => 0,
32             );
33              
34             for my $datum ( keys %FIELDS ) {
35             no strict "refs"; ## to register new methods in package
36             *$datum = sub {
37             shift; ## XXX: ignore calling class/object
38             $FIELDS{$datum} = shift if @_;
39             return $FIELDS{$datum};
40             }
41             }
42              
43             our $VERSION = '1.00';
44              
45             $| = 1;
46              
47              
48             1;
49              
50             ##------------------------------------------------------------------------
51             ## Override superclass constructor
52             ##------------------------------------------------------------------------
53             sub new {
54             my $proto = shift;
55             my $class = ref( $proto ) || $proto;
56             my $self = {
57             offset => 0,
58             # size => 0,
59             audio_system_header => 0,
60             video_system_header => 0,
61             # version => 1,
62             @_, };
63             bless( $self, $class );
64              
65             ## TODO: Can these be loaded dynamically?
66             ## e.g. If we have a file that contains only video, how can we avoid
67             ## compiling MPEG::Info::Audio? Would this involve calling the
68             ## object method MPEG::Info::Audio::is_audio() before require()?
69             $self->{audio} = MPEG::Info::Audio->new();
70             $self->{video} = MPEG::Info::Video->new();
71             # $self->{system} = MPEG::Info::System->new();
72            
73             ## this hyphen stuff is kind of retarded :)
74             ## doesn't this date back to some perl4 thing?
75             $self->handle( $self->{-file} );
76            
77             return $self;
78             }
79              
80             ##------------------------------------------------------------------------
81             ## probe()
82             ##
83             ## Probe the file for content type
84             ##------------------------------------------------------------------------
85             sub probe {
86             print "probe()\n" if DEBUG;
87             my $self = shift;
88             $self->size( -s $self->{-file} );
89              
90             if ( $self->parse_system ) {
91             print "MPEG Audio/Video\n" if DEBUG;
92             $self->acodec($self->audio->acodecraw);
93             return 1;
94             }
95             elsif ( $self->audio->parse ) {
96             print "MPEG Audio Only\n" if DEBUG;
97             $self->acodec($self->audio->acodecraw);
98             $self->astreams(1); #are you sure? could be multiple audio...
99             $self->vstreams(0);
100             return 1;
101             }
102             elsif ( $self->video->parse ) {
103             print "MPEG Video Only\n" if DEBUG;
104             $self->vstreams(1); #are you sure? could be multiple video...
105             $self->astreams(0);
106             $self->vcodec( 'MPEG1' ) if $self->vcodec eq '';
107             return 1;
108             }
109              
110             return 0;
111             }
112              
113             sub audio { $_[0]->{audio} };
114             sub video { $_[0]->{video} };
115              
116              
117             ##------------------------------------------------------------------------
118             ## parse_system()
119             ##
120             ## Parse a system stream
121             ##------------------------------------------------------------------------
122             sub parse_system {
123             my $self = shift;
124             my $fh = $self->handle;
125             my $offset = 0;
126              
127             my ( $pack_start, $pack_len, $pack_head, $packet_size, $packet_type );
128             # print '-' x 74, "\n", "Parse System\n", '-' x 74, "\n";
129              
130             ## Get the first sequence start code (ssc)
131             if ( !$self->next_start_code( PACK_PKT ) ) {
132             print "Couldn't find packet start code\n" if DEBUG;
133             return 0;
134             }
135              
136             $offset = $self->{last_offset};
137              
138             # print "Found system stream start code at $self->{last_offset} $self->{offset}\n";
139              
140             if ( $self->{last_offset} > 0 ) {
141             print "Warning: junk at the beginning!\n" if DEBUG;
142             }
143              
144             # print "Beginning Search for system packets (audio/video)\n";
145              
146             while ( $offset <= $self->size ) {
147             # print '-' x 20, '[ LOOP ]', '-' x 20, "\n";
148             # print " OFFSET: $self->{offset} $offset\n";
149            
150             my $code = $self->next_start_code( undef, $offset );
151              
152             $offset = $self->{last_offset};
153             # printf( "Found marker '%s' (0x%02x) at %d\n",
154             # $STREAM_ID->{$code},
155             # $code,
156             # $offset );
157              
158             if ( $code == VIDEO_PKT || $code == AUDIO_PKT ) {
159             # print "Audio or Video @ $offset\n";
160             last;
161             }
162              
163             ## if this is a padding packet
164             elsif ( $code == PADDING_PKT ) {
165             # print "\t\tFound Padding Packet at $offset\n";
166             $offset += $self->grab( 2, $offset + 4 );
167             # print "Skipped to $offset\n";
168             next;
169             }
170              
171             ## if this is a PACK
172             elsif ( $code == PACK_PKT ) {
173             $self->{muxrate} = $self->get_mux_rate( $offset + 4);
174             $offset += 12;
175             next;
176             # System->muxrate = ReadPACKMuxRate(offset + 4);
177             # offset += 12; ## standard pack length
178             # continue;
179             }
180            
181             ## No more guessing
182             elsif ( $code != SYS_PKT ) {
183             # printf( "1: Unhandled packet encountered '%s' ( 0x%02x ) at offset %d\n",
184             # $STREAM_ID->{$code},
185             # $code,
186             # $offset );
187             $offset += 4;
188             next;
189             }
190              
191             ## It has to be a system packet
192             ## print "Expecting PACK system start packet\n";
193              
194             ## Check for variable length PACK in mpeg2
195             my $real_offset = $offset;
196             if ( !$self->next_start_code( PACK_PKT, 0 ) ) {
197             print "Can't find system sequence start code!\n" if DEBUG;
198             return 0;
199             }
200            
201             # printf "Found start of pack marker at %d.\n", $self->{last_offset};
202              
203             ## Found a PACK before system packet, compute it's size (mpeg1 != mpeg2)
204             $pack_start = $self->{last_offset};
205             $pack_len = 0;
206             $pack_head = $self->get_byte( $pack_start + 4 );
207              
208             # printf "pack_head: 0x%02x 0x%02x 0x%02x\n", $pack_head, $pack_head & 0xf0, $pack_head & 0xc0;
209             if ( ( $pack_head & 0xF0 ) == 0x20 ) {
210             $self->vcodec('MPEG1');
211             print "MPEG1\n" if DEBUG;
212             $pack_len = 12;
213             }
214             else {
215             if ( ( $pack_head & 0xC0 ) == 0x40 ) {
216             ## new mpeg2 pack : 14 bytes + stuffing
217             $self->vcodec('MPEG2');
218             print "MPEG2\n" if DEBUG;
219             $pack_len = 14 + $self->get_byte( $pack_start + 13 ) & 0x07;
220             }
221             else {
222             ## whazzup?!
223             printf "Weird pack encountered! 0x%02x\n", $pack_head if DEBUG;
224             $pack_len = 12;
225             }
226             }
227              
228             if ( $pack_start + $pack_len != $offset ) {
229             print "FATAL: The PACK Start offset + length don't match the current offset!\n" if DEBUG;
230             print "FATAL: $pack_start + $pack_len != $offset\n" if DEBUG;
231             ## While we should be dying here, it doesn't seem to hurt if we don't?
232             # ?? die;
233             }
234              
235             ## let's go
236             if ( !$self->parse_system_packet( $offset, $pack_start ) ) {
237             print "Strange number of packets!\n" if DEBUG;
238             die;
239             }
240              
241             # print "\n", '-' x 74, "\nResume Parse System\n", '-' x 74, "\n";
242              
243             $packet_size = $self->grab( 2, $offset + 4 );
244             $packet_type = $self->get_byte( $offset + 12 );
245             # print "Packet Size: $packet_size\n";
246             # printf "Packet Type: $packet_type '%s' (0x%02x)\n",
247             # $STREAM_ID->{$packet_type},
248             # $packet_type;
249              
250             my $byte = $self->get_byte( $offset + 15 );
251             # printf "Fetched: '%s' (0x%02x)\n", $STREAM_ID->{$byte}, $byte;
252             if ( $byte == AUDIO_PKT || $byte == VIDEO_PKT ) {
253             # print "System packet with both audio and video\n";
254             $packet_type = VIDEO_PKT;
255             }
256            
257             ## I've never seen a pack with a leading audio packet though
258             ## that might be because i force the packet type to VIDEO.
259             ## Man, a spec would be so nice. :)
260             ##
261             ## TODO: Actually fetch the audio and video header and use
262             ## the stored data when parsing the audio and video.
263              
264             my $audio_header_len = 0;
265             my $video_header_len = 0;
266              
267             if ( $packet_type == AUDIO_PKT ) {
268             ## check for multiple audio system packet headers
269             # print "Audio\n";
270            
271             if ( $self->{audio_system_header} != 0 ) {
272             print "Warning: two or more audio system headers encountered ( $offset )\n" if DEBUG;
273             undef $self->{audio_system_header};
274             }
275              
276             seek $fh, $offset - $pack_len, 0;
277             $audio_header_len = $pack_len + 4 + 2 + $packet_size;
278            
279             if ( read( $fh, $self->{audio_system_header}, $audio_header_len ) != $audio_header_len ) {
280             print "Couldn't read the audio system header\n" if DEBUG;
281             return 0;
282             }
283              
284             }
285             elsif ( $packet_type == VIDEO_PKT ) {
286             ## check for multiple video system packet headers
287             if ( $self->{video_system_header} != 0 ) {
288             print "Warning: two or more video system headers encountered ( $offset )\n" if DEBUG;
289             undef $self->{audio_system_header};
290             }
291              
292             $video_header_len = $pack_len + 6 + $packet_size;
293              
294             ## keep track of the initial timestamp
295             if ( $pack_len == 12 ) {
296             $self->{initial_ts} = $self->read_ts( $offset - $pack_len, 0 );
297             }
298             else {
299             $self->{initial_ts} = $self->read_ts( $offset - $pack_len, 1 );
300             }
301            
302             seek $fh, $offset - $pack_len, 0;
303              
304             if ( read( $fh, $self->{video_system_header}, $video_header_len ) != $video_header_len ) {
305             print "Couldn't read the video system header\n" if DEBUG;
306             return 0;
307             }
308             }
309             else {
310             printf "Unknown system packet '%s', %x @ $offset\n", $STREAM_ID->{$packet_type},
311             $packet_type if DEBUG;
312             return 0;
313             }
314              
315             $offset += 4;
316             }
317              
318             ## okay, this is a miracle but we have what we wanted here
319              
320             ## hey wait, are we really ok?
321             # print "\n\nVerifying video_system_header exists...";
322             if ( !$self->{video_system_header} ) {
323             # print "Didn't find any video system header in this mpeg system file\n";
324             return 0;
325             }
326             # print "OK\n";
327              
328             ## okay, let's go on and find the video and audio infos
329              
330             ## video!
331             # print "1. Finding sequence start code...";
332             if ( !$self->next_start_code( SEQ_HEAD, $offset ) ) {
333             print "Didn't find any video sequence header in this MPEG system file!\n" if DEBUG;
334             return 0;
335             }
336             # print "OK ($offset $self->{offset} $self->{last_offset})\n";
337              
338             # print "Parsing Video at offset $offset\n";
339             ## mmm k, we have the video sequence header
340             if ( !$self->video->parse( $offset ) ) {
341             print "parse_system: call to parse_video() failed\n" if DEBUG;
342             return 0;
343             }
344             # print "OK\n";
345              
346             ## now get the pack and the packet header just before the video sequence
347             if ( !$self->next_start_code( PACK_PKT, 0 ) ) {
348             print "Didn't find any PACK before video sequence\n" if DEBUG;
349             return 0;
350             }
351             # print "Got previous PACK: $offset $self->{offset} $self->{last_offset}\n";
352              
353             ## pack doesn't necessarily precede video sequence
354             # print "Getting next\n";
355             if ( !$self->next_start_code( VIDEO_PKT, $self->{last_offset} ) ) {
356             print "Couldn't find video start code!\n" if DEBUG;
357             die;
358             }
359             # print "Got VIDEO start code: $offset $self->{offset} $self->{last_offset}\n";
360              
361             my $main_offset = $offset;
362             print "Finding audio\n" if DEBUG;
363             if ( $self->next_start_code( AUDIO_PKT, $offset ) ) {
364             print "Found it\n" if DEBUG;
365             my $audio_offset = $self->skip_packet_header( $self->{last_offset} );
366             # print "AUDIO OFFSET: $audio_offset $self->{last_offset} \n";
367            
368             AUDIO: while ( !$self->audio->parse( $audio_offset ) ) {
369             ## mm, audio packet doesn't begin with FFF
370             while ( $audio_offset < $self->size - 10 ) {
371             if ( $self->audio->parse( $audio_offset ) ) {
372             last AUDIO;
373             }
374            
375             $audio_offset++; ## is this ok?
376             }
377             }
378             # print "Parsed audio OK!\n";
379              
380             }
381              
382             ## hrm, what is this
383             # $offset = $self->{last_offset};
384             # print "\tSearching for video stop code at $offset $self->{offset}\n";
385             # while ( 1 ) {
386             # if ( $self->next_start_code( SEQ_HEAD, $offset ) ) {
387             # print "\t\tFound video stop code 0x000001b3 at $offset $self->{last_offset}\n";
388             # last;
389             # }
390             # $offset += 4;
391             # }
392              
393             ## seek the file duration by fetching the last PACK
394             ## and reading its timestamp
395             ## Grab 13 bytes because a PACK is at least 12 bytes
396              
397             if ( $self->next_start_code( PACK_PKT, $self->size - 2500 ) ) {
398             # print "Found final PACK at $self->{last_offset}\n";
399             }
400             # $self->{last_offset} = 2530997;
401             my $byte = $self->get_byte( $self->{last_offset} + 4 );
402             # printf "0x%02x 0x%02x 0x%02x\n", $byte, $byte & 0xF0, $byte & 0xC0;
403            
404             ## see if it's a standard MPEG1
405             if ( $byte & 0xF0 == 0x20 ) {
406             $self->duration( $self->read_ts( 1, $self->{last_offset} + 4 ) );
407             }
408             ## no?
409             else {
410             ## Is it MPEG2?
411             if ( $byte & 0xC0 == 0x40 ) {
412             print "TS: ", $self->read_ts( 2, $self->{last_offset} + 4 ), "\n" if DEBUG;
413             }
414             ## try mpeg1 anyway
415             else {
416             $self->duration( $self->read_ts( 1, $self->{last_offset} + 4) );
417             }
418             }
419            
420              
421             return 1;
422             }
423              
424             ##------------------------------------------------------------------------
425             ## parse_system_packet()
426             ##
427             ## Parse a system packet
428             ##------------------------------------------------------------------------
429             sub parse_system_packet {
430             my $self = shift;
431             my $packet_start = shift;
432             my $pack_start = shift;
433              
434             if ( !defined $packet_start || !defined $pack_start ) {
435             die "parse_system_packet( packet_start, pack_start )\n";
436             }
437              
438             # print "\n", '-' x 74, "\nParse System Packet\n", '-' x 74, "\n";
439              
440             my $size = $self->grab( 2, $packet_start + 4 );
441              
442             $size -= 6; ## ??
443            
444             ## TODO: Check if there's already a system packet
445             if ( $size % 3 != 0 ) {
446             return 0;
447             }
448             # else {
449             # printf("%d streams found\n", $size/3);
450             # }
451              
452             for ( my $i = 0; $i < $size / 3; $i++ ) {
453             my $code = $self->get_byte( $packet_start + 12 + $i * 3 );
454            
455             if ( ( $code & 0xf0 ) == AUDIO_PKT ) {
456             # print "Audio Stream\n";
457             $self->{astreams}++;
458             }
459             elsif ( ( $code & 0xf0 ) == VIDEO_PKT || ( $code & 0xf0 ) == 0xD0 ) {
460             # print "Video Stream\n";
461             $self->{vstreams}++;
462             }
463             }
464              
465             $self->astreams( $self->{astreams} );
466             $self->vstreams( $self->{vstreams} );
467             # print "\t", $self->astreams, " audio\n";
468             # print "\t", $self->vstreams, " video\n";
469              
470             return 1;
471             }
472              
473             ##------------------------------------------------------------------------
474             ## parse_user_data()
475             ##
476             ## Parse user data (usually encoder version, etc.)
477             ##
478             ## TODO: Can we use this for annotating video?
479             ##------------------------------------------------------------------------
480             sub parse_user_data {
481             my $self = shift;
482             my $offset = shift;
483              
484             # print "\n", '-' x 74, "\nParse User Data\n", '-' x 74, "\n";
485              
486             $self->next_start_code( undef, $offset + 1 );
487            
488             my $all_printable = 1;
489             my $size = $self->{last_offset} - $offset - 4;
490              
491             return 0 if $size <= 0;
492            
493             for ( my $i = $offset + 4; $i < $self->{last_offset}; $i++ ) {
494             my $char = $self->get_byte( $i );
495             if ( $char < 0x20 && $char != 0x0A && $char != 0x0D ) {
496             $all_printable = 0;
497             last;
498             }
499             }
500            
501             if ( $all_printable ) {
502             my $data;
503              
504             for ( my $i = 0; $i < $size; $i++ ) {
505             $data .= chr( $self->get_byte( $offset + 4 + $i ) );
506            
507             }
508             $self->{userdata} = $data;
509             $self->comments( $data );
510             # print $data, "\n";
511             }
512            
513             return 1;
514             }
515              
516             ##------------------------------------------------------------------------
517             ## parse_extension()
518             ##
519             ## Parse extensions to MPEG.. hrm, I need some examples to really test
520             ## this.
521             ##------------------------------------------------------------------------
522             sub parse_extension {
523             my $self = shift;
524             my $offset = ( shift ) + 4;
525            
526             my $code = $self->get_byte( $offset ) >> 4;
527            
528             if ( $code == 1 ) {
529             return $self->parse_seq_ext( $offset );
530             }
531             elsif ( $code == 2 ) {
532             return $self->parse_seq_display_ext( $offset );
533             }
534             else {
535             die "Unknown Extension: $code\n";
536             }
537             }
538              
539             ##------------------------------------------------------------------------
540             ## parse_seq_ext()
541             ##
542             ## This stuff gets stored in the hashref $self->{sext}. It will also
543             ## modify width, height, vrate, and fps
544             ##------------------------------------------------------------------------
545             sub parse_seq_ext {
546             my $self = shift;
547             my $offset = shift;
548            
549             ## We are an MPEG-2 file
550             $self->version( 2 );
551              
552             my $byte1 = $self->get_byte( $offset + 1 );
553             my $byte2 = $self->get_byte( $offset + 2 );
554              
555             ## Progressive scan mode?
556             if ( $byte1 & 0x08 ) {
557             $self->{sext}->{progressive} = 1;
558             }
559            
560             ## Chroma format
561             $self->{sext}->{chroma_format} = ( $byte1 & 0x06 ) >> 1;
562              
563             ## Width
564             my $hsize = ( $byte1 & 0x01 ) << 1;
565             $hsize |= ( $byte2 & 80 ) >> 7;
566             $hsize <<= 12;
567             return 0 if !$self->{vstreams};
568             $self->{width} |= $hsize;
569            
570             ## Height
571             $self->{height} |= ( $byte2 & 0x60 ) << 7;;
572            
573             ## Video Bitrate
574             my $bitrate = ( $byte2 & 0x1F ) << 7;
575             $bitrate |= ( $self->get_byte( $offset + 3 ) & 0xFE ) >> 1;
576             $bitrate <<= 18;
577             $self->{vrate} |= $bitrate;
578              
579             ## Delay
580             if ( $self->get_byte( $offset + 5 ) & 0x80 ) {
581             $self->{sext}->{low_delay} = 1;
582             }
583             else {
584             $self->{sext}->{low_delay} = 0;
585             }
586              
587             ## Frame Rate
588             my $frate_n = ( $self->get_byte( $offset + 5 ) & 0x60 ) >> 5;
589             my $frate_d = ( $self->get_byte( $offset + 5 ) & 0x1F );
590            
591             $frate_n++;
592             $frate_d++;
593            
594             $self->{fps} = ( $self->{fps} * $frate_n ) / $frate_d;
595            
596             return 1;
597             }
598              
599             ##------------------------------------------------------------------------
600             ## parse_seq_display_ext()
601             ##
602             ## man, some specs would be nice
603             ##------------------------------------------------------------------------
604             sub parse_seq_display_ext {
605             my $self = shift;
606             my $offset = shift;
607            
608             my @codes = ();
609            
610             for ( 0..4 ) {
611             push @codes, $self->get_byte( $offset + $_ );
612             }
613              
614             $self->{dext}->{video_format} = ( $codes[0] & 0x0E ) >> 1;
615            
616             if ( $codes[0] & 0x01 ) {
617             $self->{dext}->{colour_prim} = $codes[1];
618             $self->{dext}->{transfer_char} = $codes[2];
619             $self->{dext}->{matrix_coeff} = $codes[3];
620             $offset += 3;
621             }
622             else {
623             $self->{dext}->{color_prim} = 0;
624             $self->{dext}->{transfer_char} = 0;
625             $self->{dext}->{matrix_coeff} = 0;
626             }
627              
628             $self->{dext}->{h_display_size} = $codes[1] << 6;
629             $self->{dext}->{h_display_size} |= ( $codes[2] & 0xFC ) >> 2;
630            
631             $self->{dext}->{v_display_size} = ( $codes[2] & 0x01 ) << 13;
632             $self->{dext}->{v_display_size} |= $codes[3] << 5;
633             $self->{dext}->{v_display_size} |= ( $codes[4] & 0xF8 ) >> 3;
634              
635             return 1;
636             }
637              
638             ##------------------------------------------------------------------------
639             ## next_start_code()
640             ##
641             ## Find the next sequence start code
642             ##------------------------------------------------------------------------
643             sub next_start_code {
644             my $self = shift;
645             my $start_code = shift;
646             my $offset = shift;
647             my $debug = shift || 0;
648              
649             my $fh = $self->handle;
650              
651             $offset = $self->{offset} if !defined $offset;
652             my $skip = 4;
653             if ( !$offset ) {
654             $skip = 1 if !defined $offset;
655             }
656              
657             # print "Bytes Per Iteration: $skip\n" if $debug;
658             # print "Got $start_code $offset $debug\n" if defined $start_code && $debug;
659              
660             print "Seeking to $offset\n" if $offset != $self->{offset} && DEBUG;
661             seek $fh, $offset, 0;
662              
663             # die "CALLER: ", ref( $self ), " OFFSET: $offset\n";
664             while ( $offset <= $self->size - 4 ) {
665             # print "Grabbing 4 bytes from $offset\n";
666             my $code = $self->grab( 4, $offset );
667             my ( $a, $b, $c, $d ) = unpack( 'C4', pack( "N", $code ) );
668              
669             # printf "Found 0x%02x\n", $d;
670             if ( $a == 0x00 && $b == 0x00 && $c == 0x01 ) {
671             if ( defined $start_code ) {
672             if ( ref( $start_code ) eq 'ARRAY' ) {
673             foreach my $sc ( @$start_code ) {
674             if ( $sc == $d ) {
675             print "Got it @ $offset!\n" if DEBUG;
676             $self->{last_offset} = $offset;
677             return 1;
678             }
679             }
680             }
681             else {
682             if ( $d == $start_code ) {
683             print "Got it @ $offset!\n" if DEBUG;
684             $self->{last_offset} = $offset;
685             return 1;
686             }
687             }
688             }
689             else {
690             $self->{last_offset} = $offset;
691             return $d;
692             }
693             }
694             # else {
695             # printf "Skipping 0x%02x 0x%02x 0x%02x 0x%02x @ offset %d\n", $a, $b, $c, $d, $offset;
696             # }
697            
698             $offset++;
699             }
700              
701             return 0 if defined $start_code;
702              
703             die "No More Sequence Start Codes Found!\n";
704             }
705              
706             ##------------------------------------------------------------------------
707             ## get_mux_rate()
708             ##
709             ## Calculate the mux rate
710             ##------------------------------------------------------------------------
711             sub get_mux_rate {
712             my $self = shift;
713             my $offset = shift || $self->{offset};
714              
715             my $muxrate = 0;
716              
717             my $byte = $self->get_byte( $offset );
718              
719             if ( ( $byte & 0xC0 ) == 0x40 ) {
720             $muxrate = $self->get_byte( $offset + 6 ) << 14;
721             $muxrate |= $self->get_byte( $offset + 7 ) << 6;
722             $muxrate |= $self->get_byte( $offset + 8 ) >> 2;
723             }
724             else {
725             ## maybe mpeg1
726             if ( ( $byte & 0xf0 ) != 0x20 ) {
727             print "Weird pack header while parsing muxrate (offset ", $offset, ")\n" if DEBUG;
728             # die;
729             }
730              
731             $muxrate = ( $self->get_byte( $offset + 5 ) & 0x7f ) << 15;
732             $muxrate |= $self->get_byte( $offset + 6 ) << 7;
733             $muxrate |= $self->get_byte( $offset + 7 ) >> 1;
734             }
735            
736             $muxrate *= 50;
737             return $muxrate;
738             }
739              
740             ##------------------------------------------------------------------------
741             ## grab()
742             ##
743             ## Grab n bytes from current offset
744             ##------------------------------------------------------------------------
745             sub grab {
746             my $self = shift;
747             my $bytes = shift || 1;
748             my $offset = shift;
749             my $debug = shift || 0;
750              
751             my $data;
752             my $fh = $self->handle or die "Can't get filehandle: $!\n";
753              
754             $offset = $self->{offset} if !defined $offset;
755              
756             # print "GRAB: $fh $offset $bytes (called from ", ref( $self ), ")\n";
757              
758             ## Would it be good to cache the bytes we've read to avoid the penalty
759             ## of a seek() and read() at the expense of memory?
760              
761             # print "grab: seeking to $offset to grab $bytes bytes\n";
762             if ( tell( $fh ) != $offset ) {
763             seek( $fh, $offset, 0 );
764             }
765            
766             read( $fh, $data, $bytes );
767              
768             my $type;
769              
770             if ( $bytes == 1 ) {
771             $type = 'C';
772             # return unpack( 'C', $data );
773             }
774             elsif ( $bytes == 2 ) {
775             $type = 'n';
776             # return unpack( 'n', $data );
777             }
778             elsif ( $bytes == 4 ) {
779             $type = 'N';
780             # return unpack( 'N', $data );
781             }
782             else {
783             return $data;
784             }
785              
786             $data = unpack( $type, $data );
787             # if ( defined $START_CODE->{ $data } ) {
788             # print "START CODE: $START_CODE->{ $data }\n";
789             # }
790             # elsif ( defined $STREAM_ID->{$data} ) {
791             # print "STREAM ID: $STREAM_ID->{ $data }\n";
792             # }
793              
794             return $data;
795             }
796              
797             ##------------------------------------------------------------------------
798             ## get_byte()
799             ##
800             ## Return a byte from the specified offset
801             ##------------------------------------------------------------------------
802             sub get_byte {
803             my $self = shift;
804             return $self->grab( 1, shift );
805             }
806              
807             ##------------------------------------------------------------------------
808             ## skip_packet_header()
809             ##
810             ## Skip a packet header
811             ##------------------------------------------------------------------------
812             sub skip_packet_header {
813             my $self = shift;
814             my $offset = shift;
815              
816             if ( $self->version == 1 ) {
817             ## skip startcode and packet size
818             $offset += 6;
819              
820             ## remove stuffing bytes
821             my $byte = $self->get_byte( $offset );
822              
823             while ( $byte & 0x80 ) {
824             $byte = $self->get_byte( ++$offset );
825             }
826              
827             ## next two bytes are 01
828             if ( ( $byte & 0xC0 ) == 0x40 ) {
829             $offset += 2;
830             }
831            
832             $byte = $self->get_byte( $offset );
833              
834             if ( $byte & 0xF0 == 0x20 ) {
835             $offset += 5;
836             }
837             elsif ( $byte & 0xF0 == 0x30 ) {
838             $offset += 10;
839             }
840             else {
841             $offset++;
842             }
843            
844             return $offset;
845             }
846             elsif ( $self->version == 2 ) {
847             ## this is a PES, easyer
848             ## offset + 9 is the header length (-9)
849             return $offset + 9 + ( $self->get_byte + 8 );
850             }
851             else {
852             return $offset + 10;
853             }
854             }
855              
856             ##------------------------------------------------------------------------
857             ## read_ts()
858             ##
859             ## Read an MPEG-1 or MPEG-2 timestamp
860             ##------------------------------------------------------------------------
861             sub read_ts {
862             my $self = shift;
863             my $type = shift;
864             my $offset = shift;
865              
866             my $ts = 0;
867              
868             if ( $type == 1 ) {
869             my $highbit = ( $self->get_byte( $offset ) >> 3 ) & 0x01;
870             my $low4bytes = ( ( $self->get_byte( $offset ) >> 1 ) & 0x30 ) << 30;
871             $low4bytes |= ( $self->get_byte( $offset + 1 ) << 22 );
872             $low4bytes |= ( ( $self->get_byte( $offset + 2 ) >> 1 ) << 15 );
873             $low4bytes |= ( $self->get_byte( $offset + 3 ) << 7 );
874             $low4bytes |= ( $self->get_byte( $offset + 4 ) >> 1 );
875              
876             $ts = $highbit * ( 1 << 16 );
877             $ts += $low4bytes;
878             $ts /= 90000;
879             }
880             elsif ( $type == 2 ) {
881             print "Define mpeg-2 timestamps\n" if DEBUG;
882             }
883             return $ts;
884              
885             }
886              
887             ##------------------------------------------------------------------------
888             ## get_header()
889             ##
890             ## Grab the four bytes we need for the header
891             ##------------------------------------------------------------------------
892             sub get_header {
893             my $self = shift;
894              
895             ## we only need these four bytes
896             ## should do this differently though :|
897             return [ $self->get_byte( $self->{offset} ),
898             $self->get_byte( $self->{offset} + 1 ),
899             $self->get_byte( $self->{offset} + 2 ),
900             $self->get_byte( $self->{offset} + 3 ) ];
901            
902             }
903              
904             ##------------------------------------------------------------------------
905             ## vframes()
906             ## this is just calculated given fps and duration. MPEG doesn't contain
907             ## this information in the file directly
908             ##------------------------------------------------------------------------
909             sub vframes(){
910             my $self = shift;
911             return int($self->duration * $self->fps) if $self->duration;
912             return 0;
913             }
914              
915              
916             # Preloaded methods go here.
917              
918             __END__