File Coverage

blib/lib/Image/ExifTool/RIFF.pm
Criterion Covered Total %
statement 123 316 38.9
branch 54 182 29.6
condition 23 84 27.3
subroutine 7 13 53.8
pod 0 10 0.0
total 207 605 34.2


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: RIFF.pm
3             #
4             # Description: Read RIFF/AVI/WAV meta information
5             #
6             # Revisions: 09/14/2005 - P. Harvey Created
7             # 06/28/2017 - PH Added MBWF/RF64 support
8             #
9             # References: 1) http://www.exif.org/Exif2-2.PDF
10             # 2) http://www.vlsi.fi/datasheets/vs1011.pdf
11             # 3) http://www.music-center.com.br/spec_rif.htm
12             # 4) http://www.codeproject.com/audio/wavefiles.asp
13             # 5) http://msdn.microsoft.com/archive/en-us/directx9_c/directx/htm/avirifffilereference.asp
14             # 6) http://research.microsoft.com/invisible/tests/riff.h.htm
15             # 7) http://www.onicos.com/staff/iz/formats/wav.html
16             # 8) http://graphics.cs.uni-sb.de/NMM/dist-0.9.1/Docs/Doxygen/html/mmreg_8h-source.html
17             # 9) http://developers.videolan.org/vlc/vlc/doc/doxygen/html/codecs_8h-source.html
18             # 10) http://wiki.multimedia.cx/index.php?title=TwoCC
19             # 11) Andreas Winter (SCLive) private communication
20             # 12) http://abcavi.kibi.ru/infotags.htm
21             # 13) http://tech.ebu.ch/docs/tech/tech3285.pdf
22             # 14) https://developers.google.com/speed/webp/docs/riff_container
23             # 15) https://tech.ebu.ch/docs/tech/tech3306-2009.pdf
24             # 16) https://sites.google.com/site/musicgapi/technical-documents/wav-file-format
25             #------------------------------------------------------------------------------
26              
27             package Image::ExifTool::RIFF;
28              
29 9     9   3614 use strict;
  9         20  
  9         258  
30 9     9   45 use vars qw($VERSION);
  9         21  
  9         315  
31 9     9   42 use Image::ExifTool qw(:DataAccess :Utils);
  9         16  
  9         50464  
