File Coverage

blib/lib/Image/ExifTool/M2TS.pm
Criterion Covered Total %
statement 209 421 49.6
branch 88 268 32.8
condition 14 59 23.7
subroutine 7 7 100.0
pod 0 4 0.0
total 318 759 41.9


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: M2TS.pm
3             #
4             # Description: Read M2TS (AVCHD) meta information
5             #
6             # Revisions: 2009/07/03 - P. Harvey Created
7             #
8             # References: 1) http://neuron2.net/library/mpeg2/iso13818-1.pdf
9             # 2) http://www.blu-raydisc.com/Assets/Downloadablefile/BD-RE_Part3_V2.1_WhitePaper_080406-15271.pdf
10             # 3) http://www.videohelp.com/forum/archive/reading-avchd-playlist-files-bdmv-playlist-mpl-t358888.html
11             # 4) http://en.wikipedia.org/wiki/MPEG_transport_stream
12             # 5) http://www.dunod.com/documents/9782100493463/49346_DVB.pdf
13             # 6) http://trac.handbrake.fr/browser/trunk/libhb/stream.c
14             # 7) http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=04560141
15             # 8) http://www.w6rz.net/xport.zip
16             # 9) https://en.wikipedia.org/wiki/Program-specific_information
17             #
18             # Notes: Variable names containing underlines are the same as in ref 1.
19             #
20             # Glossary: PES = Packetized Elementary Stream
21             # PAT = Program Association Table
22             # PMT = Program Map Table
23             # PCR = Program Clock Reference
24             # PID = Packet Identifier
25             #
26             # To Do: - parse PCR to obtain average bitrates?
27             #------------------------------------------------------------------------------
28              
29             package Image::ExifTool::M2TS;
30              
31 1     1   4566 use strict;
  1         2  
  1         55  
32 1     1   6 use vars qw($VERSION);
  1         2  
  1         45  
