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