File Coverage

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