File Coverage

blib/lib/Image/ExifTool/FLIR.pm
Criterion Covered Total %
statement 86 157 54.7
branch 21 82 25.6
condition 14 46 30.4
subroutine 9 11 81.8
pod 0 6 0.0
total 130 302 43.0


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: FLIR.pm
3             #
4             # Description: Read FLIR meta information
5             #
6             # Revisions: 2013/03/28 - P. Harvey Created
7             #
8             # References: 1) https://exiftool.org/forum/index.php/topic,4898.0.html
9             # 2) http://www.nuage.ch/site/flir-i7-some-analysis/
10             # 3) http://www.workswell.cz/manuals/flir/hardware/A3xx_and_A6xx_models/Streaming_format_ThermoVision.pdf
11             # 4) http://support.flir.com/DocDownload/Assets/62/English/1557488%24A.pdf
12             # 5) http://code.google.com/p/dvelib/source/browse/trunk/flirPublicFormat/fpfConverter/Fpfimg.h?spec=svn3&r=3
13             # 6) https://exiftool.org/forum/index.php/topic,5538.0.html
14             # JD) Jens Duttke private communication
15             #
16             # Glossary: FLIR = Forward Looking Infra Red
17             #------------------------------------------------------------------------------
18              
19             package Image::ExifTool::FLIR;
20              
21 3     3   4689 use strict;
  3         9  
  3         126  
22 3     3   18 use vars qw($VERSION);
  3         8  
  3         150  
23 3     3   20 use Image::ExifTool qw(:DataAccess :Utils);
  3         8  
  3         748  
24 3     3   1316 use Image::ExifTool::Exif;
  3         12  
  3         204  
25 3     3   602 use Image::ExifTool::GPS;
  3         9  
  3         14947  
