File Coverage

blib/lib/Image/ExifTool/Matroska.pm
Criterion Covered Total %
statement 104 151 68.8
branch 44 98 44.9
condition 23 45 51.1
subroutine 5 5 100.0
pod 0 2 0.0
total 176 301 58.4


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             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::Matroska;
12              
13 1     1   4352 use strict;
  1         3  
  1         32  
14 1     1   6 use vars qw($VERSION);
  1         2  
  1         39  
15 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         3046  
16              
17             $VERSION = '1.12';
18              
19             my %noYes = ( 0 => 'No', 1 => 'Yes' );
20              
21             # Matroska tags
22             # Note: The tag ID's in the Matroska documentation include the length designation
23             # (the upper bits), which is not included in the tag ID's below
24             %Image::ExifTool::Matroska::Main = (
25             GROUPS => { 2 => 'Video' },
26             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
27             NOTES => q{
28             The following tags are extracted from Matroska multimedia container files.
29             This container format is used by file types such as MKA, MKV, MKS and WEBM.
30             For speed, by default ExifTool extracts tags only up to the first Cluster.
31             However, the L (-v) and L = 2 (-U) options force processing of
32             Cluster data, and the L (-ee) option skips over Clusters to
33             read subsequent tags. See
34             L for the official
35             Matroska specification.
36             },
37             # supported Format's: signed, unsigned, float, date, string, utf8
38             # (or undef by default)
39             #
40             # EBML Header
41             #
42             0xa45dfa3 => {
43             Name => 'EBMLHeader',
44             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
45             },
46             0x286 => { Name => 'EBMLVersion', Format => 'unsigned' },
47             0x2f7 => { Name => 'EBMLReadVersion', Format => 'unsigned' },
48             0x2f2 => { Name => 'EBMLMaxIDLength', Format => 'unsigned', Unknown => 1 },
49             0x2f3 => { Name => 'EBMLMaxSizeLength', Format => 'unsigned', Unknown => 1 },
50             0x282 => {
51             Name => 'DocType',
52             Format => 'string',
53             # override FileType for "webm" files
54             RawConv => '$self->OverrideFileType("WEBM") if $val eq "webm"; $val',
55             },
56             0x287 => { Name => 'DocTypeVersion', Format => 'unsigned' },
57             0x285 => { Name => 'DocTypeReadVersion',Format => 'unsigned' },
58             #
59             # General
60             #
61             0x3f => { Name => 'CRC-32', Binary => 1, Unknown => 1 },
62             0x6c => { Name => 'Void', NoSave => 1, Unknown => 1 },
63             #
64             # Signature
65             #
66             0xb538667 => {
67             Name => 'SignatureSlot',
68             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
69             },
70             0x3e8a => { Name => 'SignatureAlgo', Format => 'unsigned' },
71             0x3e9a => { Name => 'SignatureHash', Format => 'unsigned' },
72             0x3ea5 => { Name =>'SignaturePublicKey',Binary => 1, Unknown => 1 },
73             0x3eb5 => { Name => 'Signature', Binary => 1, Unknown => 1 },
74             0x3e5b => {
75             Name => 'SignatureElements',
76             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
77             },
78             0x3e7b => {
79             Name => 'SignatureElementList',
80             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
81             },
82             0x2532 => { Name => 'SignedElement', Binary => 1, Unknown => 1 },
83             #
84             # Segment
85             #
86             0x8538067 => {
87             Name => 'SegmentHeader',
88             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
89             },
90             0x14d9b74 => {
91             Name => 'SeekHead',
92             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
93             },
94             0xdbb => {
95             Name => 'Seek',
96             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
97             },
98             0x13ab => { Name => 'SeekID', Binary => 1, Unknown => 1 },
99             0x13ac => { Name => 'SeekPosition', Format => 'unsigned', Unknown => 1 },
100             #
101             # Segment Info
102             #
103             0x549a966 => {
104             Name => 'Info',
105             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
106             },
107             0x33a4 => { Name => 'SegmentUID', Binary => 1, Unknown => 1 },
108             0x3384 => { Name => 'SegmentFileName', Format => 'utf8' },
109             0x1cb923 => { Name => 'PrevUID', Binary => 1, Unknown => 1 },
110             0x1c83ab => { Name => 'PrevFileName', Format => 'utf8' },
111             0x1eb923 => { Name => 'NextUID', Binary => 1, Unknown => 1 },
112             0x1e83bb => { Name => 'NextFileName', Format => 'utf8' },
113             0x0444 => { Name => 'SegmentFamily', Binary => 1, Unknown => 1 },
114             0x2924 => {
115             Name => 'ChapterTranslate',
116             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
117             },
118             0x29fc => { Name => 'ChapterTranslateEditionUID',Format => 'unsigned', Unknown => 1 },
119             0x29bf => {
120             Name => 'ChapterTranslateCodec',
121             Format => 'unsigned',
122             PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' },
123             },
124             0x29a5 => { Name => 'ChapterTranslateID',Binary => 1, Unknown => 1 },
125             0xad7b1 => {
126             Name => 'TimecodeScale',
127             Format => 'unsigned',
128             RawConv => '$$self{TimecodeScale} = $val',
129             ValueConv => '$val / 1e9',
130             PrintConv => '($val * 1000) . " ms"',
131             },
132             0x489 => {
133             Name => 'Duration',
134             Format => 'float',
135             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
136             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
137             },
138             0x461 => {
139             Name => 'DateTimeOriginal', # called "DateUTC" by the spec
140             Description => 'Date/Time Original',
141             Groups => { 2 => 'Time' },
142             Format => 'date',
143             PrintConv => '$self->ConvertDateTime($val)',
144             },
145             0x3ba9 => { Name => 'Title', Format => 'utf8' },
146             0xd80 => { Name => 'MuxingApp', Format => 'utf8' },
147             0x1741 => { Name => 'WritingApp', Format => 'utf8' },
148             #
149             # Cluster
150             #
151             0xf43b675 => {
152             Name => 'Cluster',
153             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
154             },
155             0x67 => {
156             Name => 'TimeCode',
157             Format => 'unsigned',
158             Unknown => 1,
159             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
160             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
161             },
162             0x1854 => {
163             Name => 'SilentTracks',
164             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
165             },
166             0x18d7 => { Name => 'SilentTrackNumber',Format => 'unsigned' },
167             0x27 => { Name => 'Position', Format => 'unsigned' },
168             0x2b => { Name => 'PrevSize', Format => 'unsigned' },
169             0x23 => { Name => 'SimpleBlock', NoSave => 1, Unknown => 1 },
170             0x20 => {
171             Name => 'BlockGroup',
172             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
173             },
174             0x21 => { Name => 'Block', NoSave => 1, Unknown => 1 },
175             0x22 => { Name => 'BlockVirtual', NoSave => 1, Unknown => 1 },
176             0x35a1 => {
177             Name => 'BlockAdditions',
178             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
179             },
180             0x26 => {
181             Name => 'BlockMore',
182             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
183             },
184             0x6e => { Name => 'BlockAddID', Format => 'unsigned', Unknown => 1 },
185             0x25 => { Name => 'BlockAdditional', NoSave => 1, Unknown => 1 },
186             0x1b => {
187             Name => 'BlockDuration',
188             Format => 'unsigned',
189             Unknown => 1,
190             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
191             PrintConv => '$$self{TimecodeScale} ? "$val s" : $val',
192             },
193             0x7a => { Name => 'ReferencePriority',Format => 'unsigned', Unknown => 1 },
194             0x7b => {
195             Name => 'ReferenceBlock',
196             Format => 'signed',
197             Unknown => 1,
198             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
199             PrintConv => '$$self{TimecodeScale} ? "$val s" : $val',
200             },
201             0x7d => { Name => 'ReferenceVirtual', Format => 'signed', Unknown => 1 },
202             0x24 => { Name => 'CodecState', Binary => 1, Unknown => 1 },
203             0x0e => {
204             Name => 'Slices',
205             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
206             },
207             0x68 => {
208             Name => 'TimeSlice',
209             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
210             },
211             0x4c => { Name => 'LaceNumber', Format => 'unsigned', Unknown => 1 },
212             0x4d => { Name => 'FrameNumber', Format => 'unsigned', Unknown => 1 },
213             0x4b => { Name => 'BlockAdditionalID',Format => 'unsigned', Unknown => 1 },
214             0x4e => { Name => 'Delay', Format => 'unsigned', Unknown => 1 },
215             0x4f => { Name => 'ClusterDuration', Format => 'unsigned', Unknown => 1 },
216             0x2f => { Name => 'EncryptedBlock', NoSave => 1, Unknown => 1 },
217             #
218             # Tracks
219             #
220             0x654ae6b => {
221             Name => 'Tracks',
222             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
223             },
224             0x2e => {
225             Name => 'TrackEntry',
226             # reset TrackType member at the start of each track
227             Condition => 'delete $$self{TrackType}; 1',
228             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
229             },
230             0x57 => { Name => 'TrackNumber', Format => 'unsigned' },
231             0x33c5 => { Name => 'TrackUID', Format => 'unsigned', Unknown => 1 },
232             0x03 => {
233             Name => 'TrackType',
234             Format => 'unsigned',
235             PrintHex => 1,
236             # remember types of all tracks encountered, as well as the current track type
237             RawConv => '$$self{TrackTypes}{$val} = 1; $$self{TrackType} = $val',
238             PrintConv => {
239             0x01 => 'Video',
240             0x02 => 'Audio',
241             0x03 => 'Complex', # (audio+video)
242             0x10 => 'Logo',
243             0x11 => 'Subtitle',
244             0x12 => 'Buttons',
245             0x20 => 'Control',
246             },
247             },
248             0x39 => { Name => 'TrackUsed', Format => 'unsigned', PrintConv => \%noYes },
249             0x08 => { Name => 'TrackDefault', Format => 'unsigned', PrintConv => \%noYes },
250             0x15aa => { Name => 'TrackForced', Format => 'unsigned', PrintConv => \%noYes },
251             0x1c => {
252             Name => 'TrackLacing',
253             Format => 'unsigned',
254             Unknown => 1,
255             PrintConv => \%noYes,
256             },
257             0x2de7 => { Name => 'MinCache', Format => 'unsigned', Unknown => 1 },
258             0x2df8 => { Name => 'MaxCache', Format => 'unsigned', Unknown => 1 },
259             0x3e383 => [
260             {
261             Name => 'VideoFrameRate',
262             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
263             Format => 'unsigned',
264             ValueConv => '$val ? 1e9 / $val : 0',
265             PrintConv => 'int($val * 1000 + 0.5) / 1000',
266             },{
267             Name => 'DefaultDuration',
268             Format => 'unsigned',
269             ValueConv => '$val / 1e9',
270             PrintConv => '($val * 1000) . " ms"',
271             }
272             ],
273             0x3314f => { Name => 'TrackTimecodeScale',Format => 'float' },
274             0x137f => { Name => 'TrackOffset', Format => 'signed', Unknown => 1 },
275             0x15ee => { Name => 'MaxBlockAdditionID',Format => 'unsigned', Unknown => 1 },
276             0x136e => { Name => 'TrackName', Format => 'utf8' },
277             0x2b59c => { Name => 'TrackLanguage', Format => 'string' },
278             0x2b59d => { Name => 'TrackLanguageIETF', Format => 'string' },
279             0x06 => [
280             {
281             Name => 'VideoCodecID',
282             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
283             Format => 'string',
284             },{
285             Name => 'AudioCodecID',
286             Condition => '$$self{TrackType} and $$self{TrackType} == 0x02',
287             Format => 'string',
288             },{
289             Name => 'CodecID',
290             Format => 'string',
291             }
292             ],
293             0x23a2 => { Name => 'CodecPrivate', Binary => 1, Unknown => 1 },
294             0x58688 => [
295             {
296             Name => 'VideoCodecName',
297             Condition => '$$self{TrackType} and $$self{TrackType} == 0x01',
298             Format => 'utf8',
299             },{
300             Name => 'AudioCodecName',
301             Condition => '$$self{TrackType} and $$self{TrackType} == 0x02',
302             Format => 'utf8',
303             },{
304             Name => 'CodecName',
305             Format => 'utf8',
306             }
307             ],
308             0x3446 => { Name => 'TrackAttachmentUID',Format => 'unsigned' },
309             0x1a9697=>{ Name => 'CodecSettings', Format => 'utf8' },
310             0x1b4040=>{ Name => 'CodecInfoURL', Format => 'string' },
311             0x6b240 =>{ Name => 'CodecDownloadURL', Format => 'string' },
312             0x2a => { Name => 'CodecDecodeAll', Format => 'unsigned', PrintConv => \%noYes },
313             0x2fab => { Name => 'TrackOverlay', Format => 'unsigned', Unknown => 1 },
314             0x2624 => {
315             Name => 'TrackTranslate',
316             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
317             },
318             0x26fc => { Name => 'TrackTranslateEditionUID',Format => 'unsigned', Unknown => 1 },
319             0x26bf => {
320             Name => 'TrackTranslateCodec',
321             Format => 'unsigned',
322             PrintConv => { 0 => 'Matroska Script', 1 => 'DVD Menu' },
323             },
324             0x26a5 => { Name => 'TrackTranslateTrackID', Binary => 1, Unknown => 1 },
325             #
326             # Video
327             #
328             0x60 => {
329             Name => 'Video',
330             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
331             },
332             0x1a => {
333             Name => 'VideoScanType',
334             Format => 'unsigned',
335             PrintConv => {
336             0 => 'Progressive',
337             1 => 'Interlaced',
338             },
339             },
340             0x13b8 => {
341             Name => 'Stereo3DMode',
342             Format => 'unsigned',
343             Printconv => {
344             0 => 'Mono',
345             1 => 'Right Eye',
346             2 => 'Left Eye',
347             3 => 'Both Eyes',
348             },
349             },
350             0x30 => { Name => 'ImageWidth', Format => 'unsigned' },
351             0x3a => { Name => 'ImageHeight', Format => 'unsigned' },
352             0x14aa => { Name => 'CropBottom', Format => 'unsigned' },
353             0x14bb => { Name => 'CropTop', Format => 'unsigned' },
354             0x14cc => { Name => 'CropLeft', Format => 'unsigned' },
355             0x14dd => { Name => 'CropRight', Format => 'unsigned' },
356             0x14b0 => { Name => 'DisplayWidth', Format => 'unsigned' },
357             0x14ba => { Name => 'DisplayHeight', Format => 'unsigned' },
358             0x14b2 => {
359             Name => 'DisplayUnit',
360             Format => 'unsigned',
361             PrintConv => {
362             0 => 'Pixels',
363             1 => 'cm',
364             2 => 'inches',
365             },
366             },
367             0x14b3 => {
368             Name => 'AspectRatioType',
369             Format => 'unsigned',
370             PrintConv => {
371             0 => 'Free Resizing',
372             1 => 'Keep Aspect Ratio',
373             2 => 'Fixed',
374             },
375             },
376             0xeb524 => { Name => 'ColorSpace', Binary => 1, Unknown => 1 },
377             0xfb523 => { Name => 'Gamma', Format => 'float' },
378             0x383e3 => { Name => 'FrameRate', Format => 'float' },
379             #
380             # Audio
381             #
382             0x61 => {
383             Name => 'Audio',
384             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
385             },
386             0x35 => { Name => 'AudioSampleRate', Format => 'float', Groups => { 2 => 'Audio' } },
387             0x38b5 => { Name => 'OutputAudioSampleRate',Format => 'float', Groups => { 2 => 'Audio' } },
388             0x1f => { Name => 'AudioChannels', Format => 'unsigned', Groups => { 2 => 'Audio' } },
389             0x3d7b => {
390             Name => 'ChannelPositions',
391             Binary => 1,
392             Unknown => 1,
393             Groups => { 2 => 'Audio' },
394             },
395             0x2264 => { Name => 'AudioBitsPerSample', Format => 'unsigned', Groups => { 2 => 'Audio' } },
396             #
397             # Content Encoding
398             #
399             0x2d80 => {
400             Name => 'ContentEncodings',
401             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
402             },
403             0x2240 => {
404             Name => 'ContentEncoding',
405             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
406             },
407             0x1031 => { Name => 'ContentEncodingOrder', Format => 'unsigned', Unknown => 1 },
408             0x1032 => { Name => 'ContentEncodingScope', Format => 'unsigned', Unknown => 1 },
409             0x1033 => {
410             Name => 'ContentEncodingType',
411             Format => 'unsigned',
412             PrintConv => { 0 => 'Compression', 1 => 'Encryption' },
413             },
414             0x1034 => {
415             Name => 'ContentCompression',
416             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
417             },
418             0x254 => {
419             Name => 'ContentCompressionAlgorithm',
420             Format => 'unsigned',
421             PrintConv => {
422             0 => 'zlib',
423             1 => 'bzlib',
424             2 => 'lzo1x',
425             3 => 'Header Stripping',
426             },
427             },
428             0x255 => { Name => 'ContentCompressionSettings',Binary => 1, Unknown => 1 },
429             0x1035 => {
430             Name => 'ContentEncryption',
431             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
432             },
433             0x7e1 => {
434             Name => 'ContentEncryptionAlgorithm',
435             Format => 'unsigned',
436             PrintConv => {
437             0 => 'Not Encrypted',
438             1 => 'DES',
439             2 => '3DES',
440             3 => 'Twofish',
441             4 => 'Blowfish',
442             5 => 'AES',
443             },
444             },
445             0x7e2 => { Name => 'ContentEncryptionKeyID',Binary => 1, Unknown => 1 },
446             0x7e3 => { Name => 'ContentSignature', Binary => 1, Unknown => 1 },
447             0x7e4 => { Name => 'ContentSignatureKeyID', Binary => 1, Unknown => 1 },
448             0x7e5 => {
449             Name => 'ContentSignatureAlgorithm',
450             Format => 'unsigned',
451             PrintConv => {
452             0 => 'Not Signed',
453             1 => 'RSA',
454             },
455             },
456             0x7e6 => {
457             Name => 'ContentSignatureHashAlgorithm',
458             Format => 'unsigned',
459             PrintConv => {
460             0 => 'Not Signed',
461             1 => 'SHA1-160',
462             2 => 'MD5',
463             },
464             },
465             #
466             # Cues
467             #
468             0xc53bb6b => {
469             Name => 'Cues',
470             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
471             },
472             0x3b => {
473             Name => 'CuePoint',
474             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
475             },
476             0x33 => {
477             Name => 'CueTime',
478             Format => 'unsigned',
479             Unknown => 1,
480             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
481             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
482             },
483             0x37 => {
484             Name => 'CueTrackPositions',
485             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
486             },
487             0x77 => { Name => 'CueTrack', Format => 'unsigned', Unknown => 1 },
488             0x71 => { Name => 'CueClusterPosition',Format => 'unsigned', Unknown => 1 },
489             0x1378 => { Name => 'CueBlockNumber', Format => 'unsigned', Unknown => 1 },
490             0x6a => { Name => 'CueCodecState', Format => 'unsigned', Unknown => 1 },
491             0x5b => {
492             Name => 'CueReference',
493             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
494             },
495             0x16 => {
496             Name => 'CueRefTime',
497             Format => 'unsigned',
498             Unknown => 1,
499             ValueConv => '$$self{TimecodeScale} ? $val * $$self{TimecodeScale} / 1e9 : $val',
500             PrintConv => '$$self{TimecodeScale} ? ConvertDuration($val) : $val',
501             },
502             0x17 => { Name => 'CueRefCluster', Format => 'unsigned', Unknown => 1 },
503             0x135f=> { Name => 'CueRefNumber', Format => 'unsigned', Unknown => 1 },
504             0x6b => { Name => 'CueRefCodecState', Format => 'unsigned', Unknown => 1 },
505             #
506             # Attachments
507             #
508             0x941a469 => {
509             Name => 'Attachments',
510             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
511             },
512             0x21a7 => {
513             Name => 'AttachedFile',
514             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
515             },
516             0x67e => { Name => 'AttachedFileDescription',Format => 'utf8' },
517             0x66e => { Name => 'AttachedFileName', Format => 'utf8' },
518             0x660 => { Name => 'AttachedFileMIMEType', Format => 'string' },
519             0x65c => { Name => 'AttachedFileData', Binary => 1 },
520             0x6ae => { Name => 'AttachedFileUID', Format => 'unsigned' },
521             0x675 => { Name => 'AttachedFileReferral', Binary => 1, Unknown => 1 },
522             #
523             # Chapters
524             #
525             0x43a770 => {
526             Name => 'Chapters',
527             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
528             },
529             0x5b9 => {
530             Name => 'EditionEntry',
531             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
532             },
533             0x5bc => { Name => 'EditionUID', Format => 'unsigned', Unknown => 1 },
534             0x5bd => { Name => 'EditionFlagHidden', Format => 'unsigned', Unknown => 1 },
535             0x5db => { Name => 'EditionFlagDefault',Format => 'unsigned', Unknown => 1 },
536             0x5dd => { Name => 'EditionFlagOrdered',Format => 'unsigned', Unknown => 1 },
537             0x36 => {
538             Name => 'ChapterAtom',
539             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
540             },
541             0x33c4 => { Name => 'ChapterUID', Format => 'unsigned', Unknown => 1 },
542             0x11 => {
543             Name => 'ChapterTimeStart',
544             Groups => { 1 => 'Chapter#' },
545             Format => 'unsigned',
546             ValueConv => '$val / 1e9',
547             PrintConv => 'ConvertDuration($val)',
548             },
549             0x12 => {
550             Name => 'ChapterTimeEnd',
551             Format => 'unsigned',
552             ValueConv => '$val / 1e9',
553             PrintConv => 'ConvertDuration($val)',
554             },
555             0x18 => { Name => 'ChapterFlagHidden', Format => 'unsigned', Unknown => 1 },
556             0x598 => { Name => 'ChapterFlagEnabled',Format => 'unsigned', Unknown => 1 },
557             0x2e67=> { Name => 'ChapterSegmentUID', Binary => 1, Unknown => 1 },
558             0x2ebc=> { Name => 'ChapterSegmentEditionUID', Binary => 1, Unknown => 1 },
559             0x23c3 => {
560             Name => 'ChapterPhysicalEquivalent',
561             Format => 'unsigned',
562             PrintConv => {
563             10 => 'Index',
564             20 => 'Track',
565             30 => 'Session',
566             40 => 'Layer',
567             50 => 'Side',
568             60 => 'CD / DVD',
569             70 => 'Set / Package',
570             },
571             },
572             0x0f => {
573             Name => 'ChapterTrack',
574             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
575             },
576             0x09 => { Name => 'ChapterTrackNumber', Format => 'unsigned', Unknown => 1 },
577             0x00 => {
578             Name => 'ChapterDisplay',
579             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
580             },
581             0x05 => { Name => 'ChapterString', Format => 'utf8' },
582             0x37c => { Name => 'ChapterLanguage', Format => 'string' },
583             0x37e => { Name => 'ChapterCountry', Format => 'string' },
584             0x2944 => {
585             Name => 'ChapterProcess',
586             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
587             },
588             0x2955 => {
589             Name => 'ChapterProcessCodecID',
590             Format => 'unsigned',
591             Unknown => 1,
592             PrintConv => { 0 => 'Matroska', 1 => 'DVD' },
593             },
594             0x50d => { Name => 'ChapterProcessPrivate', Binary => 1, Unknown => 1 },
595             0x2911 => {
596             Name => 'ChapterProcessCommand',
597             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
598             },
599             0x2922 => {
600             Name => 'ChapterProcessTime',
601             Format => 'unsigned',
602             Unknown => 1,
603             PrintConv => {
604             0 => 'For Duration of Chapter',
605             1 => 'Before Chapter',
606             2 => 'After Chapter',
607             },
608             },
609             0x2933 => { Name => 'ChapterProcessData', Binary => 1, Unknown => 1 },
610             #
611             # Tags
612             #
613             0x254c367 => {
614             Name => 'Tags',
615             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
616             },
617             0x3373 => {
618             Name => 'Tag',
619             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
620             },
621             0x23c0 => {
622             Name => 'Targets',
623             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
624             },
625             0x28ca => { Name => 'TargetTypeValue', Format => 'unsigned' },
626             0x23ca => { Name => 'TargetType', Format => 'string' },
627             0x23c5 => { Name => 'TagTrackUID', Format => 'unsigned', Unknown => 1 },
628             0x23c9 => { Name => 'TagEditionUID', Format => 'unsigned', Unknown => 1 },
629             0x23c4 => { Name => 'TagChapterUID', Format => 'unsigned', Unknown => 1 },
630             0x23c6 => { Name => 'TagAttachmentUID', Format => 'unsigned', Unknown => 1 },
631             0x27c8 => {
632             Name => 'SimpleTag',
633             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Main' },
634             },
635             0x5a3 => { Name => 'TagName', Format => 'utf8' },
636             0x47a => { Name => 'TagLanguage', Format => 'string' },
637             0x484 => { Name => 'TagDefault', Format => 'unsigned', PrintConv => \%noYes },
638             0x487 => { Name => 'TagString', Format => 'utf8' },
639             0x485 => { Name => 'TagBinary', Binary => 1 },
640             #
641             # Spherical Video V2 (untested)
642             #
643             0x7670 => {
644             Name => 'Projection',
645             SubDirectory => { TagTable => 'Image::ExifTool::Matroska::Projection' },
646             },
647             );
648              
649             # Spherical video v2 projection tags (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md)
650             %Image::ExifTool::Matroska::Projection = (
651             GROUPS => { 2 => 'Video' },
652             VARS => { NO_LOOKUP => 1 }, # omit tags from lookup
653             NOTES => q{
654             Projection tags defined by the Spherical Video V2 specification. See
655             L
656             for the specification.
657             },
658             0x7671 => {
659             Name => 'ProjectionType',
660             Format => 'unsigned',
661             DataMember => 'ProjectionType',
662             RawConv => '$$self{ProjectionType} = $val',
663             PrintConv => {
664             0 => 'Rectangular',
665             1 => 'Equirectangular',
666             2 => 'Cubemap',
667             3 => 'Mesh',
668             },
669             },
670             # ProjectionPrivate in the spec
671             0x7672 => [{
672             Name => 'EquirectangularProj',
673             Condition => '$$self{ProjectionType} == 1',
674             SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::equi' },
675             },{
676             Name => 'CubemapProj',
677             Condition => '$$self{ProjectionType} == 2',
678             SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::cbmp' },
679             },{ # (don't decode 3 because it is a PITA)
680             Name => 'ProjectionPrivate',
681             Binary => 1,
682             }],
683             0x7673 => { Name => 'ProjectionPoseYaw', Format => 'float' },
684             0x7674 => { Name => 'ProjectionPosePitch', Format => 'float' },
685             0x7675 => { Name => 'ProjectionPoseRoll', Format => 'float' },
686             );
687              
688             #------------------------------------------------------------------------------
689             # Get variable-length Matroska integer
690             # Inputs: 0) data buffer, 1) position in data
691             # Returns: integer value and updates position, -1 for unknown/reserved value,
692             # or undef if no data left
693             sub GetVInt($$)
694             {
695 132 100   132 0 251 return undef if $_[1] >= length $_[0];
696 131         234 my $val = ord(substr($_[0], $_[1]++));
697 131         161 my $num = 0;
698 131 50       237 unless ($val) {
699 0 0       0 return undef if $_[1] >= length $_[0];
700 0         0 $val = ord(substr($_[0], $_[1]++));
701 0 0       0 return undef unless $val; # can't be this large!
702 0         0 $num += 7; # 7 more bytes to read (we just read one)
703             }
704 131         165 my $mask = 0x7f;
705 131         247 while ($val == ($val & $mask)) {
706 67         92 $mask >>= 1;
707 67         122 ++$num;
708             }
709 131         186 $val = ($val & $mask);
710 131         194 my $unknown = ($val == $mask);
711 131 50       251 return undef if $_[1] + $num > length $_[0];
712 131         223 while ($num) {
713 67         108 my $b = ord(substr($_[0], $_[1]++));
714 67 50       184 $unknown = 0 if $b != 0xff;
715 67         98 $val = $val * 256 + $b;
716 67         128 --$num;
717             }
718 131 50       245 return $unknown ? -1 : $val;
719             }
720              
721             #------------------------------------------------------------------------------
722             # Read information from a Matroska multimedia file (MKV, MKA, MKS)
723             # Inputs: 0) ExifTool object reference, 1) Directory information reference
724             # Returns: 1 on success, 0 if this wasn't a valid Matroska file
725             sub ProcessMKV($$)
726             {
727 1     1 0 4 my ($et, $dirInfo) = @_;
728 1         4 my $raf = $$dirInfo{RAF};
729 1         3 my ($buff, $buf2, @dirEnd, $trackIndent, %trackTypes);
730              
731 1 50       5 $raf->Read($buff, 4) == 4 or return 0;
732 1 50       16 return 0 unless $buff =~ /^\x1a\x45\xdf\xa3/;
733              
734             # read in 64kB blocks (already read 4 bytes)
735 1 50       5 $raf->Read($buff, 65532) or return 0;
736 1         3 my $dataLen = length $buff;
737 1         3 my ($pos, $dataPos) = (0, 4);
738              
739             # verify header length
740 1         4 my $hlen = GetVInt($buff, $pos);
741 1 50 33     6 return 0 unless $hlen and $hlen > 0;
742 1 50       5 $pos + $hlen > $dataLen and $et->Warn('Truncated Matroska header'), return 1;
743 1         7 $et->SetFileType();
744 1         5 SetByteOrder('MM');
745 1         3 my $tagTablePtr = GetTagTable('Image::ExifTool::Matroska::Main');
746              
747             # set flag to process entire file (otherwise we stop at the first Cluster)
748 1         5 my $verbose = $et->Options('Verbose');
749 1 50 33     7 my $processAll = ($verbose or $et->Options('Unknown') > 1) ? 2 : 0;
750 1 50       4 ++$processAll if $et->Options('ExtractEmbedded');
751 1         3 $$et{TrackTypes} = \%trackTypes; # store Track types reference
752 1         2 my $oldIndent = $$et{INDENT};
753 1         4 my $chapterNum = 0;
754              
755             # loop over all Matroska elements
756 1         1 for (;;) {
757 66   100     230 while (@dirEnd and $pos + $dataPos >= $dirEnd[-1][0]) {
758 11         18 pop @dirEnd;
759             # use INDENT to decide whether or not we are done this Track element
760 11 100 100     39 delete $$et{SET_GROUP1} if $trackIndent and $trackIndent eq $$et{INDENT};
761 11         41 $$et{INDENT} = substr($$et{INDENT}, 0, -2);
762             }
763             # read more if we are getting close to the end of our buffer
764             # (24 more bytes should be enough to read this element header)
765 66 50 66     201 if ($pos + 24 > $dataLen and $raf->Read($buf2, 65536)) {
766 0         0 $buff = substr($buff, $pos) . $buf2;
767 0         0 undef $buf2;
768 0         0 $dataPos += $pos;
769 0         0 $dataLen = length $buff;
770 0         0 $pos = 0;
771             }
772 66         130 my $tag = GetVInt($buff, $pos);
773 66 100 66     197 last unless defined $tag and $tag >= 0;
774 65         101 my $size = GetVInt($buff, $pos);
775 65 50       122 last unless defined $size;
776 65         72 my $unknownSize;
777 65 50       123 $size < 0 and $unknownSize = 1, $size = 1e20;
778 65 50 66     198 if (@dirEnd and $pos + $dataPos + $size > $dirEnd[-1][0]) {
779 0         0 $et->Warn("Invalid or corrupted $dirEnd[-1][1] master element");
780 0         0 $pos = $dirEnd[-1][0] - $dataPos;
781 0 0 0     0 if ($pos < 0 or $pos > $dataLen) {
782 0         0 $buff = '';
783 0         0 $dataPos += $pos;
784 0         0 $dataLen = 0;
785 0         0 $pos = 0;
786 0 0       0 $raf->Seek($dataPos, 0) or last;
787             }
788 0         0 next;
789             }
790 65         172 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
791             # just fall through into the contained EBML elements
792 65 100 66     225 if ($tagInfo and $$tagInfo{SubDirectory}) {
793             # stop processing at first cluster unless we are using -v -U or -ee
794 12 50 33     34 if ($$tagInfo{Name} eq 'Cluster' and $processAll < 2) {
795 0 0       0 last unless $processAll;
796 0         0 undef $tagInfo; # just skip the Cluster when -ee is used
797             } else {
798 12         24 $$et{INDENT} .= '| ';
799 12         44 $et->VerboseDir($$tagTablePtr{$tag}{Name}, undef, $size);
800 12         46 push @dirEnd, [ $pos + $dataPos + $size, $$tagInfo{Name} ];
801 12 50       31 if ($$tagInfo{Name} eq 'ChapterAtom') {
802 0         0 $$et{SET_GROUP1} = 'Chapter' . (++$chapterNum);
803 0         0 $trackIndent = $$et{INDENT};
804             }
805 12         22 next;
806             }
807             }
808 53 50       95 last if $unknownSize;
809 53 50       109 if ($pos + $size > $dataLen) {
810             # how much more do we need to read?
811 0         0 my $more = $pos + $size - $dataLen;
812             # just skip unknown and large data blocks
813 0 0 0     0 if (not $tagInfo or $more > 10000000) {
814             # don't try to skip very large blocks unless LargeFileSupport is enabled
815 0 0 0     0 last if $more >= 0x80000000 and not $et->Options('LargeFileSupport');
816 0 0       0 $raf->Seek($more, 1) or last;
817 0         0 $buff = '';
818 0         0 $dataPos += $dataLen + $more;
819 0         0 $dataLen = 0;
820 0         0 $pos = 0;
821 0         0 next;
822             } else {
823             # read data in multiples of 64kB
824 0         0 $more = (int($more / 65536) + 1) * 65536;
825 0 0       0 if ($raf->Read($buf2, $more)) {
826 0         0 $buff = substr($buff, $pos) . $buf2;
827 0         0 undef $buf2;
828 0         0 $dataPos += $pos;
829 0         0 $dataLen = length $buff;
830 0         0 $pos = 0;
831             }
832 0 0       0 last if $pos + $size > $dataLen;
833             }
834             }
835 53 50       94 unless ($tagInfo) {
836             # ignore the element
837 0         0 $pos += $size;
838 0         0 next;
839             }
840 53         71 my $val;
841 53 100       110 if ($$tagInfo{Format}) {
842 47         76 my $fmt = $$tagInfo{Format};
843 47 100 100     161 if ($fmt eq 'string' or $fmt eq 'utf8') {
    100          
844 7         22 ($val = substr($buff, $pos, $size)) =~ s/\0.*//s;
845 7 100       22 $val = $et->Decode($val, 'UTF8') if $fmt eq 'utf8';
846             } elsif ($fmt eq 'float') {
847 4 50       11 if ($size == 4) {
    0          
848 4         13 $val = GetFloat(\$buff, $pos);
849             } elsif ($size == 8) {
850 0         0 $val = GetDouble(\$buff, $pos);
851             } else {
852 0         0 $et->Warn("Illegal float size ($size)");
853             }
854             } else {
855 36         129 my @vals = unpack("x${pos}C$size", $buff);
856 36         55 $val = 0;
857 36 100 66     112 if ($fmt eq 'signed' or $fmt eq 'date') {
858 1         2 my $over = 1;
859 1         3 foreach (@vals) {
860 8         11 $val = $val * 256 + $_;
861 8         13 $over *= 256;
862             }
863             # interpret negative numbers
864 1 50       4 $val -= $over if $vals[0] & 0x80;
865             # convert dates (nanoseconds since 2001:01:01)
866 1 50       3 if ($fmt eq 'date') {
867 1         4 my $t = $val / 1e9;
868 1         3 my $f = $t - int($t); # fractional seconds
869 1         7 $f =~ s/^\d+//; # remove leading zero
870             # (8 leap days between 1970 and 2001)
871 1         2 $t += (((2001-1970)*365+8)*24*3600);
872 1         7 $val = Image::ExifTool::ConvertUnixTime($t) . $f . 'Z';
873             }
874             } else { # must be unsigned
875 35         88 $val = $val * 256 + $_ foreach @vals;
876             }
877             }
878             # set group1 to Track/Chapter number
879 47 100       1033 if ($$tagInfo{Name} eq 'TrackNumber') {
880 2         6 $$et{SET_GROUP1} = 'Track' . $val;
881 2         4 $trackIndent = $$et{INDENT};
882             }
883             }
884 53         188 my %parms = (
885             DataPt => \$buff,
886             DataPos => $dataPos,
887             Start => $pos,
888             Size => $size,
889             );
890 53 50       94 if ($$tagInfo{NoSave}) {
891 0 0       0 $et->VerboseInfo($tag, $tagInfo, Value => $val, %parms) if $verbose;
892             } else {
893 53         184 $et->HandleTag($tagTablePtr, $tag, $val, %parms);
894             }
895 53         132 $pos += $size; # step to next element
896             }
897 1         3 $$et{INDENT} = $oldIndent;
898 1         4 delete $$et{SET_GROUP1};
899             # override file type if necessary based on existing track types
900 1 0 33     5 unless ($trackTypes{0x01} or $trackTypes{0x03}) { # video or complex?
901 0 0       0 if ($trackTypes{0x02}) { # audio?
    0          
902 0         0 $et->OverrideFileType('MKA');
903             } elsif ($trackTypes{0x11}) { # subtitle?
904 0         0 $et->OverrideFileType('MKS');
905             }
906             }
907 1         6 return 1;
908             }
909              
910             1; # end
911              
912             __END__