exiftool_vendored 11.71.0 → 11.75.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of exiftool_vendored might be problematic. Click here for more details.

Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +39 -0
  3. data/bin/MANIFEST +17 -4
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +23 -26
  7. data/bin/config_files/convert_regions.config +139 -5
  8. data/bin/exiftool +21 -21
  9. data/bin/fmt_files/gpx.fmt +2 -1
  10. data/bin/fmt_files/gpx_wpt.fmt +2 -1
  11. data/bin/fmt_files/kml.fmt +2 -2
  12. data/bin/fmt_files/kml_track.fmt +46 -0
  13. data/bin/lib/Image/ExifTool.pm +42 -33
  14. data/bin/lib/Image/ExifTool.pod +21 -21
  15. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +2 -2
  16. data/bin/lib/Image/ExifTool/Canon.pm +2 -1
  17. data/bin/lib/Image/ExifTool/CanonCustom.pm +30 -4
  18. data/bin/lib/Image/ExifTool/Exif.pm +36 -63
  19. data/bin/lib/Image/ExifTool/FujiFilm.pm +1 -0
  20. data/bin/lib/Image/ExifTool/Lang/ru.pm +4233 -503
  21. data/bin/lib/Image/ExifTool/MakerNotes.pm +6 -12
  22. data/bin/lib/Image/ExifTool/Nikon.pm +2 -1
  23. data/bin/lib/Image/ExifTool/Olympus.pm +2 -1
  24. data/bin/lib/Image/ExifTool/OpenEXR.pm +1 -1
  25. data/bin/lib/Image/ExifTool/Parrot.pm +751 -0
  26. data/bin/lib/Image/ExifTool/Photoshop.pm +12 -11
  27. data/bin/lib/Image/ExifTool/QuickTime.pm +18 -5
  28. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +316 -44
  29. data/bin/lib/Image/ExifTool/Shortcuts.pm +1 -2
  30. data/bin/lib/Image/ExifTool/Sigma.pm +72 -55
  31. data/bin/lib/Image/ExifTool/TagLookup.pm +73 -7
  32. data/bin/lib/Image/ExifTool/TagNames.pod +249 -18
  33. data/bin/lib/Image/ExifTool/Text.pm +160 -0
  34. data/bin/lib/Image/ExifTool/WriteXMP.pl +1 -2
  35. data/bin/lib/Image/ExifTool/Writer.pl +1 -1
  36. data/bin/lib/Image/ExifTool/XMP.pm +19 -26
  37. data/bin/lib/Image/ExifTool/XMP2.pl +46 -9
  38. data/bin/lib/Image/ExifTool/XMPStruct.pl +14 -4
  39. data/bin/perl-Image-ExifTool.spec +19 -19
  40. data/lib/exiftool_vendored/version.rb +1 -1
  41. metadata +6 -7
  42. data/bin/config_files/blueskysea.config +0 -101
  43. data/bin/config_files/dji.config +0 -131
  44. data/bin/config_files/mini0806.config +0 -99
  45. data/bin/config_files/thinkware.config +0 -144
@@ -828,7 +828,7 @@ sub ProcessDocumentData($$$)
828
828
  my $raf = $$dirInfo{RAF};
829
829
  my $dirLen = $$dirInfo{DirLen};
830
830
  my $pos = 36; # length of header
831
- my ($buff, $n);
831
+ my ($buff, $n, $err);
832
832
 
833
833
  $et->VerboseDir('Photoshop Document Data', undef, $dirLen);
834
834
  unless ($raf) {
@@ -849,24 +849,24 @@ sub ProcessDocumentData($$$)
849
849
  my %dinfo = ( DataPt => \$buff );
850
850
  $$et{IsPSB} = $psb; # set PSB flag (needed when handling Layers directory)
851
851
  while ($pos + 12 <= $dirLen) {
852
- $raf->Read($buff, 8) == 8 or last;
852
+ $raf->Read($buff, 8) == 8 or $err = 'Error reading document data', last;
853
853
  # set byte order according to byte order of first signature
854
854
  SetByteOrder($buff =~ /^(8BIM|8B64)/ ? 'MM' : 'II') if $pos == 36;
855
855
  $buff = pack 'N*', unpack 'V*', $buff if GetByteOrder() eq 'II';
856
856
  my $sig = substr($buff, 0, 4);
857
- last unless $sig eq '8BIM' or $sig eq '8B64'; # verify signature
857
+ $sig eq '8BIM' or $sig eq '8B64' or $err = 'Bad photoshop resource', last; # verify signature
858
858
  my $tag = substr($buff, 4, 4);
859
859
  if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) {
860
- last if $pos + 16 > $dirLen;
861
- $raf->Read($buff, 8) == 8 or last;
860
+ $pos + 16 > $dirLen and $err = 'Short PSB resource', last;
861
+ $raf->Read($buff, 8) == 8 or $err = 'Error reading PSB resource', last;
862
862
  $n = Get64u(\$buff, 0);
863
863
  $pos += 4;
864
864
  } else {
865
- $raf->Read($buff, 4) == 4 or last;
865
+ $raf->Read($buff, 4) == 4 or $err = 'Error reading PSD resource', last;
866
866
  $n = Get32u(\$buff, 0);
867
867
  }
868
868
  $pos += 12;
869
- last if $pos + $n > $dirLen;
869
+ $pos + $n > $dirLen and $err = 'Truncated photoshop resource', last;
870
870
  my $pad = (4 - ($n & 3)) & 3; # number of padding bytes
871
871
  my $tagInfo = $$tagTablePtr{$tag};
872
872
  if ($tagInfo or $verbose) {
@@ -874,20 +874,21 @@ sub ProcessDocumentData($$$)
874
874
  my $fpos = $raf->Tell() + $n + $pad;
875
875
  my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable});
