exiftool-vendored.exe 12.30.0 → 12.38.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 (52) hide show
  1. package/bin/exiftool_files/Changes +129 -3
  2. package/bin/exiftool_files/README +2 -2
  3. package/bin/exiftool_files/arg_files/xmp2exif.args +2 -1
  4. package/bin/exiftool_files/config_files/example.config +1 -1
  5. package/bin/exiftool_files/exiftool.pl +100 -58
  6. package/bin/exiftool_files/fmt_files/gpx.fmt +1 -1
  7. package/bin/exiftool_files/fmt_files/gpx_wpt.fmt +1 -1
  8. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +13 -3
  9. package/bin/exiftool_files/lib/Image/ExifTool/CBOR.pm +331 -0
  10. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +175 -12
  11. package/bin/exiftool_files/lib/Image/ExifTool/CanonCustom.pm +12 -2
  12. package/bin/exiftool_files/lib/Image/ExifTool/Charset.pm +2 -0
  13. package/bin/exiftool_files/lib/Image/ExifTool/DPX.pm +13 -2
  14. package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +2 -2
  15. package/bin/exiftool_files/lib/Image/ExifTool/Exif.pm +109 -3
  16. package/bin/exiftool_files/lib/Image/ExifTool/FLIR.pm +33 -8
  17. package/bin/exiftool_files/lib/Image/ExifTool/GIF.pm +5 -1
  18. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +14 -10
  19. package/bin/exiftool_files/lib/Image/ExifTool/Geotag.pm +13 -2
  20. package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +16 -1
  21. package/bin/exiftool_files/lib/Image/ExifTool/ICC_Profile.pm +96 -4
  22. package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +7 -3
  23. package/bin/exiftool_files/lib/Image/ExifTool/Jpeg2000.pm +154 -24
  24. package/bin/exiftool_files/lib/Image/ExifTool/M2TS.pm +27 -12
  25. package/bin/exiftool_files/lib/Image/ExifTool/MacOS.pm +2 -2
  26. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +1204 -96
  27. package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +5 -1
  28. package/bin/exiftool_files/lib/Image/ExifTool/NikonSettings.pm +135 -71
  29. package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +5 -1
  30. package/bin/exiftool_files/lib/Image/ExifTool/OpenEXR.pm +4 -2
  31. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +11 -12
  32. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +4 -1
  33. package/bin/exiftool_files/lib/Image/ExifTool/Panasonic.pm +2 -2
  34. package/bin/exiftool_files/lib/Image/ExifTool/Pentax.pm +2 -1
  35. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +69 -10
  36. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +141 -111
  37. package/bin/exiftool_files/lib/Image/ExifTool/README +9 -2
  38. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +56 -13
  39. package/bin/exiftool_files/lib/Image/ExifTool/TagInfoXML.pm +9 -4
  40. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +6473 -5827
  41. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +1401 -54
  42. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -0
  43. package/bin/exiftool_files/lib/Image/ExifTool/WritePNG.pl +2 -0
  44. package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +10 -0
  45. package/bin/exiftool_files/lib/Image/ExifTool/WriteXMP.pl +10 -11
  46. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +50 -5
  47. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +125 -31
  48. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +3 -1
  49. package/bin/exiftool_files/lib/Image/ExifTool/XMPStruct.pl +3 -1
  50. package/bin/exiftool_files/lib/Image/ExifTool.pm +8931 -8864
  51. package/bin/exiftool_files/lib/Image/ExifTool.pod +21 -13
  52. package/package.json +3 -3
@@ -47,7 +47,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
47
47
  use Image::ExifTool::Exif;
48
48
  use Image::ExifTool::GPS;
49
49
 
50
- $VERSION = '2.68';
50
+ $VERSION = '2.73';
51
51
 
52
52
  sub ProcessMOV($$;$);
53
53
  sub ProcessKeys($$$);
