exiftool_vendored 12.81.0 → 12.82.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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