876
876
  $et->ProcessDirectory({ RAF => $raf, DirLen => $n }, $subTable);
877
- $raf->Seek($fpos, 0) or last;
877
+ $raf->Seek($fpos, 0) or $err = 'Seek error', last;
878
878
  } else {
879
879
  $dinfo{DataPos} = $raf->Tell();
880
880
  $dinfo{Start} = 0;
881
881
  $dinfo{Size} = $n;
882
- $raf->Read($buff, $n) == $n or last;
882
+ $raf->Read($buff, $n) == $n or $err = 'Error reading photoshop resource', last;
883
883
  $et->HandleTag($tagTablePtr, $tag, undef, %dinfo);
884
- $raf->Seek($pad, 1) or last;
884
+ $raf->Seek($pad, 1) or $err = 'Seek error', last;
885
885
  }
886
886
  } else {
887
- $raf->Seek($n + $pad, 1) or last;
887
+ $raf->Seek($n + $pad, 1) or $err = 'Seek error', last;
888
888
  }
889
889
  $pos += $n + $pad; # step to start of next structure
890
890
  }
891
+ $err and $et->Warn($err);
891
892
  return 1;
892
893
  }
893
894
 
@@ -44,7 +44,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
44
44
  use Image::ExifTool::Exif;
45
45
  use Image::ExifTool::GPS;
46
46
 
47
- $VERSION = '2.37';
47
+ $VERSION = '2.38';
48
48
 
49
49
  sub ProcessMOV($$;$);
50
50
  sub ProcessKeys($$$);
@@ -59,6 +59,7 @@ sub Process_mebx($$$);
59
59
  sub Process_3gf($$$);
60
60
  sub Process_gps0($$$);
61
61
  sub Process_gsen($$$);
62
+ sub ProcessRIFFTrailer($$$);
62
63
  sub ProcessTTAD($$$);
63
64
  sub ProcessNMEA($$$);
64
65
  sub SaveMetaKeys($$$);
@@ -8293,7 +8294,7 @@ sub ProcessMOV($$;$)
8293
8294
  my $str = $$dirInfo{DirName} . ' with ' . ($raf->Tell() - $pos) . ' bytes';
8294
8295
  $et->VPrint(0,"$$et{INDENT}\[Terminator found in $str remaining]");
8295
8296
  } else {
8296
- my $t = PrintableTagID($tag);
8297
+ my $t = PrintableTagID($tag,2);
8297
8298
  $et->VPrint(0,"$$et{INDENT}Tag '${t}' extends to end of file");
8298
8299
  }
8299
8300
  last;
@@ -8394,9 +8395,21 @@ sub ProcessMOV($$;$)
8394
8395
  }
8395
8396
  # load values only if associated with a tag (or verbose) and not too big