@@ -482,6 +482,17 @@ my %eeBox2 = (
482
482
  # (Pittasoft Blackview dashcam MP4 videos)
483
483
  Condition => '$$valPt =~ /^\0\0..(cprt|sttm|ptnm|ptrh|thum|gps |3gf )/s',
484
484
  SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Pittasoft' },
485
+ },{
486
+ Name => 'ThumbnailImage',
487
+ # (DJI Zenmuse XT2 thermal camera)
488
+ Groups => { 2 => 'Preview' },
489
+ Condition => '$$valPt =~ /^.{4}mdat\xff\xd8\xff/s',
490
+ RawConv => q{
491
+ my $len = unpack('N', $val);
492
+ return undef if $len <= 8 or $len > length($val);
493
+ return substr($val, 8, $len-8);
494
+ },
495
+ Binary => 1,
485
496
  },{
486
497
  Unknown => 1,
487
498
  Binary => 1,
@@ -558,6 +569,7 @@ my %eeBox2 = (
558
569
  # *** this is where ExifTool writes XMP in MP4 videos (as per XMP spec) ***
559
570
  Condition => '$$valPt=~/^\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac/',
560
571
  WriteGroup => 'XMP', # (write main XMP tags here)
572
+ PreservePadding => 1,
561
573
  SubDirectory => {
562
574
  TagTable => 'Image::ExifTool::XMP::Main',
563
575
  Start => 16,
@@ -607,6 +619,7 @@ my %eeBox2 = (
607
619
  Name => 'PreviewImage',
608
620
  Condition => '$$valPt=~/^\xea\xf4\x2b\x5e\x1c\x98\x4b\x88\xb9\xfb\xb7\xdc\x40\x6e\x4d\x16/',
609
621
  Groups => { 2 => 'Preview' },
622
+ PreservePadding => 1,
610
623
  # 0x00 - undef[16]: UUID
611
624
  # 0x10 - int32u[2]: "0 1" (version and/or item count?)
612
625
  # 0x18 - int32u: PRVW atom size
@@ -738,6 +751,7 @@ my %eeBox2 = (
738
751
  Name => 'SamsungTrailer',
739
752
  SubDirectory => { TagTable => 'Image::ExifTool::Samsung::Trailer' },
740
753
  },
754
+ # 'samn'? - seen in Vantrue N2S sample video
741
755
  );
742
756
 
743
757
  # MPEG-4 'ftyp' atom
@@ -6356,7 +6370,7 @@ my %eeBox2 = (
6356
6370
  'player.movie.visual.tint' => 'Tint',
6357
6371
  'player.movie.visual.contrast' => 'Contrast',
6358
6372
  'player.movie.audio.gain' => 'AudioGain',
6359
- 'player.movie.audio.treble' => 'Trebel',
6373
+ 'player.movie.audio.treble' => 'Treble',
6360
6374
  'player.movie.audio.bass' => 'Bass',
6361
6375
  'player.movie.audio.balance' => 'Balance',
6362
6376
  'player.movie.audio.pitchshift' => 'PitchShift',
@@ -6450,10 +6464,12 @@ my %eeBox2 = (
6450
6464
  # iTunes info ('----') atoms
6451
6465
  %Image::ExifTool::QuickTime::iTunesInfo = (
6452
6466
  PROCESS_PROC => \&ProcessMOV,
6453
- GROUPS => { 2 => 'Audio' },
6467
+ GROUPS => { 1 => 'iTunes', 2 => 'Audio' },
6468
+ VARS => { LONG_TAGS => 0 }, # (hack for discrepancy in the way long tags are counted in BuildTagLookup)
6454
6469
  NOTES => q{
6455
6470
  ExifTool will extract any iTunesInfo tags that exist, even if they are not
6456
- defined in this table.
6471
+ defined in this table. These tags belong to the family 1 "iTunes" group,
6472
+ and are not currently writable.
6457
6473
  },
6458
6474
  # 'mean'/'name'/'data' atoms form a triplet, but unfortunately
6459
6475
  # I haven't been able to find any documentation on this.
@@ -6514,9 +6530,45 @@ my %eeBox2 = (
6514
6530
  SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::EncodingParams' },
6515
6531
  },
6516
6532
  # also heard about 'iTunPGAP', but I haven't seen a sample
6517
- DISCNUMBER => 'DiscNumber', #PH
6518
- TRACKNUMBER => 'TrackNumber', #PH
6519
- popularimeter => 'Popularimeter', #PH
6533
+ # all tags below were added based on samples I have seen - PH
6534
+ DISCNUMBER => 'DiscNumber',
6535
+ TRACKNUMBER => 'TrackNumber',
6536
+ ARTISTS => 'Artists',
6537
+ CATALOGNUMBER => 'CatalogNumber',
6538
+ RATING => 'Rating',
6539
+ MEDIA => 'Media',
6540
+ SCRIPT => 'Script', # character set? (seen 'Latn')
6541
+ BARCODE => 'Barcode',
6542
+ LABEL => 'Label',
6543
+ MOOD => 'Mood',
6544
+ popularimeter => 'Popularimeter',
6545
+ 'Dynamic Range (DR)'=> 'DynamicRange',
6546
+ initialkey => 'InitialKey',
6547
+ originalyear => 'OriginalYear',
6548
+ originaldate => 'OriginalDate',
6549
+ '~length' => 'Length', # play length? (ie. duration?)
6550
+ replaygain_track_gain=>'ReplayTrackGain',
6551
+ replaygain_track_peak=>'ReplayTrackPeak',
6552
+ 'Volume Level (ReplayGain)'=> 'ReplayVolumeLevel',
6553
+ 'Dynamic Range (R128)'=> 'DynamicRangeR128',
6554
+ 'Volume Level (R128)' => 'VolumeLevelR128',
6555
+ 'Peak Level (Sample)' => 'PeakLevelSample',
6556
+ 'Peak Level (R128)' => 'PeakLevelR128',
6557
+ # also seen (many from forum12777):
6558
+ # 'MusicBrainz Album Release Country'
6559
+ # 'MusicBrainz Album Type'
6560
+ # 'MusicBrainz Album Status'
6561
+ # 'MusicBrainz Track Id'
6562
+ # 'MusicBrainz Release Track Id'
6563
+ # 'MusicBrainz Album Id'
6564
+ # 'MusicBrainz Album Artist Id'
6565
+ # 'MusicBrainz Artist Id'
6566
+ # 'Acoustid Id' (sic)
6567
+ # 'Tool Version'
6568
+ # 'Tool Name'
6569
+ # 'ISRC'
6570
+ # 'HDCD'
6571
+ # 'Waveform'
6520
6572
  );
6521
6573
 
6522
6574
  # iTunes audio encoding parameters
@@ -9075,6 +9127,12 @@ sub ProcessMOV($$;$)
9075
9127
  } else {
9076
9128
  my $t = PrintableTagID($tag,2);
9077
9129
  $et->VPrint(0,"$$et{INDENT}Tag '${t}' extends to end of file");
9130
+ if ($$tagTablePtr{"$tag-size"}) {
9131
+ my $pos = $raf->Tell();
9132
+ $raf->Seek(0, 2);
9133
+ $et->HandleTag($tagTablePtr, "$tag-size", $raf->Tell() - $pos);
9134
+ $et->HandleTag($tagTablePtr, "$tag-offset", $pos) if $$tagTablePtr{"$tag-offset"};
9135
+ }
9078
9136
  }
9079
9137
  last;
9080
9138
  }
@@ -9301,6 +9359,7 @@ ItemID: foreach $id (keys %$items) {
9301
9359
  Name => $name,
9302
9360
  Description => $desc,
9303
9361
  };
9362
+ $et->VPrint(0, $$et{INDENT}, "[adding QuickTime:$name]\n");
9304
9363
  AddTagToTable($tagTablePtr, $tag, $tagInfo);
9305
9364
  }
9306
9365
  # ignore 8-byte header
@@ -9312,9 +9371,9 @@ ItemID: foreach $id (keys %$items) {
9312
9371
  $val = \$buff;
9313
9372
  }
9314
9373
  }
9315
- undef %triplet;
9374
+ $$tagInfo{List} = 1; # (allow any of these tags to have multiple data elements)
9375
+ $et->VerboseInfo($tag, $tagInfo, Value => $val) if $verbose;
9316
9376
  } else {
9317
- undef %triplet if $tag eq 'mean';
9318
9377
  $triplet{$tag} = substr($val,4) if length($val) > 4;
9319
9378
  undef $tagInfo; # don't store this tag
9320
9379
  }
