exiftool-vendored.pl 12.80.0 → 12.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/bin/Changes +81 -0
  2. package/bin/MANIFEST +6 -18
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +4 -2
  6. package/bin/build_geolocation +872 -0
  7. package/bin/config_files/example.config +2 -2
  8. package/bin/exiftool +61 -17
  9. package/bin/fmt_files/gpx.fmt +2 -1
  10. package/bin/fmt_files/gpx_wpt.fmt +2 -1
  11. package/bin/lib/Image/ExifTool/Apple.pm +51 -7
  12. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +47 -31
  13. package/bin/lib/Image/ExifTool/CanonVRD.pm +19 -6
  14. package/bin/lib/Image/ExifTool/DJI.pm +29 -0
  15. package/bin/lib/Image/ExifTool/Exif.pm +19 -2
  16. package/bin/lib/Image/ExifTool/FujiFilm.pm +20 -7
  17. package/bin/lib/Image/ExifTool/GM.pm +552 -0
  18. package/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  19. package/bin/lib/Image/ExifTool/Geolocation.pm +423 -178
  20. package/bin/lib/Image/ExifTool/Geotag.pm +26 -13
  21. package/bin/lib/Image/ExifTool/M2TS.pm +32 -4
  22. package/bin/lib/Image/ExifTool/MakerNotes.pm +2 -2
  23. package/bin/lib/Image/ExifTool/Microsoft.pm +1 -1
  24. package/bin/lib/Image/ExifTool/Nikon.pm +337 -27
  25. package/bin/lib/Image/ExifTool/NikonCustom.pm +55 -1
  26. package/bin/lib/Image/ExifTool/Olympus.pm +1 -0
  27. package/bin/lib/Image/ExifTool/OpenEXR.pm +21 -3
  28. package/bin/lib/Image/ExifTool/PNG.pm +3 -3
  29. package/bin/lib/Image/ExifTool/QuickTime.pm +45 -24
  30. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +66 -30
  31. package/bin/lib/Image/ExifTool/README +2 -0
  32. package/bin/lib/Image/ExifTool/Sony.pm +16 -7
  33. package/bin/lib/Image/ExifTool/TagLookup.pm +4827 -4778
  34. package/bin/lib/Image/ExifTool/TagNames.pod +953 -620
  35. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +32 -9
  36. package/bin/lib/Image/ExifTool/Writer.pl +169 -130
  37. package/bin/lib/Image/ExifTool/XMP.pm +4 -2
  38. package/bin/lib/Image/ExifTool/XMP2.pl +3 -0
  39. package/bin/lib/Image/ExifTool.pm +106 -48
  40. package/bin/lib/Image/ExifTool.pod +47 -25
  41. package/bin/perl-Image-ExifTool.spec +1 -1
  42. package/bin/pp_build_exe.args +4 -4
  43. package/package.json +3 -3