8396
8397
  if ($size > 0x2000000) { # start to get worried above 32 MB
8398
+ # check for RIFF trailer (written by Auto-Vox dashcam)
8399
+ if ($buff =~ /^(gpsa|gps0|gsen|gsea)...\0/s) { # (yet seen only gpsa as first record)
8400
+ $et->VPrint(0, "Found RIFF trailer");
8401
+ if ($et->Options('ExtractEmbedded')) {
8402
+ $raf->Seek(-8, 1) or last; # seek back to start of trailer
8403
+ my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
8404
+ ProcessRIFFTrailer($et, { RAF => $raf }, $tbl);
8405
+ } else {
8406
+ EEWarn($et);
8407
+ }
8408
+ last;
8409
+ }
8397
8410
  $ignore = 1;
8398
8411
  if ($tagInfo and not $$tagInfo{Unknown} and not $eeTag) {
8399
- my $t = PrintableTagID($tag);
8412
+ my $t = PrintableTagID($tag,2);
8400
8413
  if ($size > 0x8000000) {
8401
8414
  $et->Warn("Skipping '${t}' atom > 128 MB", 1);
8402
8415
  } else {
@@ -8444,7 +8457,7 @@ ItemID: foreach $id (keys %$items) {
8444
8457
  my $val;
8445
8458
  my $missing = $size - $raf->Read($val, $size);
8446
8459
  if ($missing) {
8447
- my $t = PrintableTagID($tag);
8460
+ my $t = PrintableTagID($tag,2);
8448
8461
  $et->Warn("Truncated '${t}' data (missing $missing bytes)");
8449
8462
  last;
8450
8463
  }
@@ -8721,7 +8734,7 @@ ItemID: foreach $id (keys %$items) {
8721
8734
  Extra => sprintf(' at offset 0x%.4x', $raf->Tell()),
8722
8735
  ) if $verbose;
8723
8736
  if ($size and (not $raf->Seek($size-1, 1) or $raf->Read($buff, 1) != 1)) {
8724
- my $t = PrintableTagID($tag);
8737
+ my $t = PrintableTagID($tag,2);
8725
8738
  $et->Warn("Truncated '${t}' data");
8726
8739
  last;
8727
8740
  }
@@ -89,20 +89,34 @@ my %insvLimit = (
89
89
  the ExtractEmbedded option is used.
90
90
  },
91
91
  VARS => { NO_ID => 1 },
92
- GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")' },
92
+ GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
93
93
  GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
94
94
  GPSAltitude => { PrintConv => '(sprintf("%.4f", $val) + 0) . " m"' }, # round to 4 decimals
95
95
  GPSSpeed => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
96
96
  GPSSpeedRef => { PrintConv => { K => 'km/h', M => 'mph', N => 'knots' } },
97
97
  GPSTrack => { PrintConv => 'sprintf("%.4f", $val) + 0' }, # round to 4 decimals
98
98
  GPSTrackRef => { PrintConv => { M => 'Magnetic North', T => 'True North' } },
99
- GPSDateTime => { PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' } },
99
+ GPSDateTime => {
100
+ Groups => { 2 => 'Time' },
101
+ Description => 'GPS Date/Time',
102
+ RawConv => '$$self{FoundGPSDateTime} = 1; $val',
103
+ PrintConv => '$self->ConvertDateTime($val)',
104
+ },
100
105
  GPSTimeStamp => { PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', Groups => { 2 => 'Time' } },
101
106
  GPSSatellites=> { },
102
107
  GPSDOP => { Description => 'GPS Dilution Of Precision' },
108
+ Distance => { PrintConv => '"$val m"' },
109
+ VerticalSpeed=> { PrintConv => '"$val m/s"' },
110
+ FNumber => { PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)', Groups => { 2 => 'Camera' } },
111
+ ExposureTime => { PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', Groups => { 2 => 'Camera' } },
112
+ ExposureCompensation => { PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', Groups => { 2 => 'Camera' } },
113
+ ISO => { Groups => { 2 => 'Camera' } },
103
114
  CameraDateTime=>{ PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' } },
104
- Accelerometer=> { Notes => 'right/up/backward acceleration in units of g' },
115
+ Accelerometer=> { Notes => '3-axis acceleration in units of g' },
116
+ AccelerometerData => { },
105
117
  AngularVelocity => { },
118
+ GSensor => { },
119
+ Car => { },
106
120
  RawGSensor => {
107
121
  # (same as GSensor, but offset by some unknown value)
108
122
  ValueConv => 'my @a=split " ",$val; $_/=1000 foreach @a; "@a"',
@@ -221,6 +235,10 @@ my %insvLimit = (
221
235
  ByteOrder => 'Little-Endian',
222
236
  },
223
237
  }],
238
+ mett => { # Parrot drones
239
+ Name => 'mett',
240
+ SubDirectory => { TagTable => 'Image::ExifTool::Parrot::mett' },
241
+ },
224
242
  JPEG => { # (in CR3 images) - [vide HandlerType with JPEG in SampleDescription, not MetaFormat]
225
243
  Name => 'JpgFromRaw',
226
244
  Groups => { 2 => 'Preview' },
@@ -319,6 +337,7 @@ my %insvLimit = (
319
337
  4 => {
320
338
  Name => 'GPSLatitude',
321
339
  Format => 'double',
340
+ RawConv => '$$self{FoundGPSLatitude} = 1; $val',
322
341
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
323
342
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
324
343
  },
@@ -342,8 +361,10 @@ my %insvLimit = (
342
361
  FIRST_ENTRY => 0,
343
362
  0x04 => {
344
363
  Name => 'GPSDateTime',
364
+ Description => 'GPS Date/Time',
345
365
  Groups => { 2 => 'Time' },
346
366
  Format => 'double',
367
+ RawConv => '$$self{FoundGPSDateTime} = 1; $val',
347
368
  ValueConv => q{
348
369
  my $str = ConvertUnixTime($val);
349
370
  my $frac = $val - int($val);
@@ -369,6 +390,7 @@ my %insvLimit = (
369
390
  0x10 => {
370
391
  Name => 'GPSLatitude',
371
392
  Format => 'double',
393
+ RawConv => '$$self{FoundGPSLatitude} = 1; $val',
372
394
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
373
395
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
374
396
  },
@@ -425,6 +447,7 @@ my %insvLimit = (
425
447
  4 => {
426
448
  Name => 'GPSLatitude',
427
449
  Format => 'int32s',
450
+ RawConv => '$$self{FoundGPSLatitude} = 1; $val',
428
451
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val/1e6, 1)',
429
452
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
430
453
  },
@@ -470,6 +493,7 @@ my %insvLimit = (
470
493
  NOTES => 'Tags extracted from the tx3g sbtl timed metadata of Yuneec drones.',
471
494
  Lat => {
472
495
  Name => 'GPSLatitude',
496
+ RawConv => '$$self{FoundGPSLatitude} = 1; $val',
473
497
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
474
498
  },
475
499
  Lon => {
@@ -571,7 +595,7 @@ sub SaveMetaKeys($$$)
571
595
  } else {
572
596
  $str = '';
573
597
  }
574
- $et->VPrint(1, $$et{INDENT}."- Tag '".PrintableTagID($tag)."' ($len bytes)$str\n");
598
+ $et->VPrint(1, $$et{INDENT}."- Tag '".PrintableTagID($tag,2)."' ($len bytes)$str\n");
575
599
  $et->VerboseDump(\$val);
576
600
  }
577
601
  }
@@ -599,14 +623,53 @@ sub FoundSomething($$;$$)
599
623
  }
600
624
 
601
625
  #------------------------------------------------------------------------------
602
- # Parse textual metadata
603
- # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref
604
- sub ParseText($$$)
626
+ # Approximate GPSDateTime value from sample time and CreateDate
627
+ # Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (ms)
628
+ # 3) true if CreateDate is at end of video
629
+ # Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
630
+ sub SetGPSDateTime($$$)
631
+ {
632
+ my ($et, $tagTbl, $sampleTime) = @_;
633
+ my $value = $$et{VALUE};
634
+ if (defined $sampleTime and $$value{CreateDate}) {
635
+ $sampleTime += $$value{CreateDate}; # adjust sample time to seconds since the epoch
636
+ if ($$et{CreateDateAtEnd}) { # adjust if CreateDate is at end of video
637
+ return unless $$value{TimeScale} and $$value{Duration};
638
+ $sampleTime -= $$value{Duration} / $$value{TimeScale};
639
+ $et->WarnOnce('Approximating GPSDateTime as CreateDate - Duration + SampleTime', 1);
640
+ } else {
641
+ $et->WarnOnce('Approximating GPSDateTime as CreateDate + SampleTime', 1);
642
+ }
643
+ unless ($et->Options('QuickTimeUTC')) {
644
+ my $tzOff = $$et{tzOff}; # use previously calculated offset
645
+ unless (defined $tzOff) {
646
+ # adjust to UTC, assuming time is local
647
+ my @tm = localtime $$value{CreateDate};
648
+ my @gm = gmtime $$value{CreateDate};
649
+ $tzOff = $$et{tzOff} = Image::ExifTool::GetTimeZone(\@tm, \@gm) * 60;
650
+ }
651
+ $sampleTime -= $tzOff; # shift from local time to UTC
652
+ }
653
+ $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($sampleTime,0,3) . 'Z');
654
+ }
655
+ }
656
+
657
+ #------------------------------------------------------------------------------
658
+ # Process subtitle 'text'
659
+ # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref, 3) optional sample time
660
+ sub Process_text($$$)
605
661
  {
606
662
  my ($et, $tagTbl, $buffPt) = @_;
663
+ my ($found, $val, %tags, $tag);
664
+
665
+ return if $$et{NoMoreTextDecoding};
666
+
607
667
  while ($$buffPt =~ /\$(\w+)([^\$]*)/g) {
608
668
  my ($tag, $dat) = ($1, $2);
609
669
  if ($tag =~ /^[A-Z]{2}RMC$/ and $dat =~ /^,(\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+)/) {
670
+ my $year = $15 + ($15 >= 70 ? 1900 : 2000);
671
+ my $str = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
672
+ $et->HandleTag($tagTbl, GPSDateTime => $str);
610
673
  $et->HandleTag($tagTbl, GPSLatitude => (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1));
611
674
  $et->HandleTag($tagTbl, GPSLongitude => (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1));
612
675
  if (length $11) {
@@ -617,23 +680,126 @@ sub ParseText($$$)
617
680
  $et->HandleTag($tagTbl, GPSTrack => $11);
618
681
  $et->HandleTag($tagTbl, GPSTrackRef => 'T');
619
682
  }
620
- my $year = $15 + ($15 >= 70 ? 1900 : 2000);
621
- my $str = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
622
- $et->HandleTag($tagTbl, GPSDateTime => $str);
683
+ $found = 1;
623
684
  } elsif ($tag eq 'BEGINGSENSOR' and $dat =~ /^:([-+]\d+\.\d+):([-+]\d+\.\d+):([-+]\d+\.\d+)/) {
624
685
  $et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3");
686
+ $found = 1;
625
687
  } elsif ($tag eq 'TIME' and $dat =~ /^:(\d+)/) {
626
688
  $et->HandleTag($tagTbl, TimeCode => $1 / ($$et{MediaTS} || 1));
689
+ $found = 1;
627
690
  } elsif ($tag eq 'BEGIN') {
628
691
  $et->HandleTag($tagTbl, Text => $dat) if length $dat;
692
+ $found = 1;
629
693
  } elsif ($tag ne 'END') {
630
694
  $et->HandleTag($tagTbl, Text => "\$$tag$dat");
695
+ $found = 1;
696
+ }
697
+ }
698
+ return if $found;
699
+
700
+ # check for BlueSkySea enciphered binary GPS data
701
+ if ($$buffPt =~ /^\0\0..\xaa\xaa/s and length $$buffPt >= 282) {
702
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 8, 14)));
703
+ if ($val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) {
704
+ $tags{GPSDateTime} = "$1:$2:$3 $4:$5:$6";
705
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 38, 9)));
706
+ if ($val =~ /^([NS])(\d{2})(\d+$)$/) {
707
+ $tags{GPSLatitude} = ($2 + $3 / 600000) * ($1 eq 'S' ? -1 : 1);
708
+ }
709
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 47, 10)));
710
+ if ($val =~ /^([EW])(\d{3})(\d+$)$/) {
711
+ $tags{GPSLongitude} = ($2 + $3 / 600000) * ($1 eq 'W' ? -1 : 1);
712
+ }
713
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 62, 3)));
714
+ $tags{GPSSpeed} = $val + 0 if $val =~ /^\d+$/;
715
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0xad, 12)));
716
+ # the first X,Y,Z accelerometer readings from the AccelerometerData
717
+ if ($val =~ /^([-+]\d{3})([-+]\d{3})([-+]\d{3})$/) {
718
+ $tags{Accelerometer} = "$1 $2 $3";
719
+ }
720
+ $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$buffPt, 0xba, 96)));
721
+ my $order = GetByteOrder();
722
+ SetByteOrder('II');
723
+ $val = ReadValue(\$val, 0, 'float');
724
+ SetByteOrder($order);
725
+ $tags{AccelerometerData} = $val;
726
+ }
727
+ }
728
+
729
+ # check for DJI telemetry data, eg:
730
+ # "F/3.5, SS 1000, ISO 100, EV 0, GPS (8.6499, 53.1665, 18), D 24.26m,
731
+ # H 6.00m, H.S 2.10m/s, V.S 0.00m/s \n"
732
+ if (not %tags and $$buffPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
733
+ $$et{CreateDateAtEnd} = 1; # set flag indicating the file creation date is at the end
734
+ $tags{GPSLatitude} = $2;
735
+ $tags{GPSLongitude} = $1;
736
+ $tags{GPSAltitude} = $1 if $$buffPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
737
+ if ($$buffPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
738
+ $tags{GPSSpeed} = $1 * $mpsToKph;
739
+ $tags{GPSSpeedRef} = 'K';
740
+ }
741
+ $tags{Distance} = $1 * $mpsToKph if $$buffPt =~ /,\s*D\s+(\d+\.?\d*)m/;
742
+ $tags{VerticalSpeed} = $1 if $$buffPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
743
+ $tags{FNumber} = $1 if $$buffPt =~ /\bF\/(\d+\.?\d*)/;
744
+ $tags{ExposureTime} = 1 / $1 if $$buffPt =~ /\bSS\s+(\d+\.?\d*)/;
745
+ $tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$buffPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/;
746
+ $tags{ISO} = $1 if $$buffPt =~ /\bISO\s+(\d+\.?\d*)/;
747
+ }
748
+
749
+ # check for Mini 0806 dashcam GPS, eg:
750
+ # "A,270519,201555.000,3356.8925,N,08420.2071,W,000.0,331.0M,+01.84,-09.80,-00.61;\n"
751
+ if (not %tags and $$buffPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
752
+ $tags{GPSDateTime} = "20$3:$2:$1 $4:$5:$6Z";
753
+ if ($$buffPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) {
754
+ $tags{GPSLatitude} = ($1 + $2/60) * ($3 eq 'S' ? -1 : 1);
755
+ }
756
+ if ($$buffPt =~ /^A,.*?,.*?,.*?,.*?,(\d{3})(\d+\.\d+),([EW])/) {
757
+ $tags{GPSLongitude} = ($1 + $2/60) * ($3 eq 'W' ? -1 : 1);
631
758
  }
759
+ my @a = split ',', $$buffPt;
760
+ $tags{GPSAltitude} = $a[8] if $a[8] and $a[8] =~ s/M$//;
761
+ $tags{GPSSpeed} = $a[7] if $a[7] and $a[7] =~ /^\d+\.\d+$/; # (NC)
762
+ $tags{Accelerometer} = "$a[9] $a[10] $a[11]" if $a[11] and $a[11] =~ s/;\s*$//;
763
+ }
764
+
765
+ # check for Thinkware format, eg:
766
+ # "gsensori,4,512,-67,-12,100;GNRMC,161313.00,A,4529.87489,N,07337.01215,W,6.225,35.34,310819,,,A*52..;
767
+ # CAR,0,0,0,0.0,0,0,0,0,0,0,0,0"
768
+ unless (%tags) {
769
+ if ($$buffPt =~ /[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+)/ and
770
+ # do some basic sanity checks on the date
771
+ $13 <= 31 and $14 <= 12 and $15 <= 99)
772
+ {
773
+ my $year = $15 + ($15 >= 70 ? 1900 : 2000);
774
+ $tags{GPSDateTime} = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $year, $14, $13, $1, $2, $3);
775
+ $tags{GPSLatitude} = (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1);
776
+ $tags{GPSLongitude} = (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1);
777
+ if (length $11) {
778
+ $tags{GPSSpeed} = $11 * $knotsToKph;
779
+ $tags{GPSSpeedRef} = 'K';
780
+ }
781
+ if (length $12) {
782
+ $tags{GPSTrack} = $12;
783
+ $tags{GPSTrackRef} = 'T';
784
+ }
785
+ }
786
+ $tags{GSensor} = $1 if $$buffPt =~ /\bgsensori,(.*?)(;|$)/;
787
+ $tags{Car} = $1 if $$buffPt =~ /\bCAR,(.*?)(;|$)/;
788
+ }
789
+ if (%tags) {
790
+ foreach $tag (sort keys %tags) {
791
+ $et->HandleTag($tagTbl, $tag => $tags{$tag});
792
+ }
793
+ $$et{UnknownTextCount} = 0;
794
+ } else {
795
+ $$et{UnknownTextCount} = ($$et{UnknownTextCount} || 0) + 1;
796
+ # give up trying to decode useful information if we haven't found anything for a while
797
+ $$et{NoMoreTextDecoding} = 1 if $$et{UnknownTextCount} > 100;
632
798
  }