@@ -9380,7 +9439,7 @@ ItemID: foreach $id (keys %$items) {
9380
9439
  for (;;) {
9381
9440
  last if $pos + 16 > $size;
9382
9441
  my ($len, $type, $flags, $ctry, $lang) = unpack("x${pos}Na4Nnn", $val);
9383
- last if $pos + $len > $size;
9442
+ last if $pos + $len > $size or not $len;
9384
9443
  my ($value, $langInfo, $oldDir);
9385
9444
  my $format = $$tagInfo{Format};
9386
9445
  if ($type eq 'data' and $len >= 16) {
@@ -25,6 +25,7 @@ sub Process_mebx($$$);
25
25
  sub ProcessFreeGPS($$$);
26
26
  sub ProcessFreeGPS2($$$);
27
27
  sub Process360Fly($$$);
28
+ sub ProcessFMAS($$$);
28
29
 
29
30
  # QuickTime data types that have ExifTool equivalents
30
31
  # (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
@@ -98,15 +99,15 @@ my %insvLimit = (
98
99
  The tags below are extracted from timed metadata in QuickTime and other
99
100
  formats of video files when the ExtractEmbedded option is used. Although
100
101
  most of these tags are combined into the single table below, ExifTool
101
- currently reads 55 different formats of timed GPS metadata from video files.
102
+ currently reads 59 different formats of timed GPS metadata from video files.
102
103
  },
103
104
  VARS => { NO_ID => 1 },
104
105
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
105
106
  GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
106
107
  GPSAltitude => { PrintConv => '(sprintf("%.4f", $val) + 0) . " m"' }, # round to 4 decimals
107
- GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
108
+ GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'in km/h unless GPSSpeedRef says otherwise' },
108
109
  GPSSpeedRef => { PrintConv => { K => 'km/h', M => 'mph', N => 'knots' } },
109
- GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
110
+ GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0', Notes => 'relative to true north unless GPSTrackRef says otherwise' },
110
111
  GPSTrackRef => { PrintConv => { M => 'Magnetic North', T => 'True North' } },
111
112
  GPSDateTime => {
112
113
  Groups => { 2 => 'Time' },
@@ -181,6 +182,13 @@ my %insvLimit = (
181
182
  TagTable => 'Image::ExifTool::QuickTime::Stream',
182
183
  ProcessProc => \&Process_text,
183
184
  },
185
+ },{
186
+ Name => 'gpmd_FMAS', # Vantrue N2S binary format
187
+ Condition => '$$valPt =~ /^FMAS\0\0\0\0/',
188
+ SubDirectory => {
189
+ TagTable => 'Image::ExifTool::QuickTime::Stream',
190
+ ProcessProc => \&ProcessFMAS,
191
+ },
184
192
  },{
185
193
  Name => 'gpmd_GoPro',
186
194
  SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
@@ -901,14 +909,8 @@ sub Process_text($$$)
901
909
  $tags{GPSDateTime} = $dateTime;
902
910
  $tags{GPSLatitude} = (($4 || 0) + $5/60) * ($6 eq 'N' ? 1 : -1);
903
911
  $tags{GPSLongitude} = (($7 || 0) + $8/60) * ($9 eq 'E' ? 1 : -1);
904
- if (length $10) {
905
- $tags{GPSSpeed} = $10 * $knotsToKph;
906
- $tags{GPSSpeedRef} = 'K';
907
- }
908
- if (length $11) {
909
- $tags{GPSTrack} = $11;
910
- $tags{GPSTrackRef} = 'T';
911
- }
912
+ $tags{GPSSpeed} = $10 * $knotsToKph if length $10;
913
+ $tags{GPSTrack} = $11 if length $11;
912
914
  } elsif ($tag =~ /^[A-Z]{2}GGA$/ and $dat =~ /^,(\d{2})(\d{2})(\d+(?:\.\d*)?),(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),[1-6]?,(\d+)?,(\.\d+|\d+\.?\d*)?,(-?\d+\.?\d*)?,M?/s) {
913
915
  my $time = "$1:$2:$3";
914
916
  if ($$et{LastTime}) {
@@ -988,10 +990,7 @@ sub Process_text($$$)
988
990
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5)));
989
991
  $tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/;
990
992
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3)));
991
- if ($val =~ /^\d+$/) {
992
- $tags{GPSSpeed} = $val + 0;
993
- $tags{GPSSpeedRef} = 'K';
994
- }
993
+ $tags{GPSSpeed} = $val + 0 if $val =~ /^\d+$/;
995
994
  if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