@@ -57,7 +57,7 @@ use vars qw($VERSION $AUTOLOAD @formatSize @formatName %formatNumber %intFormat
57
57
  use Image::ExifTool qw(:DataAccess :Utils);
58
58
  use Image::ExifTool::MakerNotes;
59
59
 
60
- $VERSION = '4.51';
60
+ $VERSION = '4.52';
61
61
 
62
62
  sub ProcessExif($$$);
63
63
  sub WriteExif($$$);
@@ -4342,7 +4342,7 @@ my %opcodeInfo = (
4342
4342
  Count => -1,
4343
4343
  Protected => 1,
4344
4344
  },
4345
- 0xcd3b => { # DNG 1.6
4345
+ 0xcd3f => { # DNG 1.6
4346
4346
  Name => 'RGBTables',
4347
4347
  Writable => 'undef',
4348
4348
  WriteGroup => 'IFD0',
@@ -4404,6 +4404,23 @@ my %opcodeInfo = (
4404
4404
  WriteGroup => 'IFD0',
4405
4405
  Protected => 1,
4406
4406
  },
4407
+ 0xcd49 => { # DNG 1.7.1
4408
+ Name => 'JXLDistance',
4409
+ Writable => 'float',
4410
+ WriteGroup => 'IFD0',
4411
+ },
4412
+ 0xcd4a => { # DNG 1.7.1
4413
+ Name => 'JXLEffort',
4414
+ Notes => 'values range from 1=low to 9=high',
4415
+ Writable => 'int32u',
4416
+ WriteGroup => 'IFD0',
4417
+ },
4418
+ 0xcd4b => { # DNG 1.7.1
4419
+ Name => 'JXLDecodeSpeed',
4420
+ Notes => 'values range from 1=slow to 4=fast',
4421
+ Writable => 'int32u',
4422
+ WriteGroup => 'IFD0',
4423
+ },
4407
4424
  0xea1c => { #13
4408
4425
  Name => 'Padding',
4409
4426
  Binary => 1,
@@ -31,7 +31,7 @@ use vars qw($VERSION);
31
31
  use Image::ExifTool qw(:DataAccess :Utils);
32
32
  use Image::ExifTool::Exif;
33
33
 
34
- $VERSION = '1.92';
34
+ $VERSION = '1.94';
35
35
 
36
36
  sub ProcessFujiDir($$$);
37
37
  sub ProcessFaceRec($$$);
@@ -550,8 +550,16 @@ my %faceCategories = (
550
550
  3 => 'Electronic Front Curtain', #10
551
551
  },
552
552
  },
553
- 0x1051 => { Name => 'CropTopLeft', Writable => 'int32u' }, #forum15784
554
- 0x1052 => { Name => 'CropCenter', Writable => 'int32u' }, #forum15784
553
+ 0x1051 => { #forum15784
554
+ Name => 'CropFlag',
555
+ Writable => 'int8u',
556
+ Notes => q(
557
+ this tag exists only if the image was cropped, and is 0 for cropped JPG
558
+ image or 1 for a cropped RAF
559
+ ),
560
+ },
561
+ 0x1052 => { Name => 'CropTopLeft', Writable => 'int32u' }, #forum15784
562
+ 0x1053 => { Name => 'CropSize', Writable => 'int32u' }, #forum15784
555
563
  # 0x1100 - This may not work well for newer cameras (ref forum12682)
556
564
  0x1100 => [{
557
565
  Name => 'AutoBracketing',
@@ -1033,14 +1041,19 @@ my %faceCategories = (
1033
1041
  },
1034
1042
  0.5 => {
1035
1043
  Name => 'AFAreaZoneSize',
1036
- Mask => 0xf0000,
1044
+ Mask => 0xff0000,
1037
1045
  PrintConv => {
1038
1046
  0 => 'n/a',
1039
1047
  OTHER => sub {
1040
1048
  my ($val, $inv) = @_;
1041
- return "$val x $val" unless $inv;
1042
- $val =~ s/ ?x.*//;
1043
- return $val;
1049
+ my ($w, $h);
1050
+ if ($inv) {
1051
+ my ($w, $h) = $val =~ /(\d+)/g;
1052
+ return 0 unless $w and $h;
1053
+ return((($h << 5) & 0xf0) | ($w & 0x0f));
1054
+ }
1055
+ ($w, $h) = ($val & 0x0f, $val >> 5);
1056
+ return "$w x $h";
1044
1057
  },
1045
1058
  },
1046
1059
  },
@@ -0,0 +1,552 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: GM.pm
3
+ #
4
+ # Description: Read GM PDR metadata from automobile videos
5
+ #
6
+ # Revisions: 2024-04-01 - P. Harvey Created
7
+ #
8
+ # References: 1) https://exiftool.org/forum/index.php?topic=11335
9
+ #------------------------------------------------------------------------------
10
+
11
+ package Image::ExifTool::GM;
12
+
13
+ use strict;
14
+ use vars qw($VERSION);
15
+ use Image::ExifTool qw(:DataAccess :Utils);
16
+ use Image::ExifTool::GPS;
17
+
18
+ $VERSION = '1.01';
19
+
20
+ sub Process_marl($$$);
21
+ sub Process_mrld($$$);
22
+ sub Process_mrlv($$$);
23
+ sub PrintCSV($;$);
24
+
25
+ # rename some units strings
26
+ my %convertUnits = (
27
+ "\xc2\xb0" => 'deg',
28
+ "\xc2\xb0C" => 'C',
29
+ "\xc2\xb0/sec" => 'deg/sec',
30
+ ltr => 'L',
31
+ );
32
+
33
+ my $pi = 3.141592653589793;
34
+
35
+ # offsets and scaling factors to convert to reasonable units
36
+ my %changeOffset = (
37
+ C => -273.15, # K to C
38
+ );
39
+ my %changeScale = (
40
+ G => 1 / 9.80665, # m/s2 to G
41
+ kph => 3.6, # m/s to km/h
42
+ deg => 180 / $pi, # radians to degrees
43
+ 'deg/sec' => 180 / $pi, # rad/s to deg/s
44
+ '%' => 100, # decimal to %
45
+ kPa => 1/1000, # Pa to kPa
46
+ rpm => 10, # ? (arbitrary factor of 10)
47
+ km => 1/1000, # m to km
48
+ L => 1000, # m3 to L
49
+ mm => 1000, # m to mm
50
+ );
51
+
52
+ # default print conversions for various types of units
53
+ my %printConv = (
54
+ rpm => 'sprintf("%.2f rpm", $val)',
55
+ '%' => 'sprintf("%.2f %%", $val)',
56
+ kPa => 'sprintf("%.2f kPa", $val)',
57
+ G => 'sprintf("%.3f G", $val)',
58
+ km => 'sprintf("%.3f km", $val)',
59
+ kph => 'sprintf("%.2f km/h", $val)',
60
+ deg => 'sprintf("%.2f deg", $val)',
61
+ 'deg/sec' => 'sprintf("%.2f deg/sec", $val)',
62
+ );
63
+
64
+ # channel parameters extracted from marl dictionary
65
+ my @channel = qw(
66
+ ID Type Num Units Flags Interval Min Max DispMin DispMax Multiplier Offset
67
+ Name Description
68
+ );
69
+ my %channelStruct = (
70
+ STRUCT_NAME => 'GM Channel',
71
+ NOTES => 'Information stored for each channel in the Marlin dictionary.',
72
+ SORT_ORDER => \@channel,
73
+ ID => { Writable => 0, Notes => 'channel ID number' },
74
+ Type => { Writable => 0, Notes => 'measurement type' },
75
+ Num => { Writable => 0, Notes => 'units ID number' },
76
+ Units => { Writable => 0, Notes => 'units string' },
77
+ Flags => { Writable => 0, Notes => 'channel flags' },
78
+ Interval=> { Writable => 0, Notes => 'measurement interval', ValueConv => '$val / 1e7', PrintConv => '"$val s"' },
79
+ Min => { Writable => 0, Notes => 'raw value minimum' },
80
+ Max => { Writable => 0, Notes => 'raw value maximum' },
81
+ DispMin => { Writable => 0, Notes => 'displayed value minimum' },
82
+ DispMax => { Writable => 0, Notes => 'displayed value maximum' },
83
+ Multiplier=>{Writable => 0, Notes => 'multiplier for raw value' },
84
+ Offset => { Writable => 0, Notes => 'offset for scaled value' },
85
+ Name => { Writable => 0, Notes => 'channel name' },
86
+ Description=>{Writable=> 0, Notes => 'channel description' },
87
+ );
88
+
89
+ # tags found in the 'mrlh' (marl header) atom
90
+ %Image::ExifTool::GM::mrlh = (
91
+ PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
92
+ NOTES => 'The Marlin PDR header.',
93
+ 0 => { Name => 'MarlinDataVersion', Format => 'int16u[2]', PrintConv => '$val =~ tr/ /./; $val' },
94
+ );
95
+
96
+ # tags found in the 'mrlv' (Marlin values) atom
97
+ %Image::ExifTool::GM::mrlv = (
98
+ PROCESS_PROC => \&Process_mrlv,
99
+ FORMAT => 'string',
100
+ NOTES => q{Tags found in the 'mrlv' (Marlin values) box.},
101
+ 'time'=> { Name => 'Time1', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
102
+ date => { Name => 'Date1', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
103
+ ltim => { Name => 'Time2', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
104
+ ldat => { Name => 'Date2', Groups => { 2 => 'Time' }, ValueConv => '$val =~ tr/-/:/; $val' },
105
+ tstm => {
106
+ Name => 'StartTime',
107
+ Groups => { 2 => 'Time' },
108
+ Format => 'int64u',
109
+ RawConv => '$$self{GMStartTime} = $val / 1e7',
110
+ ValueConv => 'ConvertUnixTime($val, undef, 6)', # (likely UTC, but not sure so don't add time zone)
111
+ PrintConv => '$self->ConvertDateTime($val)',
112
+ },
113
+ zone => { Name => 'TimeZone', Groups => { 2 => 'Time' } },
114
+ lang => 'Language',
115
+ unit => { Name => 'Units', PrintConv => { usim => 'U.S. Imperial' } },
116
+ swvs => 'SoftwareVersion',
117
+ # id ? ""
118
+ # cntr ? ""
119
+ # flap ? ""
120
+ );
121
+
122
+ # tags found in the 'mrld' (Marlin dictionary) atom
123
+ %Image::ExifTool::GM::mrld = (
124
+ PROCESS_PROC => \&Process_mrld,
125
+ VARS => { ADD_FLATTENED => 1 },
126
+ WRITABLE => 0,
127
+ NOTES => q{
128
+ The Marlin dictionary. Only one channel is listed but all available
129
+ channels are extracted. Use the -struct (L<API Struct|../ExifTool.html#Struct>) option to extract the
130
+ channel information as structures.
131
+ },
132
+ Channel01 => { Struct => \%channelStruct },
133
+ );
134
+
135
+ # tags found in 'marl' ctbx timed metadata
136
+ %Image::ExifTool::GM::marl = (
137
+ PROCESS_PROC => \&Process_marl,
138
+ GROUPS => { 2 => 'Other' },
139
+ VARS => { NO_ID => 1, NO_LOOKUP => 1 },
140
+ NOTES => q{
141
+ Tags extracted from the 'ctbx' 'marl' (Marlin) box of timed PDR metadata
142
+ from GM cars. Use the -ee (L<API ExtractEmbedded|../ExifTool.html#ExtractEmbedded>) option to extract this
143
+ information, or the L<API PrintCSV|../ExifTool.html#PrintCSV> option to output in CSV format.
144
+ },
145
+ TimeStamp => { # (the marl timestamp)
146
+ Groups => { 2 => 'Time' },
147
+ Notes => q{
148
+ the numerical value is seconds since start of video, but the print
149
+ conversion adds StartTime to provide a date/time value. Extracted as
150
+ GPSDateTime if requested
151
+ },
152
+ ValueConv => '$val / 1e7',
153
+ PrintConv => q{
154
+ return "$val s" unless $$self{GMStartTime};
155
+ return $self->ConvertDateTime(ConvertUnixTime($val+$$self{GMStartTime},undef,6));
156
+ },
157
+ },
158
+ GPSDateTime => { # (alternative for TimeStamp)
159
+ Groups => { 2 => 'Time' },
160
+ Notes => 'generated from the TimeStamp only if specifically requested',
161
+ RawConv => '$$self{GMStartTime} ? $val : undef',
162
+ ValueConv => 'ConvertUnixTime($val / 1e7 + $$self{GMStartTime}) . "Z"',
163
+ PrintConv => '$self->ConvertDateTime($val,undef,6)',
164
+ },
165
+ Latitude => {
166
+ Name => 'GPSLatitude',
167
+ Description => 'GPS Latitude', # (need description so we don't set it from the mrld)
168
+ Groups => { 2 => 'Location' },
169
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
170
+ },
171
+ Longitude => {
172
+ Name => 'GPSLongitude',
173
+ Description => 'GPS Longitude',
174
+ Groups => { 2 => 'Location' },
175
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
176
+ },
177
+ Altitude => {
178
+ Name => 'GPSAltitude',
179
+ Description => 'GPS Altitude',
180
+ Groups => { 2 => 'Location' },
181
+ },
182
+ Heading => {
183
+ Name => 'GPSTrack',
184
+ Description => 'GPS Track',
185
+ Groups => { 2 => 'Location' },
186
+ PrintConv => '$val > 360 ? "n/a" : sprintf("%.2f",$val)', # (seen 655.35)
187
+ },
188
+ ABSActive => { },
189
+ AccelPos => { },
190
+ BatteryVoltage => { },
191
+ Beacon => { },
192
+ BoostPressureInd => { },
193
+ BrakePos => { },
194
+ ClutchPos => { },
195
+ CoolantTemp => { },
196
+ CornerExitSetting => { },
197
+ CPUFree => { },
198
+ CPUIO => { },
199
+ CPUIRQ => { },
200
+ CPUSystem => { },
201
+ CPUUser => { },
202
+ DiskReadOperations => { },
203
+ DiskReadRate => { },
204
+ DiskReadTime => { },
205
+ DiskWriteOperations => { },
206
+ DiskWriteRate => { },
207
+ DiskWriteTime => { },
208
+ Distance => { },
209
+ DriverPerformanceMode => { },
210
+ EngineSpeedRequest => { },
211
+ EngineTorqureReq => { },
212
+ FuelCapacity => { },
213
+ FuelLevel => { },
214
+ Gear => {
215
+ Notes => q{
216
+ in the PrintCSV output, the value for Neutral is set to -1, and Reverse to
217
+ -100 for compatibility with RaceRender
218
+ },
219
+ CSVConv => { 13 => -1, 14 => -100 },
220
+ PrintConv => { 1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 13=>'N', 14=>'R' }
221
+ },
222
+ GPSFix => { },
223
+ InfotainOpMode => { },
224
+ IntakeAirTemperature => { },
225
+ IntakeBoostPressure => { },
226
+ LateralAcceleration => { },
227
+ LFTyrePressure => { },
228
+ LFTyreTemp => { },
229
+ LongitudinalAcceleration => { },
230
+ LRTyrePressure => { },
231
+ LRTyreTemp => { },
232
+ OilPressure => { },
233
+ OilTemp => { },
234
+ OutsideAirTemperature => { },
235
+ RecordingEventOdometer => { },
236
+ RFTyrePressure => { },
237
+ RFTyreTemp => { },
238
+ RPM => { },
239
+ RRTyrePressure => { },
240
+ RRTyreTemp => { },
241
+ Speed => { Groups => { 2 => 'Location' } },
242
+ SpeedControlResponse => { },
243
+ SpeedRequestIntervention => { },
244
+ Steering1Switch => { },
245
+ Steering2Switch => { },
246
+ SteeringAngle => { },
247
+ SuspensionDisplacementLeftFront => { },
248
+ SuspensionDisplacementLeftRear => { },
249
+ SuspensionDisplacementRightFront => { },
250
+ SuspensionDisplacementRightRear => { },
251
+ SystemBackupPowerEnabled => { },
252
+ SystemBackupPowerMode => { },
253
+ SystemPowerMode => { },
254
+ TractionControlActive => { },
255
+ TransOilTemp => { },
256
+ TransportStorageMode => { },
257
+ ValetMode => { },
258
+ VehicleStabilityActive => { },
259
+ VerticalAcceleration => { },
260
+ WheelspeedLeftDriven => { },
261
+ 'WheelspeedLeftNon-Driven' => { },
262
+ WheelspeedRightDriven => { },
263
+ 'WheelspeedRightNon-Driven' => { },
264
+ YawRate => { },
265
+ );
266
+
267
+ #------------------------------------------------------------------------------
268
+ # Print a CSV row
269
+ # Inputs: 0) ExifTool ref, 1) time stamp
270
+ sub PrintCSV($;$)
271
+ {
272
+ my ($et, $ts) = @_;
273
+ my $csv = $$et{GMCsv} or return; # get the list of channels with measurements
274
+ @$csv or return;
275
+ my $vals = $$et{GMVals};
276
+ my $gmDict = $$et{GMDictionary};
277
+ my @items = ('') x scalar(@$gmDict);
278
+ $items[0] = ($ts || $$et{GMMaxTS}) / 1e7;
279
+ # fill in scaled measurements for this TimeStamp
280
+ foreach (@$csv) {
281
+ my $gmChan = $$gmDict[$_];
282
+ $items[$_] = $$vals[$_] * $$gmChan{Mult} + $$gmChan{Off};
283
+ # apply CSV conversion if applicable (ie. Gear)
284
+ next unless $$gmChan{Conv} and defined $$gmChan{Conv}{$items[$_]};
285
+ $items[$_] = $$gmChan{Conv}{$items[$_]};
286
+ }
287
+ my $out = $$et{OPTIONS}{TextOut};
288
+ print $out join(',',@items),"\n";
289
+ @$csv = (); # clear the channel list
290
+ }
291
+
292
+ #------------------------------------------------------------------------------
293
+ # Process GM Marlin values ('mrlv' box)
294
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
295
+ # Returns: 1 on success
296
+ sub Process_mrlv($$$)
297
+ {
298
+ my ($et, $dirInfo, $tagTablePtr) = @_;
299
+ my $dataPt = $$dirInfo{DataPt};
300
+ my $dataPos = $$dirInfo{DataPos};
301
+ my $dirLen = length $$dataPt;
302
+ my $pos = 0;
303
+ # data lengths for known formats
304
+ my %fmtLen = (
305
+ strs => 64, lang => 64, strl => 256, 'time' => 32, date => 32,
306
+ tmzn => 32, tstm => 8, focc => 4, "kvp\0" => 64+256,
307
+ );
308
+ $et->VerboseDir('mrlv', undef, $dirLen);
309
+ while ($pos + 8 <= $dirLen) {
310
+ my $tag = substr($$dataPt, $pos, 4);
311
+ my $fmt = substr($$dataPt, $pos + 4, 4);
312
+ my $len = $fmtLen{$fmt};
313
+ unless ($len) {
314
+ ($tag, $fmt) = (PrintableTagID($tag), PrintableTagID($fmt));
315
+ $et->Warn("Unknown format ($fmt) for tag $tag");
316
+ last;
317
+ }
318
+ $pos + 8 + $len > $dirLen and $et->Warn('Truncated mrlv data'), last;
319
+ $et->HandleTag($tagTablePtr, $tag, undef,
320
+ DataPt => $dataPt,
321
+ DataPos => $dataPos,
322
+ Start => $pos + 8,
323
+ Size => $len,
324
+ );
325
+ $pos += 8 + $len;
326
+ }
327
+ return 1;
328
+ }
329
+
330
+ #------------------------------------------------------------------------------
331
+ # Process GM Marlin dictionary ('mrld' box)
332
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
333
+ # Returns: 1 on success
334
+ sub Process_mrld($$$)
335
+ {
336
+ my ($et, $dirInfo, $tagTablePtr) = @_;
337
+ my $dataPt = $$dirInfo{DataPt};
338
+ my $dataPos = $$dirInfo{DataPos};
339
+ my $dirLen = length $$dataPt;
340
+ my $struct = $et->Options('Struct') || 0;
341
+ my $gmDict = $$et{GMDictionary} = [ ];
342
+ my $marl = GetTagTable('Image::ExifTool::GM::marl');
343
+ my ($pos, $item, $csv);
344
+
345
+ $et->VerboseDir('mrld', undef, $dirLen);
346
+ require 'Image/ExifTool/XMPStruct.pl';
347
+ Image::ExifTool::XMP::AddFlattenedTags($tagTablePtr);
348
+ $csv = [ ] if $et->Options('PrintCSV');
349
+
350
+ for ($pos=0; $pos+448<=$dirLen; $pos+=448) {
351
+ # unpack 448-byte records:
352
+ # 0. int32u - channel number
353
+ # 1. int32u - measurement type
354
+ # 2. int32u - units number
355
+ # 3. string[64] - units string
356
+ # 4. int32u - flags (0.visible, 1.linear conversion, 2.interpolation OK)
357
+ # 5. int64u - interval
358
+ # 6. int32s - min reading
359
+ # 7. int32s - max reading
360
+ # 8. double - disp min
361
+ # 9. double - disp max
362
+ # 10. double - multiplier
363
+ # 11. double - offset
364
+ # 12. string[64] - channel name
365
+ # 13. string[64] - channel description
366
+ my @a = unpack("x${pos}NNNZ64Na8N2a8a8a8a8Z64Z64", $$dataPt);
367
+ my $units = $convertUnits{$a[3]} || $a[3];
368
+ $a[3] = $et->Decode($a[3], 'UTF8'); # convert from UTF8
369
+ $_ & 0x8000000 and $_ -= 4294967296 foreach @a[6,7]; # convert signed ints
370
+ map { $_ = GetDouble(\$_,0) } @a[8,9,10,11]; # convert doubles
371
+ $a[5] = Get64u(\$a[5],0); # convert 64-bit int
372
+ my $chan = $a[0];
373
+ my $tag = sprintf('Channel%.2d', $chan);
374
+ my $tagInfo = $$tagTablePtr{$tag};
375
+ my $hash = { map { $channel[$_] => $a[$_] } 1..$#a };
376
+ unless ($tagInfo) {
377
+ $tagInfo = AddTagToTable($tagTablePtr, $tag, { Name => $tag, Struct => \%channelStruct });
378
+ Image::ExifTool::XMP::AddFlattenedTags($tagTablePtr, $tag);
379
+ }
380
+ # extract channel structure if specified
381
+ if ($struct) {
382
+ $$hash{_ordered_keys_} = [ @channel[1..$#channel] ];
383
+ $et->FoundTag($tagInfo, $hash);
384
+ }
385
+ # extract flattened channel elements
386
+ if ($struct == 0 or $struct == 2) {
387
+ $et->HandleTag($tagTablePtr, "$tag$channel[$_]", $a[$_]) foreach 1..$#a;
388
+ }
389
+ # add corresponding tag to marl table
390
+ my $name = Image::ExifTool::MakeTagName($a[12]);
391
+ $tagInfo = $$marl{$name};
392
+ unless ($tagInfo) {
393
+ $et->VPrint(0, $$et{INDENT}, "[adding $name]\n");
394
+ $tagInfo = AddTagToTable($marl, $name, { });
395
+ }
396
+ $$tagInfo{Description} = $a[13] unless $$tagInfo{Description};
397
+ unless ($$tagInfo{PrintConv}) {
398
+ # add a default print conversion
399
+ $units =~ tr/"\\//d; # (just to be safe, probably never happen)
400
+ $$tagInfo{PrintConv} = $printConv{$units} || qq("\$val $units");
401
+ }
402
+ # adjust multiplier/offset as necessary to scale to more appropriate units
403
+ # (ie. to the units actually specified in this dictionary -- d'oh)
404
+ my $mult = $a[10] * ($changeScale{$units} || 1);
405
+ my $off = $a[11] * ($changeScale{$units} || 1) + ($changeOffset{$units} || 0);
406
+ my $init = int(($a[6] + $a[7]) / 2); # initial value for difference readings
407
+ # save information about this channel necessary for processing the marl data
408
+ $$gmDict[$chan] = { Name => $name, Mult => $mult, Off => $off, Init => $init };
409
+ $$gmDict[$chan]{Conv} = $$tagInfo{CSVConv};
410
+ $csv and $$csv[$chan] = $a[12] . ($a[3] ? " ($a[3])" : '');
411
+ }
412
+ # channel 0 must not be defined because we use it for the TimeStamp
413
+ if (defined $$gmDict[0]) {
414
+ $et->Warn('Internal error: PDR channel 0 is used');
415
+ delete $$et{GMDictionary};
416
+ } elsif ($csv) {
417
+ $$csv[0] = 'Time (s)';
418
+ defined $_ or $_ = '' foreach @$csv;
419
+ my $out = $$et{OPTIONS}{TextOut};
420
+ print $out join(',',@$csv),"\n";
421
+ $$et{GMCsv} = [ ];
422
+ }
423
+ $et->AddCleanup(\&PrintCSV); # print last CSV line when we are done
424
+ # initialize variables for processing marl box
425
+ $$et{GMVals} = [ ];
426
+ $$et{GMMaxTS} = 0;
427
+ $$et{GMBadChan} = 0;
428
+ return 1;
429
+ }
430
+
431
+ #------------------------------------------------------------------------------
432
+ # Process GM 'marl' ctbx data (ref PH)
433
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
434
+ # Returns: 1 on success
435
+ # (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393)
436
+ sub Process_marl($$$)
437
+ {
438
+ my ($et, $dirInfo, $tagTablePtr) = @_;
439
+ my $dataPt = $$dirInfo{DataPt};
440
+ my $dataPos = $$dirInfo{DataPos} + $$dirInfo{Base};
441
+ my $dataLen = length $$dataPt;
442
+ my $vals = $$et{GMVals}; # running values for each channel (0=TimeStamp)
443
+ my $chan = $$et{GMChan}; # running channel number
444
+ my $gmDict = $$et{GMDictionary};
445
+ my $csv = $$et{GMCsv};
446
+ my $maxTS = $$et{GMMaxTS};
447
+ my $reqGPSDateTime = $$et{REQ_TAG_LOOKUP}{gpsdatetime};
448
+ my $reqTimeStamp = $reqGPSDateTime ? $$et{REQ_TAG_LOOKUP}{timestamp} : 1;
449
+ my ($pos, $verbose2);
450
+
451
+ $et->VerboseDir('marl', undef, $dataLen);
452
+ $gmDict or $et->Warn('Missing marl dictionary'), return 0;
453
+ my $maxChan = $#$gmDict;
454
+ $verbose2 = 1 if $et->Options('Verbose') > 1;
455
+ $$vals[0] = -1 unless defined $$vals[0]; # (we use the 0th channel for the TimeStamp)
456
+ my $ts = $$vals[0];
457
+
458
+ for ($pos=0; $pos + 8 <= $dataLen; $pos += 8) {
459
+ my @a = unpack("x${pos}NN", $$dataPt);
460
+ my $ah = $a[0] >> 24;
461
+ my $a2 = $ah & 0xc0;
462
+ my ($val, $chanDiff, $valDiff, @ts, $gmChan);
463
+ if ($a2 == 0xc0) { # 16-byte full record?
464
+ last if $ah == 0xff; # exit at first empty record
465
+ $chan = $a[0] & 0x0fffffff;
466
+ $gmChan = $$gmDict[$chan] or next; # (shouldn't happen)
467
+ $val = $a[1] - ($a[1] & 0x80000000 ? 4294967296 : 0);
468
+ $$vals[$chan] = $val;
469
+ last if $pos + 16 > $dataLen; # (shouldn't happen)
470
+ $pos += 8; # point at time stamp
471
+ @ts = unpack("x${pos}NN", $$dataPt);
472
+ $ts = $ts[0] * 4294967296 + $ts[1];
473
+ } elsif ($a2 == 0x40) { # 8-byte difference record?
474
+ next unless defined $chan; # (shouldn't happen)
475
+ $ts += $a[1]; # increment time stamp
476
+ $chanDiff = ($ah & 0x3f) - ($ah & 0x20 ? 0x40 : 0);
477
+ $chan += $chanDiff; # increment the running channel number
478
+ $gmChan = $$gmDict[$chan] or next; # (shouldn't happen)
479
+ defined $$vals[$chan] or $$vals[$chan] = $$gmChan{Init}; # init if necessary
480
+ $valDiff = ($a[0] & 0x00ffffff) - ($a[0] & 0x00800000 ? 0x01000000 : 0);
481
+ $val = ($$vals[$chan] += $valDiff); # increment the running value for this channel
482
+ } else {
483
+ next; # (shouldn't happen)
484
+ }
485
+ # ensure that the timestamps are monotonically increasing
486
+ # (have seen backward steps up to 0.033 sec, so fudge these)
487
+ if ($ts > $maxTS) {
488
+ if ($csv) {
489
+ PrintCSV($et, $maxTS);
490
+ } else {
491
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
492
+ $et->HandleTag($tagTablePtr, TimeStamp => $ts) if $reqTimeStamp;
493
+ $et->HandleTag($tagTablePtr, GPSDateTime => $ts) if $reqGPSDateTime;
494
+ }
495
+ $maxTS = $ts;
496
+ }
497
+ $csv and push(@$csv, $chan), next;
498
+ my $scaled = $val * $$gmChan{Mult} + $$gmChan{Off};
499
+ $et->HandleTag($tagTablePtr, $$gmChan{Name}, $scaled);
500
+ if ($verbose2) {
501
+ my $str = " * $$gmChan{Mult} + $$gmChan{Off} = $scaled";
502
+ my $p0 = $dataPos + $pos - ($a2 == 0xc0 ? 8 : 0);
503
+ my ($cd,$vd) = @ts ? ('','') : (sprintf('%+d',$chanDiff),sprintf('%+d',$valDiff));
504
+ printf "| %8.4x: %.8x %.8x chan$cd=%.2d $$gmChan{Name}$vd = $val$str\n", $p0, @a, $chan;
505
+ printf("| %8.4x: %.8x %.8x TimeStamp = %.6f sec\n", $dataPos + $pos, @ts, $ts / 1e7) if @ts;
506
+ }
507
+ }
508
+ $$vals[0] = $ts; # save last timestamp
509
+ $$et{GMChan} = $chan; # save last channel number
510
+ $$et{GMMaxTS} = $ts;
511
+ delete $$et{DOC_NUM};
512
+ return 1;
513
+ }
514
+
515
+ 1; # end
516
+
517
+ __END__
518
+
519
+ =head1 NAME
520
+
521
+ Image::ExifTool::GM - Read GM PDR Data from automobile videos
522
+
523
+ =head1 SYNOPSIS
524
+
525
+ This module is loaded automatically by Image::ExifTool when required.
526
+
527
+ =head1 DESCRIPTION
528
+
529
+ This module contains definitions required by Image::ExifTool to read PDR
530
+ metadata from videos written by some GM models such as Corvette and Camero.
531
+
532
+ =head1 AUTHOR
533
+
534
+ Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
535
+
536
+ This library is free software; you can redistribute it and/or modify it
537
+ under the same terms as Perl itself.
538
+
539
+ =head1 REFERENCES
540
+
541
+ =over 4
542
+
543
+ =item L<https://exiftool.org/forum/index.php?topic=11335>
544
+
545
+ =back
546
+
547
+ =head1 SEE ALSO
548
+
549
+ L<Image::ExifTool::TagNames/GM Tags>,
550
+ L<Image::ExifTool(3pm)|Image::ExifTool>
551
+
552
+ =cut