633
799
  }
634
800
 
635
801
  #------------------------------------------------------------------------------
636
- # Exract embedded metadata from media samples
802
+ # Extract embedded metadata from media samples
637
803
  # Inputs: 0) ExifTool ref
638
804
  # Notes: Also accesses ExifTool RAF*, SET_GROUP1, HandlerType, MetaFormat,
639
805
  # ee*, and avcC elements (* = must exist)
@@ -725,6 +891,10 @@ sub ProcessSamples($)
725
891
  # loop through all samples
726
892
  for ($i=0; $i<@$start and $i<@$size; ++$i) {
727
893
 
894
+ # initialize our flags for setting GPSDateTime
895
+ delete $$et{FoundGPSLatitude};
896
+ delete $$et{FoundGPSDateTime};
897
+
728
898
  # read the sample data
729
899
  my $size = $$size[$i];
730
900
  next unless $raf->Seek($$start[$i], 0) and $raf->Read($buff, $size) == $size;
@@ -782,14 +952,14 @@ sub ProcessSamples($)
782
952
  $et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16) * 180/0x80000000);
783
953
  $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8));
784
954
  $et->HandleTag($tagTbl, GPSSpeedRef => 'M');
785
- next;
955
+ SetGPSDateTime($et, $tagTbl, $time[$i]);
956
+ next; # all done (don't store/process as text)
786
957
  }
