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