File Coverage

blib/lib/Image/ExifTool/FLIR.pm
Criterion Covered Total %
statement 86 157 54.7
branch 21 82 25.6
condition 13 43 30.2
subroutine 9 11 81.8
pod 0 6 0.0
total 129 299 43.1


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