32              
33             $VERSION = '1.59';
34              
35             sub ConvertTimecode($);
36             sub ProcessSGLT($$$);
37             sub ProcessSLLT($$$);
38             sub ProcessLucas($$$);
39              
40             # recognized RIFF variants
41             my %riffType = (
42             'WAVE' => 'WAV', 'AVI ' => 'AVI', 'WEBP' => 'WEBP',
43             'LA02' => 'LA', 'LA03' => 'LA', 'LA04' => 'LA',
44             'OFR ' => 'OFR', 'LPAC' => 'PAC', 'wvpk' => 'WV',
45             );
46              
47             # MIME types of recognized RIFF-format files
48             my %riffMimeType = (
49             WAV => 'audio/x-wav',
50             AVI => 'video/x-msvideo',
51             WEBP => 'image/webp',
52             LA => 'audio/x-nspaudio',
53             OFR => 'audio/x-ofr',
54             PAC => 'audio/x-lpac',
55             WV => 'audio/x-wavpack',
56             );
57              
58             # character sets for recognized Windows code pages
59             my %code2charset = (
60             0 => 'Latin',
61             65001 => 'UTF8',
62             1252 => 'Latin',
63             1250 => 'Latin2',
64             1251 => 'Cyrillic',
65             1253 => 'Greek',
66             1254 => 'Turkish',
67             1255 => 'Hebrew',
68             1256 => 'Arabic',
69             1257 => 'Baltic',
70             1258 => 'Vietnam',
71             874 => 'Thai',
72             10000 => 'MacRoman',
73             10029 => 'MacLatin2',
74             10007 => 'MacCyrillic',
75             10006 => 'MacGreek',
76             10081 => 'MacTurkish',
77             10010 => 'MacRomanian',
78             10079 => 'MacIceland',
79             10082 => 'MacCroatian',
80             );
81              
82             %Image::ExifTool::RIFF::audioEncoding = ( #2
83             Notes => 'These "TwoCC" audio encoding codes are used in RIFF and ASF files.',
84             0x01 => 'Microsoft PCM',
85             0x02 => 'Microsoft ADPCM',
86             0x03 => 'Microsoft IEEE float',
87             0x04 => 'Compaq VSELP', #4
88             0x05 => 'IBM CVSD', #4
89             0x06 => 'Microsoft a-Law',
90             0x07 => 'Microsoft u-Law',
91             0x08 => 'Microsoft DTS', #4
92             0x09 => 'DRM', #4
93             0x0a => 'WMA 9 Speech', #9
94             0x0b => 'Microsoft Windows Media RT Voice', #10
95             0x10 => 'OKI-ADPCM',
96             0x11 => 'Intel IMA/DVI-ADPCM',
97             0x12 => 'Videologic Mediaspace ADPCM', #4
98             0x13 => 'Sierra ADPCM', #4
99             0x14 => 'Antex G.723 ADPCM', #4
100             0x15 => 'DSP Solutions DIGISTD',
101             0x16 => 'DSP Solutions DIGIFIX',
102             0x17 => 'Dialoic OKI ADPCM', #6
103             0x18 => 'Media Vision ADPCM', #6
104             0x19 => 'HP CU', #7
105             0x1a => 'HP Dynamic Voice', #10
106             0x20 => 'Yamaha ADPCM', #6
107             0x21 => 'SONARC Speech Compression', #6
108             0x22 => 'DSP Group True Speech', #6
109             0x23 => 'Echo Speech Corp.', #6
110             0x24 => 'Virtual Music Audiofile AF36', #6
111             0x25 => 'Audio Processing Tech.', #6
112             0x26 => 'Virtual Music Audiofile AF10', #6
113             0x27 => 'Aculab Prosody 1612', #7
114             0x28 => 'Merging Tech. LRC', #7
115             0x30 => 'Dolby AC2',
116             0x31 => 'Microsoft GSM610',
117             0x32 => 'MSN Audio', #6
118             0x33 => 'Antex ADPCME', #6
119             0x34 => 'Control Resources VQLPC', #6
120             0x35 => 'DSP Solutions DIGIREAL', #6
121             0x36 => 'DSP Solutions DIGIADPCM', #6
122             0x37 => 'Control Resources CR10', #6
123             0x38 => 'Natural MicroSystems VBX ADPCM', #6
124             0x39 => 'Crystal Semiconductor IMA ADPCM', #6
125             0x3a => 'Echo Speech ECHOSC3', #6
126             0x3b => 'Rockwell ADPCM',
127             0x3c => 'Rockwell DIGITALK',
128             0x3d => 'Xebec Multimedia', #6
129             0x40 => 'Antex G.721 ADPCM',
130             0x41 => 'Antex G.728 CELP',
131             0x42 => 'Microsoft MSG723', #7
132             0x43 => 'IBM AVC ADPCM', #10
133             0x45 => 'ITU-T G.726', #9
134             0x50 => 'Microsoft MPEG',
135             0x51 => 'RT23 or PAC', #7
136             0x52 => 'InSoft RT24', #4
137             0x53 => 'InSoft PAC', #4
138             0x55 => 'MP3',
139             0x59 => 'Cirrus', #7
140             0x60 => 'Cirrus Logic', #6
141             0x61 => 'ESS Tech. PCM', #6
142             0x62 => 'Voxware Inc.', #6
143             0x63 => 'Canopus ATRAC', #6
144             0x64 => 'APICOM G.726 ADPCM',
145             0x65 => 'APICOM G.722 ADPCM',
146             0x66 => 'Microsoft DSAT', #6
147             0x67 => 'Microsoft DSAT DISPLAY', #6
148             0x69 => 'Voxware Byte Aligned', #7
149             0x70 => 'Voxware AC8', #7
150             0x71 => 'Voxware AC10', #7
151             0x72 => 'Voxware AC16', #7
152             0x73 => 'Voxware AC20', #7
153             0x74 => 'Voxware MetaVoice', #7
154             0x75 => 'Voxware MetaSound', #7
155             0x76 => 'Voxware RT29HW', #7
156             0x77 => 'Voxware VR12', #7
157             0x78 => 'Voxware VR18', #7
158             0x79 => 'Voxware TQ40', #7
159             0x7a => 'Voxware SC3', #10
160             0x7b => 'Voxware SC3', #10
161             0x80 => 'Soundsoft', #6
162             0x81 => 'Voxware TQ60', #7
163             0x82 => 'Microsoft MSRT24', #7
164             0x83 => 'AT&T G.729A', #7
165             0x84 => 'Motion Pixels MVI MV12', #7
166             0x85 => 'DataFusion G.726', #7
167             0x86 => 'DataFusion GSM610', #7
168             0x88 => 'Iterated Systems Audio', #7
169             0x89 => 'Onlive', #7
170             0x8a => 'Multitude, Inc. FT SX20', #10
171             0x8b => 'Infocom ITS A/S G.721 ADPCM', #10
172             0x8c => 'Convedia G729', #10
173             0x8d => 'Not specified congruency, Inc.', #10
174             0x91 => 'Siemens SBC24', #7
175             0x92 => 'Sonic Foundry Dolby AC3 APDIF', #7
176             0x93 => 'MediaSonic G.723', #8
177             0x94 => 'Aculab Prosody 8kbps', #8
178             0x97 => 'ZyXEL ADPCM', #7,
179             0x98 => 'Philips LPCBB', #7
180             0x99 => 'Studer Professional Audio Packed', #7
181             0xa0 => 'Malden PhonyTalk', #8
182             0xa1 => 'Racal Recorder GSM', #10
183             0xa2 => 'Racal Recorder G720.a', #10
184             0xa3 => 'Racal G723.1', #10
185             0xa4 => 'Racal Tetra ACELP', #10
186             0xb0 => 'NEC AAC NEC Corporation', #10
187             0xff => 'AAC', #10
188             0x100 => 'Rhetorex ADPCM', #6
189             0x101 => 'IBM u-Law', #3
190             0x102 => 'IBM a-Law', #3
191             0x103 => 'IBM ADPCM', #3
192             0x111 => 'Vivo G.723', #7
193             0x112 => 'Vivo Siren', #7
194             0x120 => 'Philips Speech Processing CELP', #10
195             0x121 => 'Philips Speech Processing GRUNDIG', #10
196             0x123 => 'Digital G.723', #7
197             0x125 => 'Sanyo LD ADPCM', #8
198             0x130 => 'Sipro Lab ACEPLNET', #8
199             0x131 => 'Sipro Lab ACELP4800', #8
200             0x132 => 'Sipro Lab ACELP8V3', #8
201             0x133 => 'Sipro Lab G.729', #8
202             0x134 => 'Sipro Lab G.729A', #8
203             0x135 => 'Sipro Lab Kelvin', #8
204             0x136 => 'VoiceAge AMR', #10
205             0x140 => 'Dictaphone G.726 ADPCM', #8
206             0x150 => 'Qualcomm PureVoice', #8
207             0x151 => 'Qualcomm HalfRate', #8
208             0x155 => 'Ring Zero Systems TUBGSM', #8
209             0x160 => 'Microsoft Audio1', #8
210             0x161 => 'Windows Media Audio V2 V7 V8 V9 / DivX audio (WMA) / Alex AC3 Audio', #10
211             0x162 => 'Windows Media Audio Professional V9', #10
212             0x163 => 'Windows Media Audio Lossless V9', #10
213             0x164 => 'WMA Pro over S/PDIF', #10
214             0x170 => 'UNISYS NAP ADPCM', #10
215             0x171 => 'UNISYS NAP ULAW', #10
216             0x172 => 'UNISYS NAP ALAW', #10
217             0x173 => 'UNISYS NAP 16K', #10
218             0x174 => 'MM SYCOM ACM SYC008 SyCom Technologies', #10
219             0x175 => 'MM SYCOM ACM SYC701 G726L SyCom Technologies', #10
220             0x176 => 'MM SYCOM ACM SYC701 CELP54 SyCom Technologies', #10
221             0x177 => 'MM SYCOM ACM SYC701 CELP68 SyCom Technologies', #10
222             0x178 => 'Knowledge Adventure ADPCM', #10
223             0x180 => 'Fraunhofer IIS MPEG2AAC', #10
224             0x190 => 'Digital Theater Systems DTS DS', #10
225             0x200 => 'Creative Labs ADPCM', #6
226             0x202 => 'Creative Labs FASTSPEECH8', #6
227             0x203 => 'Creative Labs FASTSPEECH10', #6
228             0x210 => 'UHER ADPCM', #8
229             0x215 => 'Ulead DV ACM', #10
230             0x216 => 'Ulead DV ACM', #10
231             0x220 => 'Quarterdeck Corp.', #6
232             0x230 => 'I-Link VC', #8
233             0x240 => 'Aureal Semiconductor Raw Sport', #8
234             0x241 => 'ESST AC3', #10
235             0x250 => 'Interactive Products HSX', #8
236             0x251 => 'Interactive Products RPELP', #8
237             0x260 => 'Consistent CS2', #8
238             0x270 => 'Sony SCX', #8
239             0x271 => 'Sony SCY', #10
240             0x272 => 'Sony ATRAC3', #10
241             0x273 => 'Sony SPC', #10
242             0x280 => 'TELUM Telum Inc.', #10
243             0x281 => 'TELUMIA Telum Inc.', #10
244             0x285 => 'Norcom Voice Systems ADPCM', #10
245             0x300 => 'Fujitsu FM TOWNS SND', #6
246             0x301 => 'Fujitsu (not specified)', #10
247             0x302 => 'Fujitsu (not specified)', #10
248             0x303 => 'Fujitsu (not specified)', #10
249             0x304 => 'Fujitsu (not specified)', #10
250             0x305 => 'Fujitsu (not specified)', #10
251             0x306 => 'Fujitsu (not specified)', #10
252             0x307 => 'Fujitsu (not specified)', #10
253             0x308 => 'Fujitsu (not specified)', #10
254             0x350 => 'Micronas Semiconductors, Inc. Development', #10
255             0x351 => 'Micronas Semiconductors, Inc. CELP833', #10
256             0x400 => 'Brooktree Digital', #6
257             0x401 => 'Intel Music Coder (IMC)', #10
258             0x402 => 'Ligos Indeo Audio', #10
259             0x450 => 'QDesign Music', #8
260             0x500 => 'On2 VP7 On2 Technologies', #10
261             0x501 => 'On2 VP6 On2 Technologies', #10
262             0x680 => 'AT&T VME VMPCM', #7
263             0x681 => 'AT&T TCP', #8
264             0x700 => 'YMPEG Alpha (dummy for MPEG-2 compressor)', #10
265             0x8ae => 'ClearJump LiteWave (lossless)', #10
266             0x1000 => 'Olivetti GSM', #6
267             0x1001 => 'Olivetti ADPCM', #6
268             0x1002 => 'Olivetti CELP', #6
269             0x1003 => 'Olivetti SBC', #6
270             0x1004 => 'Olivetti OPR', #6
271             0x1100 => 'Lernout & Hauspie', #6
272             0x1101 => 'Lernout & Hauspie CELP codec', #10
273             0x1102 => 'Lernout & Hauspie SBC codec', #10
274             0x1103 => 'Lernout & Hauspie SBC codec', #10
275             0x1104 => 'Lernout & Hauspie SBC codec', #10
276             0x1400 => 'Norris Comm. Inc.', #6
277             0x1401 => 'ISIAudio', #7
278             0x1500 => 'AT&T Soundspace Music Compression', #7
279             0x181c => 'VoxWare RT24 speech codec', #10
280             0x181e => 'Lucent elemedia AX24000P Music codec', #10
281             0x1971 => 'Sonic Foundry LOSSLESS', #10
282             0x1979 => 'Innings Telecom Inc. ADPCM', #10
283             0x1c07 => 'Lucent SX8300P speech codec', #10
284             0x1c0c => 'Lucent SX5363S G.723 compliant codec', #10
285             0x1f03 => 'CUseeMe DigiTalk (ex-Rocwell)', #10
286             0x1fc4 => 'NCT Soft ALF2CD ACM', #10
287             0x2000 => 'FAST Multimedia DVM', #7
288             0x2001 => 'Dolby DTS (Digital Theater System)', #10
289             0x2002 => 'RealAudio 1 / 2 14.4', #10
290             0x2003 => 'RealAudio 1 / 2 28.8', #10
291             0x2004 => 'RealAudio G2 / 8 Cook (low bitrate)', #10
292             0x2005 => 'RealAudio 3 / 4 / 5 Music (DNET)', #10
293             0x2006 => 'RealAudio 10 AAC (RAAC)', #10
294             0x2007 => 'RealAudio 10 AAC+ (RACP)', #10
295             0x2500 => 'Reserved range to 0x2600 Microsoft', #10
296             0x3313 => 'makeAVIS (ffvfw fake AVI sound from AviSynth scripts)', #10
297             0x4143 => 'Divio MPEG-4 AAC audio', #10
298             0x4201 => 'Nokia adaptive multirate', #10
299             0x4243 => 'Divio G726 Divio, Inc.', #10
300             0x434c => 'LEAD Speech', #10
301             0x564c => 'LEAD Vorbis', #10
302             0x5756 => 'WavPack Audio', #10
303             0x674f => 'Ogg Vorbis (mode 1)', #10
304             0x6750 => 'Ogg Vorbis (mode 2)', #10
305             0x6751 => 'Ogg Vorbis (mode 3)', #10
306             0x676f => 'Ogg Vorbis (mode 1+)', #10
307             0x6770 => 'Ogg Vorbis (mode 2+)', #10
308             0x6771 => 'Ogg Vorbis (mode 3+)', #10
309             0x7000 => '3COM NBX 3Com Corporation', #10
310             0x706d => 'FAAD AAC', #10
311             0x7a21 => 'GSM-AMR (CBR, no SID)', #10
312             0x7a22 => 'GSM-AMR (VBR, including SID)', #10
313             0xa100 => 'Comverse Infosys Ltd. G723 1', #10
314             0xa101 => 'Comverse Infosys Ltd. AVQSBC', #10
315             0xa102 => 'Comverse Infosys Ltd. OLDSBC', #10
316             0xa103 => 'Symbol Technologies G729A', #10
317             0xa104 => 'VoiceAge AMR WB VoiceAge Corporation', #10
318             0xa105 => 'Ingenient Technologies Inc. G726', #10
319             0xa106 => 'ISO/MPEG-4 advanced audio Coding', #10
320             0xa107 => 'Encore Software Ltd G726', #10
321             0xa109 => 'Speex ACM Codec xiph.org', #10
322             0xdfac => 'DebugMode SonicFoundry Vegas FrameServer ACM Codec', #10
323             0xe708 => 'Unknown -', #10
324             0xf1ac => 'Free Lossless Audio Codec FLAC', #10
325             0xfffe => 'Extensible', #7
326             0xffff => 'Development', #4
327             );
328              
329             # RIFF info
330             %Image::ExifTool::RIFF::Main = (
331             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
332             NOTES => q{
333             The RIFF container format is used various types of fines including AVI, WAV,
334             WEBP, LA, OFR, PAC and WV. According to the EXIF specification, Meta
335             information is embedded in two types of RIFF C chunks: C and
336             C, and information about the audio content is stored in the C
337             chunk. As well as this information, some video information and proprietary
338             manufacturer-specific information is also extracted.
339              
340             Large AVI videos may be a concatenation of two or more RIFF chunks. For
341             these files, information is extracted from subsequent RIFF chunks as
342             sub-documents, but the Duration is calculated for the full video.
343             },
344             # (not 100% sure that the concatenation technique mentioned above is valid - PH)
345             'fmt ' => {
346             Name => 'AudioFormat',
347             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AudioFormat' },
348             },
349             'bext' => {
350             Name => 'BroadcastExtension',
351             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::BroadcastExt' },
352             },
353             ds64 => { #15
354             Name => 'DataSize64',
355             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::DS64' },
356             },
357             list => 'ListType', #15
358             labl => { #16 (in 'adtl' chunk)
359             Name => 'CuePointLabel',
360             Priority => 0, # (so they are stored in sequence)
361             ValueConv => 'my $str=substr($val,4); $str=~s/\0+$//; unpack("V",$val) . " " . $str',
362             },
363             note => { #16 (in 'adtl' chunk)
364             Name => 'CuePointNote',
365             Priority => 0, # (so they are stored in sequence)
366             ValueConv => 'my $str=substr($val,4); $str=~s/\0+$//; unpack("V",$val) . " " . $str',
367             },
368             ltxt => { #16 (in 'adtl' chunk)
369             Name => 'LabeledText',
370             Notes => 'CuePointID Length Purpose Country Language Dialect Codepage Text',
371             Priority => 0, # (so they are stored in sequence)
372             ValueConv => q{
373             my @a = unpack('VVa4vvvv', $val);
374             $a[2] = "'$a[2]'";
375             my $txt = substr($val, 18);
376             $txt =~ s/\0+$//; # remove null terminator
377             return join(' ', @a, $txt);
378             },
379             },
380             smpl => { #16
381             Name => 'Sampler',
382             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Sampler' },
383             },
384             inst => { #16
385             Name => 'Instrument',
386             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Instrument' },
387             },
388             LIST_INFO => {
389             Name => 'Info',
390             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Info' },
391             },
392             LIST_exif => {
393             Name => 'Exif',
394             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Exif' },
395             },
396             LIST_hdrl => { # AVI header LIST chunk
397             Name => 'Hdrl',
398             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Hdrl' },
399             },
400             LIST_Tdat => { #PH (Adobe CS3 Bridge)
401             Name => 'Tdat',
402             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Tdat' },
403             },
404             LIST_ncdt => { #PH (Nikon metadata)
405             Name => 'NikonData',
406             SubDirectory => {
407             TagTable => 'Image::ExifTool::Nikon::AVI',
408             # define ProcessProc here so we don't need to load RIFF.pm from Nikon.pm
409             ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks,
410             },
411             },
412             LIST_hydt => { #PH (Pentax metadata)
413             Name => 'PentaxData',
414             SubDirectory => {
415             TagTable => 'Image::ExifTool::Pentax::AVI',
416             ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks,
417             },
418             },
419             LIST_pntx => { #Andras Salamon (Q-S1 AVI)
420             Name => 'PentaxData2',
421             SubDirectory => {
422             TagTable => 'Image::ExifTool::Pentax::AVI',
423             ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks,
424             },
425             },
426             LIST_adtl => { #PH (ref 16, forum12387)
427             Name => 'AssociatedDataList',
428             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Main' },
429             },
430             # seen LIST_JUNK
431             JUNK => [
432             {
433             Name => 'OlympusJunk',
434             Condition => '$$valPt =~ /^OLYMDigital Camera/',
435             SubDirectory => { TagTable => 'Image::ExifTool::Olympus::AVI' },
436             },
437             {
438             Name => 'CasioJunk',
439             Condition => '$$valPt =~ /^QVMI/',
440             # Casio stores standard EXIF-format information in AVI videos (EX-S600)
441             SubDirectory => {
442             TagTable => 'Image::ExifTool::Exif::Main',
443             DirName => 'IFD0',
444             Multi => 0, # (IFD1 is not written)
445             Start => 10,
446             ByteOrder => 'BigEndian',
447             },
448             },
449             {
450             Name => 'RicohJunk',
451             # the Ricoh Caplio GX stores sub-chunks in here
452             Condition => '$$valPt =~ /^ucmt/',
453             SubDirectory => {
454             TagTable => 'Image::ExifTool::Ricoh::AVI',
455             ProcessProc => \&Image::ExifTool::RIFF::ProcessChunks,
456             },
457             },
458             {
459             Name => 'PentaxJunk', # (Optio RS1000)
460             Condition => '$$valPt =~ /^IIII\x01\0/',
461             SubDirectory => { TagTable => 'Image::ExifTool::Pentax::Junk' },
462             },
463             {
464             Name => 'PentaxJunk2', # (Optio RZ18)
465             Condition => '$$valPt =~ /^PENTDigital Camera/',
466             SubDirectory => { TagTable => 'Image::ExifTool::Pentax::Junk2' },
467             },
468             {
469             Name => 'LucasJunk', # (Lucas LK-7900 Ace)
470             Condition => '$$valPt =~ /^0G(DA|PS)/',
471             SubDirectory => {
472             TagTable => 'Image::ExifTool::QuickTime::Stream',
473             ProcessProc => \&ProcessLucas,
474             },
475             },
476             {
477             Name => 'TextJunk',
478             # try to interpret unknown junk as an ASCII string
479             RawConv => '$val =~ /^([^\0-\x1f\x7f-\xff]+)\0*$/ ? $1 : undef',
480             }
481             ],
482             _PMX => { #PH (Adobe CS3 Bridge)
483             Name => 'XMP',
484             Notes => 'AVI and WAV files',
485             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
486             },
487             JUNQ => { #PH (Adobe CS3 Bridge)
488             # old XMP is preserved when metadata is replaced in Bridge
489             Name => 'OldXMP',
490             Binary => 1,
491             },
492             olym => {
493             Name => 'Olym',
494             SubDirectory => { TagTable => 'Image::ExifTool::Olympus::WAV' },
495             },
496             fact => {
497             Name => 'NumberOfSamples',
498             RawConv => 'Get32u(\$val, 0)',
499             },
500             'cue '=> {
501             Name => 'CuePoints',
502             Binary => 1,
503             Notes => q{
504             config_files/cutepointlist.config from full distribution will decode this
505             and generate a list of cue points with labels
506             },
507             },
508             plst => { Name => 'Playlist', Binary => 1 }, #16
509             afsp => { },
510             IDIT => {
511             Name => 'DateTimeOriginal',
512             Description => 'Date/Time Original',
513             Groups => { 2 => 'Time' },
514             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
515             PrintConv => '$self->ConvertDateTime($val)',
516             },
517             CSET => {
518             Name => 'CharacterSet',
519             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::CSET' },
520             },
521             # tx_ tags are generated based on the Codec used for the txts stream
522             tx_USER => {
523             Name => 'UserText',
524             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::UserText' },
525             },
526             tx_Unknown => { # (untested)
527             Name => 'Text',
528             Notes => 'streamed text, extracted when the ExtractEmbedded option is used',
529             },
530             'id3 ' => {
531             Name => 'ID3',
532             SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' },
533             },
534             #
535             # WebP-specific tags
536             #
537             EXIF => [{ # (WebP)
538             Name => 'EXIF',
539             Condition => '$$valPt =~ /^(II\x2a\0|MM\0\x2a)/',
540             Notes => 'WebP files',
541             SubDirectory => {
542             TagTable => 'Image::ExifTool::Exif::Main',
543             ProcessProc => \&Image::ExifTool::ProcessTIFF,
544             },
545             },{ # (WebP) - have also seen with "Exif\0\0" header - PH
546             Name => 'EXIF',
547             Condition => '$$valPt =~ /^Exif\0\0(II\x2a\0|MM\0\x2a)/',
548             SubDirectory => {
549             TagTable => 'Image::ExifTool::Exif::Main',
550             ProcessProc => \&Image::ExifTool::ProcessTIFF,
551             Start => 6,
552             },
553             },{
554             Name => 'UnknownEXIF',
555             Binary => 1,
556             }],
557             'XMP ' => { #14 (WebP)
558             Name => 'XMP',
559             Notes => 'WebP files',
560             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
561             },
562             ICCP => { #14 (WebP)
563             Name => 'ICC_Profile',
564             Notes => 'WebP files',
565             SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
566             },
567             'VP8 ' => { # (WebP lossy)
568             Name => 'VP8Bitstream',
569             Condition => '$$valPt =~ /^...\x9d\x01\x2a/s',
570             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8' },
571             },
572             VP8L => { #14 (WebP lossless)
573             Name => 'VP8L',
574             Condition => '$$valPt =~ /^\x2f/',
575             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8L' },
576             },
577             VP8X => { #14 (WebP extended)
578             Name => 'VP8X',
579             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8X' },
580             },
581             ANIM => { #14 (WebP animation)
582             Name => 'ANIM',
583             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANIM' },
584             },
585             ANMF => { #14 (WebP animation frame)
586             Name => 'ANMF',
587             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANMF' },
588             },
589             ALPH => { #14 (WebP alpha)
590             Name => 'ALPH',
591             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ALPH' },
592             },
593             SGLT => { #PH (BikeBro)
594             Name => 'BikeBroAccel',
595             SubDirectory => {
596             TagTable => 'Image::ExifTool::QuickTime::Stream',
597             ProcessProc => \&ProcessSGLT,
598             },
599             },
600             SLLT => { #PH (BikeBro)
601             Name => 'BikeBroGPS',
602             SubDirectory => {
603             TagTable => 'Image::ExifTool::QuickTime::Stream',
604             ProcessProc => \&ProcessSLLT,
605             },
606             },
607             iXML => { #PH
608             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
609             },
610             aXML => { #PH
611             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
612             },
613             #
614             # tags found in an AlphaImagingTech AVI video - PH
615             #
616             LIST_INF0 => { # ('0' instead of 'O' -- odd)
617             Name => 'Info',
618             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Info' },
619             },
620             gps0 => {
621             Name => 'GPSTrack',
622             SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group)
623             SubDirectory => {
624             TagTable => 'Image::ExifTool::QuickTime::Stream',
625             # (don't use code ref here or get "Prototype mismatch" warning with some Perl versions)
626             ProcessProc => 'Image::ExifTool::QuickTime::Process_gps0',
627             },
628             },
629             gsen => {
630             Name => 'GSensor',
631             SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group)
632             SubDirectory => {
633             TagTable => 'Image::ExifTool::QuickTime::Stream',
634             ProcessProc => 'Image::ExifTool::QuickTime::Process_gsen',
635             },
636             },
637             # gpsa - seen hex "01 20 00 00", same as QuickTime
638             # gsea - 16 bytes hex "04 08 02 00 20 02 00 00 1f 03 00 00 01 00 00 00"
639             );
640              
641             # the maker notes used by some digital cameras
642             %Image::ExifTool::RIFF::Junk = (
643             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
644             GROUPS => { 2 => 'Audio' },
645             );
646              
647             # Format and Audio Stream Format chunk data
648             %Image::ExifTool::RIFF::AudioFormat = (
649             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
650             GROUPS => { 2 => 'Audio' },
651             FORMAT => 'int16u',
652             0 => {
653             Name => 'Encoding',
654             PrintHex => 1,
655             PrintConv => \%Image::ExifTool::RIFF::audioEncoding,
656             SeparateTable => 'AudioEncoding',
657             },
658             1 => 'NumChannels',
659             2 => {
660             Name => 'SampleRate',
661             Format => 'int32u',
662             },
663             4 => {
664             Name => 'AvgBytesPerSec',
665             Format => 'int32u',
666             },
667             # uninteresting
668             # 6 => 'BlockAlignment',
669             7 => 'BitsPerSample',
670             );
671              
672             # Broadcast Audio Extension 'bext' information (ref 13)
673             %Image::ExifTool::RIFF::BroadcastExt = (
674             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
675             GROUPS => { 2 => 'Audio' },
676             NOTES => q{
677             Information found in the Broadcast Audio Extension chunk (see
678             L).
679             },
680             0 => {
681             Name => 'Description',
682             Format => 'string[256]',
683             },
684             256 => {
685             Name => 'Originator',
686             Format => 'string[32]',
687             },
688             288 => {
689             Name => 'OriginatorReference',
690             Format => 'string[32]',
691             },
692             320 => {
693             Name => 'DateTimeOriginal',
694             Description => 'Date/Time Original',
695             Groups => { 2 => 'Time' },
696             Format => 'string[18]',
697             ValueConv => '$_=$val; tr/-/:/; s/^(\d{4}:\d{2}:\d{2})/$1 /; $_',
698             PrintConv => '$self->ConvertDateTime($val)',
699             },
700             338 => {
701             Name => 'TimeReference',
702             Notes => 'first sample count since midnight',
703             Format => 'int32u[2]',
704             ValueConv => 'my @v=split(" ",$val); $v[0] + $v[1] * 4294967296',
705             },
706             346 => {
707             Name => 'BWFVersion',
708             Format => 'int16u',
709             },
710             348 => {
711             Name => 'BWF_UMID',
712             Format => 'undef[64]',
713             ValueConv => '$_=unpack("H*",$val); s/0{64}$//; uc $_',
714             },
715             # 412 - int8u[190] - reserved
716             602 => {
717             Name => 'CodingHistory',
718             Format => 'string[$size-602]',
719             },
720             );
721              
722             # 64-bit chunk sizes (ref 15)
723             %Image::ExifTool::RIFF::DS64 = (
724             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
725             GROUPS => { 2 => 'Audio' },
726             FORMAT => 'int64u',
727             NOTES => q{
728             64-bit data sizes for MBWF/RF64 files. See
729             L for the specification.
730             },
731             0 => {
732             Name => 'RIFFSize64',
733             PrintConv => \&Image::ExifTool::ConvertFileSize,
734             },
735             1 => {
736             Name => 'DataSize64',
737             DataMember => 'DataSize64',
738             RawConv => '$$self{DataSize64} = $val',
739             PrintConv => \&Image::ExifTool::ConvertFileSize,
740             },
741             2 => 'NumberOfSamples64',
742             # (after this comes a table of size overrides for chunk
743             # types other than 'data', but since these are currently
744             # very unlikely, support for these is not yet implemented)
745             );
746              
747             # Sampler chunk (ref 16)
748             %Image::ExifTool::RIFF::Sampler = (
749             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
750             GROUPS => { 2 => 'Audio' },
751             FORMAT => 'int32u',
752             0 => 'Manufacturer',
753             1 => 'Product',
754             2 => 'SamplePeriod',
755             3 => 'MIDIUnityNote',
756             4 => 'MIDIPitchFraction',
757             5 => {
758             Name => 'SMPTEFormat',
759             PrintConv => {
760             0 => 'none',
761             24 => '24 fps',
762             25 => '25 fps',
763             29 => '29 fps',
764             30 => '30 fps',
765             },
766             },
767             6 => {
768             Name => 'SMPTEOffset',
769             Notes => 'HH:MM:SS:FF',
770             ValueConv => q{
771             my $str = sprintf('%.8x', $val);
772             $str =~ s/(..)(..)(..)(..)/$1:$2:$3:$4/;
773             return $str;
774             },
775             },
776             7 => 'NumSampleLoops',
777             8 => 'SamplerDataLen',
778             9 => { Name => 'SamplerData', Format => 'undef[$size-40]', Binary => 1 },
779             );
780              
781             # Instrument chunk (ref 16)
782             %Image::ExifTool::RIFF::Instrument = (
783             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
784             GROUPS => { 2 => 'Audio' },
785             FORMAT => 'int8s',
786             0 => 'UnshiftedNote',
787             1 => 'FineTune',
788             2 => 'Gain',
789             3 => 'LowNote',
790             4 => 'HighNote',
791             5 => 'LowVelocity',
792             6 => 'HighVelocity',
793             );
794              
795             # Sub chunks of INFO LIST chunk
796             %Image::ExifTool::RIFF::Info = (
797             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
798             GROUPS => { 2 => 'Audio' },
799             FORMAT => 'string',
800             NOTES => q{
801             RIFF INFO tags found in AVI video and WAV audio files. Tags which are part
802             of the EXIF 2.3 specification have an underlined Tag Name in the HTML
803             version of this documentation. Other tags are found in AVI files generated
804             by some software.
805             },
806             IARL => 'ArchivalLocation',
807             IART => { Name => 'Artist', Groups => { 2 => 'Author' } },
808             ICMS => 'Commissioned',
809             ICMT => 'Comment',
810             ICOP => { Name => 'Copyright', Groups => { 2 => 'Author' } },
811             ICRD => {
812             Name => 'DateCreated',
813             Groups => { 2 => 'Time' },
814             ValueConv => '$_=$val; s/-/:/g; $_',
815             },
816             ICRP => 'Cropped',
817             IDIM => 'Dimensions',
818             IDPI => 'DotsPerInch',
819             IENG => 'Engineer',
820             IGNR => 'Genre',
821             IKEY => 'Keywords',
822             ILGT => 'Lightness',
823             IMED => 'Medium',
824             INAM => 'Title',
825             ITRK => 'TrackNumber',
826             IPLT => 'NumColors',
827             IPRD => 'Product',
828             ISBJ => 'Subject',
829             ISFT => {
830             Name => 'Software',
831             # remove trailing nulls/spaces and split at first null
832             # (Casio writes "CASIO" in unicode after the first null)
833             ValueConv => '$_=$val; s/(\s*\0)+$//; s/(\s*\0)/, /; s/\0+//g; $_',
834             },
835             ISHP => 'Sharpness',
836             ISRC => 'Source',
837             ISRF => 'SourceForm',
838             ITCH => 'Technician',
839             #
840             # 3rd party tags
841             #
842             # internet movie database (ref 12)
843             ISGN => 'SecondaryGenre',
844             IWRI => 'WrittenBy',
845             IPRO => 'ProducedBy',
846             ICNM => 'Cinematographer',
847             IPDS => 'ProductionDesigner',
848             IEDT => 'EditedBy',
849             ICDS => 'CostumeDesigner',
850             IMUS => 'MusicBy',
851             ISTD => 'ProductionStudio',
852             IDST => 'DistributedBy',
853             ICNT => 'Country',
854             ILNG => 'Language',
855             IRTD => 'Rating',
856             ISTR => 'Starring',
857             # MovieID (ref12)
858             TITL => 'Title',
859             DIRC => 'Directory',
860             YEAR => 'Year',
861             GENR => 'Genre',
862             COMM => 'Comments',
863             LANG => 'Language',
864             AGES => 'Rated',
865             STAR => 'Starring',
866             CODE => 'EncodedBy',
867             PRT1 => 'Part',
868             PRT2 => 'NumberOfParts',
869             # Morgan Multimedia INFO tags (ref 12)
870             IAS1 => 'FirstLanguage',
871             IAS2 => 'SecondLanguage',
872             IAS3 => 'ThirdLanguage',
873             IAS4 => 'FourthLanguage',
874             IAS5 => 'FifthLanguage',
875             IAS6 => 'SixthLanguage',
876             IAS7 => 'SeventhLanguage',
877             IAS8 => 'EighthLanguage',
878             IAS9 => 'NinthLanguage',
879             ICAS => 'DefaultAudioStream',
880             IBSU => 'BaseURL',
881             ILGU => 'LogoURL',
882             ILIU => 'LogoIconURL',
883             IWMU => 'WatermarkURL',
884             IMIU => 'MoreInfoURL',
885             IMBI => 'MoreInfoBannerImage',
886             IMBU => 'MoreInfoBannerURL',
887             IMIT => 'MoreInfoText',
888             # GSpot INFO tags (ref 12)
889             IENC => 'EncodedBy',
890             IRIP => 'RippedBy',
891             # Sound Forge Pro tags
892             DISP => 'SoundSchemeTitle',
893             TLEN => { Name => 'Length', ValueConv => '$val/1000', PrintConv => '"$val s"' },
894             TRCK => 'TrackNumber',
895             TURL => 'URL',
896             TVER => 'Version',
897             LOCA => 'Location',
898             TORG => 'Organization',
899             # Sony Vegas AVI tags, also used by SCLive and Adobe Premier (ref 11)
900             TAPE => {
901             Name => 'TapeName',
902             Groups => { 2 => 'Video' },
903             },
904             TCOD => {
905             Name => 'StartTimecode',
906             # this is the tape time code for the start of the video
907             Groups => { 2 => 'Video' },
908             ValueConv => '$val * 1e-7',
909             PrintConv => \&ConvertTimecode,
910             },
911             TCDO => {
912             Name => 'EndTimecode',
913             Groups => { 2 => 'Video' },
914             ValueConv => '$val * 1e-7',
915             PrintConv => \&ConvertTimecode,
916             },
917             VMAJ => {
918             Name => 'VegasVersionMajor',
919             Groups => { 2 => 'Video' },
920             },
921             VMIN => {
922             Name => 'VegasVersionMinor',
923             Groups => { 2 => 'Video' },
924             },
925             CMNT => {
926             Name => 'Comment',
927             Groups => { 2 => 'Video' },
928             },
929             RATE => {
930             Name => 'Rate', #? (video? units?)
931             Groups => { 2 => 'Video' },
932             },
933             STAT => {
934             Name => 'Statistics',
935             Groups => { 2 => 'Video' },
936             # ("7318 0 3.430307 1", "0 0 3500.000000 1", "7 0 3.433228 1")
937             PrintConv => [
938             '"$val frames captured"',
939             '"$val dropped"',
940             '"Data rate $val"',
941             { 0 => 'Bad', 1 => 'OK' }, # capture OK?
942             ],
943             },
944             DTIM => {
945             Name => 'DateTimeOriginal',
946             Description => 'Date/Time Original',
947             Groups => { 2 => 'Time' },
948             ValueConv => q{
949             my @v = split ' ', $val;
950             return undef unless @v == 2;
951             # the Kodak EASYSHARE Sport stores this incorrectly as a string:
952             return $val if $val =~ /^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$/;
953             # get time in seconds
954             $val = 1e-7 * ($v[0] * 4294967296 + $v[1]);
955             # shift from Jan 1, 1601 to Jan 1, 1970
956             $val -= 134774 * 24 * 3600 if $val != 0;
957             return Image::ExifTool::ConvertUnixTime($val);
958             },
959             PrintConv => '$self->ConvertDateTime($val)',
960             },
961             # not observed, but apparently part of the standard:
962             IDIT => {
963             Name => 'DateTimeOriginal',
964             Description => 'Date/Time Original',
965             Groups => { 2 => 'Time' },
966             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
967             PrintConv => '$self->ConvertDateTime($val)',
968             },
969             ISMP => 'TimeCode',
970             );
971              
972             # Sub chunks of EXIF LIST chunk
973             %Image::ExifTool::RIFF::Exif = (
974             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
975             GROUPS => { 2 => 'Audio' },
976             NOTES => 'These tags are part of the EXIF 2.3 specification for WAV audio files.',
977             ever => 'ExifVersion',
978             erel => 'RelatedImageFile',
979             etim => { Name => 'TimeCreated', Groups => { 2 => 'Time' } },
980             ecor => { Name => 'Make', Groups => { 2 => 'Camera' } },
981             emdl => { Name => 'Model', Groups => { 2 => 'Camera' }, Description => 'Camera Model Name' },
982             emnt => { Name => 'MakerNotes', Binary => 1 },
983             eucm => {
984             Name => 'UserComment',
985             PrintConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,"RIFF:UserComment")',
986             },
987             );
988              
989             # Sub chunks of hdrl LIST chunk
990             %Image::ExifTool::RIFF::Hdrl = (
991             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
992             GROUPS => { 2 => 'Image' },
993             avih => {
994             Name => 'AVIHeader',
995             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AVIHeader' },
996             },
997             IDIT => {
998             Name => 'DateTimeOriginal',
999             Description => 'Date/Time Original',
1000             Groups => { 2 => 'Time' },
1001             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
1002             PrintConv => '$self->ConvertDateTime($val)',
1003             },
1004             ISMP => 'TimeCode',
1005             LIST_strl => {
1006             Name => 'Stream',
1007             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Stream' },
1008             },
1009             LIST_odml => {
1010             Name => 'OpenDML',
1011             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::OpenDML' },
1012             },
1013             );
1014              
1015             # Sub chunks of Tdat LIST chunk (ref PH)
1016             %Image::ExifTool::RIFF::Tdat = (
1017             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1018             GROUPS => { 2 => 'Video' },
1019             # (have seen tc_O, tc_A, rn_O and rn_A)
1020             );
1021              
1022             # RIFF character set chunk
1023             %Image::ExifTool::RIFF::CSET = (
1024             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1025             GROUPS => { 2 => 'Other' },
1026             FORMAT => 'int16u',
1027             0 => {
1028             Name => 'CodePage',
1029             RawConv => '$$self{CodePage} = $val',
1030             },
1031             1 => 'CountryCode',
1032             2 => 'LanguageCode',
1033             3 => 'Dialect',
1034             );
1035              
1036             %Image::ExifTool::RIFF::AVIHeader = (
1037             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1038             GROUPS => { 2 => 'Video' },
1039             FORMAT => 'int32u',
1040             FIRST_ENTRY => 0,
1041             0 => {
1042             Name => 'FrameRate',
1043             # (must use RawConv because raw value used in Composite tag)
1044             RawConv => '$val ? 1e6 / $val : undef',
1045             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1046             },
1047             1 => {
1048             Name => 'MaxDataRate',
1049             PrintConv => 'sprintf("%.4g kB/s",$val / 1024)',
1050             },
1051             # 2 => 'PaddingGranularity',
1052             # 3 => 'Flags',
1053             4 => 'FrameCount',
1054             # 5 => 'InitialFrames',
1055             6 => 'StreamCount',
1056             # 7 => 'SuggestedBufferSize',
1057             8 => 'ImageWidth',
1058             9 => 'ImageHeight',
1059             );
1060              
1061             %Image::ExifTool::RIFF::Stream = (
1062             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1063             GROUPS => { 2 => 'Image' },
1064             strh => {
1065             Name => 'StreamHeader',
1066             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamHeader' },
1067             },
1068             strn => 'StreamName',
1069             strd => { #PH
1070             Name => 'StreamData',
1071             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamData' },
1072             },
1073             strf => [
1074             {
1075             Name => 'AudioFormat',
1076             Condition => '$$self{RIFFStreamType} eq "auds"',
1077             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AudioFormat' },
1078             },
1079             {
1080             Name => 'VideoFormat',
1081             Condition => '$$self{RIFFStreamType} eq "vids"',
1082             SubDirectory => { TagTable => 'Image::ExifTool::BMP::Main' },
1083             },
1084             {
1085             Name => 'TextFormat',
1086             Condition => '$$self{RIFFStreamType} eq "txts"',
1087             Hidden => 1,
1088             RawConv => '$self->Options("ExtractEmbedded") or $self->WarnOnce("Use ExtractEmbedded option to extract timed text",3); undef',
1089             },
1090             ],
1091             );
1092              
1093             # Open DML tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf)
1094             %Image::ExifTool::RIFF::OpenDML = (
1095             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1096             GROUPS => { 2 => 'Video' },
1097             dmlh => {
1098             Name => 'ExtendedAVIHeader',
1099             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ExtAVIHdr' },
1100             },
1101             );
1102              
1103             # Extended AVI Header tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf)
1104             %Image::ExifTool::RIFF::ExtAVIHdr = (
1105             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1106             GROUPS => { 2 => 'Video' },
1107             FORMAT => 'int32u',
1108             0 => 'TotalFrameCount',
1109             );
1110              
1111             %Image::ExifTool::RIFF::StreamHeader = (
1112             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1113             GROUPS => { 2 => 'Video' },
1114             FORMAT => 'int32u',
1115             FIRST_ENTRY => 0,
1116             PRIORITY => 0, # so we get values from the first stream
1117             0 => {
1118             Name => 'StreamType',
1119             Format => 'string[4]',
1120             RawConv => '$$self{RIFFStreamNum} = ($$self{RIFFStreamNum} || 0) + 1; $$self{RIFFStreamType} = $val',
1121             PrintConv => {
1122             auds => 'Audio',
1123             mids => 'MIDI',
1124             txts => 'Text',
1125             vids => 'Video',
1126             iavs => 'Interleaved Audio+Video',
1127             },
1128             },
1129             1 => [
1130             {
1131             Name => 'AudioCodec',
1132             Condition => '$$self{RIFFStreamType} eq "auds"',
1133             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1134             Format => 'string[4]',
1135             },
1136             {
1137             Name => 'VideoCodec',
1138             Condition => '$$self{RIFFStreamType} eq "vids"',
1139             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1140             Format => 'string[4]',
1141             },
1142             {
1143             Name => 'Codec',
1144             Format => 'string[4]',
1145             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1146             },
1147             ],
1148             # 2 => 'StreamFlags',
1149             # 3 => 'StreamPriority',
1150             # 3.5 => 'Language',
1151             # 4 => 'InitialFrames',
1152             5 => [
1153             {
1154             Name => 'AudioSampleRate',
1155             Condition => '$$self{RIFFStreamType} eq "auds"',
1156             Format => 'rational64u',
1157             ValueConv => '$val ? 1/$val : 0',
1158             PrintConv => 'int($val * 100 + 0.5) / 100',
1159             },
1160             {
1161             Name => 'VideoFrameRate',
1162             Condition => '$$self{RIFFStreamType} eq "vids"',
1163             Format => 'rational64u',
1164             # (must use RawConv because raw value used in Composite tag)
1165             RawConv => '$val ? 1/$val : undef',
1166             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1167             },
1168             {
1169             Name => 'StreamSampleRate',
1170             Format => 'rational64u',
1171             ValueConv => '$val ? 1/$val : 0',
1172             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1173             },
1174             ],
1175             # 7 => 'Start',
1176             8 => [
1177             {
1178             Name => 'AudioSampleCount',
1179             Condition => '$$self{RIFFStreamType} eq "auds"',
1180             },
1181             {
1182             Name => 'VideoFrameCount',
1183             Condition => '$$self{RIFFStreamType} eq "vids"',
1184             },
1185             {
1186             Name => 'StreamSampleCount',
1187             },
1188             ],
1189             # 9 => 'SuggestedBufferSize',
1190             10 => {
1191             Name => 'Quality',
1192             PrintConv => '$val eq 0xffffffff ? "Default" : $val',
1193             },
1194             11 => {
1195             Name => 'SampleSize',
1196             PrintConv => '$val ? "$val byte" . ($val==1 ? "" : "s") : "Variable"',
1197             },
1198             # 12 => { Name => 'Frame', Format => 'int16u[4]' },
1199             );
1200              
1201             %Image::ExifTool::RIFF::StreamData = ( #PH
1202             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessStreamData,
1203             GROUPS => { 2 => 'Video' },
1204             NOTES => q{
1205             This chunk is used to store proprietary information in AVI videos from some
1206             cameras. The first 4 characters of the data are used as the Tag ID below.
1207             },
1208             AVIF => {
1209             Name => 'AVIF',
1210             SubDirectory => {
1211             TagTable => 'Image::ExifTool::Exif::Main',
1212             DirName => 'IFD0',
1213             Start => 8,
1214             ByteOrder => 'LittleEndian',
1215             },
1216             },
1217             CASI => { # (used by Casio GV-10)
1218             Name => 'CasioData',
1219             SubDirectory => { TagTable => 'Image::ExifTool::Casio::AVI' },
1220             },
1221             Zora => 'VendorName', # (Samsung PL90 AVI files)
1222             unknown => {
1223             Name => 'UnknownData',
1224             # try to interpret unknown stream data as a string
1225             RawConv => '$_=$val; /^[^\0-\x1f\x7f-\xff]+$/ ? $_ : undef',
1226             },
1227             );
1228              
1229             # VP8 bitstream (ref http://www.rfc-editor.org/rfc/pdfrfc/rfc6386.txt.pdf)
1230             %Image::ExifTool::RIFF::VP8 = (
1231             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1232             GROUPS => { 2 => 'Image' },
1233             NOTES => q{
1234             This chunk is found in simple-format (lossy) WebP files. See
1235             L for the WebP
1236             container specification.
1237             },
1238             0 => {
1239             Name => 'VP8Version',
1240             Mask => 0x0e,
1241             PrintConv => {
1242             0 => '0 (bicubic reconstruction, normal loop)',
1243             1 => '1 (bilinear reconstruction, simple loop)',
1244             2 => '2 (bilinear reconstruction, no loop)',
1245             3 => '3 (no reconstruction, no loop)',
1246             },
1247             },
1248             6 => {
1249             Name => 'ImageWidth',
1250             Format => 'int16u',
1251             Mask => 0x3fff,
1252             },
1253             6.1 => {
1254             Name => 'HorizontalScale',
1255             Format => 'int16u',
1256             Mask => 0xc000,
1257             },
1258             8 => {
1259             Name => 'ImageHeight',
1260             Format => 'int16u',
1261             Mask => 0x3fff,
1262             },
1263             8.1 => {
1264             Name => 'VerticalScale',
1265             Format => 'int16u',
1266             Mask => 0xc000,
1267             },
1268             );
1269              
1270             # WebP lossless info (ref 14)
1271             %Image::ExifTool::RIFF::VP8L = (
1272             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1273             NOTES => 'This chunk is found in lossless WebP files.',
1274             GROUPS => { 2 => 'Image' },
1275             1 => {
1276             Name => 'ImageWidth',
1277             Format => 'int16u',
1278             ValueConv => '($val & 0x3fff) + 1',
1279             },
1280             2 => {
1281             Name => 'ImageHeight',
1282             Format => 'int32u',
1283             ValueConv => '(($val >> 6) & 0x3fff) + 1',
1284             },
1285             );
1286              
1287             # WebP extended info (ref 14)
1288             %Image::ExifTool::RIFF::VP8X = (
1289             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1290             GROUPS => { 2 => 'Image' },
1291             NOTES => 'This chunk is found in extended WebP files.',
1292             # 0 - bitmask: 2=ICC, 3=alpha, 4=EXIF, 5=XMP, 6=animation
1293             4 => {
1294             Name => 'ImageWidth',
1295             Format => 'int32u',
1296             ValueConv => '($val & 0xffffff) + 1',
1297             },
1298             6 => {
1299             Name => 'ImageHeight',
1300             Format => 'int32u',
1301             ValueConv => '($val >> 8) + 1',
1302             },
1303             );
1304              
1305             # WebP animation info (ref 14)
1306             %Image::ExifTool::RIFF::ANIM = (
1307             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1308             GROUPS => { 2 => 'Image' },
1309             NOTES => 'WebP animation chunk.',
1310             0 => {
1311             Name => 'BackgroundColor',
1312             Format => 'int8u[4]',
1313             },
1314             4 => {
1315             Name => 'AnimationLoopCount',
1316             PrintConv => '$val || "inf"',
1317             },
1318             );
1319              
1320             # WebP animation frame info (ref 14)
1321             %Image::ExifTool::RIFF::ANMF = (
1322             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1323             GROUPS => { 2 => 'Image' },
1324             NOTES => 'WebP animation frame chunk.',
1325             12 => {
1326             Name => 'Duration',
1327             Format => 'int32u',
1328             Notes => 'extracted as the sum of durations of all animation frames',
1329             RawConv => q{
1330             if (defined $$self{VALUE}{Duration}) {
1331             $$self{VALUE}{Duration} += $val & 0x0fff;
1332             return undef;
1333             }
1334             return $val & 0x0fff;
1335             },
1336             ValueConv => '$val / 1000',
1337             PrintConv => 'ConvertDuration($val)',
1338             },
1339             );
1340              
1341             # streamed USER txts written by Momento M6 dashcam (ref PH)
1342             %Image::ExifTool::RIFF::UserText = (
1343             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1344             GROUPS => { 2 => 'Location' },
1345             NOTES => q{
1346             Tags decoded from the USER-format txts stream written by Momento M6 dashcam.
1347             Extracted only if the ExtractEmbedded option is used.
1348             },
1349             # (little-endian)
1350             # 0 - int32u: 32
1351             # 4 - int32u: sample number (starting from unknown offset)
1352             # 8 - int8u[4]: "w x y z" ? (w 0=front cam, 1=rear cam, z mostly 5-8)
1353             # 12 - int8u[4]: "0 x 1 0" ? (x incrementing once per second)
1354             # 16 - int8u[4]: "0 32 0 x" ?
1355             # 20 - int32u: 100-150(mostly), 250-300(once per second)
1356             # 24 - int8u[4]: "0 x y 0" ?
1357             28 => { Name => 'GPSAltitude', Format => 'int32u', ValueConv => '$val / 10' }, # (NC)
1358             # 32 - int32u: 0(mostly), 23(once per second)
1359             # 36 - int32u: 0
1360             40 => { Name => 'Accelerometer', Format => 'float[3]' },
1361             # 52 - int32u: 1
1362             56 => { Name => 'GPSSpeed', Format => 'float' }, # km/h
1363             60 => {
1364             Name => 'GPSLatitude',
1365             Format => 'float',
1366             # Note: these values are unsigned and I don't know where the hemisphere is stored,
1367             # but my only sample is from the U.S., so assume a positive latitude (for now)
1368             ValueConv => 'my $deg = int($val / 100); $deg + ($val - $deg * 100) / 60',
1369             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
1370             },
1371             64 => {
1372             Name => 'GPSLongitude',
1373             Format => 'float',
1374             # Note: these values are unsigned and I don't know where the hemisphere is stored,
1375             # but my only sample is from the U.S., so assume a negative longitude (for now)
1376             ValueConv => 'my $deg = int($val / 100); -($deg + ($val - $deg * 100) / 60)',
1377             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
1378             },
1379             68 => {
1380             Name => 'GPSDateTime',
1381             Description => 'GPS Date/Time',
1382             Groups => { 2 => 'Time' },
1383             Format => 'int32u',
1384             ValueConv => 'ConvertUnixTime($val)',
1385             # (likely local time, but clock seemed off by 3 hours in my sample)
1386             PrintConv => '$self->ConvertDateTime($val)',
1387             },
1388             );
1389              
1390             # WebP alpha info (ref 14)
1391             %Image::ExifTool::RIFF::ALPH = (
1392             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1393             GROUPS => { 2 => 'Image' },
1394             NOTES => 'WebP alpha chunk.',
1395             0 => {
1396             Name => 'AlphaPreprocessing',
1397             Mask => 0x03,
1398             PrintConv => {
1399             0 => 'none',
1400             1 => 'Level Reduction',
1401             },
1402             },
1403             0.1 => {
1404             Name => 'AlphaFiltering',
1405             Mask => 0x03,
1406             PrintConv => {
1407             0 => 'none',
1408             1 => 'Horizontal',
1409             2 => 'Vertical',
1410             3 => 'Gradient',
1411             },
1412             },
1413             0.2 => {
1414             Name => 'AlphaCompression',
1415             Mask => 0x03,
1416             PrintConv => {
1417             0 => 'none',
1418             1 => 'Lossless',
1419             },
1420             },
1421             );
1422              
1423             # RIFF composite tags
1424             %Image::ExifTool::RIFF::Composite = (
1425             Duration => {
1426             Require => {
1427             0 => 'RIFF:FrameRate',
1428             1 => 'RIFF:FrameCount',
1429             },
1430             Desire => {
1431             2 => 'VideoFrameRate',
1432             3 => 'VideoFrameCount',
1433             },
1434             RawConv => 'Image::ExifTool::RIFF::CalcDuration($self, @val)',
1435             PrintConv => 'ConvertDuration($val)',
1436             },
1437             Duration2 => {
1438             Name => 'Duration',
1439             Require => {
1440             0 => 'RIFF:AvgBytesPerSec',
1441             1 => 'FileSize',
1442             },
1443             Desire => {
1444             # check FrameCount because this calculation only applies
1445             # to audio-only files (eg. WAV)
1446             2 => 'FrameCount',
1447             3 => 'VideoFrameCount',
1448             },
1449             # (can't calculate duration like this for compressed audio types)
1450             RawConv => q{
1451             return undef if $$self{VALUE}{FileType} =~ /^(LA|OFR|PAC|WV)$/;
1452             return(($val[0] and not ($val[2] or $val[3])) ? $val[1] / $val[0] : undef);
1453             },
1454             PrintConv => 'ConvertDuration($val)',
1455             },
1456             );
1457              
1458             # add our composite tags
1459             Image::ExifTool::AddCompositeTags('Image::ExifTool::RIFF');
1460              
1461              
1462             #------------------------------------------------------------------------------
1463             # Convert RIFF date to EXIF format
1464             my %monthNum = (
1465             Jan=>1, Feb=>2, Mar=>3, Apr=>4, May=>5, Jun=>6,
1466             Jul=>7, Aug=>8, Sep=>9, Oct=>10,Nov=>11,Dec=>12
1467             );
1468             sub ConvertRIFFDate($)
1469             {
1470 2     2 0 5 my $val = shift;
1471 2         9 my @part = split ' ', $val;
1472 2         4 my $mon;
1473 2 100 66     21 if (@part >= 5 and $mon = $monthNum{ucfirst(lc($part[1]))}) {
    50          
    0          
1474             # the standard AVI date format (eg. "Mon Mar 10 15:04:43 2003")
1475 1         7 $val = sprintf("%.4d:%.2d:%.2d %s", $part[4],
1476             $mon, $part[2], $part[3]);
1477             } elsif ($val =~ m{(\d{4})/\s*(\d+)/\s*(\d+)/?\s+(\d+):\s*(\d+)\s*(P?)}) {
1478             # but the Casio QV-3EX writes dates like "2001/ 1/27 1:42PM",
1479             # and the Casio EX-Z30 writes "2005/11/28/ 09:19"... doh!
1480 1 50       12 $val = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:00",$1,$2,$3,$4+($6?12:0),$5);
1481             } elsif ($val =~ m{(\d{4})[-/](\d+)[-/](\d+)\s+(\d+:\d+:\d+)}) {
1482             # the Konica KD500Z writes "2002-12-16 15:35:01\0\0"
1483 0         0 $val = "$1:$2:$3 $4";
1484             }
1485 2         15 return $val;
1486             }
1487              
1488             #------------------------------------------------------------------------------
1489             # Print time
1490             # Inputs: 0) time in seconds
1491             # Returns: time string
1492             sub ConvertTimecode($)
1493             {
1494 0     0 0 0 my $val = shift;
1495 0         0 my $hr = int($val / 3600);
1496 0         0 $val -= $hr * 3600;
1497 0         0 my $min = int($val / 60);
1498 0         0 $val -= $min * 60;
1499 0         0 my $ss = sprintf('%05.2f', $val);
1500 0 0       0 if ($ss >= 60) { # handle round-off problems
1501 0         0 $ss = '00.00';
1502 0 0       0 ++$min >= 60 and $min -= 60, ++$hr;
1503             }
1504 0         0 return sprintf('%d:%.2d:%s', $hr, $min, $ss);
1505             }
1506              
1507             #------------------------------------------------------------------------------
1508             # Calculate duration of RIFF
1509             # Inputs: 0) ExifTool ref, 1/2) RIFF:FrameRate/Count, 2/3) VideoFrameRate/Count
1510             # Returns: Duration in seconds or undef
1511             # Notes: Sums duration of all sub-documents (concatenated AVI files)
1512             sub CalcDuration($@)
1513             {
1514 2     2 0 9 my ($et, @val) = @_;
1515 2         5 my $totalDuration = 0;
1516 2         4 my $subDoc = 0;
1517 2         4 my @keyList;
1518 2         3 for (;;) {
1519             # this is annoying. Apparently (although I couldn't verify this), FrameCount
1520             # in the RIFF header includes multiple video tracks if they exist (eg. with the
1521             # FujiFilm REAL 3D AVI's), but the video stream information isn't reliable for
1522             # some cameras (eg. Olympus FE models), so use the video stream information
1523             # only if the RIFF header duration is 2 to 3 times longer
1524 2         4 my $dur1;
1525 2 50       10 $dur1 = $val[1] / $val[0] if $val[0];
1526 2 50 33     13 if ($val[2] and $val[3]) {
1527 2         5 my $dur2 = $val[3] / $val[2];
1528 2         5 my $rat = $dur1 / $dur2;
1529 2 50 33     16 $dur1 = $dur2 if $rat > 1.9 and $rat < 3.1;
1530             }
1531 2 50       8 $totalDuration += $dur1 if defined $dur1;
1532 2 50       28 last unless $subDoc++ < $$et{DOC_COUNT};
1533             # get tag values for next sub-document
1534 0         0 my @tags = qw(FrameRate FrameCount VideoFrameRate VideoFrameCount);
1535 0         0 my $rawValue = $$et{VALUE};
1536 0         0 my ($i, $j, $key, $keys);
1537 0         0 for ($i=0; $i<@tags; ++$i) {
1538 0 0       0 if ($subDoc == 1) {
1539             # generate list of available keys for each tag
1540 0         0 $keys = $keyList[$i] = [ ];
1541 0         0 for ($j=0; ; ++$j) {
1542 0         0 $key = $tags[$i];
1543 0 0       0 $key .= " ($j)" if $j;
1544 0 0       0 last unless defined $$rawValue{$key};
1545 0         0 push @$keys, $key;
1546             }
1547             } else {
1548 0         0 $keys = $keyList[$i];
1549             }
1550             # find key for tag in this sub-document
1551 0         0 my $grp = "Doc$subDoc";
1552 0 0       0 $grp .= ":RIFF" if $i < 2; # (tags 0 and 1 also in RIFF group)
1553 0         0 $key = $et->GroupMatches($grp, $keys);
1554 0 0       0 $val[$i] = $key ? $$rawValue{$key} : undef;
1555             }
1556 0 0 0     0 last unless defined $val[0] and defined $val[1]; # (Require'd tags)
1557             }
1558 2         19 return $totalDuration;
1559             }
1560              
1561             #------------------------------------------------------------------------------
1562             # Process stream data
1563             # Inputs: 0) ExifTool object ref, 1) dirInfo reference, 2) tag table ref
1564             # Returns: 1 on success
1565             sub ProcessStreamData($$$)
1566             {
1567 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1568 0         0 my $dataPt = $$dirInfo{DataPt};
1569 0         0 my $start = $$dirInfo{DirStart};
1570 0         0 my $size = $$dirInfo{DirLen};
1571 0 0       0 return 0 if $size < 4;
1572 0 0       0 if ($et->Options('Verbose')) {
1573 0         0 $et->VerboseDir($$dirInfo{DirName}, 0, $size);
1574             }
1575 0         0 my $tag = substr($$dataPt, $start, 4);
1576 0         0 my $tagInfo = $et->GetTagInfo($tagTbl, $tag);
1577 0 0       0 unless ($tagInfo) {
1578 0         0 $tagInfo = $et->GetTagInfo($tagTbl, 'unknown');
1579 0 0       0 return 1 unless $tagInfo;
1580             }
1581 0         0 my $subdir = $$tagInfo{SubDirectory};
1582 0 0       0 if ($$tagInfo{SubDirectory}) {
1583 0   0     0 my $offset = $$subdir{Start} || 0;
1584 0         0 my $baseShift = $$dirInfo{DataPos} + $$dirInfo{DirStart} + $offset;
1585             my %subdirInfo = (
1586             DataPt => $dataPt,
1587             DataPos => $$dirInfo{DataPos} - $baseShift,
1588             Base => ($$dirInfo{Base} || 0) + $baseShift,
1589             DataLen => $$dirInfo{DataLen},
1590             DirStart=> $$dirInfo{DirStart} + $offset,
1591             DirLen => $$dirInfo{DirLen} - $offset,
1592             DirName => $$subdir{DirName},
1593             Parent => $$dirInfo{DirName},
1594 0   0     0 );
1595 0 0       0 unless ($offset) {
1596             # allow processing of 2nd directory at the same address
1597 0         0 my $addr = $subdirInfo{DirStart} + $subdirInfo{DataPos} + $subdirInfo{Base};
1598 0         0 delete $$et{PROCESSED}{$addr}
1599             }
1600             # (we could set FIRST_EXIF_POS to $subdirInfo{Base} here to make
1601             # htmlDump offsets relative to EXIF base if we wanted...)
1602 0         0 my $subTable = GetTagTable($$subdir{TagTable});
1603 0         0 $et->ProcessDirectory(\%subdirInfo, $subTable);
1604             } else {
1605             $et->HandleTag($tagTbl, $tag, undef,
1606             DataPt => $dataPt,
1607             DataPos => $$dirInfo{DataPos},
1608 0         0 Start => $start,
1609             Size => $size,
1610             TagInfo => $tagInfo,
1611             );
1612             }
1613 0         0 return 1;
1614             }
1615              
1616             #------------------------------------------------------------------------------
1617             # Make tag information hash for unknown tag
1618             # Inputs: 0) Tag table ref, 1) tag ID
1619             sub MakeTagInfo($$)
1620             {
1621 0     0 0 0 my ($tagTbl, $tag) = @_;
1622 0         0 my $name = $tag;
1623 0         0 my $n = ($name =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg);
  0         0  
1624             # print in hex if tag is numerical
1625 0 0       0 $name = sprintf('0x%.4x',unpack('N',$tag)) if $n > 2;
1626 0         0 AddTagToTable($tagTbl, $tag, {
1627             Name => "Unknown_$name",
1628             Description => "Unknown $name",
1629             Unknown => 1,
1630             Binary => 1,
1631             });
1632             }
1633              
1634             #------------------------------------------------------------------------------
1635             # Process RIFF chunks
1636             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1637             # Returns: 1 on success
1638             sub ProcessChunks($$$)
1639             {
1640 13     13 0 30 my ($et, $dirInfo, $tagTbl) = @_;
1641 13         28 my $dataPt = $$dirInfo{DataPt};
1642 13         18 my $start = $$dirInfo{DirStart};
1643 13         28 my $size = $$dirInfo{DirLen};
1644 13         18 my $end = $start + $size;
1645 13         18 my $base = $$dirInfo{Base};
1646 13         43 my $verbose = $et->Options('Verbose');
1647 13         39 my $unknown = $et->Options('Unknown');
1648 13         29 my $charset = $et->Options('CharsetRIFF');
1649              
1650 13 50       31 unless ($charset) {
1651 13 50 33     72 if ($$et{CodePage}) {
    50          
1652 0         0 $charset = $$et{CodePage};
1653             } elsif (defined $charset and $charset eq '0') {
1654 13         24 $charset = 'Latin';
1655             }
1656             }
1657              
1658 13 50       34 $et->VerboseDir($$dirInfo{DirName}, 0, $size) if $verbose;
1659              
1660 13         33 while ($start + 8 < $end) {
1661 34         64 my $tag = substr($$dataPt, $start, 4);
1662 34         77 my $len = Get32u($dataPt, $start + 4);
1663 34         67 $start += 8;
1664 34 50       74 if ($start + $len > $end) {
1665 0         0 $et->Warn("Bad $tag chunk");
1666 0         0 return 0;
1667             }
1668 34 100 66     184 if ($tag eq 'LIST' and $len >= 4) {
1669 5         14 $tag .= '_' . substr($$dataPt, $start, 4);
1670 5         8 $len -= 4;
1671 5         9 $start += 4;
1672             }
1673 34         79 my $tagInfo = $et->GetTagInfo($tagTbl, $tag);
1674 34         50 my $baseShift = 0;
1675 34         38 my $val;
1676 34 100 33     71 if ($tagInfo) {
    50          
1677 28 100       71 if ($$tagInfo{SubDirectory}) {
    100          
1678             # adjust base if necessary (needed for Ricoh maker notes)
1679 17         30 my $newBase = $tagInfo->{SubDirectory}{Base};
1680 17 100       38 if (defined $newBase) {
1681             # different than your average Base eval...
1682             # here we use an absolute $start address
1683 1         2 $start += $base;
1684             #### eval Base ($start)
1685 1         61 $newBase = eval $newBase;
1686 1         3 $baseShift = $newBase - $base;
1687 1         2 $start -= $base;
1688             }
1689             } elsif (not $$tagInfo{Binary}) {
1690 10   66     36 my $format = $$tagInfo{Format} || $$tagTbl{FORMAT};
1691 10 100 66     30 if ($format and $format eq 'string') {
1692 3         9 $val = substr($$dataPt, $start, $len);
1693 3         20 $val =~ s/\0+$//; # remove trailing nulls from strings
1694             # decode if necessary
1695 3 50       14 $val = $et->Decode($val, $charset) if $charset;
1696             }
1697             }
1698             } elsif ($verbose or $unknown) {
1699 0         0 MakeTagInfo($tagTbl, $tag);
1700             }
1701             $et->HandleTag($tagTbl, $tag, $val,
1702             DataPt => $dataPt,
1703 34         162 DataPos => $$dirInfo{DataPos} - $baseShift,
1704             Start => $start,
1705             Size => $len,
1706             Base => $base + $baseShift,
1707             Addr => $base + $baseShift + $start,
1708             );
1709 34 100       80 ++$len if $len & 0x01; # must account for padding if odd number of bytes
1710 34         88 $start += $len;
1711             }
1712 13         32 return 1;
1713             }
1714              
1715             #------------------------------------------------------------------------------
1716             # Process BikeBro SGLT chunk (accelerometer data) (ref PH)
1717             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1718             # Returns: 1 on success
1719             sub ProcessSGLT($$$)
1720             {
1721 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1722 0         0 my $dataPt = $$dirInfo{DataPt};
1723 0         0 my $dataLen = length $$dataPt;
1724 0         0 my $ee = $et->Options('ExtractEmbedded');
1725 0         0 my $pos;
1726             # example accelerometer record:
1727             # 0 1 2 3 4 5 6 7
1728             # 00 00 00 24 02 00 00 01 17 04 00 00 00 00 00 00 00 00 9b 02
1729             # frame------ ?? Xs X---------- Ys Y---------- Zs Z----------
1730 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1731 0         0 for ($pos=0; $pos<=$dataLen-20; $pos+=20) {
1732 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1733 0         0 my $buff = substr($$dataPt, $pos);
1734 0         0 my @a = unpack('NCCNCNCN', $buff);
1735 0 0       0 my @acc = ($a[3]*($a[2]?-1:1)/1e5, $a[5]*($a[4]?-1:1)/1e5, $a[7]*($a[6]?-1:1)/1e5);
    0          
    0          
1736 0         0 $et->HandleTag($tagTbl, FrameNumber => $a[0]);
1737 0         0 $et->HandleTag($tagTbl, Accelerometer => "@acc");
1738 0 0       0 unless ($ee) {
1739 0         0 $et->Warn('Use ExtractEmbedded option to extract all accelerometer data', 3);
1740 0         0 last;
1741             }
1742             }
1743 0         0 delete $$et{SET_GROUP0};
1744 0         0 delete $$et{SET_GROUP1};
1745 0         0 $$et{DOC_NUM} = 0;
1746 0         0 return 0;
1747             }
1748              
1749             #------------------------------------------------------------------------------
1750             # Process BikeBro SLLT chunk (GPS information) (ref PH)
1751             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1752             # Returns: 1 on success
1753             sub ProcessSLLT($$$)
1754             {
1755 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1756 0         0 my $dataPt = $$dirInfo{DataPt};
1757 0         0 my $dataLen = length $$dataPt;
1758 0         0 my $ee = $et->Options('ExtractEmbedded');
1759 0         0 my $pos;
1760             # example GPS record:
1761             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1762             # 00 00 00 17 01 00 00 03 fa 21 ec 00 35 01 6e c0 06 00 08 00 62 10 0b 1b 07 e2 03 0e 57 4e
1763             # frame------ ?? lonDD lonDDDDDDDD latDD latDDDDDDDD alt-- spd-- hr mn sc yr--- mn dy EW NS
1764 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1765 0         0 for ($pos=0; $pos<=$dataLen-30; $pos+=30) {
1766 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1767 0         0 my $buff = substr($$dataPt, $pos);
1768 0         0 my @a = unpack('NCnNnNnnCCCnCCaa', $buff);
1769             # - is $a[1] perhaps GPSStatus? (only seen 1, or perhaps record type 1=GPS, 2=acc?)
1770 0         0 my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', @a[11..13, 8..10]);
1771 0         0 $et->HandleTag($tagTbl, FrameNumber => $a[0]);
1772 0         0 $et->HandleTag($tagTbl, GPSDateTime => $time);
1773 0 0       0 $et->HandleTag($tagTbl, GPSLatitude => ($a[4] + $a[5]/1e8) * ($a[15] eq 'S' ? -1 : 1));
1774 0 0       0 $et->HandleTag($tagTbl, GPSLongitude => ($a[2] + $a[3]/1e8) * ($a[14] eq 'W' ? -1 : 1));
1775 0         0 $et->HandleTag($tagTbl, GPSAltitude => $a[6]);
1776 0         0 $et->HandleTag($tagTbl, GPSSpeed => $a[7]);
1777 0         0 $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1778 0 0       0 unless ($ee) {
1779 0         0 $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3);
1780 0         0 last;
1781             }
1782             }
1783 0         0 delete $$et{SET_GROUP0};
1784 0         0 delete $$et{SET_GROUP1};
1785 0         0 $$et{DOC_NUM} = 0;
1786 0         0 return 1;
1787             }
1788              
1789             #------------------------------------------------------------------------------
1790             # Process Lucas streaming GPS information (Lucas LK-7900 Ace) (ref PH)
1791             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1792             # Returns: 1 on success
1793             sub ProcessLucas($$$)
1794             {
1795 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1796 0         0 my $dataPt = $$dirInfo{DataPt};
1797 0         0 my $dataLen = length $$dataPt;
1798              
1799 0 0       0 unless ($et->Options('ExtractEmbedded')) {
1800 0         0 $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3);
1801 0         0 return 1;
1802             }
1803 0         0 my %recLen = ( # record lengths (not including 4-byte ID)
1804             '0GDA' => 24,
1805             '0GPS' => 48,
1806             );
1807 0         0 my ($date,$time,$lat,$lon,$alt,$spd,$sat,$dop,$ew,$ns);
1808 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1809 0         0 while ($$dataPt =~ /(0GDA|0GPS)/g) {
1810 0         0 my ($rec, $pos) = ($1, pos $$dataPt);
1811 0 0       0 $pos + $recLen{$rec} > $dataLen and $et->Warn("Truncated $1 record"), last;
1812 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1813             # records start with int64u sample date/time in ms since 1970
1814 0         0 $et->HandleTag($tagTbl, SampleDateTime => Get64u($dataPt, $pos) / 1000);
1815 0 0       0 if ($rec eq '0GPS') {
1816 0         0 my $len = Get32u($dataPt, $pos+8);
1817 0         0 my $endPos = $pos + $recLen{$rec} + $len;
1818 0 0       0 $endPos > $dataLen and $et->Warn('Truncated 0GPS record'), last;
1819 0         0 my $buff = substr($$dataPt, $pos+$recLen{$rec}, $len);
1820 0         0 while ($buff =~ /\$(GC|GA),(\d+),/g) {
1821 0         0 my $p = pos $buff;
1822 0         0 $time = $2;
1823 0 0       0 if ($1 eq 'GC') {
1824             # time date dist ? sat dop alt A
1825             # $GC,052350,180914,0000955,1,08,1.1,0017,,A*45\x0d\x0a\0
1826 0 0       0 if ($buff =~ /\G(\d+),\d*,\d*,(\d+),([-\d.]+),(\d+),\d*,A/g) {
1827 0         0 ($date,$sat,$dop,$alt) = ($1,$2,$3,$4);
1828             }
1829             } else {
1830             # time A lat lon spd N W
1831             # $GA,052351,A,0949.6626,07635.4439,049,N,E,*4C\x0d\x0a\0
1832 0 0       0 if ($buff =~ /\GA,([\d.]+),([\d.]+),(\d+),([NS]),([EW])/g) {
1833 0         0 ($lat,$lon,$spd,$ns,$ew) = ($1,$2,$3,$4,$5,$6);
1834             # lat/long are in DDDMM.MMMM format
1835 0         0 my $deg = int($lat / 100);
1836 0         0 $lat = $deg + ($lat - $deg * 100) / 60;
1837 0         0 $deg = int($lon / 100);
1838 0         0 $lon = $deg + ($lon - $deg * 100) / 60;
1839 0 0       0 $lat *= -1 if $ns eq 'S';
1840 0 0       0 $lon *= -1 if $ew eq 'W';
1841             }
1842             }
1843             # look ahead to next NMEA-like sentence, and store the fix
1844             # now only if the next sentence is not at the same time
1845 0 0       0 if ($buff !~ /\$(GC|GA),$time,/g) {
1846 0         0 pos($$dataPt) = $endPos;
1847 0 0 0     0 if ($$dataPt !~ /\$(GC|GA),(\d+)/ or $1 ne $time) {
1848 0         0 $time =~ s/(\d{2})(\d{2})(\d{2})/$1:$2:$3Z/;
1849 0 0       0 if ($date) {
1850 0         0 $date =~ s/(\d{2})(\d{2})(\d{2})/20$3:$2:$1/;
1851 0         0 $et->HandleTag($tagTbl, GPSDateTime => "$date $time");
1852             } else {
1853 0         0 $et->HandleTag($tagTbl, GPSTimeStamp => $time);
1854             }
1855 0 0       0 if (defined $lat) {
1856 0         0 $et->HandleTag($tagTbl, GPSLatitude => $lat);
1857 0         0 $et->HandleTag($tagTbl, GPSLongitude => $lon);
1858 0         0 $et->HandleTag($tagTbl, GPSSpeed => $spd);
1859             }
1860 0 0       0 if (defined $alt) {
1861 0         0 $et->HandleTag($tagTbl, GPSAltitude => $alt);
1862 0         0 $et->HandleTag($tagTbl, GPSSatellites => $sat);
1863 0         0 $et->HandleTag($tagTbl, GPSDOP => $dop);
1864             }
1865 0         0 undef $lat;
1866 0         0 undef $alt;
1867             }
1868             }
1869 0         0 pos($buff) = $p;
1870             }
1871 0         0 $pos += $len;
1872             } else { # this is an accelerometer (0GDA) record
1873             # record has 4 more int32s values (the last is always 57 or 58 --
1874             # maybe related to sample time in ms? -- not extracted)
1875 0         0 my @acc = unpack('x'.($pos+8).'V3', $$dataPt);
1876             # change to signed integer and divide by 256
1877 0 0       0 map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
  0         0  
  0         0  
1878 0         0 $et->HandleTag($tagTbl, Accelerometer => "@acc");
1879             }
1880 0         0 pos($$dataPt) = $pos + $recLen{$rec};
1881             }
1882 0         0 delete $$et{SET_GROUP0};
1883 0         0 delete $$et{SET_GROUP1};
1884 0         0 $$et{DOC_NUM} = 0;
1885 0         0 return 1;
1886             }
1887              
1888             #------------------------------------------------------------------------------
1889             # Extract information from a RIFF file
1890             # Inputs: 0) ExifTool object reference, 1) DirInfo reference
1891             # Returns: 1 on success, 0 if this wasn't a valid RIFF file
1892             sub ProcessRIFF($$)
1893             {
1894 4     4 0 10 my ($et, $dirInfo) = @_;
1895 4         9 my $raf = $$dirInfo{RAF};
1896 4         7 my ($buff, $buf2, $type, $mime, $err, $rf64);
1897 4         13 my $verbose = $et->Options('Verbose');
1898 4         9 my $unknown = $et->Options('Unknown');
1899 4         15 my $ee = $et->Options('ExtractEmbedded');
1900              
1901             # verify this is a valid RIFF file
1902 4 50       19 return 0 unless $raf->Read($buff, 12) == 12;
1903 4 50       27 if ($buff =~ /^(RIFF|RF64)....(.{4})/s) {
1904 4         15 $type = $riffType{$2};
1905 4 50       15 $rf64 = 1 if $1 eq 'RF64';
1906             } else {
1907             # minimal support for a few obscure lossless audio formats...
1908 0 0 0     0 return 0 unless $buff =~ /^(LA0[234]|OFR |LPAC|wvpk)/ and $raf->Read($buf2, 1024);
1909 0         0 $type = $riffType{$1};
1910 0         0 $buff .= $buf2;
1911 0 0 0     0 return 0 unless $buff =~ /WAVE(.{4})?fmt /sg and $raf->Seek(pos($buff) - 4, 0);
1912             }
1913 4 50       12 $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode
1914 4 50       14 $mime = $riffMimeType{$type} if $type;
1915 4         30 $et->SetFileType($type, $mime);
1916 4 50       10 $$et{VALUE}{FileType} .= ' (RF64)' if $rf64;
1917 4         14 $$et{RIFFStreamType} = ''; # initialize stream type
1918 4         8 $$et{RIFFStreamCodec} = []; # initialize codec array
1919 4         14 SetByteOrder('II');
1920 4         10 my $tagTbl = GetTagTable('Image::ExifTool::RIFF::Main');
1921 4         9 my $pos = 12;
1922             #
1923             # Read chunks in RIFF image
1924             #
1925 4         7 for (;;) {
1926 19         64 my $num = $raf->Read($buff, 8);
1927 19 100       45 if ($num < 8) {
1928 4 50       11 $err = 1 if $num;
1929 4         8 last;
1930             }
1931 15         23 $pos += 8;
1932 15         57 my ($tag, $len) = unpack('a4V', $buff);
1933             # special case: construct new tag name from specific LIST type
1934 15 100 66     49 if ($tag eq 'LIST') {
    50 33        
1935 10 50       25 $raf->Read($buff, 4) == 4 or $err=1, last;
1936 10         17 $pos += 4;
1937 10         21 $tag .= "_$buff";
1938 10         19 $len -= 4; # already read 4 bytes (the LIST type)
1939             } elsif ($tag eq 'data' and $len == 0xffffffff and $$et{DataSize64}) {
1940 0         0 $len = $$et{DataSize64};
1941             }
1942 15         72 $et->VPrint(0, "RIFF '${tag}' chunk ($len bytes of data):\n");
1943 15 100       36 if ($len <= 0) {
1944 3 50       14 if ($len < 0) {
    50          
1945 0         0 $et->Warn('Invalid chunk length');
1946             } elsif ($tag eq "\0\0\0\0") {
1947             # avoid reading through corupted files filled with nulls because it takes forever
1948 0         0 $et->Warn('Encountered empty null chunk. Processing aborted');
1949             } else {
1950 3         5 next;
1951             }
1952 0         0 last;
1953             }
1954             # stop when we hit the audio data or AVI index or AVI movie data
1955             # --> no more because Adobe Bridge stores XMP after this!!
1956             # (so now we only do this on the FastScan option)
1957 12 0 0     30 if ($et->Options('FastScan') and ($tag eq 'data' or $tag eq 'idx1' or
      33        
1958             ($tag eq 'LIST_movi' and not $ee)))
1959             {
1960 0         0 $et->VPrint(0, "(end of parsing)\n");
1961 0         0 last;
1962             }
1963             # RIFF chunks are padded to an even number of bytes
1964 12         26 my $len2 = $len + ($len & 0x01);
1965             # change name of stream txts data depending on the Codec
1966 12 50 33     31 if ($ee and $tag =~ /^(\d{2})tx$/) {
1967 0   0     0 $tag = 'tx_' . ($$et{RIFFStreamCodec}[$1] || 'Unknown');
1968 0 0       0 $tag = "tx_Unknown" unless defined $$tagTbl{$tag};
1969 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1970             }
1971 12         28 my $tagInfo = $$tagTbl{$tag};
1972 12 50 0     32 if ($tagInfo or (($verbose or $unknown) and $tag !~ /^(data|idx1|LIST_movi|RIFF|\d{2}(db|dc|wb))$/)) {
    0 0        
    0 33        
      0        
1973 12 50       28 $raf->Read($buff, $len2) == $len2 or $err=1, last;
1974 12         19 my $setGroups;
1975 12 50 66     70 if ($tagInfo and ref $tagInfo eq 'HASH' and $$tagInfo{SetGroups}) {
      66        
1976 0         0 $setGroups = $$et{SET_GROUP0} = $$et{SET_GROUP1} = $$tagInfo{SetGroups};
1977             }
1978 12 0 0     28 MakeTagInfo($tagTbl, $tag) if not $tagInfo and ($verbose or $unknown);
      33        
1979 12         58 $et->HandleTag($tagTbl, $tag, $buff,
1980             DataPt => \$buff,
1981             DataPos => 0, # (relative to Base)
1982             Start => 0,
1983             Size => $len2,
1984             Base => $pos,
1985             );
1986 12 50       27 if ($setGroups) {
1987 0         0 delete $$et{SET_GROUP0};
1988 0         0 delete $$et{SET_GROUP1};
1989             }
1990 12 50       31 delete $$et{DOC_NUM} if $ee;
1991             } elsif ($tag eq 'RIFF') {
1992             # don't read into RIFF chunk (eg. concatenated video file)
1993 0 0       0 $raf->Read($buff, 4) == 4 or $err=1, last;
1994             # extract information from remaining file as an embedded file
1995             $$et{DOC_NUM} = ++$$et{DOC_COUNT}
1996 0         0 } elsif ($tag eq 'LIST_movi' and $ee) {
1997 0         0 next; # parse into movi chunk
1998             } else {
1999 0 0 0     0 if ($len > 0x7fffffff and not $et->Options('LargeFileSupport')) {
2000 0         0 $et->Warn("Stopped parsing at large $tag chunk (LargeFileSupport not set)");
2001 0         0 last;
2002             }
2003 0 0       0 $raf->Seek($len2, 1) or $err=1, last;
2004             }
2005 12         23 $pos += $len2;
2006             }
2007 4         9 delete $$et{DOC_NUM};
2008 4 50       11 $err and $et->Warn('Error reading RIFF file (corrupted?)');
2009 4         16 return 1;
2010             }
2011              
2012             1; # end
2013              
2014             __END__