996
995
  $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12)));
997
996
  # the first X,Y,Z accelerometer readings from the AccelerometerData
@@ -1022,10 +1021,7 @@ sub Process_text($$$)
1022
1021
  $tags{GPSLatitude} = $2;
1023
1022
  $tags{GPSLongitude} = $1;
1024
1023
  $tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
1025
- if ($$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
1026
- $tags{GPSSpeed} = $1 * $mpsToKph;
1027
- $tags{GPSSpeedRef} = 'K';
1028
- }
1024
+ $tags{GPSSpeed} = $1 * $mpsToKph if $$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/;
1029
1025
  $tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/;
1030
1026
  $tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
1031
1027
  $tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/;
@@ -1086,14 +1082,8 @@ sub Process_text($$$)
1086
1082
  $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
1087
1083
  $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
1088
1084
  $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
1089
- if (length $11) {
1090
- $tags{GPSSpeed} = $11 * $knotsToKph;
1091
- $tags{GPSSpeedRef} = 'K';
1092
- }
1093
- if (length $12) {
1094
- $tags{GPSTrack} = $12;
1095
- $tags{GPSTrackRef} = 'T';
1096
- }
1085
+ $tags{GPSSpeed} = $11 * $knotsToKph if length $11;
1086
+ $tags{GPSTrack} = $12 if length $12;
1097
1087
  }
1098
1088
  $tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/;
1099
1089
  $tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/;
@@ -1268,8 +1258,7 @@ sub ProcessSamples($)
1268
1258
  next if length($buff) < 20 + $n;
1269
1259
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
1270
1260
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
1271
- $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1272
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1261
+ $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n) * $mphToKph);
1273
1262
  SetGPSDateTime($et, $tagTbl, $time[$i]);
1274
1263
  next; # all done (don't store/process as text)
1275
1264
  }
@@ -1342,6 +1331,19 @@ sub ProcessSamples($)
1342
1331
  $$et{HandlerType} = $$et{HanderDesc} = '';
1343
1332
  }
1344
1333
 
1334
+ #------------------------------------------------------------------------------
1335
+ # Convert latitude/longitude from DDDMM.MMMM format to decimal degrees
1336
+ # Inputs: 0) latitude, 1) longitude
1337
+ # Returns: lat/lon are changed in place
1338
+ # (note: this method works fine for negative coordinates)
1339
+ sub ConvertLatLon($$)
1340
+ {
1341
+ my $deg = int($_[0] / 100); # latitude
1342
+ $_[0] = $deg + ($_[0] - $deg * 100) / 60;
1343
+ $deg = int($_[1] / 100); # longitude
1344
+ $_[1] = $deg + ($_[1] - $deg * 100) / 60;
1345
+ }
1346
+
1345
1347
  #------------------------------------------------------------------------------
1346
1348
  # Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
1347
1349
  # Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
@@ -1353,7 +1355,7 @@ sub ProcessFreeGPS($$$)
1353
1355
  my ($et, $dirInfo, $tagTbl) = @_;
1354
1356
  my $dataPt = $$dirInfo{DataPt};
1355
1357
  my $dirLen = length $$dataPt;
1356
- my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl);
1358
+ my ($yr, $mon, $day, $hr, $min, $sec, $stat, $lbl, $ddd);
1357
1359
  my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, @acc, @xtra);
1358
1360
 
1359
1361
  return 0 if $dirLen < 92;
@@ -1478,7 +1480,7 @@ sub ProcessFreeGPS($$$)
1478
1480
  $lat = GetFloat($dataPt, 0x1c);
1479
1481
  $lon = GetFloat($dataPt, 0x20);
1480
1482
  $et->VPrint(0, sprintf("Raw lat/lon = %.9f %.9f\n", $lat, $lon));
1481
- $et->WarnOnce('GPSLatitude/Longitude encoding is not yet known, so these will be wrong');
1483
+ $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1482
1484
  $lat = abs $lat;
1483
1485
  $lon = abs $lon;
1484
1486
  $spd = GetFloat($dataPt, 0x24) * $knotsToKph; # (convert knots to km/h)
@@ -1508,6 +1510,23 @@ sub ProcessFreeGPS($$$)
1508
1510
  $trk -= 360 if $trk >= 360;
1509
1511
  SetByteOrder('MM');
1510
1512
 
