File Coverage

blib/lib/Image/ExifTool/Matroska.pm
Criterion Covered Total %
statement 134 240 55.8
branch 66 166 39.7
condition 30 78 38.4
subroutine 5 6 83.3
pod 0 3 0.0
total 235 493 47.6


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Matroska.pm
3             #
4             # Description: Read meta information from Matroska multimedia files
5             #
6             # Revisions: 05/26/2010 - P. Harvey Created
7             #
8             # References: 1) http://www.matroska.org/technical/specs/index.html
9             # 2) https://www.matroska.org/technical/tagging.html
10             #------------------------------------------------------------------------------
11              
12             package Image::ExifTool::Matroska;
13              
14 1     1   6408 use strict;
  1         2  
  1         49  
15 1     1   7 use vars qw($VERSION);
  1         1  
  1         69  
16 1     1   4 use Image::ExifTool qw(:DataAccess :Utils);
  1         1  
  1         4493  
17              
18             $VERSION = '1.19';
19              
20             sub HandleStruct($$;$$$$);
21              
22             my %noYes = ( 0 => 'No', 1 => 'Yes' );
23              
24             my %dateInfo = (
25             Groups => { 2 => 'Time' },
26             # the spec says to use "-" as a date separator, but my only sample uses ":", so
27             # convert to ":" if necessary, and avoid translating all "-" in case someone wants
28             # to include a negative time zone (although the spec doesn't mention time zones)
29             ValueConv => '$val =~ s/^(\d{4})-(\d{2})-/$1:$2:/; $val',
30             PrintConv => '$self->ConvertDateTime($val)',
31             );
32              
33             my %uidInfo = (
34             Format => 'string',
35             ValueConv => 'unpack("H*",$val)'
36             );
37              
38             # Matroska tags
39             # Note: The tag ID's in the Matroska documentation include the length designation
40             # (the upper bits), which is not included in the tag ID's below
41             %Image::ExifTool::Matroska::Main = (
42             GROUPS => { 2 => 'Video' },
43             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
44             NOTES => q{
45             The following tags are extracted from Matroska multimedia container files.
46             This container format is used by file types such as MKA, MKV, MKS and WEBM.
47             For speed, by default ExifTool extracts tags only up to the first Cluster
48             unless a Seek element specifies the position of a Tags element after this.
49             However, the L (-v) and L = 2 (-U) options force processing of
50             Cluster data, and the L (-ee) option skips over Clusters to
51             read subsequent tags. See
52             L for the official
53             Matroska specification.
54             },
55             # supported Format's: signed, unsigned, float, date, string, utf8
56             # (or undef by default)
57             #
58             # EBML Header
59             #
60             0xa45dfa3 => {
61             Name => 'EBMLHeader',
62             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
63             },
64             0x286 => { Name => 'EBMLVersion', Format => 'unsigned' },
65             0x2f7 => { Name => 'EBMLReadVersion', Format => 'unsigned' },
66             0x2f2 => { Name => 'EBMLMaxIDLength', Format => 'unsigned', Unknown => 1 },
67             0x2f3 => { Name => 'EBMLMaxSizeLength', Format => 'unsigned', Unknown => 1 },
68             0x282 => {
69             Name => 'DocType',
70             Format => 'string',
71             # override FileType for "webm" files
72             RawConv => '$self->OverrideFileType("WEBM") if $val eq "webm"; $val',
73             },
74             0x287 => { Name => 'DocTypeVersion', Format => 'unsigned' },
75             0x285 => { Name => 'DocTypeReadVersion',Format => 'unsigned' },
76             #
77             # General
78             #
79             0x3f => { Name => 'CRC-32', Format => 'unsigned', Unknown => 1 },
80             0x6c => { Name => 'Void', NoSave => 1, Unknown => 1 },
81             #
82             # Signature
83             #
84             0xb538667 => {
85             Name => 'SignatureSlot',
86             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
87             },
88             0x3e8a => { Name => 'SignatureAlgo', Format => 'unsigned' },
89             0x3e9a => { Name => 'SignatureHash', Format => 'unsigned' },
90             0x3ea5 => { Name =>'SignaturePublicKey',Binary => 1, Unknown => 1 },
91             0x3eb5 => { Name => 'Signature', Binary => 1, Unknown => 1 },
92             0x3e5b => {
93             Name => 'SignatureElements',
94             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
95             },
96             0x3e7b => {
97             Name => 'SignatureElementList',
98             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
99             },
100             0x2532 => { Name => 'SignedElement', Binary => 1, Unknown => 1 },
101             #
102             # Segment
103             #
104             0x8538067 => {
105             Name => 'SegmentHeader',
106             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
107             },
108             0x14d9b74 => {
109             Name => 'SeekHead',
110             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
111             },
112             0xdbb => {
113             Name => 'Seek',
114             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
115             },
116             0x13ab => {
117             Name => 'SeekID',
118             Unknown => 1,
119             SeekInfo => 'ID', # save seek ID's
120             # (note: converted from VInt internally)
121             PrintConv => q{
122             my $tagInfo = $Image::ExifTool::Matroska::Main{$val};
123             $val = sprintf('0x%x', $val);
124             $val .= " ($$tagInfo{Name})" if ref $tagInfo eq 'HASH' and $$tagInfo{Name};
125             return $val;
126             },
127             },
128             0x13ac => {
129             Name => 'SeekPosition',
130             Format => 'unsigned',
131             Unknown => 1,
132             SeekInfo => 'Position', # save seek positions
133             RawConv => '$val + $$self{SeekHeadOffset}',
134             },
135             #
136             # Segment Info
137             #
138             0x549a966 => {
139             Name => 'Info',
140             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
141             },
142             0x33a4 => { Name => 'SegmentUID', %uidInfo, Unknown => 1 },
143             0x3384 => { Name => 'SegmentFileName', Format => 'utf8' },
144             0x1cb923 => { Name => 'PrevUID', %uidInfo, Unknown => 1 },
145             0x1c83ab => { Name => 'PrevFileName', Format => 'utf8' },
146             0x1eb923 => { Name => 'NextUID', %uidInfo, Unknown => 1 },
147             0x1e83bb => { Name => 'NextFileName', Format => 'utf8' },
148             0x0444 => { Name => 'SegmentFamily', Binary => 1, Unknown => 1 },
149             0x2924 => {
150             Name => 'ChapterTranslate',
151             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
152             },
153             0x29fc => { Name => 'ChapterTranslateEditionUID', %uidInfo, Unknown => 1 },
154             0x29bf => {
155             Name => 'ChapterTranslateCodec',
156             Format => 'unsigned',
157             PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' },
158             },
159             0x29a5 => { Name => 'ChapterTranslateID',Binary => 1, Unknown => 1 },
160             0xad7b1 => {
161             Name => 'TimecodeScale',
162             Format => 'unsigned',
163             RawConv => '$$self{TimecodeScale} = $val',
164             ValueConv => '$val / 1e9',
165             PrintConv => '($val * 1000) . " ms"',
166             },
167             0x489 => {
168             Name => 'Duration',
169             Format => 'float',
170             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val / 1000',
171             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
172             },
173             0x461 => {
174             Name => 'DateTimeOriginal', # called "DateUTC" by the spec
175             Description => 'Date/Time Original',
176             Groups => { 2 => 'Time' },
177             Format => 'date',
178             PrintConv => '$self->ConvertDateTime($val)',
179             },
180             0x3ba9 => { Name => 'Title', Format => 'utf8' },
181             0xd80 => { Name => 'MuxingApp', Format => 'utf8' },
182             0x1741 => { Name => 'WritingApp', Format => 'utf8' },
183             #
184             # Cluster
185             #
186             0xf43b675 => {
187             Name => 'Cluster',
188             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
189             },
190             0x67 => {
191             Name => 'TimeCode',
192             Format => 'unsigned',
193             Unknown => 1,
194             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
195             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
196             },
197             0x1854 => {
198             Name => 'SilentTracks',
199             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
200             },
201             0x18d7 => { Name => 'SilentTrackNumber',Format => 'unsigned' },
202             0x27 => { Name => 'Position', Format => 'unsigned' },
203             0x2b => { Name => 'PrevSize', Format => 'unsigned' },
204             0x23 => { Name => 'SimpleBlock', NoSave => 1, Unknown => 1 },
205             0x20 => {
206             Name => 'BlockGroup',
207             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
208             },
209             0x21 => { Name => 'Block', NoSave => 1, Unknown => 1 },
210             0x22 => { Name => 'BlockVirtual', NoSave => 1, Unknown => 1 },
211             0x35a1 => {
212             Name => 'BlockAdditions',
213             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
214             },
215             0x26 => {
216             Name => 'BlockMore',
217             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
218             },
219             0x6e => { Name => 'BlockAddID', Format => 'unsigned', Unknown => 1 },
220             0x25 => { Name => 'BlockAdditional', NoSave => 1, Unknown => 1 },
221             0x1b => {
222             Name => 'BlockDuration',
223             Format => 'unsigned',
224             Unknown => 1,
225             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
226             PrintConv => '$$self{TimecodeScale} ? "$val s" : $val',
227             },
228             0x7a => { Name => 'ReferencePriority',Format => 'unsigned', Unknown => 1 },
229             0x7b => {
230             Name => 'ReferenceBlock',
231             Format => 'signed',
232             Unknown => 1,
233             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
234             PrintConv => '$$self{TimecodeScale} ? "$val s" : $val',
235             },
236             0x7d => { Name => 'ReferenceVirtual', Format => 'signed', Unknown => 1 },
237             0x24 => { Name => 'CodecState', Binary => 1, Unknown => 1 },
238             0x0e => {
239             Name => 'Slices',
240             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
241             },
242             0x68 => {
243             Name => 'TimeSlice',
244             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
245             },
246             0x4c => { Name => 'LaceNumber', Format => 'unsigned', Unknown => 1 },
247             0x4d => { Name => 'FrameNumber', Format => 'unsigned', Unknown => 1 },
248             0x4b => { Name => 'BlockAdditionalID',Format => 'unsigned', Unknown => 1 },
249             0x4e => { Name => 'Delay', Format => 'unsigned', Unknown => 1 },
250             0x4f => { Name => 'ClusterDuration', Format => 'unsigned', Unknown => 1 },
251             0x2f => { Name => 'EncryptedBlock', NoSave => 1, Unknown => 1 },
252             #
253             # Tracks
254             #
255             0x654ae6b => {
256             Name => 'Tracks',
257             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
258             },
259             0x2e => {
260             Name => 'TrackEntry',
261             # reset TrackType member at the start of each track
262             Condition => 'delete $$self{TrackType}; 1',
263             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
264             },
265             0x57 => { Name => 'TrackNumber', Format => 'unsigned' },
266             0x33c5 => { Name => 'TrackUID', %uidInfo },
267             0x03 => {
268             Name => 'TrackType',
269             Format => 'unsigned',
270             PrintHex => 1,
271             # remember types of all tracks encountered, as well as the current track type
272             RawConv => '$$self{TrackTypes}{$val} = 1; $$self{TrackType} = $val',
273             PrintConv => {
274             0x01 => 'Video',
275             0x02 => 'Audio',
276             0x03 => 'Complex', # (audio+video)
277             0x10 => 'Logo',
278             0x11 => 'Subtitle',
279             0x12 => 'Buttons',
280             0x20 => 'Control',
281             },
282             },
283             0x39 => { Name => 'TrackUsed', Format => 'unsigned', PrintConv => \%noYes },
284             0x08 => { Name => 'TrackDefault', Format => 'unsigned', PrintConv => \%noYes },
285             0x15aa => { Name => 'TrackForced', Format => 'unsigned', PrintConv => \%noYes },
286             0x1c => {
287             Name => 'TrackLacing',
288             Format => 'unsigned',
289             Unknown => 1,
290             PrintConv => \%noYes,
291             },
292             0x2de7 => { Name => 'MinCache', Format => 'unsigned', Unknown => 1 },
293             0x2df8 => { Name => 'MaxCache', Format => 'unsigned', Unknown => 1 },
294             0x3e383 => [
295             {
296             Name => 'VideoFrameRate',
297             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
298             Format => 'unsigned',
299             ValueConv => '$val ? 1e9 / $val : 0',
300             PrintConv => 'int($val * 1000 + 0.5) / 1000',
301             },{
302             Name => 'DefaultDuration',
303             Format => 'unsigned',
304             ValueConv => '$val / 1e9',
305             PrintConv => '($val * 1000) . " ms"',
306             }
307             ],
308             0x3314f => { Name => 'TrackTimecodeScale',Format => 'float' },
309             0x137f => { Name => 'TrackOffset', Format => 'signed', Unknown => 1 },
310             0x15ee => { Name => 'MaxBlockAdditionID',Format => 'unsigned', Unknown => 1 },
311             0x136e => { Name => 'TrackName', Format => 'utf8' },
312             0x2b59c => { Name => 'TrackLanguage', Format => 'string' },
313             0x2b59d => { Name => 'TrackLanguageIETF', Format => 'string' },
314             0x06 => [
315             {
316             Name => 'VideoCodecID',
317             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
318             Format => 'string',
319             },{
320             Name => 'AudioCodecID',
321             Condition => '$$self{TrackType} and $$self{TrackType} == 0x02',
322             Format => 'string',
323             },{
324             Name => 'CodecID',
325             Format => 'string',
326             }
327             ],
328             0x23a2 => { Name => 'CodecPrivate', Binary => 1, Unknown => 1 },
329             0x58688 => [
330             {
331             Name => 'VideoCodecName',
332             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
333             Format => 'utf8',
334             },{
335             Name => 'AudioCodecName',
336             Condition => '$$self{TrackType} and $$self{TrackType} == 0x02',
337             Format => 'utf8',
338             },{
339             Name => 'CodecName',
340             Format => 'utf8',
341             }
342             ],
343             0x3446 => { Name => 'TrackAttachmentUID',%uidInfo },
344             0x1a9697=>{ Name => 'CodecSettings', Format => 'utf8' },
345             0x1b4040=>{ Name => 'CodecInfoURL', Format => 'string' },
346             0x6b240 =>{ Name => 'CodecDownloadURL', Format => 'string' },
347             0x2a => { Name => 'CodecDecodeAll', Format => 'unsigned', PrintConv => \%noYes },
348             0x2fab => { Name => 'TrackOverlay', Format => 'unsigned', Unknown => 1 },
349             0x2624 => {
350             Name => 'TrackTranslate',
351             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
352             },
353             0x26fc => { Name => 'TrackTranslateEditionUID', %uidInfo, Unknown => 1 },
354             0x26bf => {
355             Name => 'TrackTranslateCodec',
356             Format => 'unsigned',
357             PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' },
358             },
359             0x26a5 => { Name => 'TrackTranslateTrackID', Binary => 1, Unknown => 1 },
360             #
361             # Video
362             #
363             0x60 => {
364             Name => 'Video',
365             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
366             },
367             0x1a => {
368             Name => 'VideoScanType',
369             Format => 'unsigned',
370             PrintConv => {
371             0 => 'Undetermined',
372             1 => 'Interlaced',
373             2 => 'Progressive',
374             },
375             },
376             0x13b8 => {
377             Name => 'Stereo3DMode',
378             Format => 'unsigned',
379             Printconv => {
380             0 => 'Mono',
381             1 => 'Right Eye',
382             2 => 'Left Eye',
383             3 => 'Both Eyes',
384             },
385             },
386             0x30 => { Name => 'ImageWidth', Format => 'unsigned' },
387             0x3a => { Name => 'ImageHeight', Format => 'unsigned' },
388             0x14aa => { Name => 'CropBottom', Format => 'unsigned' },
389             0x14bb => { Name => 'CropTop', Format => 'unsigned' },
390             0x14cc => { Name => 'CropLeft', Format => 'unsigned' },
391             0x14dd => { Name => 'CropRight', Format => 'unsigned' },
392             0x14b0 => { Name => 'DisplayWidth', Format => 'unsigned' },
393             0x14ba => { Name => 'DisplayHeight', Format => 'unsigned' },
394             0x14b2 => {
395             Name => 'DisplayUnit',
396             Format => 'unsigned',
397             PrintConv => {
398             0 => 'Pixels',
399             1 => 'cm',
400             2 => 'inches',
401             3 => 'Display Aspect Ratio',
402             4 => 'Unknown',
403             },
404             },
405             0x14b3 => {
406             Name => 'AspectRatioType',
407             Format => 'unsigned',
408             PrintConv => {
409             0 => 'Free Resizing',
410             1 => 'Keep Aspect Ratio',
411             2 => 'Fixed',
412             },
413             },
414             0xeb524 => { Name => 'ColorSpace', Binary => 1, Unknown => 1 },
415             0xfb523 => { Name => 'Gamma', Format => 'float' },
416             0x383e3 => { Name => 'FrameRate', Format => 'float' },
417             #
418             # Audio
419             #
420             0x61 => {
421             Name => 'Audio',
422             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
423             },
424             0x35 => { Name => 'AudioSampleRate', Format => 'float', Groups => { 2 => 'Audio' } },
425             0x38b5 => { Name => 'OutputAudioSampleRate',Format => 'float', Groups => { 2 => 'Audio' } },
426             0x1f => { Name => 'AudioChannels', Format => 'unsigned', Groups => { 2 => 'Audio' } },
427             0x3d7b => {
428             Name => 'ChannelPositions',
429             Binary => 1,
430             Unknown => 1,
431             Groups => { 2 => 'Audio' },
432             },
433             0x2264 => { Name => 'AudioBitsPerSample', Format => 'unsigned', Groups => { 2 => 'Audio' } },
434             #
435             # Content Encoding
436             #
437             0x2d80 => {
438             Name => 'ContentEncodings',
439             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
440             },
441             0x2240 => {
442             Name => 'ContentEncoding',
443             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
444             },
445             0x1031 => { Name => 'ContentEncodingOrder', Format => 'unsigned', Unknown => 1 },
446             0x1032 => { Name => 'ContentEncodingScope', Format => 'unsigned', Unknown => 1 },
447             0x1033 => {
448             Name => 'ContentEncodingType',
449             Format => 'unsigned',
450             PrintConv => { 0 => 'Compression', 1 => 'Encryption' },
451             },
452             0x1034 => {
453             Name => 'ContentCompression',
454             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
455             },
456             0x254 => {
457             Name => 'ContentCompressionAlgorithm',
458             Format => 'unsigned',
459             PrintConv => {
460             0 => 'zlib',
461             1 => 'bzlib',
462             2 => 'lzo1x',
463             3 => 'Header Stripping',
464             },
465             },
466             0x255 => { Name => 'ContentCompressionSettings',Binary => 1, Unknown => 1 },
467             0x1035 => {
468             Name => 'ContentEncryption',
469             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
470             },
471             0x7e1 => {
472             Name => 'ContentEncryptionAlgorithm',
473             Format => 'unsigned',
474             PrintConv => {
475             0 => 'Not Encrypted',
476             1 => 'DES',
477             2 => '3DES',
478             3 => 'Twofish',
479             4 => 'Blowfish',
480             5 => 'AES',
481             },
482             },
483             0x7e2 => { Name => 'ContentEncryptionKeyID',Binary => 1, Unknown => 1 },
484             0x7e3 => { Name => 'ContentSignature', Binary => 1, Unknown => 1 },
485             0x7e4 => { Name => 'ContentSignatureKeyID', Binary => 1, Unknown => 1 },
486             0x7e5 => {
487             Name => 'ContentSignatureAlgorithm',
488             Format => 'unsigned',
489             PrintConv => {
490             0 => 'Not Signed',
491             1 => 'RSA',
492             },
493             },
494             0x7e6 => {
495             Name => 'ContentSignatureHashAlgorithm',
496             Format => 'unsigned',
497             PrintConv => {
498             0 => 'Not Signed',
499             1 => 'SHA1-160',
500             2 => 'MD5',
501             },
502             },
503             #
504             # Cues
505             #
506             0xc53bb6b => {
507             Name => 'Cues',
508             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
509             },
510             0x3b => {
511             Name => 'CuePoint',
512             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
513             },
514             0x33 => {
515             Name => 'CueTime',
516             Format => 'unsigned',
517             Unknown => 1,
518             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
519             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
520             },
521             0x37 => {
522             Name => 'CueTrackPositions',
523             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
524             },
525             0x77 => { Name => 'CueTrack', Format => 'unsigned', Unknown => 1 },
526             0x71 => { Name => 'CueClusterPosition',Format => 'unsigned', Unknown => 1 },
527             0x1378 => { Name => 'CueBlockNumber', Format => 'unsigned', Unknown => 1 },
528             0x6a => { Name => 'CueCodecState', Format => 'unsigned', Unknown => 1 },
529             0x5b => {
530             Name => 'CueReference',
531             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
532             },
533             0x16 => {
534             Name => 'CueRefTime',
535             Format => 'unsigned',
536             Unknown => 1,
537             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
538             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
539             },
540             0x17 => { Name => 'CueRefCluster', Format => 'unsigned', Unknown => 1 },
541             0x135f=> { Name => 'CueRefNumber', Format => 'unsigned', Unknown => 1 },
542             0x6b => { Name => 'CueRefCodecState', Format => 'unsigned', Unknown => 1 },
543             #
544             # Attachments
545             #
546             0x941a469 => {
547             Name => 'Attachments',
548             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
549             },
550             0x21a7 => {
551             Name => 'AttachedFile',
552             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
553             },
554             0x67e => { Name => 'AttachedFileDescription',Format => 'utf8' },
555             0x66e => { Name => 'AttachedFileName', Format => 'utf8' },
556             0x660 => { Name => 'AttachedFileMIMEType', Format => 'string' },
557             0x65c => { Name => 'AttachedFileData', Binary => 1 },
558             0x6ae => { Name => 'AttachedFileUID', %uidInfo },
559             0x675 => { Name => 'AttachedFileReferral', Binary => 1, Unknown => 1 },
560             #
561             # Chapters
562             #
563             0x43a770 => {
564             Name => 'Chapters',
565             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
566             },
567             0x5b9 => {
568             Name => 'EditionEntry',
569             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
570             },
571             0x5bc => { Name => 'EditionUID', %uidInfo, Unknown => 1 },
572             0x5bd => { Name => 'EditionFlagHidden', Format => 'unsigned', Unknown => 1 },
573             0x5db => { Name => 'EditionFlagDefault',Format => 'unsigned', Unknown => 1 },
574             0x5dd => { Name => 'EditionFlagOrdered',Format => 'unsigned', Unknown => 1 },
575             0x36 => {
576             Name => 'ChapterAtom',
577             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
578             },
579             0x33c4 => { Name => 'ChapterUID', %uidInfo, Unknown => 1 },
580             0x11 => {
581             Name => 'ChapterTimeStart',
582             Groups => { 1 => 'Chapter#' },
583             Format => 'unsigned',
584             ValueConv => '$val / 1e9',
585             PrintConv => 'ConvertDuration($val)',
586             },
587             0x12 => {
588             Name => 'ChapterTimeEnd',
589             Format => 'unsigned',
590             ValueConv => '$val / 1e9',
591             PrintConv => 'ConvertDuration($val)',
592             },
593             0x18 => { Name => 'ChapterFlagHidden', Format => 'unsigned', Unknown => 1 },
594             0x598 => { Name => 'ChapterFlagEnabled',Format => 'unsigned', Unknown => 1 },
595             0x2e67=> { Name => 'ChapterSegmentUID', %uidInfo, Unknown => 1 },
596             0x2ebc=> { Name => 'ChapterSegmentEditionUID', %uidInfo, Unknown => 1 },
597             0x23c3 => {
598             Name => 'ChapterPhysicalEquivalent',
599             Format => 'unsigned',
600             PrintConv => {
601             10 => 'Index',
602             20 => 'Track',
603             30 => 'Session',
604             40 => 'Layer',
605             50 => 'Side',
606             60 => 'CD / DVD',
607             70 => 'Set / Package',
608             },
609             },
610             0x0f => {
611             Name => 'ChapterTrack',
612             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
613             },
614             0x09 => { Name => 'ChapterTrackNumber', Format => 'unsigned', Unknown => 1 },
615             0x00 => {
616             Name => 'ChapterDisplay',
617             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
618             },
619             0x05 => { Name => 'ChapterString', Format => 'utf8' },
620             0x37c => { Name => 'ChapterLanguage', Format => 'string' },
621             0x37e => { Name => 'ChapterCountry', Format => 'string' },
622             0x2944 => {
623             Name => 'ChapterProcess',
624             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
625             },
626             0x2955 => {
627             Name => 'ChapterProcessCodecID',
628             Format => 'unsigned',
629             Unknown => 1,
630             PrintConv => { 0 => 'Matroska', 1 => 'DVD' },
631             },
632             0x50d => { Name => 'ChapterProcessPrivate', Binary => 1, Unknown => 1 },
633             0x2911 => {
634             Name => 'ChapterProcessCommand',
635             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
636             },
637             0x2922 => {
638             Name => 'ChapterProcessTime',
639             Format => 'unsigned',
640             Unknown => 1,
641             PrintConv => {
642             0 => 'For Duration of Chapter',
643             1 => 'Before Chapter',
644             2 => 'After Chapter',
645             },
646             },
647             0x2933 => { Name => 'ChapterProcessData', Binary => 1, Unknown => 1 },
648             #
649             # Tags
650             #
651             0x254c367 => {
652             Name => 'Tags',
653             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
654             },
655             0x3373 => {
656             Name => 'Tag',
657             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
658             },
659             0x23c0 => {
660             Name => 'Targets',
661             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
662             },
663             # Targets elements
664             0x28ca => {
665             Name => 'TargetTypeValue',
666             Format => 'unsigned',
667             PrintConv => {
668             10 => 'Shot',
669             20 => 'Scene/Subtrack',
670             30 => 'Chapter/Track',
671             40 => 'Session',
672             50 => 'Movie/Album',
673             60 => 'Season/Edition',
674             70 => 'Collection',
675             },
676             },
677             0x23ca => { Name => 'TargetType', Format => 'string' },
678             0x23c5 => { Name => 'TagTrackUID', %uidInfo },
679             0x23c9 => { Name => 'TagEditionUID', %uidInfo },
680             0x23c4 => { Name => 'TagChapterUID', %uidInfo },
681             0x23c6 => { Name => 'TagAttachmentUID', %uidInfo },
682             0x27c8 => {
683             Name => 'SimpleTag',
684             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
685             },
686             # SimpleTag elements
687             0x5a3 => { Name => 'TagName', Format => 'utf8' },
688             0x47a => { Name => 'TagLanguage', Format => 'string' },
689             0x47a => { Name => 'TagLanguageBCP47', Format => 'string' },
690             0x484 => { Name => 'TagDefault', Format => 'unsigned', PrintConv => \%noYes },
691             0x487 => { Name => 'TagString', Format => 'utf8' },
692             0x485 => { Name => 'TagBinary', Binary => 1 },
693             #
694             # Spherical Video V2 (untested)
695             #
696             0x7670 => {
697             Name => 'Projection',
698             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Projection' },
699             },
700             #
701             # other
702             #
703             0x5345414c => { # ('SEAL' in hex)
704             Name => 'SEAL',
705             NotEBML => 1, # don't process SubDirectory as EBML elements
706             SubDirectory => { TagTable => 'Image::ExifTool::XMP::SEAL' },
707             },
708             );
709              
710             # Spherical video v2 projection tags (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md)
711             %Image::ExifTool::Matroska::Projection = (
712             GROUPS => { 2 => 'Video' },
713             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
714             NOTES => q{
715             Projection tags defined by the Spherical Video V2 specification. See
716             L
717             for the specification.
718             },
719             0x7671 => {
720             Name => 'ProjectionType',
721             Format => 'unsigned',
722             DataMember => 'ProjectionType',
723             RawConv => '$$self{ProjectionType} = $val',
724             PrintConv => {
725             0 => 'Rectangular',
726             1 => 'Equirectangular',
727             2 => 'Cubemap',
728             3 => 'Mesh',
729             },
730             },
731             # ProjectionPrivate in the spec
732             0x7672 => [{
733             Name => 'EquirectangularProj',
734             Condition => '$$self{ProjectionType} == 1',
735             SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::equi' },
736             },{
737             Name => 'CubemapProj',
738             Condition => '$$self{ProjectionType} == 2',
739             SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::cbmp' },
740             },{ # (don't decode 3 because it is a PITA)
741             Name => 'ProjectionPrivate',
742             Binary => 1,
743             }],
744             0x7673 => { Name => 'ProjectionPoseYaw', Format => 'float' },
745             0x7674 => { Name => 'ProjectionPosePitch', Format => 'float' },
746             0x7675 => { Name => 'ProjectionPoseRoll', Format => 'float' },
747             );
748              
749             # standardized tag names (ref 2)
750             %Image::ExifTool::Matroska::StdTag = (
751             GROUPS => { 2 => 'Video' },
752             PRIORITY => 0, # (don't want named tags to override numbered tags, eg. "DURATION")
753             VARS => { LONG_TAGS => 3 },
754             NOTES => q{
755             Standardized Matroska tags, stored in a SimpleTag structure (see
756             L).
757             },
758             ORIGINAL => 'Original', # struct
759             SAMPLE => 'Sample', # struct
760             COUNTRY => 'Country', # struct (should deal with this properly!)
761             TOTAL_PARTS => 'TotalParts',
762             PART_NUMBER => 'PartNumber',
763             PART_OFFSET => 'PartOffset',
764             TITLE => 'Title',
765             SUBTITLE => 'Subtitle',
766             URL => 'URL', # nested
767             SORT_WITH => 'SortWith', # nested
768             INSTRUMENTS => { # nested
769             Name => 'Instruments',
770             IsList => 1,
771             ValueConv => 'my @a = split /,\s?/, $val; \@a',
772             },
773             EMAIL => 'Email', # nested
774             ADDRESS => 'Address', # nested
775             FAX => 'FAX', # nested
776             PHONE => 'Phone', # nested
777             ARTIST => 'Artist',
778             LEAD_PERFORMER => 'LeadPerformer',
779             ACCOMPANIMENT => 'Accompaniment',
780             COMPOSER => 'Composer',
781             ARRANGER => 'Arranger',
782             LYRICS => 'Lyrics',
783             LYRICIST => 'Lyricist',
784             CONDUCTOR => 'Conductor',
785             DIRECTOR => 'Director',
786             ASSISTANT_DIRECTOR => 'AssistantDirector',
787             DIRECTOR_OF_PHOTOGRAPHY => 'DirectorOfPhotography',
788             SOUND_ENGINEER => 'SoundEngineer',
789             ART_DIRECTOR => 'ArtDirector',
790             PRODUCTION_DESIGNER => 'ProductionDesigner',
791             CHOREGRAPHER => 'Choregrapher',
792             COSTUME_DESIGNER => 'CostumeDesigner',
793             ACTOR => 'Actor',
794             CHARACTER => 'Character',
795             WRITTEN_BY => 'WrittenBy',
796             SCREENPLAY_BY => 'ScreenplayBy',
797             EDITED_BY => 'EditedBy',
798             PRODUCER => 'Producer',
799             COPRODUCER => 'Coproducer',
800             EXECUTIVE_PRODUCER => 'ExecutiveProducer',
801             DISTRIBUTED_BY => 'DistributedBy',
802             MASTERED_BY => 'MasteredBy',
803             ENCODED_BY => 'EncodedBy',
804             MIXED_BY => 'MixedBy',
805             REMIXED_BY => 'RemixedBy',
806             PRODUCTION_STUDIO => 'ProductionStudio',
807             THANKS_TO => 'ThanksTo',
808             PUBLISHER => 'Publisher',
809             LABEL => 'Label',
810             GENRE => 'Genre',
811             MOOD => 'Mood',
812             ORIGINAL_MEDIA_TYPE => 'OriginalMediaType',
813             CONTENT_TYPE => 'ContentType',
814             SUBJECT => 'Subject',
815             DESCRIPTION => 'Description',
816             KEYWORDS => {
817             Name => 'Keywords',
818             IsList => 1,
819             ValueConv => 'my @a = split /,\s?/, $val; \@a',
820             },
821             SUMMARY => 'Summary',
822             SYNOPSIS => 'Synopsis',
823             INITIAL_KEY => 'InitialKey',
824             PERIOD => 'Period',
825             LAW_RATING => 'LawRating',
826             DATE_RELEASED => { Name => 'DateReleased', %dateInfo },
827             DATE_RECORDED => { Name => 'DateTimeOriginal', %dateInfo, Description => 'Date/Time Original' },
828             DATE_ENCODED => { Name => 'DateEncoded', %dateInfo },
829             DATE_TAGGED => { Name => 'DateTagged', %dateInfo },
830             DATE_DIGITIZED => { Name => 'CreateDate', %dateInfo },
831             DATE_WRITTEN => { Name => 'DateWritten', %dateInfo },
832             DATE_PURCHASED => { Name => 'DatePurchased', %dateInfo },
833             RECORDING_LOCATION => 'RecordingLocation',
834             COMPOSITION_LOCATION => 'CompositionLocation',
835             COMPOSER_NATIONALITY => 'ComposerNationality',
836             COMMENT => 'Comment',
837             PLAY_COUNTER => 'PlayCounter',
838             RATING => 'Rating',
839             ENCODER => 'Encoder',
840             ENCODER_SETTINGS => 'EncoderSettings',
841             BPS => 'BPS',
842             FPS => 'FPS',
843             BPM => 'BPM',
844             MEASURE => 'Measure',
845             TUNING => 'Tuning',
846             REPLAYGAIN_GAIN => 'ReplaygainGain',
847             REPLAYGAIN_PEAK => 'ReplaygainPeak',
848             ISRC => 'ISRC',
849             MCDI => 'MCDI',
850             ISBN => 'ISBN',
851             BARCODE => 'Barcode',
852             CATALOG_NUMBER => 'CatalogNumber',
853             LABEL_CODE => 'LabelCode',
854             LCCN => 'Lccn',
855             IMDB => 'IMDB',
856             TMDB => 'TMDB',
857             TVDB => 'TVDB',
858             PURCHASE_ITEM => 'PurchaseItem',
859             PURCHASE_INFO => 'PurchaseInfo',
860             PURCHASE_OWNER => 'PurchaseOwner',
861             PURCHASE_PRICE => 'PurchasePrice',
862             PURCHASE_CURRENCY => 'PurchaseCurrency',
863             COPYRIGHT => 'Copyright',
864             PRODUCTION_COPYRIGHT => 'ProductionCopyright',
865             LICENSE => 'License',
866             TERMS_OF_USE => 'TermsOfUse',
867             # (the following are untested)
868             'spherical-video' => { #https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md
869             Name => 'SphericalVideoXML',
870             SubDirectory => {
871             TagTable => 'Image::ExifTool::XMP::Main',
872             ProcessProc => 'Image::ExifTool::XMP::ProcessGSpherical',
873             },
874             },
875             'SPHERICAL-VIDEO' => { #https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md
876             Name => 'SphericalVideoXML',
877             SubDirectory => {
878             TagTable => 'Image::ExifTool::XMP::Main',
879             ProcessProc => 'Image::ExifTool::XMP::ProcessGSpherical',
880             },
881             },
882             #
883             # other tags seen
884             #
885             _STATISTICS_WRITING_DATE_UTC => { Name => 'StatisticsWritingDateUTC', %dateInfo },
886             _STATISTICS_WRITING_APP => 'StatisticsWritingApp',
887             _STATISTICS_TAGS => 'StatisticsTags',
888             DURATION => 'Duration',
889             NUMBER_OF_FRAMES => 'NumberOfFrames',
890             NUMBER_OF_BYTES => 'NumberOfBytes',
891             );
892              
893             #------------------------------------------------------------------------------
894             # Handle MKV SimpleTag structure
895             # Inputs: 0) ExifTool ref, 1) structure ref, 2) parent tag ID, 3) parent tag Name,
896             # 4) language code, 5) country code
897             sub HandleStruct($$;$$$$)
898             {
899 0     0 0 0 local $_;
900 0         0 my ($et, $struct, $pid, $pname, $lang, $ctry) = @_;
901 0         0 my $tagTbl = GetTagTable('Image::ExifTool::Matroska::StdTag');
902 0         0 my $tag = $$struct{TagName};
903 0         0 my $tagInfo = $$tagTbl{$tag};
904             # create tag if necessary
905 0 0       0 unless (ref $tagInfo eq 'HASH') {
906 0         0 my $name = ucfirst lc $tag;
907 0         0 $name =~ tr/0-9a-zA-Z_//dc;
908 0         0 $name =~ s/_([a-z])/\U$1/g;
909 0 0       0 $name = "Tag_$name" if length $name < 2;
910 0         0 $et->VPrint(0, " [adding $tag = $name]\n");
911 0         0 $tagInfo = AddTagToTable($tagTbl, $tag, { Name => $name });
912             }
913 0         0 my ($id, $nm);
914 0 0       0 if ($pid) {
915 0         0 $id = "$pid/$tag";
916 0         0 $nm = "$pname/$$tagInfo{Name}";
917 0 0       0 unless ($$tagTbl{$id}) {
918 0         0 my %copy = %$tagInfo;
919 0         0 $copy{Name} = $nm;
920 0         0 $et->VPrint(0, " [adding $id = $nm]\n");
921 0         0 $tagInfo = AddTagToTable($tagTbl, $id, \%copy);
922             }
923             } else {
924 0         0 ($id, $nm) = ($tag, $$tagInfo{Name});
925             }
926 0 0 0     0 if (defined $$struct{TagString} or defined $$struct{TagBinary}) {
927 0 0       0 my $val = defined $$struct{TagString} ? $$struct{TagString} : \$$struct{TagBinary};
928 0   0     0 $lang = $$struct{TagLanguageBCP47} || $$struct{TagLanguage} || $lang;
929             # (Note: not currently handling TagDefault attribute)
930 0         0 my $code = $lang;
931 0 0       0 $code = $lang ? "${lang}-${ctry}" : "eng-${ctry}" if $ctry; # ('eng' is default lang)
    0          
932 0 0       0 if ($code) {
933 0         0 $tagInfo = Image::ExifTool::GetLangInfo($tagInfo, $code);
934 0         0 $et->HandleTag($tagTbl, $$tagInfo{TagID}, $val);
935             } else {
936 0         0 $et->HandleTag($tagTbl, $id, $val);
937             }
938             # COUNTRY is handled as an attribute for contained tags
939 0 0       0 if ($tag eq 'COUNTRY') {
940 0         0 $ctry = $val;
941 0         0 ($id, $nm) = ($pid, $pname);
942             }
943             }
944 0 0       0 if ($$struct{struct}) {
945             # step into each contained structure
946 0         0 HandleStruct($et, $_, $id, $nm, $lang, $ctry) foreach @{$$struct{struct}};
  0         0  
947             }
948             }
949              
950             #------------------------------------------------------------------------------
951             # Get variable-length Matroska integer
952             # Inputs: 0) data buffer, 1) position in data
953             # Returns: integer value and updates position, -1 for unknown/reserved value,
954             # or undef if no data left
955             # Notes: Increments position pointer
956             sub GetVInt($$)
957             {
958 136 100   136 0 348 return undef if $_[1] >= length $_[0];
959 135         302 my $val = ord(substr($_[0], $_[1]++));
960 135         209 my $num = 0;
961 135 50       331 unless ($val) {
962 0 0       0 return undef if $_[1] >= length $_[0];
963 0         0 $val = ord(substr($_[0], $_[1]++));
964 0 0       0 return undef unless $val; # can't be this large!
965 0         0 $num += 7; # 7 more bytes to read (we just read one)
966             }
967 135         203 my $mask = 0x7f;
968 135         334 while ($val == ($val & $mask)) {
969 79         128 $mask >>= 1;
970 79         180 ++$num;
971             }
972 135         215 $val = ($val & $mask);
973 135         229 my $unknown = ($val == $mask);
974 135 50       341 return undef if $_[1] + $num > length $_[0];
975 135         319 while ($num) {
976 79         149 my $b = ord(substr($_[0], $_[1]++));
977 79 50       177 $unknown = 0 if $b != 0xff;
978 79         132 $val = $val * 256 + $b;
979 79         193 --$num;
980             }
981 135 50       365 return $unknown ? -1 : $val;
982             }
983              
984             #------------------------------------------------------------------------------
985             # Read information from a Matroska multimedia file (MKV, MKA, MKS)
986             # Inputs: 0) ExifTool object reference, 1) Directory information reference
987             # Returns: 1 on success, 0 if this wasn't a valid Matroska file
988             sub ProcessMKV($$)
989             {
990 1     1 0 3 my ($et, $dirInfo) = @_;
991 1         3 my $raf = $$dirInfo{RAF};
992 1         3 my ($buff, $buf2, @dirEnd, $trackIndent, %trackTypes, %trackNum,
993             $struct, %seekInfo, %seek);
994              
995 1 50       20 $raf->Read($buff, 4) == 4 or return 0;
996 1 50       6 return 0 unless $buff =~ /^\x1a\x45\xdf\xa3/;
997              
998             # read in 64kB blocks (already read 4 bytes)
999 1 50       5 $raf->Read($buff, 65532) or return 0;
1000 1         2 my $dataLen = length $buff;
1001 1         4 my ($pos, $dataPos) = (0, 4);
1002              
1003             # verify header length
1004 1         6 my $hlen = GetVInt($buff, $pos);
1005 1 50 33     7 return 0 unless $hlen and $hlen > 0;
1006 1 50       4 $pos + $hlen > $dataLen and $et->Warn('Truncated Matroska header'), return 1;
1007 1         8 $et->SetFileType();
1008 1         8 SetByteOrder('MM');
1009 1         5 my $tagTablePtr = GetTagTable('Image::ExifTool::Matroska::Main');
1010              
1011             # set flag to process entire file (otherwise we stop at the first Cluster)
1012 1         27 my $verbose = $et->Options('Verbose');
1013 1 50 33     8 my $processAll = ($verbose or $et->Options('Unknown') > 1) ? 2 : 0;
1014 1 50       5 ++$processAll if $et->Options('ExtractEmbedded');
1015 1         4 $$et{TrackTypes} = \%trackTypes; # store Track types reference
1016 1         4 $$et{SeekHeadOffset} = 0;
1017 1         4 my $oldIndent = $$et{INDENT};
1018 1         2 my $chapterNum = 0;
1019 1         3 my $dirName = 'MKV';
1020              
1021             # loop over all Matroska elements
1022 1         2 for (;;) {
1023 66         186 while (@dirEnd) {
1024 73 100       208 if ($pos + $dataPos >= $dirEnd[-1][0]) {
1025 11 100       33 if ($dirEnd[-1][1] eq 'Seek') {
1026             # save seek info
1027 4 50 33     23 if (defined $seekInfo{ID} and defined $seekInfo{Position}) {
1028 4         13 my $seekTag = $$tagTablePtr{$seekInfo{ID}};
1029 4 50 33     48 if (ref $seekTag eq 'HASH' and $$seekTag{Name}) {
1030 4         21 $seek{$$seekTag{Name}} = $seekInfo{Position} + $$et{SeekHeadOffset};
1031             }
1032             }
1033 4         9 undef %seekInfo;
1034             }
1035 11         21 pop @dirEnd;
1036 11 50       29 if ($struct) {
1037 0 0 0     0 if (@dirEnd and $dirEnd[-1][2]) {
1038             # save this nested structure
1039 0 0       0 $dirEnd[-1][2]{struct} or $dirEnd[-1][2]{struct} = [ ];
1040 0         0 push @{$dirEnd[-1][2]{struct}}, $struct;
  0         0  
1041 0         0 $struct = $dirEnd[-1][2];
1042             } else {
1043             # handle completed structures now
1044 0         0 HandleStruct($et, $struct);
1045 0         0 undef $struct;
1046             }
1047             }
1048 11 50       30 $dirName = @dirEnd ? $dirEnd[-1][1] : 'MKV';
1049             # use INDENT to decide whether or not we are done this Track element
1050 11 100 100     44 delete $$et{SET_GROUP1} if $trackIndent and $trackIndent eq $$et{INDENT};
1051 11         34 $$et{INDENT} = substr($$et{INDENT}, 0, -2);
1052 11         17 pop @{$$et{PATH}};
  11         34  
1053             } else {
1054 62         115 $dirName = $dirEnd[-1][1];
1055 62         128 last;
1056             }
1057             }
1058             # read more if we are getting close to the end of our buffer
1059             # (24 more bytes should be enough to read this element header)
1060 66 50 66     218 if ($pos + 24 > $dataLen and $raf->Read($buf2, 65536)) {
1061 0         0 $buff = substr($buff, $pos) . $buf2;
1062 0         0 undef $buf2;
1063 0         0 $dataPos += $pos;
1064 0         0 $dataLen = length $buff;
1065 0         0 $pos = 0;
1066             }
1067 66         205 my $tag = GetVInt($buff, $pos);
1068 66 100 66     306 last unless defined $tag and $tag >= 0;
1069 65 100       146 $$et{SeekHeadOffset} = $pos if $tag == 0x14d9b74; # save offset of seek head
1070 65         190 my $size = GetVInt($buff, $pos);
1071 65 50       155 last unless defined $size;
1072 65         147 my ($unknownSize, $seekInfoOnly, $tagName);
1073 65 50       184 $size < 0 and $unknownSize = 1, $size = 1e20;
1074 65 50 66     320 if (@dirEnd and $pos + $dataPos + $size > $dirEnd[-1][0]) {
1075 0         0 $et->Warn("Invalid or corrupted $dirEnd[-1][1] master element");
1076 0         0 $pos = $dirEnd[-1][0] - $dataPos;
1077 0 0 0     0 if ($pos < 0 or $pos > $dataLen) {
1078 0         0 $buff = '';
1079 0         0 $dataPos += $pos;
1080 0         0 $dataLen = 0;
1081 0         0 $pos = 0;
1082 0 0       0 $raf->Seek($dataPos, 0) or last;
1083             }
1084 0         0 next;
1085             }
1086 65         243 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
1087 65 0 33     208 if (not $tagInfo and ref $$tagTablePtr{$tag} eq 'HASH' and $$tagTablePtr{$tag}{SeekInfo}) {
      33        
1088 0         0 $tagInfo = $$tagTablePtr{$tag};
1089 0         0 $seekInfoOnly = 1;
1090             }
1091 65 50       135 if ($tagInfo) {
    0          
1092 65         140 $tagName = $$tagInfo{Name};
1093 65 100 66     209 if ($$tagInfo{SubDirectory} and not $$tagInfo{NotEBML}) {
1094             # stop processing at first cluster unless we are using -v -U or -ee
1095             # or there are Tags after this
1096 12 50 33     40 if ($tagName eq 'Cluster' and $processAll < 2) {
1097             # jump to Tags if possible
1098 0 0       0 unless ($processAll) {
1099 0 0 0     0 if ($seek{Tags} and $seek{Tags} > $pos + $dataPos and $raf->Seek($seek{Tags},0)) {
      0        
1100 0         0 $buff = '';
1101 0         0 $dataPos = $seek{Tags};
1102 0         0 $pos = $dataLen = 0;
1103 0         0 next;
1104             }
1105 0         0 last;
1106             }
1107 0         0 undef $tagInfo; # just skip the Cluster when -ee is used
1108             } else {
1109             # just fall through into the contained EBML elements
1110 12         30 $$et{INDENT} .= '| ';
1111 12         21 $dirName = $tagName;
1112 12         54 $et->VerboseDir($dirName, undef, $size);
1113 12         19 push @{$$et{PATH}}, $dirName;
  12         64  
1114 12         65 push @dirEnd, [ $pos + $dataPos + $size, $dirName, $struct ];
1115 12 50       31 $struct = { } if $dirName eq 'SimpleTag'; # keep track of SimpleTag elements
1116             # set Chapter# and Info family 1 group names
1117 12 50 66     49 if ($tagName eq 'ChapterAtom') {
    100          
1118 0         0 $$et{SET_GROUP1} = 'Chapter' . (++$chapterNum);
1119 0         0 $trackIndent = $$et{INDENT};
1120             } elsif ($tagName eq 'Info' and not $$et{SET_GROUP1}) {
1121 1         5 $$et{SET_GROUP1} = 'Info';
1122 1         3 $trackIndent = $$et{INDENT};
1123             }
1124 12         33 next;
1125             }
1126             }
1127             } elsif ($verbose) {
1128 0         0 $et->VPrint(0,sprintf("$$et{INDENT}- Tag 0x%x (Unknown, %d bytes)\n", $tag, $size));
1129             }
1130 53 50       116 last if $unknownSize;
1131 53 50       126 if ($pos + $size > $dataLen) {
1132             # how much more do we need to read?
1133 0         0 my $more = $pos + $size - $dataLen;
1134             # just skip unknown and large data blocks
1135 0 0 0     0 if (not $tagInfo or $more > 10000000) {
1136             # don't try to skip very large blocks unless LargeFileSupport is enabled
1137 0 0       0 if ($more >= 0x80000000) {
1138 0 0       0 last unless $et->Options('LargeFileSupport');
1139 0 0       0 if ($et->Options('LargeFileSupport') eq '2') {
1140 0         0 $et->Warn('Processing large block (LargeFileSupport is 2)');
1141             }
1142             }
1143 0 0       0 $raf->Seek($more, 1) or last;
1144 0         0 $buff = '';
1145 0         0 $dataPos += $dataLen + $more;
1146 0         0 $dataLen = 0;
1147 0         0 $pos = 0;
1148 0         0 next;
1149             } else {
1150             # read data in multiples of 64kB
1151 0         0 $more = (int($more / 65536) + 1) * 65536;
1152 0 0       0 if ($raf->Read($buf2, $more)) {
1153 0         0 $buff = substr($buff, $pos) . $buf2;
1154 0         0 undef $buf2;
1155 0         0 $dataPos += $pos;
1156 0         0 $dataLen = length $buff;
1157 0         0 $pos = 0;
1158             }
1159 0 0       0 last if $pos + $size > $dataLen;
1160             }
1161             }
1162 53 50       114 unless ($tagInfo) {
1163             # ignore the element
1164 0         0 $pos += $size;
1165 0         0 next;
1166             }
1167 53         90 my $val;
1168 53 100       154 if ($$tagInfo{Format}) {
1169 48         97 my $fmt = $$tagInfo{Format};
1170 48 100 100     254 if ($fmt eq 'string' or $fmt eq 'utf8') {
    100          
1171 10         64 ($val = substr($buff, $pos, $size)) =~ s/\0.*//s;
1172 10 100       36 $val = $et->Decode($val, 'UTF8') if $fmt eq 'utf8';
1173             } elsif ($fmt eq 'float') {
1174 4 50       13 if ($size == 4) {
    0          
1175 4         16 $val = GetFloat(\$buff, $pos);
1176             } elsif ($size == 8) {
1177 0         0 $val = GetDouble(\$buff, $pos);
1178             } else {
1179 0         0 $et->Warn("Illegal float size ($size)");
1180             }
1181             } else {
1182 34         179 my @vals = unpack("x${pos}C$size", $buff);
1183 34         120 $val = 0;
1184 34 100 66     137 if ($fmt eq 'signed' or $fmt eq 'date') {
1185 1         3 my $over = 1;
1186 1         3 foreach (@vals) {
1187 8         11 $val = $val * 256 + $_;
1188 8         18 $over *= 256;
1189             }
1190             # interpret negative numbers
1191 1 50       27 $val -= $over if $vals[0] & 0x80;
1192             # convert dates (nanoseconds since 2001:01:01)
1193 1 50       5 if ($fmt eq 'date') {
1194 1         4 my $t = $val / 1e9;
1195 1         4 my $f = $t - int($t); # fractional seconds
1196 1         7 $f =~ s/^\d+//; # remove leading zero
1197             # (8 leap days between 1970 and 2001)
1198 1         3 $t += (((2001-1970)*365+8)*24*3600);
1199 1         6 $val = Image::ExifTool::ConvertUnixTime($t) . $f . 'Z';
1200             }
1201             } else { # must be unsigned
1202 33         148 $val = $val * 256 + $_ foreach @vals;
1203             }
1204             }
1205 48 100 66     231 if ($tagName eq 'TrackNumber') {
    100 33        
    50          
1206             # set Track# family 1 group name for tags directly in the track
1207 2         8 $$et{SET_GROUP1} = 'Track' . $val;
1208 2         6 $trackIndent = $$et{INDENT};
1209             } elsif ($tagName eq 'TrackUID' and $$et{SET_GROUP1}) {
1210             # save the Track# group associated with this TrackUID
1211 2         9 $trackNum{$val} = $$et{SET_GROUP1};
1212             } elsif ($tagName eq 'TagTrackUID' and $trackNum{$val}) {
1213             # set Track# group for associated SimpleTags tags
1214 0         0 $$et{SET_GROUP1} = $trackNum{$val};
1215             # we're already one deeper than the level where we want to
1216             # reset the group name, so trigger at one indent level higher
1217 0         0 $trackIndent = substr($$et{INDENT}, 0, -2);
1218             }
1219             }
1220 53         320 my %parms = (
1221             DataPt => \$buff,
1222             DataPos => $dataPos,
1223             Start => $pos,
1224             Size => $size,
1225             );
1226 53 50 33     269 if ($$tagInfo{NoSave} or $struct) {
    100          
1227 0 0       0 $et->VerboseInfo($tag, $tagInfo, Value => $val, %parms) if $verbose;
1228 0 0       0 $$struct{$tagName} = $val if $struct;
1229             } elsif ($$tagInfo{SeekInfo}) {
1230 8         13 my $p = $pos;
1231 8 100       26 $val = GetVInt($buff, $p) unless defined $val;
1232 8         27 $seekInfo{$$tagInfo{SeekInfo}} = $val;
1233 8 50       45 $et->HandleTag($tagTablePtr, $tag, $val, %parms) unless $seekInfoOnly;
1234             } else {
1235 45         226 $et->HandleTag($tagTablePtr, $tag, $val, %parms);
1236             }
1237 53         208 $pos += $size; # step to next element
1238             }
1239 1         5 $$et{INDENT} = $oldIndent;
1240 1         3 delete $$et{SET_GROUP1};
1241             # override file type if necessary based on existing track types
1242 1 0 33     5 unless ($trackTypes{0x01} or $trackTypes{0x03}) { # video or complex?
1243 0 0       0 if ($trackTypes{0x02}) { # audio?
    0          
1244 0         0 $et->OverrideFileType('MKA');
1245             } elsif ($trackTypes{0x11}) { # subtitle?
1246 0         0 $et->OverrideFileType('MKS');
1247             }
1248             }
1249 1         10 return 1;
1250             }
1251              
1252             1; # end
1253              
1254             __END__