26              
27             $VERSION = '1.22';
28              
29             sub ProcessFLIR($$;$);
30             sub ProcessFLIRText($$$);
31             sub ProcessMeasInfo($$$);
32             sub GetImageType($$$);
33              
34             my %temperatureInfo = (
35             Writable => 'rational64u',
36             Format => 'rational64s', # (have seen negative values)
37             );
38              
39             # tag information for floating point Kelvin tag
40             my %floatKelvin = (
41             Format => 'float',
42             ValueConv => '$val - 273.15',
43             PrintConv => 'sprintf("%.1f C",$val)',
44             );
45              
46             # commonly used tag information elements
47             my %float1f = ( Format => 'float', PrintConv => 'sprintf("%.1f",$val)' );
48             my %float2f = ( Format => 'float', PrintConv => 'sprintf("%.2f",$val)' );
49             my %float6f = ( Format => 'float', PrintConv => 'sprintf("%.6f",$val)' );
50             my %float8g = ( Format => 'float', PrintConv => 'sprintf("%.8g",$val)' );
51              
52             # FLIR makernotes tags (ref PH)
53             %Image::ExifTool::FLIR::Main = (
54             WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
55             CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
56             GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
57             WRITABLE => 1,
58             PRIORITY => 0, # (unreliable)
59             NOTES => q{
60             Information extracted from the maker notes of JPEG images from thermal
61             imaging cameras by FLIR Systems Inc.
62             },
63             0x01 => { #2
64             Name => 'ImageTemperatureMax',
65             %temperatureInfo,
66             Notes => q{
67             these temperatures may be in Celsius, Kelvin or Fahrenheit, but there is no
68             way to tell which
69             },
70             },
71             0x02 => { Name => 'ImageTemperatureMin', %temperatureInfo }, #2
72             0x03 => { #1
73             Name => 'Emissivity',
74             Writable => 'rational64u',
75             PrintConv => 'sprintf("%.2f",$val)',
76             PrintConvInv => '$val',
77             },
78             # 0x04 does not change with temperature units; often 238, 250 or 457
79             0x04 => { Name => 'UnknownTemperature', %temperatureInfo, Unknown => 1 },
80             # 0x05,0x06 are unreliable. As written by FLIR tools, these are the
81             # CameraTemperatureRangeMax/Min, but the units vary depending on the
82             # options settings. But as written by some cameras, the values are different.
83             0x05 => { Name => 'CameraTemperatureRangeMax', %temperatureInfo, Unknown => 1 },
84             0x06 => { Name => 'CameraTemperatureRangeMin', %temperatureInfo, Unknown => 1 },
85             # 0x07 - string[33] (some sort of image ID?)
86             # 0x08 - string[33]
87             # 0x09 - undef (tool info)
88             # 0x0a - int32u: 1
89             # 0x0f - rational64u: 0/1000
90             # 0x10,0x11,0x12 - int32u: 0
91             # 0x13 - rational64u: 0/1000
92             );
93              
94             # FLIR FFF tag table (ref PH)
95             %Image::ExifTool::FLIR::FFF = (
96             GROUPS => { 0 => 'APP1', 2 => 'Image' },
97             PROCESS_PROC => \&ProcessFLIR,
98             VARS => { ALPHA_FIRST => 1 },
99             NOTES => q{
100             Information extracted from FLIR FFF images and the APP1 FLIR segment of JPEG
101             images. These tags may also be extracted from the first frame of an FLIR
102             SEQ file, or all frames if the ExtractEmbedded option is used. Setting
103             ExtractEmbedded to 2 also the raw thermal data from all frames.
104             },
105             "_header" => {
106             Name => 'FFFHeader',
107             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' },
108             },
109             # 0 = free (ref 3)
110             0x01 => {
111             Name => 'RawData',
112             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::RawData' },
113             },
114             # 2 = GainMap (ref 3)
115             # 3 = OffsMap (ref 3)
116             # 4 = DeadMap (ref 3)
117             0x05 => { #6
118             Name => 'GainDeadData',
119             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::GainDeadData' },
120             },
121             0x06 => { #6
122             Name => 'CoarseData',
123             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CoarseData' },
124             },
125             # 7 = ImageMap (ref 3)
126             0x0e => {
127             Name => 'EmbeddedImage',
128             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::EmbeddedImage' },
129             },
130             0x20 => {
131             Name => 'CameraInfo', # (BasicData - ref 3)
132             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CameraInfo' },
133             },
134             0x21 => { #6
135             Name => 'MeasurementInfo',
136             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::MeasInfo' },
137             },
138             0x22 => {
139             Name => 'PaletteInfo', # (ColorPal - ref 3)
140             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaletteInfo' },
141             },
142             0x23 => {
143             Name => 'TextInfo',
144             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::TextInfo' },
145             },
146             0x24 => {
147             Name => 'EmbeddedAudioFile',
148             # (sometimes has an unknown 8-byte header)
149             RawConv => q{
150             return \$val if $val =~ s/^.{0,16}?RIFF/RIFF/s;
151             $self->Warn('Unknown EmbeddedAudioFile format');
152             return undef;
153             },
154             },
155             # 0x27: 01 00 08 00 10 00 00 00
156             0x28 => {
157             Name => 'PaintData',
158             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaintData' },
159             },
160             0x2a => {
161             Name => 'PiP',
162             SubDirectory => {
163             TagTable => 'Image::ExifTool::FLIR::PiP',
164             ByteOrder => 'LittleEndian',
165             },
166             },
167             0x2b => {
168             Name => 'GPSInfo',
169             SubDirectory => {
170             TagTable => 'Image::ExifTool::FLIR::GPSInfo',
171             ByteOrder => 'LittleEndian',
172             },
173             },
174             0x2c => {
175             Name => 'MeterLink',
176             SubDirectory => {
177             TagTable => 'Image::ExifTool::FLIR::MeterLink' ,
178             ByteOrder => 'LittleEndian'
179             },
180             },
181             0x2e => {
182             Name => 'ParameterInfo',
183             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::ParamInfo' },
184             },
185             );
186              
187             # FFF file header (ref PH)
188             %Image::ExifTool::FLIR::Header = (
189             GROUPS => { 0 => 'APP1', 2 => 'Image' },
190             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
191             FIRST_ENTRY => 0,
192             NOTES => 'Tags extracted from the FLIR FFF/AFF header.',
193             4 => { Name => 'CreatorSoftware', Format => 'string[16]' },
194             );
195              
196             # FLIR raw data record (ref PH)
197             %Image::ExifTool::FLIR::RawData = (
198             GROUPS => { 0 => 'APP1', 2 => 'Image' },
199             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
200             FORMAT => 'int16u',
201             FIRST_ENTRY => 0,
202             NOTES => q{
203             The thermal image data may be stored either as raw data, or in PNG format.
204             If stored as raw data, ExifTool adds a TIFF header to allow the data to be
205             viewed as a TIFF image. If stored in PNG format, the PNG image is extracted
206             as-is. Note that most FLIR cameras using the PNG format seem to write the
207             16-bit raw image data in the wrong byte order.
208             },
209             0x00 => {
210             # use this tag only to determine the byte order of the raw data
211             # (the value should be 0x0002 if the byte order is correct)
212             # - always "II" when RawThermalImageType is "TIFF"
213             # - seen both "II" and "MM" when RawThermalImageType is "PNG"
214             Name => 'RawDataByteOrder',
215             Hidden => 1,
216             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
217             },
218             0x01 => {
219             Name => 'RawThermalImageWidth',
220             RawConv => '$$self{RawThermalImageWidth} = $val',
221             },
222             0x02 => {
223             Name => 'RawThermalImageHeight',
224             RawConv => '$$self{RawThermalImageHeight} = $val',
225             },
226             # 0x03-0x05: 0
227             # 0x06: raw image width - 1
228             # 0x07: 0
229             # 0x08: raw image height - 1
230             # 0x09: 0,15,16
231             # 0x0a: 0,2,3,11,12,13,30
232             # 0x0b: 0,2
233             # 0x0c: 0 or a large number
234             # 0x0d: 0,3,4,6
235             # 0x0e-0x0f: 0
236             16 => {
237             Name => 'RawThermalImageType',
238             Format => 'undef[$size-0x20]',
239             RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "RawThermalImage")',
240             },
241             16.1 => {
242             Name => 'RawThermalImage',
243             Groups => { 2 => 'Preview' },
244             # make a copy in case we want to extract more of them with -ee2
245             RawConv => 'my $copy = $$self{RawThermalImage}; \$copy',
246             },
247             );
248              
249             # GainDeadMap record (ref 6) (see RawData above)
250             %Image::ExifTool::FLIR::GainDeadData = (
251             GROUPS => { 0 => 'APP1', 2 => 'Image' },
252             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
253             FORMAT => 'int16u',
254             FIRST_ENTRY => 0,
255             NOTES => 'Information found in FFF-format .GAN calibration image files.',
256             0x00 => {
257             Name => 'GainDeadMapByteOrder',
258             Hidden => 1,
259             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
260             },
261             0x01 => {
262             Name => 'GainDeadMapImageWidth',
263             RawConv => '$$self{GainDeadMapImageWidth} = $val',
264             },
265             0x02 => {
266             Name => 'GainDeadMapImageHeight',
267             RawConv => '$$self{GainDeadMapImageHeight} = $val',
268             },
269             16 => {
270             Name => 'GainDeadMapImageType',
271             Format => 'undef[$size-0x20]',
272             RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "GainDeadMapImage")',
273             },
274             16.1 => {
275             Name => 'GainDeadMapImage',
276             RawConv => 'my $copy = \$$self{GainDeadMapImage}; \$copy',
277             },
278             );
279              
280             # CoarseMap record (ref 6) (see RawData above)
281             %Image::ExifTool::FLIR::CoarseData = (
282             GROUPS => { 0 => 'APP1', 2 => 'Image' },
283             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
284             FORMAT => 'int16u',
285             FIRST_ENTRY => 0,
286             NOTES => 'Information found in FFF-format .CRS correction image files.',
287             0x00 => {
288             Name => 'CoarseMapByteOrder',
289             Hidden => 1,
290             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
291             },
292             0x01 => {
293             Name => 'CoarseMapImageWidth',
294             RawConv => '$$self{CoarseMapImageWidth} = $val',
295             },
296             0x02 => {
297             Name => 'CoarseMapImageHeight',
298             RawConv => '$$self{CoarseMapImageHeight} = $val',
299             },
300             16 => {
301             Name => 'CoarseMapImageType',
302             Format => 'undef[$size-0x20]',
303             RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "CoarseMapImage")',
304             },
305             16.1 => {
306             Name => 'CoarseMapImage',
307             RawConv => 'my $copy = \$$self{CoarseMapImage}; \$copy',
308             },
309             );
310              
311             # "Paint colors" record (ref PH)
312             %Image::ExifTool::FLIR::PaintData = (
313             GROUPS => { 0 => 'APP1', 2 => 'Image' },
314             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
315             FORMAT => 'int16u',
316             FIRST_ENTRY => 0,
317             NOTES => 'Information generated by FLIR Tools "Paint colors" tool.',
318             0x01 => {
319             Name => 'PaintByteOrder',
320             Hidden => 1,
321             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
322             },
323             0x05 => {
324             Name => 'PaintImageWidth',
325             RawConv => '$$self{PaintImageWidth} = $val',
326             },
327             0x06 => {
328             Name => 'PaintImageHeight',
329             RawConv => '$$self{PaintImageHeight} = $val',
330             },
331             20 => {
332             Name => 'PaintImageType',
333             Format => 'undef[$size-0x28]',
334             RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "PaintImage")',
335             },
336             20.1 => {
337             Name => 'PaintImage',
338             RawConv => 'my $copy = \$$self{PaintImage}; \$copy',
339             },
340             );
341              
342             # FLIR embedded image (ref 1)
343             %Image::ExifTool::FLIR::EmbeddedImage = (
344             GROUPS => { 0 => 'APP1', 2 => 'Image' },
345             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
346             FORMAT => 'int16u',
347             FIRST_ENTRY => 0,
348             0 => {
349             # use this tag only to determine the byte order
350             # (the value should be 0x0003 if the byte order is correct)
351             Name => 'EmbeddedImageByteOrder',
352             Format => 'int16u',
353             Hidden => 1,
354             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
355             },
356             1 => 'EmbeddedImageWidth',
357             2 => 'EmbeddedImageHeight',
358             16 => {
359             Name => 'EmbeddedImageType',
360             Format => 'undef[4]',
361             RawConv => '$val =~ /^\x89PNG/s ? "PNG" : ($val =~ /^\xff\xd8\xff/ ? "JPG" : "DAT")',
362             Notes => q{
363             "PNG" for PNG image in Y Cb Cr colors, "JPG" for a JPEG image, or "DAT" for
364             other image data
365             },
366             },
367             16.1 => {
368             Name => 'EmbeddedImage',
369             Groups => { 2 => 'Preview' },
370             Format => 'undef[$size-0x20]',
371             Binary => 1,
372             },
373             );
374              
375             # FLIR camera record (ref PH)
376             %Image::ExifTool::FLIR::CameraInfo = (
377             GROUPS => { 0 => 'APP1', 2 => 'Camera' },
378             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
379             FIRST_ENTRY => 0,
380             NOTES => q{
381             FLIR camera information. The Planck tags are variables used in the
382             temperature calculation. See
383             L
384             for details.
385             },
386             0x00 => {
387             # use this tag only to determine the byte order
388             # (the value should be 0x0002 if the byte order is correct)
389             Name => 'CameraInfoByteOrder',
390             Format => 'int16u',
391             Hidden => 1,
392             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
393             },
394             # 0x02 - int16u: image width
395             # 0x04 - int16u: image height
396             # 0x0c - int32u: image width - 1
397             # 0x10 - int32u: image height - 1
398             0x20 => { Name => 'Emissivity', %float2f },
399             0x24 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' },
400             0x28 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
401             0x2c => { Name => 'AtmosphericTemperature', %floatKelvin },
402             0x30 => { Name => 'IRWindowTemperature', %floatKelvin },
403             0x34 => { Name => 'IRWindowTransmission', %float2f },
404             # 0x38: 0
405             0x3c => {
406             Name => 'RelativeHumidity',
407             Format => 'float',
408             ValueConv => '$val > 2 ? $val / 100 : $val', # have seen value expressed as percent in FFF file
409             PrintConv => 'sprintf("%.1f %%",$val*100)',
410             },
411             # 0x40 - float: 0,6
412             # 0x44,0x48,0x4c: 0
413             # 0x50 - int32u: 1
414             # 0x54: 0
415             0x58 => { Name => 'PlanckR1', %float8g }, #1
416             0x5c => { Name => 'PlanckB', %float8g }, #1
417             0x60 => { Name => 'PlanckF', %float8g }, #1
418             # 0x64,0x68,0x6c: 0
419             0x070 => { Name => 'AtmosphericTransAlpha1', %float6f }, #1 (value: 0.006569)
420             0x074 => { Name => 'AtmosphericTransAlpha2', %float6f }, #1 (value: 0.012620)
421             0x078 => { Name => 'AtmosphericTransBeta1', %float6f }, #1 (value: -0.002276)
422             0x07c => { Name => 'AtmosphericTransBeta2', %float6f }, #1 (value: -0.006670)
423             0x080 => { Name => 'AtmosphericTransX', %float6f }, #1 (value: 1.900000)
424             # 0x84,0x88: 0
425             # 0x8c - float: 0,4,6
426             0x90 => { Name => 'CameraTemperatureRangeMax', %floatKelvin },
427             0x94 => { Name => 'CameraTemperatureRangeMin', %floatKelvin },
428             0x98 => { Name => 'CameraTemperatureMaxClip', %floatKelvin }, # 50 degrees over camera max
429             0x9c => { Name => 'CameraTemperatureMinClip', %floatKelvin }, # usually 10 or 20 degrees below camera min
430             0xa0 => { Name => 'CameraTemperatureMaxWarn', %floatKelvin }, # same as camera max
431             0xa4 => { Name => 'CameraTemperatureMinWarn', %floatKelvin }, # same as camera min
432             0xa8 => { Name => 'CameraTemperatureMaxSaturated', %floatKelvin }, # usually 50 or 88 degrees over camera max
433             0xac => { Name => 'CameraTemperatureMinSaturated', %floatKelvin }, # usually 10, 20 or 40 degrees below camera min
434             0xd4 => { Name => 'CameraModel', Format => 'string[32]' },
435             0xf4 => { Name => 'CameraPartNumber', Format => 'string[16]' }, #1
436             0x104 => { Name => 'CameraSerialNumber',Format => 'string[16]' }, #1
437             0x114 => { Name => 'CameraSoftware', Format => 'string[16]' }, #1/PH (NC)
438             0x170 => { Name => 'LensModel', Format => 'string[32]' },
439             # note: it seems that FLIR updated their lenses at some point, so lenses with the same
440             # name may have different part numbers (eg. the FOL38 is either 1196456 or T197089)
441             0x190 => { Name => 'LensPartNumber', Format => 'string[16]' },
442             0x1a0 => { Name => 'LensSerialNumber', Format => 'string[16]' },
443             0x1b4 => { Name => 'FieldOfView', Format => 'float', PrintConv => 'sprintf("%.1f deg", $val)' }, #1
444             # 0x1d0 - int16u: 0,12,24,25,46
445             # 0x1d2 - int16u: 170,180,190,380,760,52320
446             0x1ec => { Name => 'FilterModel', Format => 'string[16]' },
447             0x1fc => { Name => 'FilterPartNumber', Format => 'string[32]' },
448             0x21c => { Name => 'FilterSerialNumber',Format => 'string[32]' },
449             0x308 => { Name => 'PlanckO', Format => 'int32s' }, #1
450             0x30c => { Name => 'PlanckR2', %float8g }, #1
451             0x310 => { Name => 'RawValueRangeMin', Format => 'int16u', Groups => { 2 => 'Image' } }, #forum10060
452             0x312 => { Name => 'RawValueRangeMax', Format => 'int16u', Groups => { 2 => 'Image' } }, #forum10060
453             0x338 => { Name => 'RawValueMedian', Format => 'int16u', Groups => { 2 => 'Image' } },
454             0x33c => { Name => 'RawValueRange', Format => 'int16u', Groups => { 2 => 'Image' } },
455             0x384 => {
456             Name => 'DateTimeOriginal',
457             Description => 'Date/Time Original',
458             Format => 'undef[10]',
459             Groups => { 2 => 'Time' },
460             RawConv => q{
461             my $tm = Get32u(\$val, 0);
462             my $ss = Get32u(\$val, 4) & 0xffff;
463             my $tz = Get16s(\$val, 8);
464             ConvertUnixTime($tm - $tz * 60) . sprintf('.%.3d', $ss) . TimeZoneString(-$tz);
465             },
466             PrintConv => '$self->ConvertDateTime($val)',
467             },
468             0x390 => { Name => 'FocusStepCount', Format => 'int16u' },
469             0x45c => { Name => 'FocusDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' },
470             # 0x43c - string: either "Live" or the file name
471             0x464 => { Name => 'FrameRate', Format => 'int16u' }, #SebastianHani
472             );
473              
474             # FLIR measurement tools record (ref 6)
475             %Image::ExifTool::FLIR::MeasInfo = (
476             GROUPS => { 0 => 'APP1', 2 => 'Image' },
477             PROCESS_PROC => \&ProcessMeasInfo,
478             FORMAT => 'int16u',
479             VARS => { NO_ID => 1 },
480             NOTES => q{
481             Tags listed below are only for the first measurement tool, however multiple
482             measurements may be added, and information is extracted for all of them.
483             Tags for subsequent measurements are generated as required with the prefixes
484             "Meas2", "Meas3", etc.
485             },
486             Meas1Type => {
487             PrintConv => {
488             1 => 'Spot',
489             2 => 'Area',
490             3 => 'Ellipse',
491             4 => 'Line',
492             5 => 'Endpoint', #PH (NC, FLIR Tools v2.0 for Mac generates an empty one of these after each Line)
493             6 => 'Alarm', #PH seen params: "0 1 0 1 9142 0 9142 0" (called "Isotherm" by Mac version)
494             7 => 'Unused', #PH (NC) (or maybe "Free"?)
495             8 => 'Difference',
496             },
497             },
498             Meas1Params => {
499             Notes => 'Spot=X,Y; Area=X1,Y1,W,H; Ellipse=XC,YC,X1,Y1,X2,Y2; Line=X1,Y1,X2,Y2',
500             },
501             Meas1Label => { },
502             );
503              
504             # FLIR palette record (ref PH/JD)
505             %Image::ExifTool::FLIR::PaletteInfo = (
506             GROUPS => { 0 => 'APP1', 2 => 'Image' },
507             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
508             FIRST_ENTRY => 0,
509             0x00 => { #JD
510             Name => 'PaletteColors',
511             RawConv => '$$self{PaletteColors} = $val',
512             },
513             0x06 => { Name => 'AboveColor', Format => 'int8u[3]', Notes => 'Y Cr Cb color components' }, #JD
514             0x09 => { Name => 'BelowColor', Format => 'int8u[3]' }, #JD
515             0x0c => { Name => 'OverflowColor', Format => 'int8u[3]' }, #JD
516             0x0f => { Name => 'UnderflowColor', Format => 'int8u[3]' }, #JD
517             0x12 => { Name => 'Isotherm1Color', Format => 'int8u[3]' }, #JD
518             0x15 => { Name => 'Isotherm2Color', Format => 'int8u[3]' }, #JD
519             0x1a => { Name => 'PaletteMethod' }, #JD
520             0x1b => { Name => 'PaletteStretch' }, #JD
521             0x30 => {
522             Name => 'PaletteFileName',
523             Format => 'string[32]',
524             # (not valid for all images)
525             RawConv => q{
526             $val =~ s/\0.*//;
527             $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef;
528             },
529             },
530             0x50 => {
531             Name => 'PaletteName',
532             Format => 'string[32]',
533             # (not valid for all images)
534             RawConv => q{
535             $val =~ s/\0.*//;
536             $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef;
537             },
538             },
539             0x70 => {
540             Name => 'Palette',
541             Format => 'undef[3*$$self{PaletteColors}]',
542             Notes => 'Y Cr Cb byte values for each palette color',
543             Binary => 1,
544             },
545             );
546              
547             # FLIR text information record (ref PH)
548             %Image::ExifTool::FLIR::TextInfo = (
549             GROUPS => { 0 => 'APP1', 2 => 'Image' },
550             PROCESS_PROC => \&ProcessFLIRText,
551             VARS => { NO_ID => 1 },
552             Label0 => { },
553             Value0 => { },
554             Label1 => { },
555             Value1 => { },
556             Label2 => { },
557             Value2 => { },
558             Label3 => { },
559             Value3 => { },
560             # (there could be more, and we will generate these on the fly if necessary)
561             );
562              
563             # FLIR parameter information record (ref PH)
564             %Image::ExifTool::FLIR::ParamInfo = (
565             GROUPS => { 0 => 'APP1', 2 => 'Image' },
566             PROCESS_PROC => \&ProcessFLIRText,
567             VARS => { NO_ID => 1 },
568             Generated => {
569             Name => 'DateTimeGenerated',
570             Description => 'Date/Time Generated',
571             Groups => { 2 => 'Time' },
572             ValueConv => '$val =~ tr/-/:/; $val',
573             PrintConv => '$self->ConvertDateTime($val)',
574             },
575             Param0 => { },
576             Param1 => { },
577             Param2 => { },
578             Param3 => { },
579             # (there could be more, and we will generate these on the fly if necessary)
580             );
581              
582             # FLIR Picture in Picture record (ref 1)
583             %Image::ExifTool::FLIR::PiP = (
584             GROUPS => { 0 => 'APP1', 2 => 'Image' },
585             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
586             FIRST_ENTRY => 0,
587             NOTES => 'FLIR Picture in Picture tags.',
588             FORMAT => 'int16s',
589             0x00 => {
590             Name => 'Real2IR',
591             Format => 'float',
592             },
593             2 => {
594             Name => 'OffsetX',
595             Notes => 'offset from of insertion point from center',
596             PrintConv => 'sprintf("%+d",$val)', # (add sign for direct use with IM convert)
597             },
598             3 => {
599             Name => 'OffsetY',
600             PrintConv => 'sprintf("%+d",$val)',
601             },
602             4 => {
603             Name => 'PiPX1',
604             Description => 'PiP X1',
605             Notes => 'crop size for radiometric image',
606             },
607             5 => { Name => 'PiPX2', Description => 'PiP X2' },
608             6 => { Name => 'PiPY1', Description => 'PiP Y1' },
609             7 => { Name => 'PiPY2', Description => 'PiP Y2' },
610             );
611              
612             # FLIR GPS record (ref PH/JD/forum9615)
613             %Image::ExifTool::FLIR::GPSInfo = (
614             GROUPS => { 0 => 'APP1', 2 => 'Location' },
615             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
616             FIRST_ENTRY => 0,
617             0x00 => {
618             Name => 'GPSValid',
619             Format => 'int32u',
620             RawConv => '$$self{GPSValid} = $val',
621             PrintConv => { 0 => 'No', 1 => 'Yes' },
622             },
623             0x04 => {
624             Name => 'GPSVersionID',
625             Format => 'undef[4]',
626             RawConv => '$val eq "\0\0\0\0" ? undef : $val',
627             PrintConv => 'join ".", split //, $val',
628             },
629             0x08 => {
630             Name => 'GPSLatitudeRef',
631             Format => 'string[2]',
632             RawConv => 'length($val) ? $val : undef',
633             PrintConv => {
634             N => 'North',
635             S => 'South',
636             },
637             },
638             0x0a => {
639             Name => 'GPSLongitudeRef',
640             Format => 'string[2]',
641             RawConv => 'length($val) ? $val : undef',
642             PrintConv => {
643             E => 'East',
644             W => 'West',
645             },
646             },
647             # 0x0c - 4 unknown bytes
648             0x10 => {
649             Name => 'GPSLatitude',
650             Condition => '$$self{GPSValid}', # valid only if GPSValid is 1
651             Format => 'double', # (signed)
652             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
653             },
654             0x18 => {
655             Name => 'GPSLongitude',
656             Condition => '$$self{GPSValid}', # valid only if GPSValid is 1
657             Format => 'double', # (signed)
658             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
659             },
660             0x20 => {
661             Name => 'GPSAltitude',
662             Condition => '$$self{GPSValid}', # valid only if GPSValid is 1
663             Format => 'float',
664             # (have seen likely invalid value of -1 when GPSValid is 1)
665             PrintConv => 'sprintf("%.2f m", $val)',
666             },
667             # 0x24 - 28 unknown bytes:
668             # 0x28 - int8u: seen 0,49,51,55,57 (ASCII "1","3","7","9")
669             # 0x29 - int8u: seen 0,48 (ASCII "0")
670             0x40 => {
671             Name => 'GPSDOP',
672             Description => 'GPS Dilution Of Precision',
673             Format => 'float',
674             RawConv => '$val > 0 ? $val : undef', # (have also seen likely invalid value of 1)
675             PrintConv => 'sprintf("%.2f", $val)',
676             },
677             0x44 => {
678             Name => 'GPSSpeedRef',
679             Format => 'string[2]',
680             RawConv => 'length($val) ? $val : undef',
681             PrintConv => {
682             K => 'km/h',
683             M => 'mph',
684             N => 'knots',
685             },
686             },
687             0x46 => {
688             Name => 'GPSTrackRef',
689             Format => 'string[2]',
690             RawConv => 'length($val) ? $val : undef',
691             PrintConv => {
692             M => 'Magnetic North',
693             T => 'True North',
694             },
695             },
696             0x48 => { #PH (NC)
697             Name => 'GPSImgDirectionRef',
698             Format => 'string[2]',
699             RawConv => 'length($val) ? $val : undef',
700             PrintConv => {
701             M => 'Magnetic North',
702             T => 'True North',
703             },
704             },
705             0x4c => {
706             Name => 'GPSSpeed',
707             %float2f,
708             RawConv => '$val < 0 ? undef : $val',
709             },
710             0x50 => {
711             Name => 'GPSTrack',
712             %float2f,
713             RawConv => '$val < 0 ? undef : $val',
714             },
715             0x54 => {
716             Name => 'GPSImgDirection',
717             %float2f,
718             RawConv => '$val < 0 ? undef : $val',
719             },
720             0x58 => {
721             Name => 'GPSMapDatum',
722             Format => 'string[16]',
723             RawConv => 'length($val) ? $val : undef',
724             },
725             # 0xa4 - string[6]: seen 000208,081210,020409,000608,010408,020808,091011
726             # 0x78 - double[2]: seen "-1 -1","0 0"
727             # 0x78 - float[2]: seen "-1 -1","0 0"
728             # 0xb2 - string[2]?: seen "5\0"
729             );
730              
731             # humidity meter information
732             # (ref https://exiftool.org/forum/index.php/topic,5325.0.html)
733             # The %Image::ExifTool::UserDefined hash defines new tags to be added to existing tables.
734             %Image::ExifTool::FLIR::MeterLink = (
735             GROUPS => { 0 => 'APP1', 2 => 'Image' },
736             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
737             FIRST_ENTRY => 0,
738             NOTES => 'Tags containing Meterlink humidity meter information.',
739             26 => {
740             Name => 'Reading1Units',
741             DataMember => 'Reading1Units',
742             RawConv => '$$self{Reading1Units} = $val',
743             PrintHex => 1,
744             PrintConv => {
745             0x0d => 'C',
746             0x1b => '%',
747             0x1d => 'Relative',
748             0x24 => 'g/kg',
749             },
750             },
751             28 => {
752             Name => 'Reading1Description',
753             DataMember => 'Reading1Description',
754             RawConv => '$$self{Reading1Description} = $val',
755             PrintConv => {
756             0 => 'Humidity',
757             3 => 'Moisture', # Pinless Moisture Readings with INTernal sensor
758             7 => 'Dew Point',
759             8 => 'Air Temperature',
760             9 => 'IR Temperature',
761             11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
762             },
763             },
764             32 => {
765             Name => 'Reading1Device',
766             Format => 'string[16]',
767             },
768             96 => {
769             Name => 'Reading1Value',
770             Format => 'double',
771             # convert Kelvin -> Celsius and kg/kg -> g/kg
772             ValueConv => q{
773             return $val - 273.15 if $$self{Reading1Units} == 0x0d and $$self{Reading1Description} != 11;
774             return $val *= 1000 if $$self{Reading1Units} == 0x24;
775             return $val;
776             },
777             },
778             # add 100 for subsequent readings
779             126 => {
780             Name => 'Reading2Units',
781             DataMember => 'Reading2Units',
782             RawConv => '$$self{Reading2Units} = $val',
783             PrintHex => 1,
784             PrintConv => {
785             0x0d => 'C',
786             0x1b => '%',
787             0x1d => 'rel',
788             0x24 => 'g/kg',
789             },
790             },
791             128 => {
792             Name => 'Reading2Description',
793             DataMember => 'Reading2Description',
794             RawConv => '$$self{Reading2Description} = $val',
795             PrintConv => {
796             0 => 'Humidity',
797             3 => 'Moisture',
798             7 => 'Dew Point',
799             8 => 'Air Temperature',
800             9 => 'IR Temperature',
801             11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
802             },
803             },
804             132 => {
805             Name => 'Reading2Device',
806             Format => 'string[16]',
807             },
808             196 => {
809             Name => 'Reading2Value',
810             Format => 'double',
811             # convert Kelvin -> Celsius and kg/kg -> g/kg
812             ValueConv => q{
813             return $val - 273.15 if $$self{Reading2Units} == 0x0d and $$self{Reading2Description} != 11;
814             return $val *= 1000 if $$self{Reading2Units} == 0x24;
815             return $val;
816             },
817             },
818             226 => {
819             Name => 'Reading3Units',
820             DataMember => 'Reading3Units',
821             RawConv => '$$self{Reading3Units} = $val',
822             PrintHex => 1,
823             PrintConv => {
824             0x0d => 'C',
825             0x1b => '%',
826             0x1d => 'rel',
827             0x24 => 'g/kg',
828             },
829             },
830             228 => {
831             Name => 'Reading3Description',
832             DataMember => 'Reading3Description',
833             RawConv => '$$self{Reading3Description} = $val',
834             PrintConv => {
835             0 => 'Humidity',
836             3 => 'Moisture',
837             7 => 'Dew Point',
838             8 => 'Air Temperature',
839             9 => 'IR Temperature',
840             11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
841             },
842             },
843             232 => {
844             Name => 'Reading3Device',
845             Format => 'string[16]',
846             },
847             296 => {
848             Name => 'Reading3Value',
849             Format => 'double',
850             # convert Kelvin -> Celsius and kg/kg -> g/kg
851             ValueConv => q{
852             return $val - 273.15 if $$self{Reading3Units} == 0x0d and $$self{Reading3Description} != 11;
853             return $val *= 1000 if $$self{Reading3Units} == 0x24;
854             return $val;
855             },
856             },
857              
858             326 => {
859             Name => 'Reading4Units',
860             DataMember => 'Reading4Units',
861             RawConv => '$$self{Reading4Units} = $val',
862             PrintHex => 1,
863             PrintConv => {
864             0x0d => 'C',
865             0x1b => '%',
866             0x1d => 'rel',
867             0x24 => 'g/kg',
868             },
869             },
870             328 => {
871             Name => 'Reading4Description',
872             DataMember => 'Reading4Description',
873             RawConv => '$$self{Reading4Description} = $val',
874             PrintConv => {
875             0 => 'Humidity',
876             3 => 'Moisture',
877             7 => 'Dew Point',
878             8 => 'Air Temperature',
879             9 => 'IR Temperature',
880             11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
881             },
882             },
883             332 => {
884             Name => 'Reading4Device',
885             Format => 'string[16]',
886             },
887             396 => {
888             Name => 'Reading4Value',
889             Format => 'double',
890             # convert Kelvin -> Celsius and kg/kg -> g/kg
891             ValueConv => q{
892             return $val - 273.15 if $$self{Reading4Units} == 0x0d and $$self{Reading4Description} != 11;
893             return $val *= 1000 if $$self{Reading4Units} == 0x24;
894             return $val;
895             },
896             },
897             );
898              
899             # FLIR public image format (ref 4/5)
900             %Image::ExifTool::FLIR::FPF = (
901             GROUPS => { 0 => 'FLIR', 2 => 'Image' },
902             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
903             NOTES => 'Tags extracted from FLIR Public image Format (FPF) files.',
904             0x20 => { Name => 'FPFVersion', Format => 'int32u' },
905             0x24 => { Name => 'ImageDataOffset', Format => 'int32u' },
906             0x28 => {
907             Name => 'ImageType',
908             Format => 'int16u',
909             PrintConv => {
910             0 => 'Temperature',
911             1 => 'Temperature Difference',
912             2 => 'Object Signal',
913             3 => 'Object Signal Difference',
914             },
915             },
916             0x2a => {
917             Name => 'ImagePixelFormat',
918             Format => 'int16u',
919             PrintConv => {
920             0 => '2-byte short integer',
921             1 => '4-byte long integer',
922             2 => '4-byte float',
923             3 => '8-byte double',
924             },
925             },
926             0x2c => { Name => 'ImageWidth', Format => 'int16u' },
927             0x2e => { Name => 'ImageHeight', Format => 'int16u' },
928             0x30 => { Name => 'ExternalTriggerCount',Format => 'int32u' },
929             0x34 => { Name => 'SequenceFrameNumber',Format => 'int32u' },
930             0x78 => { Name => 'CameraModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
931             0x98 => { Name => 'CameraPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
932             0xb8 => { Name => 'CameraSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
933             0xd8 => { Name => 'CameraTemperatureRangeMin', %floatKelvin, Groups => { 2 => 'Camera' } },
934             0xdc => { Name => 'CameraTemperatureRangeMax', %floatKelvin, Groups => { 2 => 'Camera' } },
935             0xe0 => { Name => 'LensModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
936             0x100 => { Name => 'LensPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
937             0x120 => { Name => 'LensSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
938             0x140 => { Name => 'FilterModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
939             0x150 => { Name => 'FilterPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
940             0x180 => { Name => 'FilterSerialNumber',Format => 'string[32]', Groups => { 2 => 'Camera' } },
941             0x1e0 => { Name => 'Emissivity', %float2f },
942             0x1e4 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' },
943             0x1e8 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
944             0x1ec => { Name => 'AtmosphericTemperature', %floatKelvin },
945             0x1f0 => { Name => 'RelativeHumidity', Format => 'float', PrintConv => 'sprintf("%.1f %%",$val*100)' },
946             0x1f4 => { Name => 'ComputedAtmosphericTrans', %float2f },
947             0x1f8 => { Name => 'EstimatedAtmosphericTrans',%float2f },
948             0x1fc => { Name => 'ReferenceTemperature', %floatKelvin },
949             0x200 => { Name => 'IRWindowTemperature', %floatKelvin, Groups => { 2 => 'Camera' } },
950             0x204 => { Name => 'IRWindowTransmission', %float2f, Groups => { 2 => 'Camera' } },
951             0x248 => {
952             Name => 'DateTimeOriginal',
953             Description => 'Date/Time Original',
954             Groups => { 2 => 'Time' },
955             Format => 'int32u[7]',
956             ValueConv => 'sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%.3d",split(" ",$val))',
957             PrintConv => '$self->ConvertDateTime($val)',
958             },
959             # Notes (based on ref 4):
960             # 1) The above date/time structure is documented to be 32 bytes for FPFVersion 1, but in
961             # fact it is only 28. Maybe this is why the full header length of my FPFVersion 2
962             # sample is 892 bytes instead of 896. If this was a documentation error, we are OK,
963             # but if the alignment was really different in version 1, then the temperatures below
964             # will be mis-aligned. I don't have any version 1 samples to check this.
965             # 2) The following temperatures may not always be in Kelvin
966             0x2a4 => { Name => 'CameraScaleMin', %float1f },
967             0x2a8 => { Name => 'CameraScaleMax', %float1f },
968             0x2ac => { Name => 'CalculatedScaleMin',%float1f },
969             0x2b0 => { Name => 'CalculatedScaleMax',%float1f },
970             0x2b4 => { Name => 'ActualScaleMin', %float1f },
971             0x2b8 => { Name => 'ActualScaleMax', %float1f },
972             );
973              
974             # top-level user data written by FLIR cameras in MP4 videos
975             %Image::ExifTool::FLIR::UserData = (
976             GROUPS => { 1 => 'FLIR', 2 => 'Camera' },
977             NOTES => q{
978             Tags written by some FLIR cameras in a top-level (!) "udta" atom of MP4
979             videos.
980             },
981             uuid => [
982             {
983             Name => 'FLIR_Parts',
984             Condition => '$$valPt=~/^\x43\xc3\x99\x3b\x0f\x94\x42\x4b\x82\x05\x6b\x66\x51\x3f\x48\x5d/s',
985             SubDirectory => {
986             TagTable => 'Image::ExifTool::FLIR::Parts',
987             Start => 16,
988             },
989             },
990             {
991             Name => 'FLIR_Serial',
992             Condition => '$$valPt=~/^\x57\xf5\xb9\x3e\x51\xe4\x48\xaf\xa0\xd9\xc3\xef\x1b\x37\xf7\x12/s',
993             SubDirectory => {
994             TagTable => 'Image::ExifTool::FLIR::SerialNums',
995             Start => 16,
996             },
997             },
998             {
999             Name => 'FLIR_Params',
1000             Condition => '$$valPt=~/^\x41\xe5\xdc\xf9\xe8\x0a\x41\xce\xad\xfe\x7f\x0c\x58\x08\x2c\x19/s',
1001             SubDirectory => {
1002             TagTable => 'Image::ExifTool::FLIR::Params',
1003             Start => 16,
1004             },
1005             },
1006             {
1007             Name => 'FLIR_UnknownUUID',
1008             Condition => '$$valPt=~/^\x57\x45\x20\x50\x2c\xbb\x44\xad\xae\x54\x15\xe9\xb8\x39\xd9\x03/s',
1009             SubDirectory => {
1010             TagTable => 'Image::ExifTool::FLIR::UnknownUUID',
1011             Start => 16,
1012             },
1013             },
1014             {
1015             Name => 'FLIR_GPS',
1016             Condition => '$$valPt=~/^\x7f\x2e\x21\x00\x8b\x46\x49\x18\xaf\xb1\xde\x70\x9a\x74\xf6\xf5/s',
1017             SubDirectory => {
1018             TagTable => 'Image::ExifTool::FLIR::GPS_UUID',
1019             Start => 16,
1020             },
1021             },
1022             {
1023             Name => 'FLIR_MoreInfo',
1024             Condition => '$$valPt=~/^\x2b\x45\x2f\xdc\x74\x35\x40\x94\xba\xee\x22\xa6\xb2\x3a\x7c\xf8/s',
1025             SubDirectory => {
1026             TagTable => 'Image::ExifTool::FLIR::MoreInfo',
1027             Start => 16,
1028             },
1029             },
1030             {
1031             Name => 'SoftwareComponents',
1032             Condition => '$$valPt=~/^\x78\x3f\xc7\x83\x0c\x95\x4b\x00\x8c\xc7\xac\xf1\xec\xb4\xd3\x9a/s',
1033             Unknown => 1,
1034             ValueConv => 'join " ", unpack "x20N4xZ*", $val',
1035             },
1036             {
1037             Name => 'FLIR_Unknown',
1038             Condition => '$$valPt=~/^\x52\xae\xda\x45\x17\x1e\x48\xb1\x92\x47\x93\xa4\x21\x4e\x43\xf5/s',
1039             Unknown => 1,
1040             ValueConv => 'unpack "x20C*", $val',
1041             },
1042             {
1043             Name => 'Units',
1044             Condition => '$$valPt=~/^\xf8\xab\x72\x1e\x84\x73\x44\xa0\xb8\xc8\x1b\x04\x82\x6e\x07\x24/s',
1045             List => 1,
1046             RawConv => 'my @a = split "\0", substr($val, 20); \@a',
1047             },
1048             {
1049             Name => 'ThumbnailImage',
1050             Groups => { 2 => 'Preview' },
1051             Condition => '$$valPt=~/^\x91\xaf\x9b\x93\x45\x9b\x44\x56\x98\xd1\x5e\x76\xea\x01\x04\xac....\xff\xd8\xff/s',
1052             RawConv => 'substr($val, 20)',
1053             Binary => 1,
1054             },
1055             ],
1056             );
1057              
1058             # uuid 43c3993b0f94424b82056b66513f485d box of MP4 videos (ref PH)
1059             %Image::ExifTool::FLIR::Parts = (
1060             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
1061             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1062             FORMAT => 'undef',
1063             NOTES => q{
1064             Tags extracted from the "uuid" box with ID 43c3993b0f94424b82056b66513f485d
1065             in FLIR MP4 videos.
1066             },
1067             4 => [
1068             {
1069             Name => 'BAHPVer',
1070             Condition => '$$valPt =~ /^bahpver\0/',
1071             Format => 'undef[$size]',
1072             RawConv => 'join " ", split "\0", substr($val, 8)',
1073             },
1074             {
1075             Name => 'BALPVer',
1076             Condition => '$$valPt =~ /^balpver\0/',
1077             Format => 'undef[$size]',
1078             ValueConv => 'join " ", split "\0", substr($val, 8)',
1079             },
1080             {
1081             Name => 'Battery',
1082             Condition => '$$valPt =~ /^battery\0/',
1083             Format => 'undef[$size]',
1084             ValueConv => 'join " ", split "\0", substr($val, 8)',
1085             },
1086             {
1087             Name => 'BAVPVer',
1088             Condition => '$$valPt =~ /^bavpver\0/',
1089             Format => 'undef[$size]',
1090             ValueConv => 'join " ", split "\0", substr($val, 8)',
1091             # (the first string corresponds with a lens part number)
1092             },
1093             {
1094             Name => 'CamCore',
1095             Condition => '$$valPt =~ /^camcore\0/',
1096             Format => 'undef[$size]',
1097             ValueConv => 'join " ", split "\0", substr($val, 8)',
1098             },
1099             {
1100             Name => 'DetectorBoard',
1101             Condition => '$$valPt =~ /^det_board\0/',
1102             Format => 'undef[$size]',
1103             ValueConv => 'join " ", split "\0", substr($val, 10)',
1104             },
1105             {
1106             Name => 'Detector',
1107             Condition => '$$valPt =~ /^detector\0/',
1108             Format => 'undef[$size]',
1109             ValueConv => 'join " ", split "\0", substr($val, 9)',
1110             },
1111             {
1112             Name => 'GIDCVer',
1113             Condition => '$$valPt =~ /^gidcver\0/',
1114             Format => 'undef[$size]',
1115             ValueConv => 'join " ", split "\0", substr($val, 8)',
1116             },
1117             {
1118             Name => 'GIDPVer',
1119             Condition => '$$valPt =~ /^gidpver\0/',
1120             Format => 'undef[$size]',
1121             ValueConv => 'join " ", split "\0", substr($val, 8)',
1122             },
1123             {
1124             Name => 'GIPC_CPLD',
1125             Condition => '$$valPt =~ /^gipccpld\0/',
1126             Format => 'undef[$size]',
1127             ValueConv => 'join " ", split "\0", substr($val, 9)',
1128             },
1129             {
1130             Name => 'GIPCVer',
1131             Condition => '$$valPt =~ /^gipcver\0/',
1132             Format => 'undef[$size]',
1133             ValueConv => 'join " ", split "\0", substr($val, 8)',
1134             },
1135             {
1136             Name => 'GIXIVer',
1137             Condition => '$$valPt =~ /^gixiver\0/',
1138             Format => 'undef[$size]',
1139             ValueConv => 'join " ", split "\0", substr($val, 8)',
1140             },
1141             {
1142             Name => 'MainBoard',
1143             Condition => '$$valPt =~ /^mainboard\0/',
1144             Format => 'undef[$size]',
1145             ValueConv => 'join " ", split "\0", substr($val, 10)',
1146             },
1147             {
1148             Name => 'Optics',
1149             Condition => '$$valPt =~ /^optics\0/',
1150             Format => 'undef[$size]',
1151             ValueConv => 'join " ", split "\0", substr($val, 7)',
1152             },
1153             {
1154             Name => 'PartNumber',
1155             Format => 'undef[$size]',
1156             ValueConv => 'join " ", split "\0", $val',
1157             },
1158             ],
1159             );
1160              
1161             # uuid 57f5b93e51e448afa0d9c3ef1b37f712 box of MP4 videos (ref PH)
1162             %Image::ExifTool::FLIR::SerialNums = (
1163             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
1164             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1165             FIRST_ENTRY => 0,
1166             NOTES => q{
1167             Tags extracted from the "uuid" box with ID 57f5b93e51e448afa0d9c3ef1b37f712
1168             in FLIR MP4 videos.
1169             },
1170             # (not sure if these offsets are constant)
1171             0x0c => { Name => 'UnknownSerial1', Format => 'string[33]', Unknown => 1 },
1172             0x2d => { Name => 'UnknownSerial2', Format => 'string[33]', Unknown => 1 },
1173             0x4e => { Name => 'UnknownSerial3', Format => 'string[33]', Unknown => 1 },
1174             0x6f => { Name => 'UnknownSerial4', Format => 'string[11]', Unknown => 1 },
1175             0x7b => { Name => 'UnknownNumber', Format => 'string[3]', Unknown => 1 },
1176             0x7e => { Name => 'CameraSerialNumber', Format => 'string[9]' },
1177             );
1178              
1179             # uuid 41e5dcf9e80a41ceadfe7f0c58082c19 box of MP4 videos (ref PH)
1180             %Image::ExifTool::FLIR::Params = (
1181             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
1182             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1183             FORMAT => 'float',
1184             FIRST_ENTRY => 0,
1185             NOTES => q{
1186             Tags extracted from the "uuid" box with ID 41e5dcf9e80a41ceadfe7f0c58082c19
1187             in FLIR MP4 videos.
1188             },
1189             1 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
1190             2 => { Name => 'AtmosphericTemperature', %floatKelvin },
1191             3 => { Name => 'Emissivity', %float2f },
1192             4 => { Name => 'ObjectDistance', PrintConv => 'sprintf("%.2f m",$val)' },
1193             5 => { Name => 'RelativeHumidity', PrintConv => 'sprintf("%.1f %%",$val*100)' },
1194             6 => { Name => 'EstimatedAtmosphericTrans', %float2f },
1195             7 => { Name => 'IRWindowTemperature', %floatKelvin },
1196             8 => { Name => 'IRWindowTransmission', %float2f },
1197             );
1198              
1199             # uuid 574520502cbb44adae5415e9b839d903 box of MP4 videos (ref PH)
1200             %Image::ExifTool::FLIR::UnknownUUID = (
1201             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
1202             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1203             FORMAT => 'float',
1204             FIRST_ENTRY => 0,
1205             NOTES => q{
1206             Tags extracted from the "uuid" box with ID 574520502cbb44adae5415e9b839d903
1207             in FLIR MP4 videos.
1208             },
1209             # 1 - 1
1210             # 2 - 0
1211             # 3 - 0
1212             );
1213              
1214             # uuid 7f2e21008b464918afb1de709a74f6f5 box of MP4 videos (ref PH)
1215             %Image::ExifTool::FLIR::GPS_UUID = (
1216             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Location' },
1217             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1218             FORMAT => 'float',
1219             FIRST_ENTRY => 0,
1220             NOTES => q{
1221             Tags extracted from the "uuid" box with ID 7f2e21008b464918afb1de709a74f6f5
1222             in FLIR MP4 videos.
1223             },
1224             1 => {
1225             Name => 'GPSLatitude',
1226             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
1227             },
1228             2 => {
1229             Name => 'GPSLongitude',
1230             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
1231             },
1232             3 => {
1233             Name => 'GPSAltitude',
1234             PrintConv => '$val=int($val*100+0.5)/100;"$val m"',
1235             },
1236             # 4 - int32u: 0x0001bf74
1237             # 5 - int32u: 0
1238             # 6 - int32u: 1
1239             );
1240              
1241             # uuid 2b452fdc74354094baee22a6b23a7cf8 box of MP4 videos (ref PH)
1242             %Image::ExifTool::FLIR::MoreInfo = (
1243             GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
1244             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1245             FIRST_ENTRY => 0,
1246             NOTES => q{
1247             Tags extracted from the "uuid" box with ID 2b452fdc74354094baee22a6b23a7cf8
1248             in FLIR MP4 videos.
1249             },
1250             5 => { Name => 'LensModel', Format => 'string[6]' },
1251             11 => { Name => 'UnknownTemperature1', %floatKelvin, Unknown => 1 }, # (-14.9 C)
1252             15 => { Name => 'UnknownTemperature2', %floatKelvin, Unknown => 1 }, # (60.0 C)
1253             );
1254              
1255             # FLIR AFF tag table (ref PH)
1256             %Image::ExifTool::FLIR::AFF = (
1257             GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Image' },
1258             NOTES => 'Tags extracted from FLIR "AFF" SEQ images.',
1259             VARS => { ALPHA_FIRST => 1 },
1260             "_header" => {
1261             Name => 'AFFHeader',
1262             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' },
1263             },
1264             0x01 => {
1265             Name => 'AFF1',
1266             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF1' },
1267             },
1268             0x05 => {
1269             Name => 'AFF5',
1270             SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF5' },
1271             },
1272             );
1273              
1274             # AFF record type 1 (ref forum?topic=4898.msg27627)
1275             %Image::ExifTool::FLIR::AFF1 = (
1276             GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' },
1277             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1278             FORMAT => 'int16u',
1279             FIRST_ENTRY => 0,
1280             0x00 => {
1281             # use this tag only to determine the byte order of the raw data
1282             # (the value should be 0x0002 if the byte order is correct)
1283             Name => 'RawDataByteOrder',
1284             Hidden => 1,
1285             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
1286             },
1287             0x01 => { Name => 'SensorWidth', Format => 'int16u' },
1288             0x02 => { Name => 'SensorHeight', Format => 'int16u' },
1289             );
1290              
1291             # AFF record type 5 (ref forum?topic=4898.msg27628)
1292             %Image::ExifTool::FLIR::AFF5 = (
1293             GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' },
1294             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
1295             FORMAT => 'int16u',
1296             FIRST_ENTRY => 0,
1297             0x12 => {
1298             # use this tag only to determine the byte order of the raw data
1299             # (the value should be 0x0002 if the byte order is correct)
1300             Name => 'RawDataByteOrder',
1301             Hidden => 1,
1302             RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
1303             },
1304             0x13 => { Name => 'SensorWidth', Format => 'int16u' },
1305             0x14 => { Name => 'SensorHeight', Format => 'int16u' },
1306             );
1307              
1308             # FLIR composite tags (ref 1)
1309             %Image::ExifTool::FLIR::Composite = (
1310             GROUPS => { 1 => 'FLIR', 2 => 'Camera' },
1311             PeakSpectralSensitivity => {
1312             Require => 'FLIR:PlanckB',
1313             ValueConv => '14387.6515/$val',
1314             PrintConv => 'sprintf("%.1f um", $val)',
1315             },
1316             );
1317              
1318             # add our composite tags
1319             Image::ExifTool::AddCompositeTags('Image::ExifTool::FLIR');
1320              
1321             #------------------------------------------------------------------------------
1322             # Get image type from raw image data
1323             # Inputs: 0) ExifTool ref, 1) image data, 2) tag name
1324             # Returns: image type (PNG, JPG, TIFF or undef)
1325             # - image itself is stored in $$et{$tag}
1326             sub GetImageType($$$)
1327             {
1328 1     1 0 3 my ($et, $val, $tag) = @_;
1329 1         7 my ($w, $h) = @$et{"${tag}Width","${tag}Height"};
1330 1         3 my $type = 'DAT';
1331             # add TIFF header only if this looks like 16-bit raw data
1332             # (note: MakeTiffHeader currently works only for little-endian,
1333             # and I haven't seen any big-endian samples, but check anwyay)
1334 1 50       7 if ($val =~ /^\x89PNG\r\n\x1a\n/) {
    0          
    0          
    0          
1335 1         2 $type = 'PNG';
1336             } elsif ($val =~ /^\xff\xd8\xff/) { # (haven't seen this, but just in case - PH)
1337 0         0 $type = 'JPG';
1338             } elsif (length $val != $w * $h * 2) {
1339 0         0 $et->Warn("Unrecognized FLIR $tag data format");
1340             } elsif (GetByteOrder() eq 'II') {
1341 0         0 $val = Image::ExifTool::MakeTiffHeader($w,$h,1,16) . $val;
1342 0         0 $type = 'TIFF';
1343             } else {
1344 0         0 $et->Warn("Don't yet support big-endian TIFF $tag");
1345             }
1346             # save image data
1347 1         3 $$et{$tag} = $val;
1348 1         7 return $type;
1349             }
1350              
1351             #------------------------------------------------------------------------------
1352             # Unescape FLIR Unicode character
1353             # Inputs: 0) escaped character code
1354             # Returns: UTF8 character
1355             sub UnescapeFLIR($)
1356             {
1357 0     0 0 0 my $char = shift;
1358 0 0       0 return $char unless length $char == 4; # escaped ASCII char (eg. '\\')
1359 0         0 my $val = hex $char;
1360 0 0       0 return chr($val) if $val < 0x80; # simple ASCII
1361 0 0       0 return pack('C0U', $val) if $] >= 5.006001;
1362 0         0 return Image::ExifTool::PackUTF8($val);
1363             }
1364              
1365             #------------------------------------------------------------------------------
1366             # Process FLIR text info record (ref PH)
1367             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1368             # Returns: 1 on success
1369             sub ProcessFLIRText($$$)
1370             {
1371 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
1372 0         0 my $dataPt = $$dirInfo{DataPt};
1373 0   0     0 my $dirStart = $$dirInfo{DirStart} || 0;
1374 0         0 my $dirLen = $$dirInfo{DirLen};
1375              
1376 0 0       0 return 0 if $dirLen < 12;
1377              
1378 0         0 $et->VerboseDir('FLIR Text');
1379              
1380 0         0 my $dat = substr($$dataPt, $dirStart+12, $dirLen-12);
1381 0         0 $dat =~ s/\0.*//s; # truncate at null
1382              
1383             # the parameter text contains an additional header entry...
1384 0 0 0     0 if ($tagTablePtr eq \%Image::ExifTool::FLIR::ParamInfo and
1385             $dat =~ /# (Generated) at (.*?)[\n\r]/)
1386             {
1387 0         0 $et->HandleTag($tagTablePtr, $1, $2);
1388             }
1389              
1390 0         0 for (;;) {
1391 0 0       0 $dat =~ /.(\d+).(label|value|param) (unicode|text) "(.*)"/g or last;
1392 0         0 my ($tag, $val) = (ucfirst($2) . $1, $4);
1393 0 0 0     0 if ($3 eq 'unicode' and $val =~ /\\/) {
1394             # convert escaped Unicode characters (backslash followed by 4 hex digits)
1395 0         0 $val =~ s/\\([0-9a-fA-F]{4}|.)/UnescapeFLIR($1)/sge;
  0         0  
1396 0         0 $et->Decode($val, 'UTF8');
1397             }
1398 0 0       0 $$tagTablePtr{$tag} or AddTagToTable($tagTablePtr, $tag, { Name => $tag });
1399 0         0 $et->HandleTag($tagTablePtr, $tag, $val);
1400             }
1401              
1402 0         0 return 1;
1403             }
1404              
1405             #------------------------------------------------------------------------------
1406             # Process FLIR measurement tool record (ref 6)
1407             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1408             # Returns: 1 on success
1409             # (code-driven decoding isn't pretty, but sometimes it is necessary)
1410             sub ProcessMeasInfo($$$)
1411             {
1412 1     1 0 12 my ($et, $dirInfo, $tagTablePtr) = @_;
1413 1         3 my $dataPt = $$dirInfo{DataPt};
1414 1   50     5 my $dirStart = $$dirInfo{DirStart} || 0;
1415 1         3 my $dataPos = $$dirInfo{DataPos};
1416 1         2 my $dirEnd = $dirStart + $$dirInfo{DirLen};
1417              
1418 1         2 my $pos = $dirStart + 12;
1419 1 50       4 return 0 if $pos > $dirEnd;
1420 1 50       4 ToggleByteOrder() if Get16u($dataPt, $dirStart) >= 0x100;
1421 1         4 my ($i, $t, $p);
1422 1         3 for ($i=1; ; ++$i) {
1423 1 50       4 last if $pos + 2 > $dirEnd;
1424 1         11 my $recLen = Get16u($dataPt, $pos);
1425 1 50 33     9 last if $recLen < 0x28 or $pos + $recLen > $dirEnd;
1426 0         0 my $pre = 'Meas' . $i;
1427 0         0 $et->VerboseDir("MeasInfo $i", undef, $recLen);
1428 0         0 $et->VerboseDump($dataPt, Len => $recLen, Start=>$pos, DataPos=>$dataPos);
1429 0         0 my $coordLen = Get16u($dataPt, $pos+4);
1430             # generate tag table entries for this tool if necessary
1431 0         0 foreach $t ('Type', 'Params', 'Label') {
1432 0         0 my $tag = $pre . $t;
1433 0 0       0 last if $$tagTablePtr{$tag};
1434 0         0 my $tagInfo = { Name => $tag };
1435 0         0 $$tagInfo{PrintConv} = $$tagTablePtr{"Meas1$t"}{PrintConv};
1436 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
1437             }
1438             # extract measurement tool type
1439 0         0 $et->HandleTag($tagTablePtr, "${pre}Type", undef,
1440             DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x0a, Size=>2);
1441 0 0       0 last if $pos + 0x24 + $coordLen > $dirEnd;
1442             # extract measurement parameters
1443 0         0 $et->HandleTag($tagTablePtr, "${pre}Params", undef,
1444             DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24, Size=>$coordLen);
1445 0         0 my @uni;
1446             # extract label (sometimes-null-terminated Unicode)
1447 0         0 for ($p=0x24+$coordLen; $p<$recLen-1; $p+=2) {
1448 0         0 my $ch = Get16u($dataPt, $p+$pos);
1449             # FLIR Tools v2.0 for Mac doesn't properly null-terminate these strings,
1450             # so end the string at any funny character
1451 0 0 0     0 last if $ch < 0x20 or $ch > 0x7f;
1452 0         0 push @uni, $ch;
1453             }
1454             # convert to the ExifTool character set
1455 0         0 require Image::ExifTool::Charset;
1456 0         0 my $val = Image::ExifTool::Charset::Recompose($et, \@uni);
1457 0         0 $et->HandleTag($tagTablePtr, "${pre}Label", $val,
1458             DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24+$coordLen, Size=>2*scalar(@uni));
1459 0         0 $pos += $recLen; # step to next record
1460             }
1461 1         3 return 1;
1462             }
1463              
1464             #------------------------------------------------------------------------------
1465             # Process FLIR FFF record (ref PH/1/3)
1466             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1467             # Returns: 1 if this was a valid FFF record
1468             sub ProcessFLIR($$;$)
1469             {
1470 1     1 0 4 my ($et, $dirInfo, $tagTablePtr) = @_;
1471 1   33     12 my $raf = $$dirInfo{RAF} || new File::RandomAccess($$dirInfo{DataPt});
1472 1         4 my $verbose = $et->Options('Verbose');
1473 1         6 my $out = $et->Options('TextOut');
1474 1         4 my $base = $raf->Tell();
1475 1         4 my ($i, $hdr, $buff, $rec);
1476              
1477             # read and verify FFF header
1478 1 50 33     3 $raf->Read($hdr, 0x40) == 0x40 and $hdr =~ /^([AF]FF)\0/ or return 0;
1479              
1480 1         3 my $type = $1;
1481              
1482             # set file type if reading from FFF or SEQ file ($tagTablePtr will not be defined)
1483 1 0       3 $et->SetFileType($type eq 'FFF' ? 'FLIR' : 'SEQ') unless $tagTablePtr;
    50          
1484              
1485             # FLIR file header (ref 3)
1486             # 0x00 - string[4] file format ID = "FFF\0"
1487             # 0x04 - string[16] file creator: seen "\0","MTX IR\0","CAMCTRL\0"
1488             # 0x14 - int32u file format version = 100
1489             # 0x18 - int32u offset to record directory
1490             # 0x1c - int32u number of entries in record directory
1491             # 0x20 - int32u next free index ID = 2
1492             # 0x24 - int16u swap pattern = 0 (?)
1493             # 0x28 - int16u[7] spares
1494             # 0x34 - int32u[2] reserved
1495             # 0x3c - int32u checksum
1496              
1497             # determine byte ordering by validating version number
1498             # (in my samples FLIR APP1 is big-endian, FFF files are little-endian)
1499 1         3 for ($i=0; ; ++$i) {
1500 2         5 my $ver = Get32u(\$hdr, 0x14);
1501 2 100 66     24 last if $ver >= 100 and $ver < 200; # (have seen 100 and 101 - PH)
1502 1         6 ToggleByteOrder();
1503 1 50       4 next unless $i;
1504 0 0       0 return 0 if $$et{DOC_NUM};
1505 0         0 $et->Warn("Unsupported FLIR $type version");
1506 0         0 return 1;
1507             }
1508              
1509             # read the FLIR record directory
1510 1         4 my $pos = Get32u(\$hdr, 0x18);
1511 1         4 my $num = Get32u(\$hdr, 0x1c);
1512 1 50 33     4 unless ($raf->Seek($base+$pos) and $raf->Read($buff, $num * 0x20) == $num * 0x20) {
1513 0         0 $et->Warn('Truncated FLIR FFF directory');
1514 0 0       0 return $$et{DOC_NUM} ? 0 : 1;
1515             }
1516              
1517 1 50       12 unless ($tagTablePtr) {
1518 0         0 $tagTablePtr = GetTagTable("Image::ExifTool::FLIR::$type");
1519 0         0 $$et{SET_GROUP0} = 'FLIR'; # (set group 0 to 'FLIR' for FFF files)
1520             }
1521              
1522             # process the header data
1523 1         10 $et->HandleTag($tagTablePtr, '_header', $hdr);
1524              
1525 1         3 my $success = 1;
1526 1         4 my $oldIndent = $$et{INDENT};
1527 1         3 $$et{INDENT} .= '| ';
1528 1         6 $et->VerboseDir($type, $num);
1529              
1530 1         4 for ($i=0; $i<$num; ++$i) {
1531              
1532             # FLIR record entry (ref 3):
1533             # 0x00 - int16u record type
1534             # 0x02 - int16u record subtype: RawData 1=BE, 2=LE, 3=PNG; 1 for other record types
1535             # 0x04 - int32u record version: seen 0x64,0x66,0x67,0x68,0x6f,0x104
1536             # 0x08 - int32u index id = 1
1537             # 0x0c - int32u record offset from start of FLIR data
1538             # 0x10 - int32u record length
1539             # 0x14 - int32u parent = 0 (?)
1540             # 0x18 - int32u object number = 0 (?)
1541             # 0x1c - int32u checksum: 0 for no checksum
1542              
1543 14         22 my $entry = $i * 0x20;
1544 14         28 my $recType = Get16u(\$buff, $entry);
1545 14 100       28 if ($recType == 0) {
1546 10 50       17 $verbose and print $out "$$et{INDENT}$i) FLIR Record 0x00 (empty)\n";
1547 10         20 next;
1548             }
1549 4         10 my $recPos = Get32u(\$buff, $entry + 0x0c);
1550 4         13 my $recLen = Get32u(\$buff, $entry + 0x10);
1551              
1552             $verbose and printf $out "%s%d) FLIR Record 0x%.2x, offset 0x%.4x, length 0x%.4x\n",
1553 4 50       20 $$et{INDENT}, $i, $recType, $recPos, $recLen;
1554              
1555             # skip RawData records for embedded documents
1556 4 50 66     17 if ($recType == 1 and $$et{DOC_NUM} and $et->Options('ExtractEmbedded') < 2) {
      33        
1557 0 0       0 $raf->Seek($base+$recPos+$recLen) or $success = 0, last;
1558 0         0 next;
1559             }
1560 4 50 33     17 unless ($raf->Seek($base+$recPos) and $raf->Read($rec, $recLen) == $recLen) {
1561 0 0       0 if ($$et{DOC_NUM}) {
1562 0         0 $success = 0; # abort processing more documents
1563             } else {
1564 0         0 $et->Warn('Invalid FLIR record');
1565             }
1566 0         0 last;
1567             }
1568 4 50       13 if ($$tagTablePtr{$recType}) {
    0          
1569 4         14 $et->HandleTag($tagTablePtr, $recType, undef,
1570             Base => $base,
1571             DataPt => \$rec,
1572             DataPos => $recPos,
1573             Start => 0,
1574             Size => $recLen,
1575             );
1576             } elsif ($verbose > 2) {
1577 0         0 $et->VerboseDump(\$rec, Len => $recLen, DataPos => $recPos);
1578             }
1579             }
1580 1         11 delete $$et{SET_GROUP0};
1581 1         7 $$et{INDENT} = $oldIndent;
1582              
1583             # extract information from subsequent frames in SEQ file if ExtractEmbedded is used
1584 1 0 33     6 if ($$dirInfo{RAF} and $et->Options('ExtractEmbedded') and not $$et{DOC_NUM}) {
      33        
1585 0         0 for (;;) {
1586 0         0 $$et{DOC_NUM} = $$et{DOC_COUNT} + 1;
1587 0 0       0 last unless ProcessFLIR($et, $dirInfo, $tagTablePtr);
1588             # (DOC_COUNT will be incremented automatically if we extracted any tags)
1589             }
1590 0         0 delete $$et{DOC_NUM};
1591             }
1592 1         10 return $success;
1593             }
1594              
1595             #------------------------------------------------------------------------------
1596             # Process FLIR public image format (FPF) file (ref PH/4)
1597             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1598             # Returns: 1 if this was a valid FFF file
1599             sub ProcessFPF($$)
1600             {
1601 1     1 0 4 my ($et, $dirInfo) = @_;
1602 1         3 my $raf = $$dirInfo{RAF};
1603 1         2 my $buff;
1604              
1605 1 50 33     4 $raf->Read($buff, 892) == 892 and $buff =~ /^FPF Public Image Format\0/ or return 0;
1606              
1607             # I think these are always little-endian, but check FPFVersion just in case
1608 1         6 SetByteOrder('II');
1609 1 50       5 ToggleByteOrder() unless Get32u(\$buff, 0x20) & 0xffff;
1610              
1611 1         4 my $tagTablePtr = GetTagTable('Image::ExifTool::FLIR::FPF');
1612 1         6 $et->SetFileType();
1613 1         7 $et->ProcessDirectory( { DataPt => \$buff, Parent => 'FPF' }, $tagTablePtr);
1614 1         6 return 1;
1615             }
1616              
1617             1; # end
1618              
1619             __END__