787
958
  unless (defined $val) {
788
959
  $et->HandleTag($tagTbl, Text => $buff); # just store any other text
789
- next;
790
960
  }
791
961
  }
792
- ParseText($et, $tagTbl, \$buff);
962
+ Process_text($et, $tagTbl, \$buff);
793
963
 
794
964
  } elsif ($processByMetaFormat{$type}) {
795
965
 
@@ -800,6 +970,7 @@ sub ProcessSamples($)
800
970
  $$et{ee} = $ee; # need ee information for 'keys'
801
971
  $et->HandleTag($tagTbl, $metaFormat, undef,
802
972
  DataPt => \$buff,
973
+ DataPos => $$start[$i],
803
974
  Base => $$start[$i],
804
975
  TagInfo => $tagInfo,
805
976
  );
@@ -809,7 +980,7 @@ sub ProcessSamples($)
809
980
  # "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a"
810
981
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
811
982
  $et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3 $4") if $buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/;
812
- ParseText($et, $tagTbl, \$buff);
983
+ Process_text($et, $tagTbl, \$buff);
813
984
  }
814
985
  } elsif ($verbose) {
815
986
  $et->VPrint(0, "Unknown meta format ($metaFormat)");
@@ -821,6 +992,7 @@ sub ProcessSamples($)
821
992
  # decode "freeGPS " data (Novatek)
822
993
  ProcessFreeGPS($et, {
823
994
  DataPt => \$buff,
995
+ DataPos => $$start[$i],
824
996
  SampleTime => $time[$i],
825
997
  SampleDuration => $dur[$i],
826
998
  }, $tagTbl) ;
@@ -833,11 +1005,14 @@ sub ProcessSamples($)
833
1005
  FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
834
1006
  $et->HandleTag($tagTbl, $type, undef,
835
1007
  DataPt => \$buff,
1008
+ DataPos => $$start[$i],
836
1009
  Base => $$start[$i],
837
1010
  TagInfo => $tagInfo,
838
1011
  );
839
1012
  }