1513
+ } elsif ($$dataPt =~ /^.{60}4W`b]S</s and length($$dataPt) >= 140) {
1514
+
1515
+ # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 f0 01 00 00 [..@.freeGPS ....]
1516
+ # 0010: 5a 58 53 42 4e 58 59 53 00 00 00 00 00 00 00 00 [ZXSBNXYS........]
1517
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1518
+ # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b]
1519
+ # 0040: 5d 53 3c 41 44 45 41 41 42 3e 40 40 3c 51 3c 45 []S<ADEAAB>@@<Q<E]
1520
+ # 0050: 41 40 43 3e 41 47 49 48 44 3c 5e 3c 40 41 46 43 [A@C>AGIHD<^<@AFC]
1521
+ # 0060: 42 3e 49 49 40 42 45 3c 55 3c 45 47 3e 45 43 41 [B>II@BE<U<EG>ECA]
1522
+ # decipher $GPRMC by subtracting 16 from each character value
1523
+ $_ = pack 'C*', map { $_>=16 and $_-=16 } unpack('x60C80', $$dataPt);
1524
+ return 0 unless /[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?\d{1,2}\.\d+),([NS]),(\d*?\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/;
1525
+ ($yr,$mon,$day,$hr,$min,$sec,$lat,$latRef,$lon,$lonRef) = ($13,$12,$11,$1,$2,$3,$5,$6,$7,$8);
1526
+ $yr += ($yr >= 70 ? 1900 : 2000);
1527
+ $spd = $9 * $knotsToKph if length $9;
1528
+ $trk = $10 if length $10;
1529
+
1511
1530
  } elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
1512
1531
 
1513
1532
  # Akaso V1 dascham
@@ -1526,13 +1545,17 @@ sub ProcessFreeGPS($$$)
1526
1545
  return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
1527
1546
  ($lonRef eq 'E' or $lonRef eq 'W');
1528
1547
 
1529
- $et->WarnOnce("Can't yet decrypt Akaso V1 timed GPS", 1);
1548
+ $et->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
1530
1549
  # (see https://exiftool.org/forum/index.php?topic=11320.0)
1531
- return 1;
1532
1550
 
1533
1551
  SetByteOrder('II');
1552
+
1553
+ $spd = GetFloat($dataPt, 0x60);
1554
+ $trk = GetFloat($dataPt, 0x64) + 180; # (why is this off by 180?)
1534
1555
  $lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
1535
1556
  $lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
1557
+ $ddd = 1; # don't convert until we know what the format is
1558
+
1536
1559
  SetByteOrder('MM');
1537
1560
  #my $serialNum = substr($$dataPt, 0x68, 20);
1538
1561
 
@@ -1558,6 +1581,31 @@ sub ProcessFreeGPS($$$)
1558
1581
  substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6);
1559
1582
  # (other values are currently unknown)
1560
1583
 
1584
+ } elsif ($$dataPt =~ /^.{64}A([NS])([EW])\0/s) {
1585
+
1586
+ # Vantrue S1 dashcam
1587
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
1588
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1589
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
1590
+ # 0030: 68 6f 72 73 6f 6e 74 65 63 68 00 00 00 00 00 00 [horsontech......]
1591
+ # 0040: 41 4e 45 00 15 00 00 00 07 00 00 00 02 00 00 00 [ANE.............]
1592
+ # 0050: 03 00 00 00 35 00 00 00 05 00 00 00 4f 74 4c 44 [....5.......OtLD]
1593
+ # 0060: e2 77 a0 45 89 c1 98 42 71 bd ac 42 02 ab 0d 43 [.w.E...Bq..B...C]
1594
+ # 0070: 05 00 00 00 7f 00 00 00 07 01 00 00 00 00 00 00 [................]
1595
+ ($latRef, $lonRef) = ($1, $2);
1596
+ ($yr,$mon,$day,$hr,$min,$sec,@acc) = unpack('x68V6x20V3', $$dataPt);
1597
+ return 0 unless $mon>=1 and $mon<=12 and $day>=1 and $day<=31;
1598
+ $yr += 2000 if $yr < 2000;
1599
+ # (not sure about acc scaling)
1600
+ map { $_ = $_ - 4294967296 if $_ >= 0x80000000; $_ /= 1000 } @acc;
1601
+ SetByteOrder('II');
1602
+ $lon = GetFloat($dataPt, 0x5c);
1603
+ $lat = GetFloat($dataPt, 0x60);
1604
+ $spd = GetFloat($dataPt, 0x64) * $knotsToKph;
1605
+ $trk = GetFloat($dataPt, 0x68);
1606
+ $alt = GetFloat($dataPt, 0x6c);
1607
+ SetByteOrder('MM');
1608
+
1561
1609
  } else {
1562
1610
 
1563
1611
  # decode binary GPS format (Viofo A119S, ref 2)
@@ -1596,10 +1644,7 @@ sub ProcessFreeGPS($$$)
1596
1644
  #
1597
1645
  FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
1598
1646
  # lat/long are in DDDMM.MMMM format
1599
- my $deg = int($lat / 100);
1600
- $lat = $deg + ($lat - $deg * 100) / 60;
1601
- $deg = int($lon / 100);
1602
- $lon = $deg + ($lon - $deg * 100) / 60;
1647
+ ConvertLatLon($lat, $lon) unless $ddd;
1603
1648
  $sec = '0' . $sec unless $sec =~ /^\d{2}/; # pad integer part of seconds to 2 digits
1604
1649
  if (defined $yr) {
1605
1650
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%sZ',$yr,$mon,$day,$hr,$min,$sec);
@@ -1611,14 +1656,8 @@ sub ProcessFreeGPS($$$)
1611
1656
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1612
1657
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1613
1658
  $et->HandleTag($tagTbl, GPSAltitude => $alt) if defined $alt;
1614
- if (defined $spd) {
1615
- $et->HandleTag($tagTbl, GPSSpeed => $spd);
1616
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1617
- }
1618
- if (defined $trk) {
1619
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1620
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1621
- }
1659
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd;
1660
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1622
1661
  while (@xtra) {
1623
1662
  my $tag = shift @xtra;
1624
1663
  $et->HandleTag($tagTbl, $tag => shift @xtra);
@@ -1737,9 +1776,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1737
1776
  $et->HandleTag($tagTbl, GPSLatitude => Get32s(\$b, 0x10) / 1e7);
1738
1777
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$b, 0x18) / 1e7);
1739
1778
  $et->HandleTag($tagTbl, GPSSpeed => Get32s(\$b, 0x20) / 100 * $mpsToKph);
1740
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1741
1779
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1742
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1743
1780
  $et->HandleTag($tagTbl, GPSAltitude => Get32s(\$b, 0x28) / 1000);
1744
1781
  $lastRecPos = $recPos;
1745
1782
  $foundNew = 1;
@@ -1860,17 +1897,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1860
1897
  $trk = GetFloat(\$dat, 16);
1861
1898
  @acc = unpack('x20V3', $dat);
1862
1899
  map { $_ = $_ - 4294967296 if $_ >= 0x80000000 } @acc;
1863
- my $deg = int($lat / 100);
1864
- $lat = $deg + ($lat - $deg * 100) / 60;
1865
- $deg = int($lon / 100);
1866
- $lon = $deg + ($lon - $deg * 100) / 60;
1900
+ ConvertLatLon($lat, $lon);
1867
1901
  $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1868
1902
  $et->HandleTag($tagTbl, GPSLatitude => $lat * (substr($dat,1,1) eq 'S' ? -1 : 1));
1869
1903
  $et->HandleTag($tagTbl, GPSLongitude => $lon * (substr($dat,2,1) eq 'W' ? -1 : 1));
1870
1904
  $et->HandleTag($tagTbl, GPSSpeed => $spd);
1871
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1872
1905
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1873
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1874
1906
  $et->HandleTag($tagTbl, Accelerometer => "@acc");
1875
1907
  }
1876
1908
  return 1;
@@ -1916,9 +1948,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1916
1948
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
1917
1949
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
1918
1950
  $et->HandleTag($tagTbl, GPSSpeed => $spd / 100 * $mpsToKph);
1919
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1920
1951
  $et->HandleTag($tagTbl, GPSTrack => $trk);
1921
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1922
1952
  last if $pos += 0x20 > length($$dataPt) - 0x1e;
1923
1953
  }
1924
1954
  return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
@@ -1931,23 +1961,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1931
1961
  $yr += 2000 if $yr < 2000;
1932
1962
  my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
1933
1963
  # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
1934
- unless ($ddd) {
1935
- my $deg = int($lat / 100);
1936
- $lat = $deg + ($lat - $deg * 100) / 60;
1937
- $deg = int($lon / 100);
1938
- $lon = $deg + ($lon - $deg * 100) / 60;
1939
- }
1964
+ ConvertLatLon($lat, $lon) unless $ddd;
1940
1965
  $et->HandleTag($tagTbl, GPSDateTime => $time);
1941
1966
  $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
1942
1967
  $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
1943
- if (defined $spd) {
1944
- $et->HandleTag($tagTbl, GPSSpeed => $spd); # (now in km/h)
1945
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1946
- }
1947
- if (defined $trk) {
1948
- $et->HandleTag($tagTbl, GPSTrack => $trk);
1949
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1950
- }
1968
+ $et->HandleTag($tagTbl, GPSSpeed => $spd) if defined $spd; # (now in km/h)
1969
+ $et->HandleTag($tagTbl, GPSTrack => $trk) if defined $trk;
1951
1970
  if (defined $alt) {
1952
1971
  $et->HandleTag($tagTbl, GPSAltitude => $alt);
1953
1972
  }
@@ -1955,6 +1974,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
1955
1974
  return 1;
1956
1975
  }
1957
1976
 
1977
+
1958
1978
  #------------------------------------------------------------------------------
1959
1979
  # Extract embedded information referenced from a track
1960
1980
  # Inputs: 0) ExifTool ref, 1) tag name, 2) data ref
@@ -2039,16 +2059,12 @@ sub ParseTag($$$)
2039
2059
  SetGPSDateTime($et, $tagTbl, $a[2]);
2040
2060
  my $lat = $a[5] / 1e3;
2041
2061
  my $lon = $a[7] / 1e3;
2042
- my $deg = int($lat / 100);
2043
- $lat = $deg + ($lat - $deg * 100) / 60;
2044
- $deg = int($lon / 100);
2045
- $lon = $deg + ($lon - $deg * 100) / 60;
2046
- $lat = -$lat if $a[4] eq 'S';
2047
- $lon = -$lon if $a[6] eq 'W';
2062
+ ConvertLatLon($lat, $lon);
2063
+ $lat = -abs($lat) if $a[4] eq 'S';
2064
+ $lon = -abs($lon) if $a[6] eq 'W';
2048
2065
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2049
2066
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2050
- $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
2051
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2067
+ $et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
2052
2068
  $pos += 36;
2053
2069
  }
2054
2070
  SetByteOrder('MM');
@@ -2190,20 +2206,14 @@ sub Process_gps0($$$)
2190
2206
  my $lat = GetDouble($dataPt, $pos);
2191
2207
  my $lon = GetDouble($dataPt, $pos+8);
2192
2208
  next if abs($lat) > 9000 or abs($lon) > 18000;
2193
- # (note: this method works fine for negative coordinates)
2194
- my $deg = int($lat / 100);
2195
- $lat = $deg + ($lat - $deg * 100) / 60;
2196
- $deg = int($lon / 100);
2197
- $lon = $deg + ($lon - $deg * 100) / 60;
2209
+ ConvertLatLon($lat, $lon);
2198
2210
  my @a = unpack('C*', substr($$dataPt, $pos+22, 6)); # unpack date/time
2199
2211
  $a[0] += 2000;
2200
2212
  $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a));
2201
2213
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2202
2214
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2203
2215
  $et->HandleTag($tagTbl, GPSSpeed => Get16u($dataPt, $pos+0x14));
2204
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2205
2216
  $et->HandleTag($tagTbl, GPSTrack => Get8u($dataPt, $pos+0x1c) * 2); # (NC)
2206
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2207
2217
  $et->HandleTag($tagTbl, GPSAltitude => Get32s($dataPt, $pos + 0x10));
2208
2218
  # yet to be decoded:
2209
2219
  # 0x1d - int8u[3] seen: "1 1 0"
@@ -2292,10 +2302,7 @@ sub ProcessRIFFTrailer($$$)
2292
2302
  my $lat = GetDouble(\$buff, $pos+4);
2293
2303
  my $lon = GetDouble(\$buff, $pos+12);
2294
2304
  $et->Warn('Bad gps0 record') and last if abs($lat) > 9000 or abs($lon) > 18000;
2295
- my $deg = int($lat / 100);
2296
- $lat = $deg + ($lat - $deg * 100) / 60;
2297
- $deg = int($lon / 100);
2298
- $lon = $deg + ($lon - $deg * 100) / 60;
2305
+ ConvertLatLon($lat, $lon);
2299
2306
  $lat = -$lat if Get8u(\$buff, $pos+0x21) == 2; # wild guess
2300
2307
  $lon = -$lon if Get8u(\$buff, $pos+0x22) == 2; # wild guess
2301
2308
  my @a = unpack('C*', substr($buff, $pos+26, 6)); # unpack date/time
@@ -2305,9 +2312,7 @@ sub ProcessRIFFTrailer($$$)
2305
2312
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
2306
2313
  $et->HandleTag($tagTbl, GPSLongitude => $lon);
2307
2314
  $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, $pos+0x18) * $knotsToKph);
2308
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2309
2315
  $et->HandleTag($tagTbl, GPSTrack => Get8u(\$buff, $pos+0x20) * 2);
2310
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2311
2316
  }
2312
2317
  } elsif ($tag eq 'gsen') {
2313
2318
  # (similar to record decoded in Process_gsen)
@@ -2361,17 +2366,11 @@ sub ProcessNMEA($$$)
2361
2366
  $et->HandleTag($tagTbl, GPSDateTime => $fix{dat});
2362
2367
  $et->HandleTag($tagTbl, GPSLatitude => $fix{lat});
2363
2368
  $et->HandleTag($tagTbl, GPSLongitude => $fix{lon});
2364
- if (defined $fix{spd}) {
2365
- $et->HandleTag($tagTbl, GPSSpeed => $fix{spd} * $knotsToKph);
2366
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2367
- }
2368
- if (defined $fix{trk}) {
2369
- $et->HandleTag($tagTbl, GPSTrack => $fix{trk});
2370
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2371
- }
2372
- $et->HandleTag($tagTbl, GPSAltitude => $fix{alt}) if defined $fix{alt};
2373
- $et->HandleTag($tagTbl, GPSSatellites => $fix{nsats}+0) if defined $fix{nsats};
2374
- $et->HandleTag($tagTbl, GPSDOP => $fix{hdop}) if defined $fix{hdop};
2369
+ $et->HandleTag($tagTbl, GPSSpeed => $fix{spd} * $knotsToKph) if defined $fix{spd};
2370
+ $et->HandleTag($tagTbl, GPSTrack => $fix{trk}) if defined $fix{trk};
2371
+ $et->HandleTag($tagTbl, GPSAltitude => $fix{alt}) if defined $fix{alt};
2372
+ $et->HandleTag($tagTbl, GPSSatellites=> $fix{nsats}+0) if defined $fix{nsats};
2373
+ $et->HandleTag($tagTbl, GPSDOP => $fix{hdop}) if defined $fix{hdop};
2375
2374
  }
2376
2375
  undef %fix;
2377
2376
  }
@@ -2525,9 +2524,7 @@ sub ProcessTTAD($$$)
2525
2524
  $et->HandleTag($tagTbl, GPSLongitude => GetDouble($dataPt, $pos+0x24));
2526
2525
  $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14));
2527
2526
  $et->HandleTag($tagTbl, GPSSpeed => GetDouble($dataPt, $pos+0x0c) * $mpsToKph);
2528
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2529
2527
  $et->HandleTag($tagTbl, GPSTrack => GetDouble($dataPt, $pos+0x30));
2530
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2531
2528
  if ($unknown) {
2532
2529
  my @a = map { GetDouble($dataPt, $pos+0x38+8*$_) } 0..2;
2533
2530
  $et->HandleTag($tagTbl, Unknown03 => "@a");
@@ -2660,15 +2657,13 @@ sub ProcessInsta360($;$)
2660
2657
  $a[$_] = GetDouble(\$a[$_], 0) foreach 4,6,8,9,10;
2661
2658
  $a[4] = -abs($a[4]) if $a[5] eq 'S'; # (abs just in case it was already signed)
2662
2659
  $a[6] = -abs($a[6]) if $a[7] ne 'E';
2663
- $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z');
2664
- $et->HandleTag($tagTbl, GPSLatitude => $a[4]);
2660
+ $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($a[0]) . 'Z');
2661
+ $et->HandleTag($tagTbl, GPSLatitude => $a[4]);
2665
2662
  $et->HandleTag($tagTbl, GPSLongitude => $a[6]);
2666
- $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph);
2667
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
2668
- $et->HandleTag($tagTbl, GPSTrack => $a[9]);
2669
- $et->HandleTag($tagTbl, GPSTrackRef => 'T');
2670
- $et->HandleTag($tagTbl, GPSAltitude => $a[10]);
2671
- $et->HandleTag($tagTbl, Unknown02 => "@a[1,2]") if $unknown; # millisecond counter (https://exiftool.org/forum/index.php?topic=9884.msg65143#msg65143)
2663
+ $et->HandleTag($tagTbl, GPSSpeed => $a[8] * $mpsToKph);
2664
+ $et->HandleTag($tagTbl, GPSTrack => $a[9]);
2665
+ $et->HandleTag($tagTbl, GPSAltitude => $a[10]);
2666
+ $et->HandleTag($tagTbl, Unknown02 => "@a[1,2]") if $unknown; # millisecond counter (https://exiftool.org/forum/index.php?topic=9884.msg65143#msg65143)
2672
2667
  }
2673
2668
  }
2674
2669
  } elsif ($id == 0x101) {
@@ -2724,6 +2719,41 @@ sub Process360Fly($$$)
2724
2719
  return 1;
2725
2720
  }
2726
2721
 
2722
+ #------------------------------------------------------------------------------
2723
+ # Process GPS from Vantrue N2S dashcam
2724
+ # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
2725
+ # Returns: 1 on success
2726
+ sub ProcessFMAS($$$)
2727
+ {
2728
+ my ($et, $dirInfo, $tagTbl) = @_;
2729
+ my $dataPt = $$dirInfo{DataPt};
2730
+ return 0 unless $$dataPt =~ /^FMAS\0\0\0\0.{72}SAMM.{36}A/s and length($$dataPt) >= 160;
2731
+ $et->VerboseDir('FMAS', undef, length($$dataPt));
2732
+ # 0000: 46 4d 41 53 00 00 00 00 00 00 00 00 00 00 00 00 [FMAS............]
2733
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
2734
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
2735
+ # 0030: 02 08 01 08 06 08 02 04 07 02 06 00 00 00 00 00 [................]
2736
+ # 0040: 00 00 00 00 00 00 00 00 4f 46 4e 49 4d 4d 41 53 [........OFNIMMAS]
2737
+ # 0050: 53 41 4d 4d 01 00 00 00 00 00 00 00 00 00 00 00 [SAMM............]
2738
+ # 0060: e5 07 09 18 08 00 22 00 02 00 00 00 a1 82 8a bf [......".........]
2739
+ # 0070: 89 23 8e bd 0b 2c 30 bc 41 57 4e 51 16 00 a1 01 [.#...,0.AWNQ....]
2740
+ # 0080: 29 26 27 0c 4b 00 49 00 00 00 00 00 00 00 00 00 [)&'.K.I.........]
2741
+ # 0090: 00 00 00 00 00 00 00 00 00 52 00 00 00 00 00 00 [.........R......]
2742
+ my @a = unpack('x96vCCCCCCx16AAACCCvCCvvv',$$dataPt);
2743
+ SetByteOrder('II');
2744
+ my $acc = ReadValue($dataPt, 0x6c, 'float', 3); # (looks like Z comes first in my sample)
2745
+ my $lon = $a[10] + ($a[11] + $a[13]/6000) / 60; # (why zero byte at $a[12]?)
2746
+ my $lat = $a[14] + ($a[15] + $a[16]/6000) / 60;
2747
+ $et->HandleTag($tagTbl, GPSDateTime => sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @a[0..5]));
2748
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($a[9] eq 'S' ? -1 : 1));
2749
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($a[8] eq 'W' ? -1 : 1));
2750
+ $et->HandleTag($tagTbl, GPSSpeed => $a[17] * $mphToKph); # convert mph -> kph
2751
+ $et->HandleTag($tagTbl, GPSTrack => $a[18]);
2752
+ $et->HandleTag($tagTbl, Accelerometer=> $acc);
2753
+ SetByteOrder('MM');
2754
+ return 1;
2755
+ }
2756
+
2727
2757
  #------------------------------------------------------------------------------
2728
2758
  # Scan media data for "freeGPS" metadata if not found already (ref PH)
2729
2759
  # Inputs: 0) ExifTool ref