File Coverage

blib/lib/Image/ExifTool/RIFF.pm
Criterion Covered Total %
statement 139 370 37.5
branch 62 234 26.5
condition 28 112 25.0
subroutine 8 14 57.1
pod 0 10 0.0
total 237 740 32.0


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 15     15   7452 use strict;
  15         27  
  15         586  
30 15     15   67 use vars qw($VERSION $AUTOLOAD);
  15         27  
  15         790  
31 15     15   120 use Image::ExifTool qw(:DataAccess :Utils);
  15         29  
  15         123885  
32              
33             $VERSION = '1.73';
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 ImageDataHash 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             C2PA => { #https://c2pa.org/specifications/
504             Name => 'JUMBF',
505             Deletable => 1,
506             SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' },
507             },
508             olym => {
509             Name => 'Olym',
510             SubDirectory => { TagTable => 'Image::ExifTool::Olympus::WAV' },
511             },
512             fact => {
513             Name => 'NumberOfSamples',
514             RawConv => 'Get32u(\$val, 0)',
515             },
516             'cue '=> {
517             Name => 'CuePoints',
518             Binary => 1,
519             Notes => q{
520             config_files/cutepointlist.config from full distribution will decode this
521             and generate a list of cue points with labels
522             },
523             },
524             plst => { Name => 'Playlist', Binary => 1 }, #16
525             afsp => { },
526             IDIT => {
527             Name => 'DateTimeOriginal',
528             Description => 'Date/Time Original',
529             Groups => { 2 => 'Time' },
530             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
531             PrintConv => '$self->ConvertDateTime($val)',
532             },
533             CSET => {
534             Name => 'CharacterSet',
535             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::CSET' },
536             },
537             # tx_ tags are generated based on the Codec used for the txts stream
538             tx_USER => {
539             Name => 'UserText',
540             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::UserText' },
541             },
542             tx_Unknown => { # (untested)
543             Name => 'Text',
544             Notes => 'streamed text, extracted when the ExtractEmbedded option is used',
545             },
546             'id3 ' => {
547             Name => 'ID3',
548             SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' },
549             },
550             'ID3 ' => { # (NC)
551             Name => 'ID3-2',
552             SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' },
553             },
554             #
555             # WebP-specific tags
556             #
557             EXIF => [{ # (WebP)
558             Name => 'EXIF',
559             Condition => '$$valPt =~ /^(II\x2a\0|MM\0\x2a)/',
560             Notes => 'WebP files',
561             SubDirectory => {
562             TagTable => 'Image::ExifTool::Exif::Main',
563             ProcessProc => \&Image::ExifTool::ProcessTIFF,
564             },
565             },{ # (WebP) - have also seen with "Exif\0\0" header - PH
566             Name => 'EXIF',
567             Condition => '$$valPt =~ /^Exif\0\0(II\x2a\0|MM\0\x2a)/ and ($self->Warn("Improper EXIF header",1) or 1)',
568             SubDirectory => {
569             TagTable => 'Image::ExifTool::Exif::Main',
570             ProcessProc => \&Image::ExifTool::ProcessTIFF,
571             Start => 6,
572             },
573             },{
574             Name => 'UnknownEXIF',
575             Binary => 1,
576             }],
577             'XMP ' => { #14 (WebP)
578             Name => 'XMP',
579             Notes => 'WebP files',
580             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
581             },
582             "XMP\0" => {
583             Name => 'XMP',
584             Notes => 'incorrectly written WebP files',
585             Condition => '$self->Warn("Incorrect XMP tag ID", 1) or 1',
586             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
587             },
588             ICCP => { #14 (WebP)
589             Name => 'ICC_Profile',
590             Notes => 'WebP files',
591             SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
592             },
593             'VP8 ' => { # (WebP lossy)
594             Name => 'VP8Bitstream',
595             Condition => '$$valPt =~ /^...\x9d\x01\x2a/s',
596             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8' },
597             },
598             VP8L => { #14 (WebP lossless)
599             Name => 'VP8L',
600             Condition => '$$valPt =~ /^\x2f/',
601             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8L' },
602             },
603             VP8X => { #14 (WebP extended)
604             Name => 'VP8X',
605             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::VP8X' },
606             },
607             ANIM => { #14 (WebP animation)
608             Name => 'ANIM',
609             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANIM' },
610             },
611             ANMF => { #14 (WebP animation frame)
612             Name => 'ANMF',
613             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ANMF' },
614             },
615             ALPH => { #14 (WebP alpha)
616             Name => 'ALPH',
617             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ALPH' },
618             },
619             SGLT => { #PH (BikeBro)
620             Name => 'BikeBroAccel',
621             SubDirectory => {
622             TagTable => 'Image::ExifTool::QuickTime::Stream',
623             ProcessProc => \&ProcessSGLT,
624             },
625             },
626             SLLT => { #PH (BikeBro)
627             Name => 'BikeBroGPS',
628             SubDirectory => {
629             TagTable => 'Image::ExifTool::QuickTime::Stream',
630             ProcessProc => \&ProcessSLLT,
631             },
632             },
633             iXML => { #PH
634             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
635             },
636             aXML => { #PH
637             SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
638             },
639             #
640             # tags found in an AlphaImagingTech AVI video - PH
641             #
642             LIST_INF0 => { # ('0' instead of 'O' -- odd)
643             Name => 'Info',
644             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Info' },
645             },
646             gps0 => {
647             Name => 'GPSTrack',
648             SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group)
649             SubDirectory => {
650             TagTable => 'Image::ExifTool::QuickTime::Stream',
651             # (don't use code ref here or get "Prototype mismatch" warning with some Perl versions)
652             ProcessProc => 'Image::ExifTool::QuickTime::Process_gps0',
653             },
654             },
655             gsen => {
656             Name => 'GSensor',
657             SetGroups => 'RIFF', # (moves "QuickTime" tags to the "RIFF" group)
658             SubDirectory => {
659             TagTable => 'Image::ExifTool::QuickTime::Stream',
660             ProcessProc => 'Image::ExifTool::QuickTime::Process_gsen',
661             },
662             },
663             # gpsa - seen hex "01 20 00 00", same as QuickTime
664             # gsea - 16 bytes hex "04 08 02 00 20 02 00 00 1f 03 00 00 01 00 00 00"
665              
666             acid => { # writen by Acidizer
667             Name => 'Acidizer',
668             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Acidizer' },
669             },
670             guan => 'Guano', #forum14831
671             SEAL => {
672             Name => 'SEAL',
673             SubDirectory => { TagTable => 'Image::ExifTool::XMP::SEAL' },
674             },
675             # LGWV - written by Logic Pro
676             # minf, elm1, regn, umid, DGDA - written by Pro Tools
677             # MXrt, muma, chrp - written by Sequoia Pro
678             );
679              
680             # the maker notes used by some digital cameras
681             %Image::ExifTool::RIFF::Junk = (
682             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
683             GROUPS => { 2 => 'Audio' },
684             );
685              
686             # Format and Audio Stream Format chunk data
687             %Image::ExifTool::RIFF::AudioFormat = (
688             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
689             GROUPS => { 2 => 'Audio' },
690             FORMAT => 'int16u',
691             0 => {
692             Name => 'Encoding',
693             PrintHex => 1,
694             PrintConv => \%Image::ExifTool::RIFF::audioEncoding,
695             SeparateTable => 'AudioEncoding',
696             },
697             1 => 'NumChannels',
698             2 => {
699             Name => 'SampleRate',
700             Format => 'int32u',
701             },
702             4 => {
703             Name => 'AvgBytesPerSec',
704             Format => 'int32u',
705             },
706             # uninteresting
707             # 6 => 'BlockAlignment',
708             7 => 'BitsPerSample',
709             );
710              
711             # Broadcast Audio Extension 'bext' information (ref 13)
712             %Image::ExifTool::RIFF::BroadcastExt = (
713             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
714             GROUPS => { 2 => 'Audio' },
715             NOTES => q{
716             Information found in the Broadcast Audio Extension chunk (see
717             L).
718             },
719             0 => {
720             Name => 'Description',
721             Format => 'string[256]',
722             },
723             256 => {
724             Name => 'Originator',
725             Format => 'string[32]',
726             },
727             288 => {
728             Name => 'OriginatorReference',
729             Format => 'string[32]',
730             },
731             320 => {
732             Name => 'DateTimeOriginal',
733             Description => 'Date/Time Original',
734             Groups => { 2 => 'Time' },
735             Format => 'string[18]',
736             ValueConv => '$_=$val; tr/-/:/; s/^(\d{4}:\d{2}:\d{2})/$1 /; $_',
737             PrintConv => '$self->ConvertDateTime($val)',
738             },
739             338 => {
740             Name => 'TimeReference',
741             Notes => 'first sample count since midnight',
742             Format => 'int32u[2]',
743             ValueConv => 'my @v=split(" ",$val); $v[0] + $v[1] * 4294967296',
744             },
745             346 => {
746             Name => 'BWFVersion',
747             Format => 'int16u',
748             },
749             348 => {
750             Name => 'BWF_UMID',
751             Format => 'undef[64]',
752             ValueConv => '$_=unpack("H*",$val); s/0{64}$//; uc $_',
753             },
754             # 412 - int8u[190] - reserved
755             602 => {
756             Name => 'CodingHistory',
757             Format => 'string[$size-602]',
758             },
759             );
760              
761             # 64-bit chunk sizes (ref 15)
762             %Image::ExifTool::RIFF::DS64 = (
763             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
764             GROUPS => { 2 => 'Audio' },
765             FORMAT => 'int64u',
766             NOTES => q{
767             64-bit data sizes for MBWF/RF64 files. See
768             L for the specification.
769             },
770             0 => {
771             Name => 'RIFFSize64',
772             PrintConv => \&Image::ExifTool::ConvertFileSize,
773             },
774             1 => {
775             Name => 'DataSize64',
776             DataMember => 'DataSize64',
777             RawConv => '$$self{DataSize64} = $val',
778             PrintConv => \&Image::ExifTool::ConvertFileSize,
779             },
780             2 => 'NumberOfSamples64',
781             # (after this comes a table of size overrides for chunk
782             # types other than 'data', but since these are currently
783             # very unlikely, support for these is not yet implemented)
784             );
785              
786             # Sampler chunk (ref 16)
787             %Image::ExifTool::RIFF::Sampler = (
788             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
789             GROUPS => { 2 => 'Audio' },
790             FORMAT => 'int32u',
791             0 => 'Manufacturer',
792             1 => 'Product',
793             2 => 'SamplePeriod',
794             3 => 'MIDIUnityNote',
795             4 => 'MIDIPitchFraction',
796             5 => {
797             Name => 'SMPTEFormat',
798             PrintConv => {
799             0 => 'none',
800             24 => '24 fps',
801             25 => '25 fps',
802             29 => '29 fps',
803             30 => '30 fps',
804             },
805             },
806             6 => {
807             Name => 'SMPTEOffset',
808             Notes => 'HH:MM:SS:FF',
809             ValueConv => q{
810             my $str = sprintf('%.8x', $val);
811             $str =~ s/(..)(..)(..)(..)/$1:$2:$3:$4/;
812             return $str;
813             },
814             },
815             7 => 'NumSampleLoops',
816             8 => 'SamplerDataLen',
817             9 => { Name => 'SamplerData', Format => 'undef[$size-40]', Binary => 1 },
818             );
819              
820             # Instrument chunk (ref 16)
821             %Image::ExifTool::RIFF::Instrument = (
822             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
823             GROUPS => { 2 => 'Audio' },
824             FORMAT => 'int8s',
825             0 => 'UnshiftedNote',
826             1 => 'FineTune',
827             2 => 'Gain',
828             3 => 'LowNote',
829             4 => 'HighNote',
830             5 => 'LowVelocity',
831             6 => 'HighVelocity',
832             );
833              
834             # Sub chunks of INFO LIST chunk
835             %Image::ExifTool::RIFF::Info = (
836             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
837             GROUPS => { 2 => 'Audio' },
838             FORMAT => 'string',
839             NOTES => q{
840             RIFF INFO tags found in AVI video and WAV audio files. Tags which are part
841             of the EXIF 2.3 specification have an underlined Tag Name in the HTML
842             version of this documentation. Other tags are found in AVI files generated
843             by some software.
844             },
845             IARL => 'ArchivalLocation',
846             IART => { Name => 'Artist', Groups => { 2 => 'Author' } },
847             ICMS => 'Commissioned',
848             ICMT => 'Comment',
849             ICOP => { Name => 'Copyright', Groups => { 2 => 'Author' } },
850             ICRD => {
851             Name => 'DateCreated',
852             Groups => { 2 => 'Time' },
853             ValueConv => '$_=$val; s/-/:/g; $_',
854             PrintConv => '$self->ConvertDateTime($val)',
855             },
856             ICRP => 'Cropped',
857             IDIM => 'Dimensions',
858             IDPI => 'DotsPerInch',
859             IENG => 'Engineer',
860             IGNR => 'Genre',
861             IKEY => 'Keywords',
862             ILGT => 'Lightness',
863             IMED => 'Medium',
864             INAM => 'Title',
865             ITRK => 'TrackNumber',
866             IPLT => 'NumColors',
867             IPRD => 'Product',
868             ISBJ => 'Subject',
869             ISFT => {
870             Name => 'Software',
871             # remove trailing nulls/spaces and split at first null
872             # (Casio writes "CASIO" in unicode after the first null)
873             ValueConv => '$_=$val; s/(\s*\0)+$//; s/(\s*\0)/, /; s/\0+//g; $_',
874             },
875             ISHP => 'Sharpness',
876             ISRC => 'Source',
877             ISRF => 'SourceForm',
878             ITCH => 'Technician',
879             #
880             # 3rd party tags
881             #
882             # internet movie database (ref 12)
883             ISGN => 'SecondaryGenre',
884             IWRI => 'WrittenBy',
885             IPRO => 'ProducedBy',
886             ICNM => 'Cinematographer',
887             IPDS => 'ProductionDesigner',
888             IEDT => 'EditedBy',
889             ICDS => 'CostumeDesigner',
890             IMUS => 'MusicBy',
891             ISTD => 'ProductionStudio',
892             IDST => 'DistributedBy',
893             ICNT => 'Country',
894             ILNG => 'Language',
895             IRTD => 'Rating',
896             ISTR => 'Starring',
897             # MovieID (ref12)
898             TITL => 'Title',
899             DIRC => 'Directory',
900             YEAR => 'Year',
901             GENR => 'Genre',
902             COMM => 'Comments',
903             LANG => 'Language',
904             AGES => 'Rated',
905             STAR => 'Starring',
906             CODE => 'EncodedBy',
907             PRT1 => 'Part',
908             PRT2 => 'NumberOfParts',
909             # Morgan Multimedia INFO tags (ref 12)
910             IAS1 => 'FirstLanguage',
911             IAS2 => 'SecondLanguage',
912             IAS3 => 'ThirdLanguage',
913             IAS4 => 'FourthLanguage',
914             IAS5 => 'FifthLanguage',
915             IAS6 => 'SixthLanguage',
916             IAS7 => 'SeventhLanguage',
917             IAS8 => 'EighthLanguage',
918             IAS9 => 'NinthLanguage',
919             ICAS => 'DefaultAudioStream',
920             IBSU => 'BaseURL',
921             ILGU => 'LogoURL',
922             ILIU => 'LogoIconURL',
923             IWMU => 'WatermarkURL',
924             IMIU => 'MoreInfoURL',
925             IMBI => 'MoreInfoBannerImage',
926             IMBU => 'MoreInfoBannerURL',
927             IMIT => 'MoreInfoText',
928             # GSpot INFO tags (ref 12)
929             IENC => 'EncodedBy',
930             IRIP => 'RippedBy',
931             # Sound Forge Pro tags
932             DISP => 'SoundSchemeTitle',
933             TLEN => { Name => 'Length', ValueConv => '$val/1000', PrintConv => '"$val s"' },
934             TRCK => 'TrackNumber',
935             TURL => 'URL',
936             TVER => 'Version',
937             LOCA => 'Location',
938             TORG => 'Organization',
939             # Sony Vegas AVI tags, also used by SCLive and Adobe Premier (ref 11)
940             TAPE => {
941             Name => 'TapeName',
942             Groups => { 2 => 'Video' },
943             },
944             TCOD => {
945             Name => 'StartTimecode',
946             # this is the tape time code for the start of the video
947             Groups => { 2 => 'Video' },
948             ValueConv => '$val * 1e-7',
949             PrintConv => \&ConvertTimecode,
950             },
951             TCDO => {
952             Name => 'EndTimecode',
953             Groups => { 2 => 'Video' },
954             ValueConv => '$val * 1e-7',
955             PrintConv => \&ConvertTimecode,
956             },
957             VMAJ => {
958             Name => 'VegasVersionMajor',
959             Groups => { 2 => 'Video' },
960             },
961             VMIN => {
962             Name => 'VegasVersionMinor',
963             Groups => { 2 => 'Video' },
964             },
965             CMNT => {
966             Name => 'Comment',
967             Groups => { 2 => 'Video' },
968             },
969             RATE => {
970             Name => 'Rate', #? (video? units?)
971             Groups => { 2 => 'Video' },
972             },
973             STAT => {
974             Name => 'Statistics',
975             Groups => { 2 => 'Video' },
976             # ("7318 0 3.430307 1", "0 0 3500.000000 1", "7 0 3.433228 1")
977             PrintConv => [
978             '"$val frames captured"',
979             '"$val dropped"',
980             '"Data rate $val"',
981             { 0 => 'Bad', 1 => 'OK' }, # capture OK?
982             ],
983             },
984             DTIM => {
985             Name => 'DateTimeOriginal',
986             Description => 'Date/Time Original',
987             Groups => { 2 => 'Time' },
988             ValueConv => q{
989             my @v = split ' ', $val;
990             return undef unless @v == 2;
991             # the Kodak EASYSHARE Sport stores this incorrectly as a string:
992             return $val if $val =~ /^\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2}$/;
993             # get time in seconds
994             $val = 1e-7 * ($v[0] * 4294967296 + $v[1]);
995             # shift from Jan 1, 1601 to Jan 1, 1970
996             $val -= 134774 * 24 * 3600 if $val != 0;
997             return Image::ExifTool::ConvertUnixTime($val);
998             },
999             PrintConv => '$self->ConvertDateTime($val)',
1000             },
1001             # not observed, but apparently part of the standard:
1002             IDIT => {
1003             Name => 'DateTimeOriginal',
1004             Description => 'Date/Time Original',
1005             Groups => { 2 => 'Time' },
1006             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
1007             PrintConv => '$self->ConvertDateTime($val)',
1008             },
1009             ISMP => 'TimeCode',
1010             );
1011              
1012             # Sub chunks of EXIF LIST chunk
1013             %Image::ExifTool::RIFF::Exif = (
1014             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1015             GROUPS => { 2 => 'Audio' },
1016             NOTES => 'These tags are part of the EXIF 2.3 specification for WAV audio files.',
1017             ever => 'ExifVersion',
1018             erel => 'RelatedImageFile',
1019             etim => { Name => 'TimeCreated', Groups => { 2 => 'Time' } },
1020             ecor => { Name => 'Make', Groups => { 2 => 'Camera' } },
1021             emdl => { Name => 'Model', Groups => { 2 => 'Camera' }, Description => 'Camera Model Name' },
1022             emnt => { Name => 'MakerNotes', Binary => 1 },
1023             eucm => {
1024             Name => 'UserComment',
1025             PrintConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,"RIFF:UserComment")',
1026             },
1027             );
1028              
1029             # Sub chunks of hdrl LIST chunk
1030             %Image::ExifTool::RIFF::Hdrl = (
1031             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1032             GROUPS => { 2 => 'Image' },
1033             avih => {
1034             Name => 'AVIHeader',
1035             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AVIHeader' },
1036             },
1037             IDIT => {
1038             Name => 'DateTimeOriginal',
1039             Description => 'Date/Time Original',
1040             Groups => { 2 => 'Time' },
1041             ValueConv => 'Image::ExifTool::RIFF::ConvertRIFFDate($val)',
1042             PrintConv => '$self->ConvertDateTime($val)',
1043             },
1044             ISMP => 'TimeCode',
1045             LIST_strl => {
1046             Name => 'Stream',
1047             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::Stream' },
1048             },
1049             LIST_odml => {
1050             Name => 'OpenDML',
1051             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::OpenDML' },
1052             },
1053             );
1054              
1055             # Sub chunks of Tdat LIST chunk (ref PH)
1056             %Image::ExifTool::RIFF::Tdat = (
1057             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1058             GROUPS => { 2 => 'Video' },
1059             # (have seen tc_O, tc_A, rn_O and rn_A)
1060             );
1061              
1062             # RIFF character set chunk
1063             %Image::ExifTool::RIFF::CSET = (
1064             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1065             GROUPS => { 2 => 'Other' },
1066             FORMAT => 'int16u',
1067             0 => {
1068             Name => 'CodePage',
1069             RawConv => '$$self{CodePage} = $val',
1070             },
1071             1 => 'CountryCode',
1072             2 => 'LanguageCode',
1073             3 => 'Dialect',
1074             );
1075              
1076             %Image::ExifTool::RIFF::AVIHeader = (
1077             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1078             GROUPS => { 2 => 'Video' },
1079             FORMAT => 'int32u',
1080             FIRST_ENTRY => 0,
1081             0 => {
1082             Name => 'FrameRate',
1083             # (must use RawConv because raw value used in Composite tag)
1084             RawConv => '$val ? 1e6 / $val : undef',
1085             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1086             },
1087             1 => {
1088             Name => 'MaxDataRate',
1089             Notes => q{
1090             converted using SI byte prefixes unles the API ByteUnit option is set to
1091             "Binary"
1092             },
1093             PrintConv => q{
1094             my ($unit, $div) = $self->Options('ByteUnit') eq 'Binary' ? ('KiB/s',1024) : ('kB/s',1000);
1095             my $tmp = $val / $div;
1096             $tmp > 9999 and $tmp /= $div, $unit =~ s/^./M/;
1097             sprintf('%.4g %s', $tmp, $unit);
1098             },
1099             },
1100             # 2 => 'PaddingGranularity',
1101             # 3 => 'Flags',
1102             4 => 'FrameCount',
1103             # 5 => 'InitialFrames',
1104             6 => 'StreamCount',
1105             # 7 => 'SuggestedBufferSize',
1106             8 => 'ImageWidth',
1107             9 => 'ImageHeight',
1108             );
1109              
1110             %Image::ExifTool::RIFF::Stream = (
1111             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1112             GROUPS => { 2 => 'Image' },
1113             strh => {
1114             Name => 'StreamHeader',
1115             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamHeader' },
1116             },
1117             strn => 'StreamName',
1118             strd => { #PH
1119             Name => 'StreamData',
1120             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::StreamData' },
1121             },
1122             strf => [
1123             {
1124             Name => 'AudioFormat',
1125             Condition => '$$self{RIFFStreamType} eq "auds"',
1126             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::AudioFormat' },
1127             },
1128             {
1129             Name => 'VideoFormat',
1130             Condition => '$$self{RIFFStreamType} eq "vids"',
1131             SubDirectory => { TagTable => 'Image::ExifTool::BMP::Main' },
1132             },
1133             {
1134             Name => 'TextFormat',
1135             Condition => '$$self{RIFFStreamType} eq "txts"',
1136             Hidden => 1,
1137             RawConv => '$self->Options("ExtractEmbedded") or $self->Warn("Use ExtractEmbedded option to extract timed text",3); undef',
1138             },
1139             ],
1140             );
1141              
1142             # Open DML tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf)
1143             %Image::ExifTool::RIFF::OpenDML = (
1144             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessChunks,
1145             GROUPS => { 2 => 'Video' },
1146             dmlh => {
1147             Name => 'ExtendedAVIHeader',
1148             SubDirectory => { TagTable => 'Image::ExifTool::RIFF::ExtAVIHdr' },
1149             },
1150             );
1151              
1152             # Extended AVI Header tags (ref http://www.morgan-multimedia.com/download/odmlff2.pdf)
1153             %Image::ExifTool::RIFF::ExtAVIHdr = (
1154             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1155             GROUPS => { 2 => 'Video' },
1156             FORMAT => 'int32u',
1157             0 => 'TotalFrameCount',
1158             );
1159              
1160             %Image::ExifTool::RIFF::StreamHeader = (
1161             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1162             GROUPS => { 2 => 'Video' },
1163             FORMAT => 'int32u',
1164             FIRST_ENTRY => 0,
1165             PRIORITY => 0, # so we get values from the first stream
1166             0 => {
1167             Name => 'StreamType',
1168             Format => 'string[4]',
1169             RawConv => '$$self{RIFFStreamNum} = ($$self{RIFFStreamNum} || 0) + 1; $$self{RIFFStreamType} = $val',
1170             PrintConv => {
1171             auds => 'Audio',
1172             mids => 'MIDI',
1173             txts => 'Text',
1174             vids => 'Video',
1175             iavs => 'Interleaved Audio+Video',
1176             },
1177             },
1178             1 => [
1179             {
1180             Name => 'AudioCodec',
1181             Condition => '$$self{RIFFStreamType} eq "auds"',
1182             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1183             Format => 'string[4]',
1184             },
1185             {
1186             Name => 'VideoCodec',
1187             Condition => '$$self{RIFFStreamType} eq "vids"',
1188             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1189             Format => 'string[4]',
1190             },
1191             {
1192             Name => 'Codec',
1193             Format => 'string[4]',
1194             RawConv => '$$self{RIFFStreamCodec}[$$self{RIFFStreamNum}-1] = $val',
1195             },
1196             ],
1197             # 2 => 'StreamFlags',
1198             # 3 => 'StreamPriority',
1199             # 3.5 => 'Language',
1200             # 4 => 'InitialFrames',
1201             5 => [
1202             {
1203             Name => 'AudioSampleRate',
1204             Condition => '$$self{RIFFStreamType} eq "auds"',
1205             Format => 'rational64u',
1206             ValueConv => '$val ? 1/$val : 0',
1207             PrintConv => 'int($val * 100 + 0.5) / 100',
1208             },
1209             {
1210             Name => 'VideoFrameRate',
1211             Condition => '$$self{RIFFStreamType} eq "vids"',
1212             Format => 'rational64u',
1213             # (must use RawConv because raw value used in Composite tag)
1214             RawConv => '$val ? 1/$val : undef',
1215             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1216             },
1217             {
1218             Name => 'StreamSampleRate',
1219             Format => 'rational64u',
1220             ValueConv => '$val ? 1/$val : 0',
1221             PrintConv => 'int($val * 1000 + 0.5) / 1000',
1222             },
1223             ],
1224             # 7 => 'Start',
1225             8 => [
1226             {
1227             Name => 'AudioSampleCount',
1228             Condition => '$$self{RIFFStreamType} eq "auds"',
1229             },
1230             {
1231             Name => 'VideoFrameCount',
1232             Condition => '$$self{RIFFStreamType} eq "vids"',
1233             },
1234             {
1235             Name => 'StreamSampleCount',
1236             },
1237             ],
1238             # 9 => 'SuggestedBufferSize',
1239             10 => {
1240             Name => 'Quality',
1241             PrintConv => '$val eq 0xffffffff ? "Default" : $val',
1242             },
1243             11 => {
1244             Name => 'SampleSize',
1245             PrintConv => '$val ? "$val byte" . ($val==1 ? "" : "s") : "Variable"',
1246             },
1247             # 12 => { Name => 'Frame', Format => 'int16u[4]' },
1248             );
1249              
1250             %Image::ExifTool::RIFF::StreamData = ( #PH
1251             PROCESS_PROC => \&Image::ExifTool::RIFF::ProcessStreamData,
1252             GROUPS => { 2 => 'Video' },
1253             NOTES => q{
1254             This chunk is used to store proprietary information in AVI videos from some
1255             cameras. The first 4 characters of the data are used as the Tag ID below.
1256             },
1257             AVIF => {
1258             Name => 'AVIF',
1259             SubDirectory => {
1260             TagTable => 'Image::ExifTool::Exif::Main',
1261             DirName => 'IFD0',
1262             Start => 8,
1263             ByteOrder => 'LittleEndian',
1264             },
1265             },
1266             CASI => { # (used by Casio GV-10)
1267             Name => 'CasioData',
1268             SubDirectory => { TagTable => 'Image::ExifTool::Casio::AVI' },
1269             },
1270             Zora => 'VendorName', # (Samsung PL90 AVI files)
1271             unknown => {
1272             Name => 'UnknownData',
1273             # try to interpret unknown stream data as a string
1274             RawConv => '$_=$val; /^[^\0-\x1f\x7f-\xff]+$/ ? $_ : undef',
1275             },
1276             );
1277              
1278             # VP8 bitstream (ref http://www.rfc-editor.org/rfc/pdfrfc/rfc6386.txt.pdf)
1279             %Image::ExifTool::RIFF::VP8 = (
1280             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1281             GROUPS => { 2 => 'Image' },
1282             NOTES => q{
1283             This chunk is found in simple-format (lossy) WebP files. See
1284             L for the WebP
1285             container specification.
1286             },
1287             0 => {
1288             Name => 'VP8Version',
1289             Mask => 0x0e,
1290             PrintConv => {
1291             0 => '0 (bicubic reconstruction, normal loop)',
1292             1 => '1 (bilinear reconstruction, simple loop)',
1293             2 => '2 (bilinear reconstruction, no loop)',
1294             3 => '3 (no reconstruction, no loop)',
1295             },
1296             },
1297             6 => {
1298             Name => 'ImageWidth',
1299             Format => 'int16u',
1300             Mask => 0x3fff,
1301             Priority => 0,
1302             },
1303             6.1 => {
1304             Name => 'HorizontalScale',
1305             Format => 'int16u',
1306             Mask => 0xc000,
1307             },
1308             8 => {
1309             Name => 'ImageHeight',
1310             Format => 'int16u',
1311             Mask => 0x3fff,
1312             Priority => 0,
1313             },
1314             8.1 => {
1315             Name => 'VerticalScale',
1316             Format => 'int16u',
1317             Mask => 0xc000,
1318             },
1319             );
1320              
1321             # WebP lossless info (ref 14)
1322             %Image::ExifTool::RIFF::VP8L = (
1323             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1324             NOTES => 'This chunk is found in lossless WebP files.',
1325             GROUPS => { 2 => 'Image' },
1326             1 => {
1327             Name => 'ImageWidth',
1328             Format => 'int16u',
1329             Priority => 0,
1330             # add " (lossless)" to FileType since image has a VP8L (lossless) chunk
1331             RawConv => q{
1332             $self->OverrideFileType($$self{VALUE}{FileType} . ' (lossless)', undef, 'webp');
1333             return $val;
1334             },
1335             ValueConv => '($val & 0x3fff) + 1',
1336             },
1337             2 => {
1338             Name => 'ImageHeight',
1339             Format => 'int32u',
1340             Priority => 0,
1341             ValueConv => '(($val >> 6) & 0x3fff) + 1',
1342             },
1343             4 => {
1344             Name => 'AlphaIsUsed',
1345             Mask => 0x10,
1346             PrintConv => { 0 => 'No', 1 => 'Yes' },
1347             },
1348             );
1349              
1350             # WebP extended info (ref 14)
1351             %Image::ExifTool::RIFF::VP8X = (
1352             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1353             GROUPS => { 2 => 'Image' },
1354             NOTES => 'This chunk is found in extended WebP files.',
1355             # 0 - bitmask: 2=ICC, 3=alpha, 4=EXIF, 5=XMP, 6=animation
1356             0 => {
1357             Name => 'WebP_Flags',
1358             Description => 'WebP Flags',
1359             Notes => 'flags used in Extended WebP images',
1360             Format => 'int32u',
1361             PrintConv => { BITMASK => {
1362             1 => 'Animation',
1363             2 => 'XMP',
1364             3 => 'EXIF',
1365             4 => 'Alpha',
1366             5 => 'ICC Profile',
1367             }},
1368             },
1369             4 => {
1370             Name => 'ImageWidth',
1371             Format => 'int32u',
1372             ValueConv => '($val & 0xffffff) + 1',
1373             },
1374             6 => {
1375             Name => 'ImageHeight',
1376             Format => 'int32u',
1377             ValueConv => '($val >> 8) + 1',
1378             },
1379             );
1380              
1381             # WebP animation info (ref 14)
1382             %Image::ExifTool::RIFF::ANIM = (
1383             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1384             GROUPS => { 2 => 'Image' },
1385             NOTES => 'WebP animation chunk.',
1386             0 => {
1387             Name => 'BackgroundColor',
1388             Format => 'int8u[4]',
1389             },
1390             4 => {
1391             Name => 'AnimationLoopCount',
1392             PrintConv => '$val || "inf"',
1393             },
1394             );
1395              
1396             # WebP animation frame info (ref 14)
1397             %Image::ExifTool::RIFF::ANMF = (
1398             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1399             GROUPS => { 2 => 'Image' },
1400             NOTES => 'WebP animation frame chunk.',
1401             12 => {
1402             Name => 'Duration',
1403             Format => 'int32u',
1404             Notes => 'extracted as the sum of durations of all animation frames',
1405             RawConv => q{
1406             if (defined $$self{VALUE}{Duration}) {
1407             $$self{VALUE}{Duration} += $val & 0x0fff;
1408             return undef;
1409             }
1410             return $val & 0x0fff;
1411             },
1412             ValueConv => '$val / 1000',
1413             PrintConv => 'ConvertDuration($val)',
1414             },
1415             );
1416              
1417             # streamed USER txts written by Momento M6 dashcam (ref PH)
1418             %Image::ExifTool::RIFF::UserText = (
1419             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1420             GROUPS => { 2 => 'Location' },
1421             NOTES => q{
1422             Tags decoded from the USER-format txts stream written by Momento M6 dashcam.
1423             Extracted only if the ExtractEmbedded option is used.
1424             },
1425             # (little-endian)
1426             # 0 - int32u: 32
1427             # 4 - int32u: sample number (starting from unknown offset)
1428             # 8 - int8u[4]: "w x y z" ? (w 0=front cam, 1=rear cam, z mostly 5-8)
1429             # 12 - int8u[4]: "0 x 1 0" ? (x incrementing once per second)
1430             # 16 - int8u[4]: "0 32 0 x" ?
1431             # 20 - int32u: 100-150(mostly), 250-300(once per second)
1432             # 24 - int8u[4]: "0 x y 0" ?
1433             28 => { Name => 'GPSAltitude', Format => 'int32u', ValueConv => '$val / 10' }, # (NC)
1434             # 32 - int32u: 0(mostly), 23(once per second)
1435             # 36 - int32u: 0
1436             40 => { Name => 'Accelerometer', Format => 'float[3]' },
1437             # 52 - int32u: 1
1438             56 => { Name => 'GPSSpeed', Format => 'float' }, # km/h
1439             60 => {
1440             Name => 'GPSLatitude',
1441             Format => 'float',
1442             # Note: these values are unsigned and I don't know where the hemisphere is stored,
1443             # but my only sample is from the U.S., so assume a positive latitude (for now)
1444             ValueConv => 'my $deg = int($val / 100); $deg + ($val - $deg * 100) / 60',
1445             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
1446             },
1447             64 => {
1448             Name => 'GPSLongitude',
1449             Format => 'float',
1450             # Note: these values are unsigned and I don't know where the hemisphere is stored,
1451             # but my only sample is from the U.S., so assume a negative longitude (for now)
1452             ValueConv => 'my $deg = int($val / 100); -($deg + ($val - $deg * 100) / 60)',
1453             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
1454             },
1455             68 => {
1456             Name => 'GPSDateTime',
1457             Description => 'GPS Date/Time',
1458             Groups => { 2 => 'Time' },
1459             Format => 'int32u',
1460             ValueConv => 'ConvertUnixTime($val)',
1461             # (likely local time, but clock seemed off by 3 hours in my sample)
1462             PrintConv => '$self->ConvertDateTime($val)',
1463             },
1464             );
1465              
1466             # WebP alpha info (ref 14)
1467             %Image::ExifTool::RIFF::ALPH = (
1468             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1469             GROUPS => { 2 => 'Image' },
1470             NOTES => 'WebP alpha chunk.',
1471             0 => {
1472             Name => 'AlphaPreprocessing',
1473             Mask => 0x03,
1474             PrintConv => {
1475             0 => 'none',
1476             1 => 'Level Reduction',
1477             },
1478             },
1479             0.1 => {
1480             Name => 'AlphaFiltering',
1481             Mask => 0x03,
1482             PrintConv => {
1483             0 => 'none',
1484             1 => 'Horizontal',
1485             2 => 'Vertical',
1486             3 => 'Gradient',
1487             },
1488             },
1489             0.2 => {
1490             Name => 'AlphaCompression',
1491             Mask => 0x03,
1492             PrintConv => {
1493             0 => 'none',
1494             1 => 'Lossless',
1495             },
1496             },
1497             );
1498              
1499             # Acidizer information (ref https://forums.cockos.com/showthread.php?t=227118)
1500             %Image::ExifTool::RIFF::Acidizer = (
1501             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1502             GROUPS => { 2 => 'Audio' },
1503             0 => {
1504             Name => 'AcidizerFlags',
1505             Format => 'int32u',
1506             PrintConv => { BITMASK => {
1507             0 => 'One shot',
1508             1 => 'Root note set',
1509             2 => 'Stretch',
1510             3 => 'Disk-based',
1511             4 => 'High octave',
1512             }},
1513             },
1514             4 => {
1515             Name => 'RootNote',
1516             Format => 'int16u',
1517             PrintConv => {
1518             0x30 => 'C', 0x3c => 'High C',
1519             0x31 => 'C#', 0x3d => 'High C#',
1520             0x32 => 'D', 0x3e => 'High D',
1521             0x33 => 'D#', 0x3f => 'High D#',
1522             0x34 => 'E', 0x40 => 'High E',
1523             0x35 => 'F', 0x41 => 'High F',
1524             0x36 => 'F#', 0x42 => 'High F#',
1525             0x37 => 'G', 0x43 => 'High G',
1526             0x38 => 'G#', 0x44 => 'High G#',
1527             0x39 => 'A', 0x45 => 'High A',
1528             0x3a => 'A#', 0x46 => 'High A#',
1529             0x3b => 'B', 0x47 => 'High B',
1530             },
1531             },
1532             12 => {
1533             Name => 'Beats',
1534             Format => 'int32u',
1535             },
1536             16 => {
1537             Name => 'Meter',
1538             Format => 'int16u[2]',
1539             PrintConv => '$val =~ s/(\d+) (\d+)/$2\/$1/; $val', # denominator comes first, so swap them
1540             },
1541             20 => {
1542             Name => 'Tempo',
1543             Format => 'float',
1544             },
1545             );
1546              
1547             # RIFF composite tags
1548             %Image::ExifTool::RIFF::Composite = (
1549             Duration => {
1550             Require => {
1551             0 => 'RIFF:FrameRate',
1552             1 => 'RIFF:FrameCount',
1553             },
1554             Desire => {
1555             2 => 'VideoFrameRate',
1556             3 => 'VideoFrameCount',
1557             },
1558             RawConv => 'Image::ExifTool::RIFF::CalcDuration($self, @val)',
1559             PrintConv => 'ConvertDuration($val)',
1560             },
1561             Duration2 => {
1562             Name => 'Duration',
1563             Require => {
1564             0 => 'RIFF:AvgBytesPerSec',
1565             },
1566             Desire => {
1567             1 => 'FileSize', # (only used if 'data' length isn't available)
1568             # check FrameCount because this calculation only applies
1569             # to audio-only files (eg. WAV)
1570             2 => 'FrameCount',
1571             3 => 'VideoFrameCount',
1572             },
1573             # (can't calculate duration like this for compressed audio types)
1574             RawConv => q{
1575             return undef if $$self{FileType} =~ /^(LA|OFR|PAC|WV)$/ or $val[2] or $val[3];
1576             return undef unless $val[0] and ($$self{RIFFDataLen} or $val[1]);
1577             return(($$self{RIFFDataLen} || $val[1]) / $val[0]);
1578             },
1579             PrintConv => 'ConvertDuration($val)',
1580             },
1581             );
1582              
1583             # add our composite tags
1584             Image::ExifTool::AddCompositeTags('Image::ExifTool::RIFF');
1585              
1586              
1587             #------------------------------------------------------------------------------
1588             # AutoLoad our writer routines when necessary
1589             #
1590             sub AUTOLOAD
1591             {
1592 1     1   5 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
1593             }
1594              
1595             #------------------------------------------------------------------------------
1596             # Convert RIFF date to EXIF format
1597             my %monthNum = (
1598             Jan=>1, Feb=>2, Mar=>3, Apr=>4, May=>5, Jun=>6,
1599             Jul=>7, Aug=>8, Sep=>9, Oct=>10,Nov=>11,Dec=>12
1600             );
1601             sub ConvertRIFFDate($)
1602             {
1603 2     2 0 7 my $val = shift;
1604 2         9 my @part = split ' ', $val;
1605 2         5 my $mon;
1606 2 100 66     44 if (@part >= 5 and $mon = $monthNum{ucfirst(lc($part[1]))}) {
    50          
    0          
1607             # the standard AVI date format (eg. "Mon Mar 10 15:04:43 2003")
1608 1         9 $val = sprintf("%.4d:%.2d:%.2d %s", $part[4],
1609             $mon, $part[2], $part[3]);
1610             } elsif ($val =~ m{(\d{4})/\s*(\d+)/\s*(\d+)/?\s+(\d+):\s*(\d+)\s*(P?)}) {
1611             # but the Casio QV-3EX writes dates like "2001/ 1/27 1:42PM",
1612             # and the Casio EX-Z30 writes "2005/11/28/ 09:19"... doh!
1613 1 50       24 $val = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:00",$1,$2,$3,$4+($6?12:0),$5);
1614             } elsif ($val =~ m{(\d{4})[-/](\d+)[-/](\d+)\s+(\d+:\d+:\d+)}) {
1615             # the Konica KD500Z writes "2002-12-16 15:35:01\0\0"
1616 0         0 $val = "$1:$2:$3 $4";
1617             }
1618 2         24 return $val;
1619             }
1620              
1621             #------------------------------------------------------------------------------
1622             # Print time
1623             # Inputs: 0) time in seconds
1624             # Returns: time string
1625             sub ConvertTimecode($)
1626             {
1627 0     0 0 0 my $val = shift;
1628 0         0 my $hr = int($val / 3600);
1629 0         0 $val -= $hr * 3600;
1630 0         0 my $min = int($val / 60);
1631 0         0 $val -= $min * 60;
1632 0         0 my $ss = sprintf('%05.2f', $val);
1633 0 0       0 if ($ss >= 60) { # handle round-off problems
1634 0         0 $ss = '00.00';
1635 0 0       0 ++$min >= 60 and $min -= 60, ++$hr;
1636             }
1637 0         0 return sprintf('%d:%.2d:%s', $hr, $min, $ss);
1638             }
1639              
1640             #------------------------------------------------------------------------------
1641             # Calculate duration of RIFF
1642             # Inputs: 0) ExifTool ref, 1/2) RIFF:FrameRate/Count, 2/3) VideoFrameRate/Count
1643             # Returns: Duration in seconds or undef
1644             # Notes: Sums duration of all sub-documents (concatenated AVI files)
1645             sub CalcDuration($@)
1646             {
1647 2     2 0 14 my ($et, @val) = @_;
1648 2         5 my $totalDuration = 0;
1649 2         5 my $subDoc = 0;
1650 2         30 my @keyList;
1651 2         6 for (;;) {
1652             # this is annoying. Apparently (although I couldn't verify this), FrameCount
1653             # in the RIFF header includes multiple video tracks if they exist (eg. with the
1654             # FujiFilm REAL 3D AVI's), but the video stream information isn't reliable for
1655             # some cameras (eg. Olympus FE models), so use the video stream information
1656             # only if the RIFF header duration is 2 to 3 times longer
1657 2         5 my $dur1;
1658 2 50       13 $dur1 = $val[1] / $val[0] if $val[0];
1659 2 50 33     19 if ($val[2] and $val[3]) {
1660 2         6 my $dur2 = $val[3] / $val[2];
1661 2         7 my $rat = $dur1 / $dur2;
1662 2 50 33     12 $dur1 = $dur2 if $rat > 1.9 and $rat < 3.1;
1663             }
1664 2 50       9 $totalDuration += $dur1 if defined $dur1;
1665 2 50       14 last unless $subDoc++ < $$et{DOC_COUNT};
1666             # get tag values for next sub-document
1667 0         0 my @tags = qw(FrameRate FrameCount VideoFrameRate VideoFrameCount);
1668 0         0 my $rawValue = $$et{VALUE};
1669 0         0 my ($i, $j, $key, $keys);
1670 0         0 for ($i=0; $i<@tags; ++$i) {
1671 0 0       0 if ($subDoc == 1) {
1672             # generate list of available keys for each tag
1673 0         0 $keys = $keyList[$i] = [ ];
1674 0         0 for ($j=0; ; ++$j) {
1675 0         0 $key = $tags[$i];
1676 0 0       0 $key .= " ($j)" if $j;
1677 0 0       0 last unless defined $$rawValue{$key};
1678 0         0 push @$keys, $key;
1679             }
1680             } else {
1681 0         0 $keys = $keyList[$i];
1682             }
1683             # find key for tag in this sub-document
1684 0         0 my $grp = "Doc$subDoc";
1685 0 0       0 $grp .= ":RIFF" if $i < 2; # (tags 0 and 1 also in RIFF group)
1686 0         0 $key = $et->GroupMatches($grp, $keys);
1687 0 0       0 $val[$i] = $key ? $$rawValue{$key} : undef;
1688             }
1689 0 0 0     0 last unless defined $val[0] and defined $val[1]; # (Require'd tags)
1690             }
1691 2         28 return $totalDuration;
1692             }
1693              
1694             #------------------------------------------------------------------------------
1695             # Process stream data
1696             # Inputs: 0) ExifTool object ref, 1) dirInfo reference, 2) tag table ref
1697             # Returns: 1 on success
1698             sub ProcessStreamData($$$)
1699             {
1700 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1701 0         0 my $dataPt = $$dirInfo{DataPt};
1702 0         0 my $start = $$dirInfo{DirStart};
1703 0         0 my $size = $$dirInfo{DirLen};
1704 0 0       0 return 0 if $size < 4;
1705 0 0       0 if ($et->Options('Verbose')) {
1706 0         0 $et->VerboseDir($$dirInfo{DirName}, 0, $size);
1707             }
1708 0         0 my $tag = substr($$dataPt, $start, 4);
1709 0         0 my $tagInfo = $et->GetTagInfo($tagTbl, $tag);
1710 0 0       0 unless ($tagInfo) {
1711 0         0 $tagInfo = $et->GetTagInfo($tagTbl, 'unknown');
1712 0 0       0 return 1 unless $tagInfo;
1713             }
1714 0         0 my $subdir = $$tagInfo{SubDirectory};
1715 0 0       0 if ($$tagInfo{SubDirectory}) {
1716 0   0     0 my $offset = $$subdir{Start} || 0;
1717 0         0 my $baseShift = $$dirInfo{DataPos} + $$dirInfo{DirStart} + $offset;
1718             my %subdirInfo = (
1719             DataPt => $dataPt,
1720             DataPos => $$dirInfo{DataPos} - $baseShift,
1721             Base => ($$dirInfo{Base} || 0) + $baseShift,
1722             DataLen => $$dirInfo{DataLen},
1723             DirStart=> $$dirInfo{DirStart} + $offset,
1724             DirLen => $$dirInfo{DirLen} - $offset,
1725             DirName => $$subdir{DirName},
1726             Parent => $$dirInfo{DirName},
1727 0   0     0 );
1728 0 0       0 unless ($offset) {
1729             # allow processing of 2nd directory at the same address
1730 0         0 my $addr = $subdirInfo{DirStart} + $subdirInfo{DataPos} + $subdirInfo{Base};
1731 0         0 delete $$et{PROCESSED}{$addr}
1732             }
1733             # (we could set FIRST_EXIF_POS to $subdirInfo{Base} here to make
1734             # htmlDump offsets relative to EXIF base if we wanted...)
1735 0         0 my $subTable = GetTagTable($$subdir{TagTable});
1736 0         0 $et->ProcessDirectory(\%subdirInfo, $subTable);
1737             } else {
1738             $et->HandleTag($tagTbl, $tag, undef,
1739             DataPt => $dataPt,
1740             DataPos => $$dirInfo{DataPos},
1741 0         0 Start => $start,
1742             Size => $size,
1743             TagInfo => $tagInfo,
1744             );
1745             }
1746 0         0 return 1;
1747             }
1748              
1749             #------------------------------------------------------------------------------
1750             # Make tag information hash for unknown tag
1751             # Inputs: 0) Tag table ref, 1) tag ID
1752             sub MakeTagInfo($$)
1753             {
1754 0     0 0 0 my ($tagTbl, $tag) = @_;
1755 0         0 my $name = $tag;
1756 0         0 my $n = ($name =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg);
  0         0  
1757             # print in hex if tag is numerical
1758 0 0       0 $name = sprintf('0x%.4x',unpack('N',$tag)) if $n > 2;
1759 0         0 AddTagToTable($tagTbl, $tag, {
1760             Name => "Unknown_$name",
1761             Description => "Unknown $name",
1762             Unknown => 1,
1763             Binary => 1,
1764             });
1765             }
1766              
1767             #------------------------------------------------------------------------------
1768             # Process RIFF chunks
1769             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1770             # Returns: 1 on success
1771             sub ProcessChunks($$$)
1772             {
1773 13     13 0 32 my ($et, $dirInfo, $tagTbl) = @_;
1774 13         30 my $dataPt = $$dirInfo{DataPt};
1775 13         24 my $start = $$dirInfo{DirStart};
1776 13         26 my $size = $$dirInfo{DirLen};
1777 13         29 my $end = $start + $size;
1778 13   50     66 my $base = $$dirInfo{Base} || 0;
1779 13         49 my $verbose = $et->Options('Verbose');
1780 13         37 my $unknown = $et->Options('Unknown');
1781 13         45 my $charset = $et->Options('CharsetRIFF');
1782              
1783 13 50       56 unless ($charset) {
1784 13 50 33     112 if ($$et{CodePage}) {
    50          
1785 0         0 $charset = $$et{CodePage};
1786             } elsif (defined $charset and $charset eq '0') {
1787 13         29 $charset = 'Latin';
1788             }
1789             }
1790              
1791 13 50       35 $et->VerboseDir($$dirInfo{DirName}, 0, $size) if $verbose;
1792              
1793 13         72 while ($start + 8 < $end) {
1794 34         86 my $tag = substr($$dataPt, $start, 4);
1795 34         113 my $len = Get32u($dataPt, $start + 4);
1796 34         64 $start += 8;
1797 34 50       82 if ($start + $len > $end) {
1798 0         0 $et->Warn("Bad $tag chunk");
1799 0         0 return 0;
1800             }
1801 34 100 66     113 if ($tag eq 'LIST' and $len >= 4) {
1802 5         14 $tag .= '_' . substr($$dataPt, $start, 4);
1803 5         9 $len -= 4;
1804 5         8 $start += 4;
1805             }
1806 34         108 my $tagInfo = $et->GetTagInfo($tagTbl, $tag);
1807 34         57 my $baseShift = 0;
1808 34         50 my $val;
1809 34 100 33     92 if ($tagInfo) {
    50          
1810 28 100       81 if ($$tagInfo{SubDirectory}) {
    100          
1811             # adjust base if necessary (needed for Ricoh maker notes)
1812 17         38 my $newBase = $tagInfo->{SubDirectory}{Base};
1813 17 100       63 if (defined $newBase) {
1814             # different than your average Base eval...
1815             # here we use an absolute $start address
1816 1         3 $start += $base;
1817             #### eval Base ($start)
1818 1         82 $newBase = eval $newBase;
1819 1         6 $baseShift = $newBase - $base;
1820 1         3 $start -= $base;
1821             }
1822             } elsif (not $$tagInfo{Binary}) {
1823 10   66     48 my $format = $$tagInfo{Format} || $$tagTbl{FORMAT};
1824 10 100 66     68 if ($format and $format eq 'string') {
1825 3         12 $val = substr($$dataPt, $start, $len);
1826 3         23 $val =~ s/\0+$//; # remove trailing nulls from strings
1827             # decode if necessary
1828 3 50       26 $val = $et->Decode($val, $charset) if $charset;
1829             }
1830             }
1831             } elsif ($verbose or $unknown) {
1832 0         0 MakeTagInfo($tagTbl, $tag);
1833             }
1834             $et->HandleTag($tagTbl, $tag, $val,
1835             DataPt => $dataPt,
1836 34         232 DataPos => $$dirInfo{DataPos} - $baseShift,
1837             Start => $start,
1838             Size => $len,
1839             Base => $base + $baseShift,
1840             Addr => $base + $baseShift + $start,
1841             );
1842 34 100       103 ++$len if $len & 0x01; # must account for padding if odd number of bytes
1843 34         110 $start += $len;
1844             }
1845 13         46 return 1;
1846             }
1847              
1848             #------------------------------------------------------------------------------
1849             # Process BikeBro SGLT chunk (accelerometer data) (ref PH)
1850             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1851             # Returns: 1 on success
1852             sub ProcessSGLT($$$)
1853             {
1854 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1855 0         0 my $dataPt = $$dirInfo{DataPt};
1856 0         0 my $dataLen = length $$dataPt;
1857 0         0 my $ee = $et->Options('ExtractEmbedded');
1858 0         0 my $pos;
1859             # example accelerometer record:
1860             # 0 1 2 3 4 5 6 7
1861             # 00 00 00 24 02 00 00 01 17 04 00 00 00 00 00 00 00 00 9b 02
1862             # frame------ ?? Xs X---------- Ys Y---------- Zs Z----------
1863 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1864 0         0 for ($pos=0; $pos<=$dataLen-20; $pos+=20) {
1865 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1866 0         0 my $buff = substr($$dataPt, $pos);
1867 0         0 my @a = unpack('NCCNCNCN', $buff);
1868 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          
1869 0         0 $et->HandleTag($tagTbl, FrameNumber => $a[0]);
1870 0         0 $et->HandleTag($tagTbl, Accelerometer => "@acc");
1871 0 0       0 unless ($ee) {
1872 0         0 $et->Warn('Use ExtractEmbedded option to extract all accelerometer data', 3);
1873 0         0 last;
1874             }
1875             }
1876 0         0 delete $$et{SET_GROUP0};
1877 0         0 delete $$et{SET_GROUP1};
1878 0         0 $$et{DOC_NUM} = 0;
1879 0         0 return 0;
1880             }
1881              
1882             #------------------------------------------------------------------------------
1883             # Process BikeBro SLLT chunk (GPS information) (ref PH)
1884             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1885             # Returns: 1 on success
1886             sub ProcessSLLT($$$)
1887             {
1888 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1889 0         0 my $dataPt = $$dirInfo{DataPt};
1890 0         0 my $dataLen = length $$dataPt;
1891 0         0 my $ee = $et->Options('ExtractEmbedded');
1892 0         0 my $pos;
1893             # example GPS record:
1894             # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
1895             # 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
1896             # frame------ ?? lonDD lonDDDDDDDD latDD latDDDDDDDD alt-- spd-- hr mn sc yr--- mn dy EW NS
1897 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1898 0         0 for ($pos=0; $pos<=$dataLen-30; $pos+=30) {
1899 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1900 0         0 my $buff = substr($$dataPt, $pos);
1901 0         0 my @a = unpack('NCnNnNnnCCCnCCaa', $buff);
1902             # - is $a[1] perhaps GPSStatus? (only seen 1, or perhaps record type 1=GPS, 2=acc?)
1903 0         0 my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', @a[11..13, 8..10]);
1904 0         0 $et->HandleTag($tagTbl, FrameNumber => $a[0]);
1905 0         0 $et->HandleTag($tagTbl, GPSDateTime => $time);
1906 0 0       0 $et->HandleTag($tagTbl, GPSLatitude => ($a[4] + $a[5]/1e8) * ($a[15] eq 'S' ? -1 : 1));
1907 0 0       0 $et->HandleTag($tagTbl, GPSLongitude => ($a[2] + $a[3]/1e8) * ($a[14] eq 'W' ? -1 : 1));
1908 0         0 $et->HandleTag($tagTbl, GPSAltitude => $a[6]);
1909 0         0 $et->HandleTag($tagTbl, GPSSpeed => $a[7]);
1910 0         0 $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1911 0 0       0 unless ($ee) {
1912 0         0 $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3);
1913 0         0 last;
1914             }
1915             }
1916 0         0 delete $$et{SET_GROUP0};
1917 0         0 delete $$et{SET_GROUP1};
1918 0         0 $$et{DOC_NUM} = 0;
1919 0         0 return 1;
1920             }
1921              
1922             #------------------------------------------------------------------------------
1923             # Process Lucas streaming GPS information (Lucas LK-7900 Ace) (ref PH)
1924             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1925             # Returns: 1 on success
1926             sub ProcessLucas($$$)
1927             {
1928 0     0 0 0 my ($et, $dirInfo, $tagTbl) = @_;
1929 0         0 my $dataPt = $$dirInfo{DataPt};
1930 0         0 my $dataLen = length $$dataPt;
1931              
1932 0 0       0 unless ($et->Options('ExtractEmbedded')) {
1933 0         0 $et->Warn('Use ExtractEmbedded option to extract timed GPS', 3);
1934 0         0 return 1;
1935             }
1936 0         0 my %recLen = ( # record lengths (not including 4-byte ID)
1937             '0GDA' => 24,
1938             '0GPS' => 48,
1939             );
1940 0         0 my ($date,$time,$lat,$lon,$alt,$spd,$sat,$dop,$ew,$ns);
1941 0         0 $$et{SET_GROUP0} = $$et{SET_GROUP1} = 'RIFF';
1942 0         0 while ($$dataPt =~ /(0GDA|0GPS)/g) {
1943 0         0 my ($rec, $pos) = ($1, pos $$dataPt);
1944 0 0       0 $pos + $recLen{$rec} > $dataLen and $et->Warn("Truncated $1 record"), last;
1945 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1946             # records start with int64u sample date/time in ms since 1970
1947 0         0 $et->HandleTag($tagTbl, SampleDateTime => Get64u($dataPt, $pos) / 1000);
1948 0 0       0 if ($rec eq '0GPS') {
1949 0         0 my $len = Get32u($dataPt, $pos+8);
1950 0         0 my $endPos = $pos + $recLen{$rec} + $len;
1951 0 0       0 $endPos > $dataLen and $et->Warn('Truncated 0GPS record'), last;
1952 0         0 my $buff = substr($$dataPt, $pos+$recLen{$rec}, $len);
1953 0         0 while ($buff =~ /\$(GC|GA),(\d+),/g) {
1954 0         0 my $p = pos $buff;
1955 0         0 $time = $2;
1956 0 0       0 if ($1 eq 'GC') {
1957             # time date dist ? sat dop alt A
1958             # $GC,052350,180914,0000955,1,08,1.1,0017,,A*45\x0d\x0a\0
1959 0 0       0 if ($buff =~ /\G(\d+),\d*,\d*,(\d+),([-\d.]+),(\d+),\d*,A/g) {
1960 0         0 ($date,$sat,$dop,$alt) = ($1,$2,$3,$4);
1961             }
1962             } else {
1963             # time A lat lon spd N W
1964             # $GA,052351,A,0949.6626,07635.4439,049,N,E,*4C\x0d\x0a\0
1965 0 0       0 if ($buff =~ /\GA,([\d.]+),([\d.]+),(\d+),([NS]),([EW])/g) {
1966 0         0 ($lat,$lon,$spd,$ns,$ew) = ($1,$2,$3,$4,$5,$6);
1967             # lat/long are in DDDMM.MMMM format
1968 0         0 my $deg = int($lat / 100);
1969 0         0 $lat = $deg + ($lat - $deg * 100) / 60;
1970 0         0 $deg = int($lon / 100);
1971 0         0 $lon = $deg + ($lon - $deg * 100) / 60;
1972 0 0       0 $lat *= -1 if $ns eq 'S';
1973 0 0       0 $lon *= -1 if $ew eq 'W';
1974             }
1975             }
1976             # look ahead to next NMEA-like sentence, and store the fix
1977             # now only if the next sentence is not at the same time
1978 0 0       0 if ($buff !~ /\$(GC|GA),$time,/g) {
1979 0         0 pos($$dataPt) = $endPos;
1980 0 0 0     0 if ($$dataPt !~ /\$(GC|GA),(\d+)/ or $1 ne $time) {
1981 0         0 $time =~ s/(\d{2})(\d{2})(\d{2})/$1:$2:$3Z/;
1982 0 0       0 if ($date) {
1983 0         0 $date =~ s/(\d{2})(\d{2})(\d{2})/20$3:$2:$1/;
1984 0         0 $et->HandleTag($tagTbl, GPSDateTime => "$date $time");
1985             } else {
1986 0         0 $et->HandleTag($tagTbl, GPSTimeStamp => $time);
1987             }
1988 0 0       0 if (defined $lat) {
1989 0         0 $et->HandleTag($tagTbl, GPSLatitude => $lat);
1990 0         0 $et->HandleTag($tagTbl, GPSLongitude => $lon);
1991 0         0 $et->HandleTag($tagTbl, GPSSpeed => $spd);
1992             }
1993 0 0       0 if (defined $alt) {
1994 0         0 $et->HandleTag($tagTbl, GPSAltitude => $alt);
1995 0         0 $et->HandleTag($tagTbl, GPSSatellites => $sat);
1996 0         0 $et->HandleTag($tagTbl, GPSDOP => $dop);
1997             }
1998 0         0 undef $lat;
1999 0         0 undef $alt;
2000             }
2001             }
2002 0         0 pos($buff) = $p;
2003             }
2004 0         0 $pos += $len;
2005             } else { # this is an accelerometer (0GDA) record
2006             # record has 4 more int32s values (the last is always 57 or 58 --
2007             # maybe related to sample time in ms? -- not extracted)
2008 0         0 my @acc = unpack('x'.($pos+8).'V3', $$dataPt);
2009             # change to signed integer and divide by 256
2010 0 0       0 map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 256 } @acc;
  0         0  
  0         0  
2011 0         0 $et->HandleTag($tagTbl, Accelerometer => "@acc");
2012             }
2013 0         0 pos($$dataPt) = $pos + $recLen{$rec};
2014             }
2015 0         0 delete $$et{SET_GROUP0};
2016 0         0 delete $$et{SET_GROUP1};
2017 0         0 $$et{DOC_NUM} = 0;
2018 0         0 return 1;
2019             }
2020              
2021             #------------------------------------------------------------------------------
2022             # Extract information from a RIFF file
2023             # Inputs: 0) ExifTool object reference, 1) DirInfo reference
2024             # Returns: 1 on success, 0 if this wasn't a valid RIFF file
2025             sub ProcessRIFF($$)
2026             {
2027 7     7 0 21 my ($et, $dirInfo) = @_;
2028 7         54 my $raf = $$dirInfo{RAF};
2029 7         17 my ($buff, $buf2, $type, $mime, $err, $rf64, $moviEnd);
2030 7         30 my $verbose = $et->Options('Verbose');
2031 7         22 my $unknown = $et->Options('Unknown');
2032 7         22 my $validate = $et->Options('Validate');
2033 7         21 my $ee = $et->Options('ExtractEmbedded');
2034 7         17 my $hash = $$et{ImageDataHash};
2035 7         12 my $base = 0;
2036              
2037             # verify this is a valid RIFF file
2038 7 50       29 return 0 unless $raf->Read($buff, 12) == 12;
2039 7 50       89 if ($buff =~ /^(RIFF|RF64)....(.{4})/s) {
2040 7         33 $type = $riffType{$2};
2041 7 50       30 $rf64 = 1 if $1 eq 'RF64';
2042             } else {
2043             # minimal support for a few obscure lossless audio formats...
2044 0 0 0     0 return 0 unless $buff =~ /^(LA0[234]|OFR |LPAC|wvpk)/ and $raf->Read($buf2, 1024);
2045 0         0 $type = $riffType{$1};
2046 0         0 $buff .= $buf2;
2047 0 0 0     0 return 0 unless $buff =~ /WAVE(.{4})?(junk|fmt )/sg and $raf->Seek(pos($buff) - 4, 0);
2048 0         0 $base = pos($buff) - 16;
2049             }
2050 7 50       27 $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode
2051 7 50       34 $mime = $riffMimeType{$type} if $type;
2052 7         38 $et->SetFileType($type, $mime);
2053 7 0 33     22 $$et{VALUE}{FileType} .= ' (RF64)' if $rf64 and $$et{VALUE}{FileType};
2054 7         22 $$et{RIFFStreamType} = ''; # initialize stream type
2055 7         18 $$et{RIFFStreamCodec} = []; # initialize codec array
2056 7         32 SetByteOrder('II');
2057 7         24 my $riffEnd = Get32u(\$buff, 4) + 8;
2058 7         18 $riffEnd += $riffEnd & 0x01; # (account for padding)
2059 7         21 my $tagTbl = GetTagTable('Image::ExifTool::RIFF::Main');
2060 7         16 my $pos = 12;
2061             #
2062             # Read chunks in RIFF image
2063             #
2064 7         16 for (;;) {
2065 39 50       123 if ($err) {
2066 0 0       0 last unless $moviEnd;
2067             # we arrived here because there was a problem parsing the movie data
2068             # so seek to the end to continue processing
2069 0 0       0 if ($moviEnd > 0x7fffffff) {
2070 0 0       0 unless ($et->Options('LargeFileSupport')) {
2071 0         0 $et->Warn('Possibly corrupt LIST_movi data');
2072 0         0 $et->Warn('Stopped parsing at large LIST_movi chunk (LargeFileSupport not set)');
2073 0         0 undef $err;
2074 0         0 last;
2075             }
2076 0 0       0 if ($et->Options('LargeFileSupport') eq '2') {
2077 0         0 $et->Warn('Processing large chunk (LargeFileSupport is 2)');
2078             }
2079             }
2080 0 0       0 if ($validate) {
2081             # (must actually try to read something after seeking to detect error)
2082 0 0 0     0 $raf->Seek($moviEnd-1, 0) and $raf->Read($buff, 1) == 1 or last;
2083             } else {
2084 0 0       0 $raf->Seek($moviEnd, 0) or last;
2085             }
2086 0         0 $pos = $moviEnd;
2087 0         0 $et->Warn('Possibly corrupt LIST_movi data');
2088 0         0 undef $err;
2089 0         0 undef $moviEnd;
2090             }
2091 39 50       119 if ($moviEnd) {
2092 0 0       0 $pos > $moviEnd and $err = 1, next; # error if we parsed past the end of the movie data
2093 0 0       0 undef $moviEnd if $pos == $moviEnd; # parsed all movie data?
2094             }
2095 39         181 my $num = $raf->Read($buff, 8);
2096 39 100       104 if ($num < 8) {
2097 7 50       28 $moviEnd and $err = 1, next;
2098 7 50       21 $err = 1 if $num;
2099 7 50 33     28 $et->Warn('Incorrect RIFF chunk size' . " $pos vs. $riffEnd") if $validate and $pos != $riffEnd;
2100 7         18 last;
2101             }
2102 32         57 $pos += 8;
2103 32         127 my ($tag, $len) = unpack('a4V', $buff);
2104             # tweak WEBP type if this is an extended WebP
2105 32 100 66     152 $et->OverrideFileType('Extended WEBP',undef,'webp') if $tag eq 'VP8X' and $type eq 'WEBP';
2106             # special case: construct new tag name from specific LIST type
2107 32 100       100 if ($tag eq 'LIST') {
    100          
2108 10 50       29 $raf->Read($buff, 4) == 4 or $err=1, next;
2109 10         18 $pos += 4;
2110 10         24 $tag .= "_$buff";
2111 10         21 $len -= 4; # already read 4 bytes (the LIST type)
2112             } elsif ($tag eq 'data') {
2113 1 0 33     5 $len = $$et{DataSize64} if $len == 0xffffffff and $$et{DataSize64};
2114 1   50     9 $$et{RIFFDataLen} = ($$et{RIFFDataLen} || 0) + $len;
2115             }
2116 32         249 $et->VPrint(0, "RIFF '${tag}' chunk ($len bytes of data):\n");
2117 32 100       103 if ($len <= 0) {
2118 3 50       14 $moviEnd and $err = 1, next;
2119 3 50       29 if ($len < 0) {
    50          
2120 0         0 $et->Warn('Invalid chunk length');
2121             } elsif ($tag eq "\0\0\0\0") {
2122             # avoid reading through corupted files filled with nulls because it takes forever
2123 0         0 $et->Warn('Encountered empty null chunk. Processing aborted');
2124             } else {
2125 3         9 next;
2126             }
2127 0         0 last;
2128             }
2129             # stop when we hit the audio data or AVI index or AVI movie data
2130             # --> no more because Adobe Bridge stores XMP after this!!
2131             # (so now we only do this on the FastScan option)
2132 29 0 0     112 if ($et->Options('FastScan') and ($tag eq 'data' or $tag eq 'idx1' or
      33        
2133             ($tag eq 'LIST_movi' and not $ee)))
2134             {
2135 0         0 $et->VPrint(0, "(end of parsing)\n");
2136 0         0 last;
2137             }
2138             # RIFF chunks are padded to an even number of bytes
2139 29         63 my $len2 = $len + ($len & 0x01);
2140             # change name of stream txts data depending on the Codec
2141 29 50 33     93 if ($ee and $tag =~ /^(\d{2})tx$/) {
2142 0   0     0 $tag = 'tx_' . ($$et{RIFFStreamCodec}[$1] || 'Unknown');
2143 0 0       0 $tag = "tx_Unknown" unless defined $$tagTbl{$tag};
2144 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2145             }
2146 29         77 my $tagInfo = $$tagTbl{$tag};
2147             # (in LIST_movi chunk: ##db = uncompressed DIB, ##dc = compressed DIB, ##wb = audio data)
2148 29 50 0     93 if ($tagInfo or (($verbose or $unknown) and $tag !~ /^(data|idx1|LIST_movi|RIFF|\d{2}(db|dc|wb))$/)) {
    0 0        
      33        
2149 29 50       89 $raf->Read($buff, $len2) >= $len or $err=1, next;
2150 29 50       74 length($buff) == $len2 or $et->Warn("No padding on odd-sized $tag chunk");
2151 29 0 33     67 if ($hash and $isImageData{$tag}) {
2152 0         0 $hash->add($buff);
2153 0         0 $et->VPrint(0, "$$et{INDENT}(ImageDataHash: '${tag}' chunk, $len2 bytes)\n");
2154             }
2155 29         48 my $setGroups;
2156 29 50 66     197 if ($tagInfo and ref $tagInfo eq 'HASH' and $$tagInfo{SetGroups}) {
      66        
2157 0         0 $setGroups = $$et{SET_GROUP0} = $$et{SET_GROUP1} = $$tagInfo{SetGroups};
2158             }
2159 29 0 0     65 MakeTagInfo($tagTbl, $tag) if not $tagInfo and ($verbose or $unknown);
      33        
2160 29         164 $et->HandleTag($tagTbl, $tag, $buff,
2161             DataPt => \$buff,
2162             DataPos => 0, # (relative to Base)
2163             Start => 0,
2164             Size => $len,
2165             Base => $pos + $base,
2166             );
2167 29 50       100 if ($setGroups) {
2168 0         0 delete $$et{SET_GROUP0};
2169 0         0 delete $$et{SET_GROUP1};
2170             }
2171 29 50       85 delete $$et{DOC_NUM} if $ee;
2172             } elsif ($tag eq 'RIFF') {
2173 0 0 0     0 $et->Warn('Incorrect RIFF chunk size') if $validate and $pos - 8 != $riffEnd;
2174 0         0 $riffEnd += $len2 + 8;
2175             # don't read into RIFF chunk (eg. concatenated video file)
2176 0 0       0 $raf->Read($buff, 4) == 4 or $err=1, next; # (skip RIFF type word)
2177 0         0 $pos += 4;
2178             # extract information from remaining file as an embedded file
2179 0         0 $$et{DOC_NUM} = ++$$et{DOC_COUNT};
2180 0         0 next; # (must not increment $pos)
2181             } else {
2182 0         0 my $rewind;
2183             # do hash if required
2184 0 0 0     0 if ($hash and $isImageData{$tag}) {
2185 0         0 $rewind = $raf->Tell();
2186 0         0 $et->ImageDataHash($raf, $len2, "'${tag}' chunk");
2187             }
2188 0 0 0     0 if ($tag eq 'LIST_movi' and $ee) {
    0          
2189 0 0 0     0 $raf->Seek($rewind, 0) or $err = 1, next if $rewind;
2190             # save end-of-movie offset so we can seek there if we get errors parsing the movie data
2191 0         0 $moviEnd = $raf->Tell() + $len2;
2192 0         0 next; # parse into movi chunk
2193             } elsif (not $rewind) {
2194 0 0       0 if ($len > 0x7fffffff) {
2195 0 0       0 unless ($et->Options('LargeFileSupport')) {
2196 0         0 $tag =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/eg;
  0         0  
2197 0         0 $et->Warn("Stopped parsing at large $tag chunk (LargeFileSupport not set)");
2198 0         0 last;
2199             }
2200 0 0       0 if ($et->Options('LargeFileSupport') eq '2') {
2201 0         0 $et->Warn('Processing large chunk (LargeFileSupport is 2)');
2202             }
2203             }
2204 0 0 0     0 if ($validate and $len2) {
2205             # (must actually try to read something after seeking to detect error)
2206 0 0 0     0 $raf->Seek($len2-1, 1) and $raf->Read($buff, 1) == 1 or $err = 1, next;
2207             } else {
2208 0 0       0 $raf->Seek($len2, 1) or $err=1, next;
2209             }
2210             }
2211             }
2212 29         74 $pos += $len2;
2213             }
2214 7         21 delete $$et{DOC_NUM};
2215 7 50       23 $err and $et->Warn('Error reading RIFF file (corrupted?)');
2216 7         41 return 1;
2217             }
2218              
2219             1; # end
2220              
2221             __END__