840
1013
  }
1014
+ # generate approximate GPSDateTime if necessary
1015
+ SetGPSDateTime($et, $tagTbl, $time[$i]) if $$et{FoundGPSLatitude} and not $$et{FoundGPSDateTime};
841
1016
  }
842
1017
  if ($verbose) {
843
1018
  $$et{INDENT} = $oldIndent;
@@ -929,7 +1104,7 @@ sub ProcessFreeGPS($$$)
929
1104
  SetByteOrder('II');
930
1105
  $lat = GetFloat($dataPt, 0x2c);
931
1106
  $lon = GetFloat($dataPt, 0x30);
932
- $spd = GetFloat($dataPt, 0x34) * 1.852; # (convert knots to km/h)
1107
+ $spd = GetFloat($dataPt, 0x34) * $knotsToKph; # (convert knots to km/h)
933
1108
  $trk = GetFloat($dataPt, 0x38);
934
1109
  SetByteOrder('MM');
935
1110
 
@@ -1425,32 +1600,6 @@ sub Process_3gf($$$)
1425
1600
  return 1;
1426
1601
  }
1427
1602
 
1428
- #------------------------------------------------------------------------------
1429
- # Process DuDuBell M1 dashcam / VSYS M6L 'gsen' atom (ref PH)
1430
- # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1431
- # Returns: 1 on success
1432
- sub Process_gsen($$$)
1433
- {
1434
- my ($et, $dirInfo, $tagTbl) = @_;
1435
- my $dataPt = $$dirInfo{DataPt};
1436
- my $dirLen = $$dirInfo{DirLen};
1437
- my $recLen = 3; # 3-byte record length
1438
- $et->VerboseDir('gsen', undef, $dirLen);
1439
- if ($dirLen > $recLen and not $et->Options('ExtractEmbedded')) {
1440
- $dirLen = $recLen;
1441
- EEWarn($et);
1442
- }
1443
- my $pos;
1444
- for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) {
1445
- $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1446
- my @acc = map { $_ /= 16 } unpack "x${pos}c3", $$dataPt;
1447
- $et->HandleTag($tagTbl, Accelerometer => "@acc");
1448
- # (there are no associated timestamps, but these are sampled at 5 Hz in my test video)
1449
- }
1450
- delete $$et{DOC_NUM};
1451
- return 1;
1452
- }
1453
-
1454
1603
  #------------------------------------------------------------------------------