33 1     1   5 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         5521  
34              
35             $VERSION = '1.23';
36              
37             # program map table "stream_type" lookup (ref 6/1/9)
38             my %streamType = (
39             0x00 => 'Reserved',
40             0x01 => 'MPEG-1 Video',
41             0x02 => 'MPEG-2 Video',
42             0x03 => 'MPEG-1 Audio',
43             0x04 => 'MPEG-2 Audio',
44             0x05 => 'ISO 13818-1 private sections',
45             0x06 => 'ISO 13818-1 PES private data',
46             0x07 => 'ISO 13522 MHEG',
47             0x08 => 'ISO 13818-1 DSM-CC',
48             0x09 => 'ISO 13818-1 auxiliary',
49             0x0A => 'ISO 13818-6 multi-protocol encap',
50             0x0B => 'ISO 13818-6 DSM-CC U-N msgs',
51             0x0C => 'ISO 13818-6 stream descriptors',
52             0x0D => 'ISO 13818-6 sections',
53             0x0E => 'ISO 13818-1 auxiliary',
54             0x0F => 'MPEG-2 AAC Audio',
55             0x10 => 'MPEG-4 Video',
56             0x11 => 'MPEG-4 LATM AAC Audio',
57             0x12 => 'MPEG-4 generic',
58             0x13 => 'ISO 14496-1 SL-packetized',
59             0x14 => 'ISO 13818-6 Synchronized Download Protocol',
60             0x15 => 'Packetized metadata',
61             0x16 => 'Sectioned metadata',
62             0x17 => 'ISO/IEC 13818-6 DSM CC Data Carousel metadata',
63             0x18 => 'ISO/IEC 13818-6 DSM CC Object Carousel metadata',
64             0x19 => 'ISO/IEC 13818-6 Synchronized Download Protocol metadata',
65             0x1a => 'ISO/IEC 13818-11 IPMP',
66             0x1b => 'H.264 (AVC) Video',
67             0x1c => 'ISO/IEC 14496-3 (MPEG-4 raw audio)',
68             0x1d => 'ISO/IEC 14496-17 (MPEG-4 text)',
69             0x1e => 'ISO/IEC 23002-3 (MPEG-4 auxiliary video)',
70             0x1f => 'ISO/IEC 14496-10 SVC (MPEG-4 AVC sub-bitstream)',
71             0x20 => 'ISO/IEC 14496-10 MVC (MPEG-4 AVC sub-bitstream)',
72             0x21 => 'ITU-T Rec. T.800 and ISO/IEC 15444 (JPEG 2000 video)',
73             0x24 => 'H.265 (HEVC) Video', #PH
74             0x42 => 'Chinese Video Standard',
75             0x7f => 'ISO/IEC 13818-11 IPMP (DRM)',
76             0x80 => 'DigiCipher II Video',
77             0x81 => 'A52/AC-3 Audio',
78             0x82 => 'HDMV DTS Audio',
79             0x83 => 'LPCM Audio',
80             0x84 => 'SDDS Audio',
81             0x85 => 'ATSC Program ID',
82             0x86 => 'DTS-HD Audio',
83             0x87 => 'E-AC-3 Audio',
84             0x8a => 'DTS Audio',
85             0x90 => 'PGS Audio', #https://www.avsforum.com/threads/bass-eq-for-filtered-movies.2995212/page-399
86             0x91 => 'A52b/AC-3 Audio',
87             0x92 => 'DVD_SPU vls Subtitle',
88             0x94 => 'SDDS Audio',
89             0xa0 => 'MSCODEC Video',
90             0xea => 'Private ES (VC-1)',
91             # 0x80-0xFF => 'User Private',
92             );
93              
94             # "table_id" values (ref 5)
95             my %tableID = (
96             0x00 => 'Program Association',
97             0x01 => 'Conditional Access',
98             0x02 => 'Program Map',
99             0x03 => 'Transport Stream Description',
100             0x40 => 'Actual Network Information',
101             0x41 => 'Other Network Information',
102             0x42 => 'Actual Service Description',
103             0x46 => 'Other Service Description',
104             0x4a => 'Bouquet Association',
105             0x4e => 'Actual Event Information - Present/Following',
106             0x4f => 'Other Event Information - Present/Following',
107             0x50 => 'Actual Event Information - Schedule', #(also 0x51-0x5f)
108             0x60 => 'Other Event Information - Schedule', # (also 0x61-0x6f)
109             0x70 => 'Time/Date',
110             0x71 => 'Running Status',
111             0x72 => 'Stuffing',
112             0x73 => 'Time Offset',
113             0x7e => 'Discontinuity Information',
114             0x7f => 'Selection Information',
115             # 0x80-0xfe => 'User Defined',
116             );
117              
118             # PES stream ID's for which a syntax field does not exist
119             my %noSyntax = (
120             0xbc => 1, # program_stream_map
121             0xbe => 1, # padding_stream
122             0xbf => 1, # private_stream_2
123             0xf0 => 1, # ECM_stream
124             0xf1 => 1, # EMM_stream
125             0xf2 => 1, # DSMCC_stream
126             0xf8 => 1, # ITU-T Rec. H.222.1 type E stream
127             0xff => 1, # program_stream_directory
128             );
129              
130             my $knotsToKph = 1.852; # knots --> km/h
131              
132             # information extracted from the MPEG-2 transport stream
133             %Image::ExifTool::M2TS::Main = (
134             GROUPS => { 2 => 'Video' },
135             VARS => { NO_ID => 1 },
136             NOTES => q{
137             The MPEG-2 transport stream is used as a container for many different
138             audio/video formats (including AVCHD). This table lists information
139             extracted from M2TS files.
140             },
141             VideoStreamType => {
142             PrintHex => 1,
143             PrintConv => \%streamType,
144             SeparateTable => 'StreamType',
145             },
146             AudioStreamType => {
147             PrintHex => 1,
148             PrintConv => \%streamType,
149             SeparateTable => 'StreamType',
150             },
151             Duration => {
152             Notes => q{
153             the -fast option may be used to avoid scanning to the end of file to
154             calculate the Duration
155             },
156             ValueConv => '$val / 27000000', # (clock is 27MHz)
157             PrintConv => 'ConvertDuration($val)',
158             },
159             # the following tags are for documentation purposes only
160             _AC3 => { SubDirectory => { TagTable => 'Image::ExifTool::M2TS::AC3' } },
161             _H264 => { SubDirectory => { TagTable => 'Image::ExifTool::H264::Main' } },
162             _MISB => { SubDirectory => { TagTable => 'Image::ExifTool::MISB::Main' } },
163             );
164              
165             # information extracted from AC-3 audio streams
166             %Image::ExifTool::M2TS::AC3 = (
167             GROUPS => { 1 => 'AC3', 2 => 'Audio' },
168             VARS => { NO_ID => 1 },
169             NOTES => 'Tags extracted from AC-3 audio streams.',
170             AudioSampleRate => {
171             PrintConv => {
172             0 => '48000',
173             1 => '44100',
174             2 => '32000',
175             },
176             },
177             AudioBitrate => {
178             PrintConvColumns => 2,
179             ValueConv => {
180             0 => 32000,
181             1 => 40000,
182             2 => 48000,
183             3 => 56000,
184             4 => 64000,
185             5 => 80000,
186             6 => 96000,
187             7 => 112000,
188             8 => 128000,
189             9 => 160000,
190             10 => 192000,
191             11 => 224000,
192             12 => 256000,
193             13 => 320000,
194             14 => 384000,
195             15 => 448000,
196             16 => 512000,
197             17 => 576000,
198             18 => 640000,
199             32 => '32000 max',
200             33 => '40000 max',
201             34 => '48000 max',
202             35 => '56000 max',
203             36 => '64000 max',
204             37 => '80000 max',
205             38 => '96000 max',
206             39 => '112000 max',
207             40 => '128000 max',
208             41 => '160000 max',
209             42 => '192000 max',
210             43 => '224000 max',
211             44 => '256000 max',
212             45 => '320000 max',
213             46 => '384000 max',
214             47 => '448000 max',
215             48 => '512000 max',
216             49 => '576000 max',
217             50 => '640000 max',
218             },
219             PrintConv => 'ConvertBitrate($val)',
220             },
221             SurroundMode => {
222             PrintConv => {
223             0 => 'Not indicated',
224             1 => 'Not Dolby surround',
225             2 => 'Dolby surround',
226             },
227             },
228             AudioChannels => {
229             PrintConvColumns => 2,
230             PrintConv => {
231             0 => '1 + 1',
232             1 => 1,
233             2 => 2,
234             3 => 3,
235             4 => '2/1',
236             5 => '3/1',
237             6 => '2/2',
238             7 => '3/2',
239             8 => 1,
240             9 => '2 max',
241             10 => '3 max',
242             11 => '4 max',
243             12 => '5 max',
244             13 => '6 max',
245             },
246             },
247             );
248              
249             #------------------------------------------------------------------------------
250             # Extract information from AC-3 audio stream
251             # Inputs: 0) ExifTool ref, 1) data ref
252             # Reference: http://www.atsc.org/standards/a_52b.pdf
253             sub ParseAC3Audio($$)
254             {
255 1     1 0 3 my ($et, $dataPt) = @_;
256 1 50       17 if ($$dataPt =~ /\x0b\x77..(.)/sg) {
257 1         8 my $sampleRate = ord($1) >> 6;
258 1         6 my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::AC3');
259 1         4 $et->HandleTag($tagTablePtr, AudioSampleRate => $sampleRate);
260             }
261             }
262              
263             #------------------------------------------------------------------------------
264             # Extract information from AC-3 stream descriptor
265             # Inputs: 0) ExifTool ref, 1) data ref
266             # Reference: http://www.atsc.org/standards/a_52b.pdf
267             # Note: This information is duplicated in the Audio stream, but it
268             # is somewhat easier to extract it from the descriptor instead
269             sub ParseAC3Descriptor($$)
270             {
271 1     1 0 3 my ($et, $dataPt) = @_;
272 1 50       4 return if length $$dataPt < 3;
273 1         4 my @v = unpack('C3', $$dataPt);
274 1         5 my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::AC3');
275             # $et->HandleTag($tagTablePtr, 'AudioSampleRate', $v[0] >> 5);
276 1         6 $et->HandleTag($tagTablePtr, 'AudioBitrate', $v[1] >> 2);
277 1         12 $et->HandleTag($tagTablePtr, 'SurroundMode', $v[1] & 0x03);
278 1         7 $et->HandleTag($tagTablePtr, 'AudioChannels', ($v[2] >> 1) & 0x0f);
279             # don't (yet) decode any more (language codes, etc)
280             }
281              
282             #------------------------------------------------------------------------------
283             # Parse PID stream data
284             # Inputs: 0) ExifTool ref, 1) PID number, 2) PID type, 3) PID name, 4) data ref
285             # Returns: 0=stream parsed OK,
286             # 1=stream parsed but we want to parse more of these,
287             # -1=can't parse yet because we don't know the type
288             sub ParsePID($$$$$)
289             {
290 2     2 0 5 my ($et, $pid, $type, $pidName, $dataPt) = @_;
291             # can't parse until we know the type (Program Map Table may be later in the stream)
292 2 50       5 return -1 unless defined $type;
293 2         7 my $verbose = $et->Options('Verbose');
294 2 50       10 if ($verbose > 1) {
295 0         0 my $out = $et->Options('TextOut');
296 0         0 printf $out "Parsing stream 0x%.4x (%s) %d bytes\n", $pid, $pidName, length($$dataPt);
297 0         0 $et->VerboseDump($dataPt);
298             }
299 2         43 my $more = 0;
300 2 50 33     25 if ($type == 0x01 or $type == 0x02) {
    50 33        
    100 33        
    50 33        
    0          
    0          
301             # MPEG-1/MPEG-2 Video
302 0         0 require Image::ExifTool::MPEG;
303 0         0 Image::ExifTool::MPEG::ParseMPEGAudioVideo($et, $dataPt);
304             } elsif ($type == 0x03 or $type == 0x04) {
305             # MPEG-1/MPEG-2 Audio
306 0         0 require Image::ExifTool::MPEG;
307 0         0 Image::ExifTool::MPEG::ParseMPEGAudio($et, $dataPt);
308             } elsif ($type == 0x1b) {
309             # H.264 Video
310 1         767 require Image::ExifTool::H264;
311 1         6 $more = Image::ExifTool::H264::ParseH264Video($et, $dataPt);
312             # force parsing additional H264 frames with ExtractEmbedded option
313 1 50       6 if ($$et{OPTIONS}{ExtractEmbedded}) {
    50          
314 0         0 $more = 1;
315             } elsif (not $$et{OPTIONS}{Validate}) {
316 1         6 $et->WarnOnce('The ExtractEmbedded option may find more tags in the video data',3);
317             }
318             } elsif ($type == 0x81 or $type == 0x87 or $type == 0x91) {
319             # AC-3 audio
320 1         4 ParseAC3Audio($et, $dataPt);
321             } elsif ($type == 0x15) {
322             # packetized metadata (look for MISB code starting after 5-byte header)
323 0 0       0 if ($$dataPt =~ /^.{5}\x06\x0e\x2b\x34/s) {
324 0         0 $more = Image::ExifTool::MISB::ParseMISB($et, $dataPt, GetTagTable('Image::ExifTool::MISB::Main'));
325 0 0       0 if (not $$et{OPTIONS}{ExtractEmbedded}) {
    0          
326 0         0 $more = 0; # extract from only the first packet unless ExtractEmbedded is used
327             } elsif ($$et{OPTIONS}{ExtractEmbedded} > 2) {
328 0         0 $more = 1; # read past unknown 0x15 packets if ExtractEmbedded > 2
329             }
330             }
331             } elsif ($type < 0) {
332 0 0 0     0 if ($$dataPt =~ /^(.{164})?(.{24})A[NS][EW]/s) {
    0          
    0          
    0          
    0          
333             # (Blueskysea B4K, Novatek NT96670)
334             # 0000: 01 00 ff 00 30 31 32 33 34 35 37 38 61 62 63 64 [....01234578abcd]
335             # 0010: 65 66 67 0a 00 00 00 00 00 00 00 00 00 00 00 00 [efg.............]
336             # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
337             # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
338             # 0040: 00 00 00 00 30 31 32 33 34 35 37 38 71 77 65 72 [....01234578qwer]
339             # 0050: 74 79 75 69 6f 70 0a 00 00 00 00 00 00 00 00 00 [tyuiop..........]
340             # 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
341             # 0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
342             # 0080: 00 00 00 00 63 38 61 61 32 35 63 66 34 35 65 65 [....c8aa25cf45ee]
343             # 0090: 61 39 65 32 34 34 32 66 61 65 62 35 65 30 39 39 [a9e2442faeb5e099]
344             # 00a0: 30 37 64 34 15 00 00 00 10 00 00 00 1b 00 00 00 [07d4............]
345             # 00b0: 15 00 00 00 01 00 00 00 09 00 00 00 41 4e 57 00 [............ANW.]
346             # 00c0: 82 9a 57 45 98 b2 00 46 66 66 e4 41 d7 e3 14 43 [..WE...Fff.A...C]
347             # 00d0: 01 00 02 00 03 00 04 00 05 00 06 00 [............]
348             # (Viofo A119V3)
349             # 0000: 08 00 00 00 07 00 00 00 18 00 00 00 15 00 00 00 [................]
350             # 0010: 03 00 00 00 0b 00 00 00 41 4e 45 00 01 f2 ac 45 [........ANE....E]
351             # 0020: 2d 7f 6e 45 b8 1e 97 41 d7 23 46 43 00 00 00 00 [-.nE...A.#FC....]
352             # pad with dummy header and parse with existing FreeGPS code (minimum 92 bytes)
353 0   0     0 my $dat = ("\0" x 16) . substr($$dataPt, length($1 || '')) . ("\0" x 20);
354 0         0 my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
355 0         0 Image::ExifTool::QuickTime::ProcessFreeGPS($et, { DataPt => \$dat }, $tbl);
356 0         0 $more = 1;
357             } elsif ($$dataPt =~ /^A([NS])([EW])\0/s) {
358             # INNOVV TS video (same format is INNOVV MP4)
359 0         0 SetByteOrder('II');
360 0         0 my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
361 0         0 while ($$dataPt =~ /(A[NS][EW]\0.{28})/g) {
362 0         0 my $dat = $1;
363 0         0 my $lat = abs(GetFloat(\$dat, 4)); # (abs just to be safe)
364 0         0 my $lon = abs(GetFloat(\$dat, 8)); # (abs just to be safe)
365 0         0 my $spd = GetFloat(\$dat, 12) * $knotsToKph;
366 0         0 my $trk = GetFloat(\$dat, 16);
367 0         0 my @acc = unpack('x20V3', $dat);
368 0 0       0 map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc;
  0         0  
369 0         0 Image::ExifTool::QuickTime::ConvertLatLon($lat, $lon);
370 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
371 0 0       0 $et->HandleTag($tagTbl, GPSLatitude => abs($lat) * (substr($dat,1,1) eq 'S' ? -1 : 1));
372 0 0       0 $et->HandleTag($tagTbl, GPSLongitude => abs($lon) * (substr($dat,2,1) eq 'W' ? -1 : 1));
373 0         0 $et->HandleTag($tagTbl, GPSSpeed => $spd);
374 0         0 $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
375 0         0 $et->HandleTag($tagTbl, GPSTrack => $trk);
376 0         0 $et->HandleTag($tagTbl, GPSTrackRef => 'T');
377 0         0 $et->HandleTag($tagTbl, Accelerometer => "@acc");
378             }
379 0         0 SetByteOrder('MM');
380 0         0 $more = 1;
381             } elsif ($$dataPt =~ /^\$(GPSINFO|GSNRINFO),/) {
382             # $GPSINFO,0x0004,2021.08.09 13:27:36,2341.54561,12031.70135,8.0,51,153,0,0,\x0d
383             # $GSNRINFO,0.01,0.04,0.25\0
384 0         0 $$dataPt =~ tr/\x0d/\x0a/;
385 0         0 $$dataPt =~ tr/\0//d;
386 0         0 my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
387 0         0 my @lines = split /\x0a/, $$dataPt;
388 0         0 my ($line, $lastTime);
389 0         0 foreach $line (@lines) {
390 0 0       0 if ($line =~ /^\$GPSINFO/) {
    0          
391 0         0 my @a = split /,/, $lines[0];
392 0 0       0 next unless @a > 7;
393             # ignore duplicate fixes
394 0 0 0     0 next if $lastTime and $a[2] eq $lastTime;
395 0         0 $lastTime = $a[2];
396 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
397 0         0 $a[2] =~ tr/./:/;
398             # (untested, and probably doesn't work for S/W hemispheres)
399 0         0 my ($lat, $lon) = @a[3,4];
400 0         0 Image::ExifTool::QuickTime::ConvertLatLon($lat, $lon);
401             # $a[0] - flags? values: '0x0001','0x0004','0x0008','0x0010'
402 0         0 $et->HandleTag($tagTbl, GPSDateTime => $a[2]);
403 0         0 $et->HandleTag($tagTbl, GPSLatitude => $lat);
404 0         0 $et->HandleTag($tagTbl, GPSLongitude => $lon);
405 0         0 $et->HandleTag($tagTbl, GPSSpeed => $a[5]);
406 0         0 $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
407             # $a[6] - values: 48-60
408 0         0 $et->HandleTag($tagTbl, GPSTrack => $a[7]);
409 0         0 $et->HandleTag($tagTbl, GPSTrackRef => 'T');
410             # #a[8,9] - always 0
411             } elsif ($line =~ /^\$GSNRINFO/) {
412 0         0 my @a = split /,/, $line;
413 0         0 shift @a;
414 0         0 $et->HandleTag($tagTbl, Accelerometer => "@a");
415             }
416             }
417 0         0 $more = 1;
418             } elsif ($$dataPt =~ /\$GPRMC,/) {
419             # Jomise T860S-GM dashcam
420             # $GPRMC,hhmmss.ss,A,ddmm.mmmmm,N,dddmm.mmmmm,W,spd-kts,dir-dg,DDMMYY,,*cs
421             # $GPRMC,172255.00,A,:985.95194,N,17170.14674,W,029.678,170.68,240822,,,D*7B
422             # $GPRMC,172355.00,A,:984.76779,N,17170.00473,W,032.219,172.04,240822,,,D*7B
423             # ddmm.mmmm: from 4742.2568 12209.2028 (should be)
424             # to 4741.7696 12209.1056
425             # stamped on video: 47.70428N, 122.15338W, 35mph (dd.ddddd)
426             # to 47.69616N, 122.15176W, 37mph
427 0         0 my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
428 0   0     0 while ($$dataPt =~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(.{2})(\d{2}\.\d+),([NS]),(.{3})(\d{2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g and
      0        
      0        
429             # do some basic sanity checks on the date
430             $13 <= 31 and $14 <= 12 and $15 <= 99)
431             {
432 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
433 0 0       0 my $year = $15 + ($15 >= 70 ? 1900 : 2000);
434 0         0 $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3));
435             #(not this simple)
436             #$et->HandleTag($tagTbl, GPSLatitude => (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1));
437             #$et->HandleTag($tagTbl, GPSLongitude => (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1));
438 0 0       0 $et->HandleTag($tagTbl, GPSSpeed => $11 * $knotsToKph) if length $11;
439 0 0       0 $et->HandleTag($tagTbl, GPSTrack => $12) if length $12;
440             # it looks like maybe the degrees are xor-ed with something,
441             # and the minutes have some scaling factor and offset?
442             # (the code below is approximately correct for my only sample)
443 0         0 my @chars = unpack('C*', $5 . $8);
444 0         0 my @xor = (0x0e,0x0e,0x00,0x05,0x03); # (empirical based on 1 sample; may be completely off base)
445 0         0 my $bad;
446 0         0 foreach (@chars) {
447 0         0 $_ ^= shift(@xor);
448 0 0 0     0 $bad = 1 if $_ < 0x30 or $_ > 0x39;
449             }
450 0 0       0 if ($bad) {
451 0         0 $et->WarnOnce('Error decrypting GPS degrees');
452             } else {
453 0         0 my $la = pack('C*', @chars[0,1]);
454 0         0 my $lo = pack('C*', @chars[2,3,4]);
455 0         0 $et->WarnOnce('Decryption of this GPS is highly experimental. More testing samples are required');
456 0 0 0     0 $et->HandleTag($tagTbl, GPSLatitude => (($la || 0) + (($6-85.95194)/2.43051724137931+42.2568)/60) * ($7 eq 'N' ? 1 : -1));
457 0 0 0     0 $et->HandleTag($tagTbl, GPSLongitude => (($lo || 0) + (($9-70.14674)/1.460987654320988+9.2028)/60) * ($10 eq 'E' ? 1 : -1));
458             }
459             }
460             } elsif ($$dataPt =~ /^.{44}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s and length($$dataPt) >= 84) {
461             #forum11320
462 0         0 SetByteOrder('II');
463 0         0 my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
464 0         0 my $lat = abs(GetFloat($dataPt, 48)); # (abs just to be safe)
465 0         0 my $lon = abs(GetFloat($dataPt, 56)); # (abs just to be safe)
466 0         0 my $spd = GetFloat($dataPt, 64);
467 0         0 my $trk = GetFloat($dataPt, 68);
468 0         0 $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
469 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
470 0         0 my @date = unpack('x32V3x28V3', $$dataPt);
471 0         0 $date[3] += 2000;
472 0         0 $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @date[3..5,0..2]));
473 0 0       0 $et->HandleTag($tagTbl, GPSLatitude => abs($lat) * ($1 eq 'S' ? -1 : 1));
474 0 0       0 $et->HandleTag($tagTbl, GPSLongitude => abs($lon) * ($2 eq 'W' ? -1 : 1));
475 0         0 $et->HandleTag($tagTbl, GPSSpeed => $spd);
476 0         0 $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
477 0         0 $et->HandleTag($tagTbl, GPSTrack => $trk);
478 0         0 $et->HandleTag($tagTbl, GPSTrackRef => 'T');
479 0         0 SetByteOrder('MM');
480 0         0 $more = 1;
481             }
482 0         0 delete $$et{DOC_NUM};
483             }
484 2         4 return $more;
485             }
486              
487             #------------------------------------------------------------------------------
488             # Extract information from a M2TS file
489             # Inputs: 0) ExifTool object reference, 1) DirInfo reference
490             # Returns: 1 on success, 0 if this wasn't a valid M2TS file
491             sub ProcessM2TS($$)
492             {
493 1     1 0 3 my ($et, $dirInfo) = @_;
494 1         6 my $raf = $$dirInfo{RAF};
495 1         5 my ($buff, $pLen, $upkPrefix, $j, $fileType, $eof);
496 1         0 my (%pmt, %pidType, %data, %sectLen, %packLen, %fromStart);
497 1         0 my ($startTime, $endTime, $fwdTime, $backScan, $maxBack);
498 1         3 my $verbose = $et->Options('Verbose');
499 1         5 my $out = $et->Options('TextOut');
500              
501             # read first packet
502 1 50       4 return 0 unless $raf->Read($buff, 8) == 8;
503             # test for magic number (sync byte is the only thing we can safely check)
504 1 50       11 return 0 unless $buff =~ /^(....)?\x47/s;
505 1 50       5 unless ($1) {
506 0         0 $pLen = 188; # no timecode
507 0         0 $fileType = 'M2T'; # (just as a way to tell there is no timecode)
508 0         0 $upkPrefix = 'N';
509             } else {
510 1         2 $pLen = 192; # 188-byte transport packet + leading 4-byte timecode (ref 4)
511 1         2 $upkPrefix = 'x4N';
512             }
513 1         3 my $prePos = $pLen - 188; # byte position of packet prefix
514 1         10 my $readSize = 64 * $pLen; # size of our read buffer
515 1         8 $raf->Seek(0,0); # rewind to start
516 1 50       7 $raf->Read($buff, $readSize) >= $pLen * 4 or return 0; # require at least 4 packets
517             # validate the sync byte in the next 3 packets
518 1         5 for ($j=1; $j<4; ++$j) {
519 3 50       11 return 0 unless substr($buff, $prePos + $pLen * $j, 1) eq 'G'; # (0x47)
520             }
521 1         16 $et->SetFileType($fileType);
522 1         11 SetByteOrder('MM');
523 1         5 my $tagTablePtr = GetTagTable('Image::ExifTool::M2TS::Main');
524              
525             # PID lookup strings (will add to this with entries from program map table)
526 1         8 my %pidName = (
527             0 => 'Program Association Table',
528             1 => 'Conditional Access Table',
529             2 => 'Transport Stream Description Table',
530             0x1fff => 'Null Packet',
531             );
532 1         3 my %didPID = ( 1 => 0, 2 => 0, 0x1fff => 0 );
533 1         4 my %needPID = ( 0 => 1 ); # lookup for stream PID's that we still need to parse
534             # PID's that may contain GPS info
535 1         4 my %gpsPID = (
536             0x0300 => 1, # Novatek INNOVV
537             0x01e4 => 1, # vsys a6l dashcam
538             0x0e1b => 1, # Jomise T860S-GM dashcam
539             );
540 1         2 my $pEnd = 0;
541              
542             # scan entire file for GPS programs if ExtractEmbedded option is 3 or higher
543             # (some dashcams write these programs but don't include it in the PMT)
544 1 50 50     16 if (($et->Options('ExtractEmbedded') || 0) > 2) {
545 0         0 foreach (keys %gpsPID) {
546 0         0 $needPID{$_} = 1;
547 0         0 $pidType{$_} = -1;
548 0         0 $pidName{$_} ='unregistered dashcam GPS';
549             }
550             }
551              
552             # parse packets from MPEG-2 Transport Stream
553 1         2 for (;;) {
554              
555 8 50       16 unless (%needPID) {
556 0 0       0 last unless defined $startTime;
557             # reconfigure to seek backwards for last PCR
558 0 0       0 unless (defined $backScan) {
559 0         0 my $saveTime = $endTime;
560 0         0 undef $endTime;
561 0 0       0 last if $et->Options('FastScan');
562 0 0       0 $verbose and print $out "[Starting backscan for last PCR]\n";
563             # remember how far we got when reading forward through the file
564 0         0 my $fwdPos = $raf->Tell() - length($buff) + $pEnd;
565             # determine the position of the last packet relative to the EOF
566 0 0       0 $raf->Seek(0, 2) or last;
567 0         0 my $fsize = $raf->Tell();
568 0         0 $backScan = int($fsize / $pLen) * $pLen - $fsize;
569             # set limit on how far back we will go
570 0         0 $maxBack = $fwdPos - $fsize;
571             # scan back a maximum of 512k (have seen last PCR at -276k)
572 0         0 my $nMax = int(512000 / $pLen); # max packets to backscan
573 0 0       0 if ($nMax < int(-$maxBack / $pLen)) {
574 0         0 $maxBack = $backScan - $nMax * $pLen;
575             } else {
576             # use this time if none found in all remaining packets
577 0         0 $fwdTime = $saveTime;
578             }
579 0         0 $pEnd = 0;
580             }
581             }
582 8         26 my $pos;
583             # read more if necessary
584 8 50       15 if (defined $backScan) {
585 0 0       0 last if defined $endTime;
586 0         0 $pos = $pEnd = $pEnd - 2 * $pLen; # step back to previous packet
587 0 0       0 if ($pos < 0) {
588             # read another buffer from end of file
589 0 0       0 last if $backScan <= $maxBack;
590 0         0 my $buffLen = $backScan - $maxBack;
591 0 0       0 $buffLen = $readSize if $buffLen > $readSize;
592 0         0 $backScan -= $buffLen;
593 0 0       0 $raf->Seek($backScan, 2) or last;
594 0 0       0 $raf->Read($buff, $buffLen) == $buffLen or last;
595 0         0 $pos = $pEnd = $buffLen - $pLen;
596             }
597             } else {
598 8         13 $pos = $pEnd;
599 8 100       15 if ($pos + $pLen > length $buff) {
600 1 50       4 $raf->Read($buff, $readSize) >= $pLen or $eof = 1, last;
601 0         0 $pos = $pEnd = 0;
602             }
603             }
604 7         13 $pEnd += $pLen;
605             # decode the packet prefix
606 7         9 $pos += $prePos;
607 7         18 my $prefix = unpack("x${pos}N", $buff); # (use unpack instead of Get32u for speed)
608             # validate sync byte
609 7 50       18 unless (($prefix & 0xff000000) == 0x47000000) {
610 0 0       0 $et->Warn('M2TS synchronization error') unless defined $backScan;
611 0         0 last;
612             }
613             # my $transport_error_indicator = $prefix & 0x00800000;
614 7         12 my $payload_unit_start_indicator = $prefix & 0x00400000;
615             # my $transport_priority = $prefix & 0x00200000;
616 7         12 my $pid =($prefix & 0x001fff00) >> 8; # packet ID
617             # my $transport_scrambling_control = $prefix & 0x000000c0;
618 7         10 my $adaptation_field_exists = $prefix & 0x00000020;
619 7         9 my $payload_data_exists = $prefix & 0x00000010;
620             # my $continuity_counter = $prefix & 0x0000000f;
621 7 50       27 if ($verbose > 1) {
622 0         0 my $i = ($raf->Tell() - length($buff) + $pEnd) / $pLen - 1;
623 0         0 print $out "Transport packet $i:\n";
624 0         0 $et->VerboseDump(\$buff, Len => $pLen, Addr => $i * $pLen, Start => $pos - $prePos);
625 0 0       0 my $str = $pidName{$pid} ? " ($pidName{$pid})" : ' ';
626 0 0       0 printf $out " Timecode: 0x%.4x\n", Get32u(\$buff, $pos - $prePos) if $pLen == 192;
627 0         0 printf $out " Packet ID: 0x%.4x$str\n", $pid;
628 0 0       0 printf $out " Start Flag: %s\n", $payload_unit_start_indicator ? 'Yes' : 'No';
629             }
630              
631 7         13 $pos += 4;
632             # handle adaptation field
633 7 100       13 if ($adaptation_field_exists) {
634 1         5 my $len = Get8u(\$buff, $pos++);
635 1 50       6 $pos + $len > $pEnd and $et->Warn('Invalid adaptation field length'), last;
636             # read PCR value for calculation of Duration
637 1 50       3 if ($len > 6) {
638 1         3 my $flags = Get8u(\$buff, $pos);
639 1 50       3 if ($flags & 0x10) { # PCR_flag
640             # combine 33-bit program_clock_reference_base and 9-bit extension
641 1         4 my $pcrBase = Get32u(\$buff, $pos + 1);
642 1         4 my $pcrExt = Get16u(\$buff, $pos + 5);
643             # ignore separate programs (PID's) and store just the
644             # first and last timestamps found in the file (is this OK?)
645 1         6 $endTime = 300 * (2 * $pcrBase + ($pcrExt >> 15)) + ($pcrExt & 0x01ff);
646 1 50       4 $startTime = $endTime unless defined $startTime;
647             }
648             }
649 1         1 $pos += $len;
650             }
651              
652             # all done with this packet unless it carries a payload
653             # or if we are just looking for the last timestamp
654 7 100 66     42 next unless $payload_data_exists and not defined $backScan;
655              
656             # decode payload data
657 6 100 100     35 if ($pid == 0 or # program association table
    50          
658             defined $pmt{$pid}) # program map table(s)
659             {
660             # must interpret pointer field if payload_unit_start_indicator is set
661 3         6 my $buf2;
662 3 50       4 if ($payload_unit_start_indicator) {
663             # skip to start of section
664 3         12 my $pointer_field = Get8u(\$buff, $pos);
665 3         6 $pos += 1 + $pointer_field;
666 3 50       8 $pos >= $pEnd and $et->Warn('Bad pointer field'), last;
667 3         7 $buf2 = substr($buff, $pEnd-$pLen, $pLen);
668 3         5 $pos -= $pEnd - $pLen;
669             } else {
670             # not the start of a section
671 0 0       0 next unless $sectLen{$pid};
672 0         0 my $more = $sectLen{$pid} - length($data{$pid});
673 0         0 my $size = $pLen - $pos;
674 0 0       0 $size = $more if $size > $more;
675 0         0 $data{$pid} .= substr($buff, $pos, $size);
676 0 0       0 next unless $size == $more;
677             # we have the complete section now, so put into $buf2 for parsing
678 0         0 $buf2 = $data{$pid};
679 0         0 $pos = 0;
680 0         0 delete $data{$pid};
681 0         0 delete $fromStart{$pid};
682 0         0 delete $sectLen{$pid};
683             }
684 3         6 my $slen = length($buf2); # section length
685 3 50       7 $pos + 8 > $slen and $et->Warn('Truncated payload'), last;
686             # validate table ID
687 3         7 my $table_id = Get8u(\$buf2, $pos);
688 3   33     14 my $name = ($tableID{$table_id} || sprintf('Unknown (0x%x)',$table_id)) . ' Table';
689 3 100       8 my $expectedID = $pid ? 0x02 : 0x00;
690 3 100       17 unless ($table_id == $expectedID) {
691 1 50       4 $verbose > 1 and print $out " (skipping $name)\n";
692 1         2 delete $needPID{$pid};
693 1         2 $didPID{$pid} = 1;
694 1         3 next;
695             }
696             # validate section syntax indicator for parsed tables (PAT, PMT)
697 2         7 my $section_syntax_indicator = Get8u(\$buf2, $pos + 1) & 0xc0;
698 2 50       6 $section_syntax_indicator == 0x80 or $et->Warn("Bad $name"), last;
699 2         7 my $section_length = Get16u(\$buf2, $pos + 1) & 0x0fff;
700 2 50       6 $section_length > 1021 and $et->Warn("Invalid $name length"), last;
701 2 50       6 if ($slen < $section_length + 3) { # (3 bytes for table_id + section_length)
702             # must wait until we have the full section
703 0         0 $data{$pid} = substr($buf2, $pos);
704 0         0 $sectLen{$pid} = $section_length + 3;
705 0         0 next;
706             }
707 2         4 my $program_number = Get16u(\$buf2, $pos + 3);
708 2         6 my $section_number = Get8u(\$buf2, $pos + 6);
709 2         6 my $last_section_number = Get8u(\$buf2, $pos + 7);
710 2 50       6 if ($verbose > 1) {
711 0         0 print $out " $name length: $section_length\n";
712 0 0       0 print $out " Program No: $program_number\n" if $pid;
713 0 0       0 printf $out " Stream ID: 0x%x\n", $program_number if not $pid;
714 0         0 print $out " Section No: $section_number\n";
715 0         0 print $out " Last Sect.: $last_section_number\n";
716             }
717 2         3 my $end = $pos + $section_length + 3 - 4; # (don't read 4-byte CRC)
718 2         4 $pos += 8;
719 2 100       9 if ($pid == 0) {
720             # decode PAT (Program Association Table)
721 1         4 while ($pos <= $end - 4) {
722 2         6 my $program_number = Get16u(\$buf2, $pos);
723 2         7 my $program_map_PID = Get16u(\$buf2, $pos + 2) & 0x1fff;
724 2         6 $pmt{$program_map_PID} = $program_number; # save our PMT PID's
725 2         12 my $str = "Program $program_number Map";
726 2         6 $pidName{$program_map_PID} = $str;
727 2 50       6 $needPID{$program_map_PID} = 1 unless $didPID{$program_map_PID};
728 2 50       5 $verbose and printf $out " PID(0x%.4x) --> $str\n", $program_map_PID;
729 2         6 $pos += 4;
730             }
731             } else {
732             # decode PMT (Program Map Table)
733 1 50       4 $pos + 4 > $slen and $et->Warn('Truncated PMT'), last;
734 1         3 my $pcr_pid = Get16u(\$buf2, $pos) & 0x1fff;
735 1         3 my $program_info_length = Get16u(\$buf2, $pos + 2) & 0x0fff;
736 1         4 my $str = "Program $program_number Clock Reference";
737 1         4 $pidName{$pcr_pid} = $str;
738 1 50       4 $verbose and printf $out " PID(0x%.4x) --> $str\n", $pcr_pid;
739 1         2 $pos += 4;
740 1 50       4 $pos + $program_info_length > $slen and $et->Warn('Truncated program info'), last;
741             # dump program information descriptors if verbose
742 1 50       3 if ($verbose > 1) { for ($j=0; $j<$program_info_length-2; ) {
  0         0  
743 0         0 my $descriptor_tag = Get8u(\$buf2, $pos + $j);
744 0         0 my $descriptor_length = Get8u(\$buf2, $pos + $j + 1);
745 0         0 $j += 2;
746 0 0       0 last if $j + $descriptor_length > $program_info_length;
747 0         0 my $desc = substr($buf2, $pos+$j, $descriptor_length);
748 0         0 $j += $descriptor_length;
749 0         0 $desc =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg;
  0         0  
750 0         0 printf $out " Program Descriptor: Type=0x%.2x \"$desc\"\n", $descriptor_tag;
751             }}
752 1         2 $pos += $program_info_length; # skip descriptors (for now)
753 1         4 while ($pos <= $end - 5) {
754 2         5 my $stream_type = Get8u(\$buf2, $pos);
755 2         7 my $elementary_pid = Get16u(\$buf2, $pos + 1) & 0x1fff;
756 2         7 my $es_info_length = Get16u(\$buf2, $pos + 3) & 0x0fff;
757 2         8 my $str = $streamType{$stream_type};
758 2 0       13 $str or $str = ($stream_type < 0x7f ? 'Reserved' : 'Private');
    50          
759 2         12 $str = sprintf('%s (0x%.2x)', $str, $stream_type);
760 2         6 $str = "Program $program_number $str";
761 2 50       5 $verbose and printf $out " PID(0x%.4x) --> $str\n", $elementary_pid;
762 2 50       21 if ($str =~ /(Audio|Video)/) {
763 2 50       7 unless ($pidName{$elementary_pid}) {
764 2         14 $et->HandleTag($tagTablePtr, $1 . 'StreamType', $stream_type)
765             }
766             # we want to parse all Audio and Video streams
767 2 50       7 $needPID{$elementary_pid} = 1 unless $didPID{$elementary_pid};
768             }
769             # save PID type and name string
770 2         5 $pidName{$elementary_pid} = $str;
771 2         5 $pidType{$elementary_pid} = $stream_type;
772 2         4 $pos += 5;
773 2 50       6 $pos + $es_info_length > $slen and $et->Warn('Truncated ES info'), $pos = $end, last;
774             # parse elementary stream descriptors
775 2         7 for ($j=0; $j<$es_info_length-2; ) {
776 3         8 my $descriptor_tag = Get8u(\$buf2, $pos + $j);
777 3         7 my $descriptor_length = Get8u(\$buf2, $pos + $j + 1);
778 3         7 $j += 2;
779 3 50       8 last if $j + $descriptor_length > $es_info_length;
780 3         8 my $desc = substr($buf2, $pos+$j, $descriptor_length);
781 3         5 $j += $descriptor_length;
782 3 50       7 if ($verbose > 1) {
783 0         0 my $dstr = $desc;
784 0         0 $dstr =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg;
  0         0  
785 0         0 printf $out " ES Descriptor: Type=0x%.2x \"$dstr\"\n", $descriptor_tag;
786             }
787             # parse type-specific descriptor information (once)
788 3 50       11 unless ($didPID{$pid}) {
789 3 100       11 if ($descriptor_tag == 0x81) { # AC-3
790 1         5 ParseAC3Descriptor($et, \$desc);
791             }
792             }
793             }
794 2         6 $pos += $es_info_length;
795             }
796             }
797             # $pos = $end + 4; # skip CRC
798              
799             } elsif (not defined $didPID{$pid}) {
800              
801             # save data from the start of each elementary stream
802 3 100       8 if ($payload_unit_start_indicator) {
803 2 50       6 if (defined $data{$pid}) {
804             # we must have a whole section, so parse now
805 0         0 my $more = ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid});
806             # start fresh even if we couldn't process this PID yet
807 0         0 delete $data{$pid};
808 0         0 delete $fromStart{$pid};
809 0 0       0 unless ($more) {
810 0         0 delete $needPID{$pid};
811 0         0 $didPID{$pid} = 1;
812 0         0 next;
813             }
814             # set flag indicating we found this PID but we still want more
815 0         0 $needPID{$pid} = -1;
816             }
817             # check for a PES header
818 2 50       6 next if $pos + 6 > $pEnd;
819 2         5 my $start_code = Get32u(\$buff, $pos);
820 2 50       7 next unless ($start_code & 0xffffff00) == 0x00000100;
821 2         4 my $stream_id = $start_code & 0xff;
822 2         6 my $pes_packet_length = Get16u(\$buff, $pos + 4);
823 2 50       6 if ($verbose > 1) {
824 0         0 printf $out " Stream ID: 0x%.2x\n", $stream_id;
825 0         0 print $out " Packet Len: $pes_packet_length\n";
826             }
827 2         5 $pos += 6;
828 2 50       6 unless ($noSyntax{$stream_id}) {
829 2 50       6 next if $pos + 3 > $pEnd;
830             # validate PES syntax
831 2         5 my $syntax = Get8u(\$buff, $pos) & 0xc0;
832 2 50       7 $syntax == 0x80 or $et->Warn('Bad PES syntax'), next;
833             # skip PES header
834 2         4 my $pes_header_data_length = Get8u(\$buff, $pos + 2);
835 2         5 $pos += 3 + $pes_header_data_length;
836 2 50       4 next if $pos >= $pEnd;
837             }
838 2         8 $data{$pid} = substr($buff, $pos, $pEnd-$pos);
839             # set flag that we read this payload from the start
840 2         3 $fromStart{$pid} = 1;
841             # save the packet length
842 2 100       6 if ($pes_packet_length > 8) {
843 1         5 $packLen{$pid} = $pes_packet_length - 8; # (where are the 8 extra bytes? - PH)
844             } else {
845 1         2 delete $packLen{$pid};
846             }
847             } else {
848 1 50       13 unless (defined $data{$pid}) {
849             # (vsys a6l dashcam GPS record doesn't have a start indicator)
850 0 0       0 next unless $gpsPID{$pid};
851 0         0 $data{$pid} = '';
852             }
853             # accumulate data for each elementary stream
854 1         5 $data{$pid} .= substr($buff, $pos, $pEnd-$pos);
855             }
856             # save only the first 256 bytes of most streams, except for
857             # unknown, H.264 or metadata streams where we save up to 1 kB
858 3         4 my $saveLen;
859 3 100 66     15 if (not $pidType{$pid} or $pidType{$pid} == 0x1b) {
    50          
860 2         5 $saveLen = 1024;
861             } elsif ($pidType{$pid} == 0x15) {
862             # use 1024 or actual size of metadata packet if smaller
863 0         0 $saveLen = 1024;
864 0 0 0     0 $saveLen = $packLen{$pid} if defined $packLen{$pid} and $saveLen > $packLen{$pid};
865             } else {
866 1         2 $saveLen = 256;
867             }
868 3 50       9 if (length($data{$pid}) >= $saveLen) {
869 0         0 my $more = ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid});
870 0 0       0 next if $more < 0; # wait for program map table (hopefully not too long)
871             # don't stop parsing if we weren't successful and may have missed the start
872 0 0 0     0 $more = 1 if not $more and not $fromStart{$pid};
873 0         0 delete $data{$pid};
874 0         0 delete $fromStart{$pid};
875 0 0       0 $more and $needPID{$pid} = -1, next; # parse more of these
876 0         0 delete $needPID{$pid};
877 0         0 $didPID{$pid} = 1;
878             }
879 3         4 next;
880             }
881 2 50       7 if ($needPID{$pid}) {
882             # we found and parsed a section with this PID, so
883             # delete from the lookup of PID's we still need to parse
884 2         4 delete $needPID{$pid};
885 2         3 $didPID{$pid} = 1;
886             }
887             }
888              
889             # calculate Duration if available
890 1 50       4 $endTime = $fwdTime unless defined $endTime;
891 1 50 33     7 if (defined $startTime and defined $endTime) {
892 1 50       3 $endTime += 0x80000000 * 1200 if $startTime > $endTime; # handle 33-bit wrap
893 1         4 $et->HandleTag($tagTablePtr, 'Duration', $endTime - $startTime);
894             }
895              
896 1 50       4 if ($verbose) {
897 0         0 my @need;
898 0         0 foreach (keys %needPID) {
899 0 0       0 push @need, sprintf('0x%.2x',$_) if $needPID{$_} > 0;
900             }
901 0 0       0 if (@need) {
902 0         0 @need = sort @need;
903 0         0 print $out "End of file. Missing PID(s): @need\n";
904             } else {
905 0 0       0 my $what = $eof ? 'of file' : 'scan';
906 0         0 print $out "End $what. All PID's parsed.\n";
907             }
908             }
909              
910             # parse any remaining partial PID streams
911 1         2 my $pid;
912 1         6 foreach $pid (sort keys %data) {
913 2         11 ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid});
914 2         6 delete $data{$pid};
915             }
916 1         28 return 1;
917             }
918              
919             1; # end
920              
921             __END__