File Coverage

blib/lib/Image/ExifTool/Matroska.pm
Criterion Covered Total %
statement 103 148 69.5
branch 42 92 45.6
condition 23 45 51.1
subroutine 5 5 100.0
pod 0 2 0.0
total 173 292 59.2


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