1455
1604
  # Process DuDuBell M1 dashcam / VSYS M6L 'gps0' atom (ref PH)
1456
1605
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
@@ -1479,7 +1628,7 @@ sub Process_gps0($$$)
1479
1628
  $lat = $deg + ($lat - $deg * 100) / 60;
1480
1629
  $deg = int($lon / 100);
1481
1630
  $lon = $deg + ($lon - $deg * 100) / 60;
1482
- my @a = unpack('C*', substr($$dataPt,$pos+22, 6)); # unpack date/time
1631
+ my @a = unpack('C*', substr($$dataPt, $pos+22, 6)); # unpack date/time
1483
1632
  $a[0] += 2000;
1484
1633
  $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a));
1485
1634
  $et->HandleTag($tagTbl, GPSLatitude => $lat);
@@ -1497,6 +1646,129 @@ sub Process_gps0($$$)
1497
1646
  return 1;
1498
1647
  }
1499
1648
 
1649
+ #------------------------------------------------------------------------------
1650
+ # Process DuDuBell M1 dashcam / VSYS M6L 'gsen' atom (ref PH)
1651
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1652
+ # Returns: 1 on success
1653
+ sub Process_gsen($$$)
1654
+ {
1655
+ my ($et, $dirInfo, $tagTbl) = @_;
1656
+ my $dataPt = $$dirInfo{DataPt};
1657
+ my $dirLen = $$dirInfo{DirLen};
1658
+ my $recLen = 3; # 3-byte record length
1659
+ $et->VerboseDir('gsen', undef, $dirLen);
1660
+ if ($dirLen > $recLen and not $et->Options('ExtractEmbedded')) {
1661
+ $dirLen = $recLen;
1662
+ EEWarn($et);
1663
+ }
1664
+ my $pos;
1665
+ for ($pos=0; $pos+$recLen<=$dirLen; $pos+=$recLen) {
1666
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1667
+ my @acc = map { $_ /= 16 } unpack "x${pos}c3", $$dataPt;
1668
+ $et->HandleTag($tagTbl, Accelerometer => "@acc");
1669
+ # (there are no associated timestamps, but these are sampled at 5 Hz in my test video)
1670
+ }
1671
+ delete $$et{DOC_NUM};
1672
+ return 1;
1673
+ }
1674
+
1675
+ #------------------------------------------------------------------------------
1676
+ # Process RIFF-format trailer written by Auto-Vox dashcam (ref PH)
1677
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
1678
+ # Returns: 1 on success
1679
+ # Note: This trailer is basically RIFF chunks added to a QuickTime-format file (augh!),
1680
+ # but there are differences in the record formats so we can't just call
1681
+ # ProcessRIFF to process the gps0 and gsen atoms using the routines above
1682
+ sub ProcessRIFFTrailer($$$)
1683
+ {
1684
+ my ($et, $dirInfo, $tagTbl) = @_;
1685
+ my $raf = $$dirInfo{RAF};
1686
+ my $verbose = $et->Options('Verbose');
1687
+ my ($buff, $pos);
1688
+ SetByteOrder('II');
1689
+ for (;;) {
1690
+ last unless $raf->Read($buff, 8) == 8;
1691
+ my ($tag, $len) = unpack('a4V', $buff);
1692
+ last if $tag eq "\0\0\0\0";
1693
+ unless ($tag =~ /^[\w ]{4}/ and $len < 0x2000000) {
1694
+ $et->Warn('Bad RIFF trailer');
1695
+ last;
1696
+ }
1697
+ $raf->Read($buff, $len) == $len or $et->Warn("Truncated $tag record in RIFF trailer"), last;
1698
+ if ($verbose) {
1699
+ $et->VPrint(0, " - RIFF trailer '${tag}' ($len bytes)\n");
1700
+ $et->VerboseDump(\$buff, Addr => $raf->Tell() - $len) if $verbose > 2;
1701
+ $$et{INDENT} .= '| ';
1702
+ $et->VerboseDir($tag, undef, $len) if $tag =~ /^(gps0|gsen)$/;
1703
+ }
1704
+ if ($tag eq 'gps0') {
1705
+ # (similar to record decoded in Process_gps0, but with some differences)
1706
+ # 0000: 41 49 54 47 74 46 94 f6 c6 c5 b4 40 34 a2 b4 37 [AITGtF.....@4..7]
1707
+ # 0010: f8 7b 8a 40 ff ff 00 00 38 00 77 0a 1a 0c 12 28 [.{.@....8.w....(]
1708
+ # 0020: 8d 01 02 40 29 07 00 00 [...@)...]
1709
+ # 0x00 - undef[4] 'AITG'
1710
+ # 0x04 - double latitude (always positive)
1711
+ # 0x0c - double longitude (always positive)
1712
+ # 0x14 - ? seen hex "ff ff 00 00" (altitude in Process_gps0 record below)
1713
+ # 0x18 - int16u speed in knots (different than km/hr in Process_gps0)
1714
+ # 0x1a - int8u[6] yr-1900,mon,day,hr,min,sec (different than -2000 in Process_gps0)
1715
+ # 0x20 - int8u direction in degrees / 2
1716
+ # 0x21 - int8u guessing that this is 1=N, 2=S - PH
1717
+ # 0x22 - int8u guessing that this is 1=E, 2=W - PH
1718
+ # 0x23 - ? seen hex "40"
1719
+ # 0x24 - in32u time since start of video (ms)
1720
+ my $recLen = 0x28;
1721
+ for ($pos=0; $pos+$recLen<$len; $pos+=$recLen) {
1722
+ substr($buff, $pos, 4) eq 'AITG' or $et->Warn('Unrecognized gps0 record'), last;
1723
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1724
+ # lat/long are in DDDMM.MMMM format
1725
+ my $lat = GetDouble(\$buff, $pos+4);
1726
+ my $lon = GetDouble(\$buff, $pos+12);
1727
+ $et->Warn('Bad gps0 record') and last if abs($lat) > 9000 or abs($lon) > 18000;
1728
+ my $deg = int($lat / 100);
1729
+ $lat = $deg + ($lat - $deg * 100) / 60;
1730
+ $deg = int($lon / 100);
1731
+ $lon = $deg + ($lon - $deg * 100) / 60;
1732
+ $lat = -$lat if Get8u(\$buff, $pos+0x21) == 2; # wild guess
1733
+ $lon = -$lon if Get8u(\$buff, $pos+0x22) == 2; # wild guess
1734
+ my @a = unpack('C*', substr($buff, $pos+26, 6)); # unpack date/time
1735
+ $a[0] += 1900; # (different than Proces_gps0)
1736
+ $et->HandleTag($tagTbl, SampleTime => Get32u(\$buff, $pos + 0x24) / 1000);
1737
+ $et->HandleTag($tagTbl, GPSDateTime => sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ", @a));
1738
+ $et->HandleTag($tagTbl, GPSLatitude => $lat);
1739
+ $et->HandleTag($tagTbl, GPSLongitude => $lon);
1740
+ $et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, $pos+0x18) * $knotsToKph);
1741
+ $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1742
+ $et->HandleTag($tagTbl, GPSTrack => Get8u(\$buff, $pos+0x20) * 2);
1743
+ $et->HandleTag($tagTbl, GPSTrackRef => 'T');
1744
+ }
1745
+ } elsif ($tag eq 'gsen') {
1746
+ # (similar to record decoded in Process_gsen)
1747
+ # 0000: 41 49 54 53 1a 0d 05 ff c8 00 00 00 [AITS........]
1748
+ # 0x00 - undef[4] 'AITS'
1749
+ # 0x04 - int8s[3] accelerometer readings
1750
+ # 0x07 - ? seen hex "ff"
1751
+ # 0x08 - in32u time since start of video (ms)
1752
+ my $recLen = 0x0c;
1753
+ for ($pos=0; $pos+$recLen<$len; $pos+=$recLen) {
1754
+ substr($buff, $pos, 4) eq 'AITS' or $et->Warn('Unrecognized gsen record'), last;
1755
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
1756
+ my @acc = map { $_ /= 24 } unpack('x'.($pos+4).'c3', $buff);
1757
+ $et->HandleTag($tagTbl, SampleTime => Get32u(\$buff, $pos + 8) / 1000);
1758
+ # 0=+Up, 1=+Right, 3=+Forward (calibration of 24 counts/g is a wild guess - PH)
1759
+ $et->HandleTag($tagTbl, Accelerometer => "@acc");
1760
+ }
1761
+ }
1762
+ # also seen, but not decoded:
1763
+ # gpsa (8 bytes): hex "01 20 00 00 08 03 02 08 "
1764
+ # gsea (20 bytes): all zeros
1765
+ $$et{INDENT} = substr($$et{INDENT}, 0, -2) if $verbose;
1766
+ }
1767
+ delete $$et{DOC_NUM};
1768
+ SetByteOrder('MM');
1769
+ return 1;
1770
+ }
1771
+
1500
1772
  #------------------------------------------------------------------------------
1501
1773
  # Process 'gps ' atom containing NMEA from Pittasoft Blackvue dashcam (ref PH)
1502
1774
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
@@ -1624,9 +1896,9 @@ sub ProcessTTAD($$$)
1624
1896
  FoundSomething($et, $tagTbl, $sampleTime / 1000);
1625
1897
  my $t = GetDouble($dataPt, $pos);
1626
1898
  $et->HandleTag($tagTbl, GPSDateTime => Image::ExifTool::ConvertUnixTime($t,undef,3).'Z');
1627
- $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14));
1628
1899
  $et->HandleTag($tagTbl, GPSLatitude => GetDouble($dataPt, $pos+0x1c));
1629
1900
  $et->HandleTag($tagTbl, GPSLongitude => GetDouble($dataPt, $pos+0x24));
1901
+ $et->HandleTag($tagTbl, GPSAltitude => GetDouble($dataPt, $pos+0x14));
1630
1902
  $et->HandleTag($tagTbl, GPSSpeed => GetDouble($dataPt, $pos+0x0c) * $mpsToKph);
1631
1903
  $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
1632
1904
  $et->HandleTag($tagTbl, GPSTrack => GetDouble($dataPt, $pos+0x30));