exiftool_vendored 11.97.0 → 12.09.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.
- checksums.yaml +4 -4
- data/bin/Changes +196 -2
- data/bin/MANIFEST +8 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +43 -42
- data/bin/exiftool +150 -102
- data/bin/lib/Image/ExifTool.pm +162 -111
- data/bin/lib/Image/ExifTool.pod +123 -90
- data/bin/lib/Image/ExifTool/AIFF.pm +2 -2
- data/bin/lib/Image/ExifTool/APE.pm +2 -2
- data/bin/lib/Image/ExifTool/BuildTagLookup.pm +21 -10
- data/bin/lib/Image/ExifTool/Canon.pm +201 -14
- data/bin/lib/Image/ExifTool/CanonCustom.pm +82 -16
- data/bin/lib/Image/ExifTool/DPX.pm +56 -2
- data/bin/lib/Image/ExifTool/DarwinCore.pm +22 -3
- data/bin/lib/Image/ExifTool/EXE.pm +8 -5
- data/bin/lib/Image/ExifTool/Exif.pm +15 -6
- data/bin/lib/Image/ExifTool/Font.pm +9 -2
- data/bin/lib/Image/ExifTool/GIF.pm +6 -1
- data/bin/lib/Image/ExifTool/GeoTiff.pm +2 -0
- data/bin/lib/Image/ExifTool/Geotag.pm +2 -2
- data/bin/lib/Image/ExifTool/GoPro.pm +48 -22
- data/bin/lib/Image/ExifTool/H264.pm +1 -1
- data/bin/lib/Image/ExifTool/HtmlDump.pm +2 -2
- data/bin/lib/Image/ExifTool/ID3.pm +91 -12
- data/bin/lib/Image/ExifTool/IPTC.pm +1 -0
- data/bin/lib/Image/ExifTool/JSON.pm +27 -4
- data/bin/lib/Image/ExifTool/Lang/de.pm +3 -1
- data/bin/lib/Image/ExifTool/Lang/es.pm +1 -1
- data/bin/lib/Image/ExifTool/M2TS.pm +44 -24
- data/bin/lib/Image/ExifTool/MacOS.pm +152 -38
- data/bin/lib/Image/ExifTool/Matroska.pm +3 -1
- data/bin/lib/Image/ExifTool/Minolta.pm +7 -2
- data/bin/lib/Image/ExifTool/MinoltaRaw.pm +11 -10
- data/bin/lib/Image/ExifTool/Nikon.pm +163 -18
- data/bin/lib/Image/ExifTool/Olympus.pm +39 -17
- data/bin/lib/Image/ExifTool/PNG.pm +14 -3
- data/bin/lib/Image/ExifTool/PPM.pm +5 -5
- data/bin/lib/Image/ExifTool/Panasonic.pm +148 -14
- data/bin/lib/Image/ExifTool/PanasonicRaw.pm +34 -0
- data/bin/lib/Image/ExifTool/Parrot.pm +2 -1
- data/bin/lib/Image/ExifTool/Pentax.pm +3 -1
- data/bin/lib/Image/ExifTool/Photoshop.pm +2 -1
- data/bin/lib/Image/ExifTool/QuickTime.pm +294 -34
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +419 -60
- data/bin/lib/Image/ExifTool/README +26 -22
- data/bin/lib/Image/ExifTool/RIFF.pm +15 -3
- data/bin/lib/Image/ExifTool/RSRC.pm +17 -11
- data/bin/lib/Image/ExifTool/RTF.pm +12 -7
- data/bin/lib/Image/ExifTool/Radiance.pm +7 -2
- data/bin/lib/Image/ExifTool/Ricoh.pm +19 -1
- data/bin/lib/Image/ExifTool/Shift.pl +1 -0
- data/bin/lib/Image/ExifTool/SigmaRaw.pm +40 -33
- data/bin/lib/Image/ExifTool/Sony.pm +420 -34
- data/bin/lib/Image/ExifTool/TagLookup.pm +5799 -5671
- data/bin/lib/Image/ExifTool/TagNames.pod +583 -95
- data/bin/lib/Image/ExifTool/Validate.pm +4 -4
- data/bin/lib/Image/ExifTool/WriteExif.pl +3 -2
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +26 -18
- data/bin/lib/Image/ExifTool/Writer.pl +44 -21
- data/bin/lib/Image/ExifTool/XMP.pm +99 -17
- data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
- data/bin/lib/Image/ExifTool/ZISRAW.pm +123 -0
- data/bin/perl-Image-ExifTool.spec +42 -41
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +8 -7
@@ -10,6 +10,7 @@
|
|
10
10
|
# 3) https://forum.flitsservice.nl/dashcam-info/dod-ls460w-gps-data-uit-mov-bestand-lezen-t87926.html
|
11
11
|
# 4) https://developers.google.com/streetview/publish/camm-spec
|
12
12
|
# 5) https://sergei.nz/extracting-gps-data-from-viofo-a119-and-other-novatek-powered-cameras/
|
13
|
+
# 6) Thomas Allen https://github.com/exiftool/exiftool/pull/62
|
13
14
|
#------------------------------------------------------------------------------
|
14
15
|
package Image::ExifTool::QuickTime;
|
15
16
|
|
@@ -19,9 +20,11 @@ use Image::ExifTool qw(:DataAccess :Utils);
|
|
19
20
|
use Image::ExifTool::QuickTime;
|
20
21
|
|
21
22
|
sub Process_tx3g($$$);
|
23
|
+
sub Process_marl($$$);
|
22
24
|
sub Process_mebx($$$);
|
23
25
|
sub ProcessFreeGPS($$$);
|
24
26
|
sub ProcessFreeGPS2($$$);
|
27
|
+
sub Process360Fly($$$);
|
25
28
|
|
26
29
|
# QuickTime data types that have ExifTool equivalents
|
27
30
|
# (ref https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW35)
|
@@ -70,12 +73,14 @@ my %processByMetaFormat = (
|
|
70
73
|
meta => 1, # ('CTMD' in CR3 images, 'priv' unknown in DJI video)
|
71
74
|
data => 1, # ('RVMI')
|
72
75
|
sbtl => 1, # (subtitle; 'tx3g' in Yuneec drone videos)
|
76
|
+
ctbx => 1, # ('marl' in GM videos)
|
73
77
|
);
|
74
78
|
|
75
79
|
# data lengths for each INSV record type
|
76
80
|
my %insvDataLen = (
|
77
81
|
0x300 => 56, # accelerometer
|
78
|
-
0x400 => 16, #
|
82
|
+
0x400 => 16, # exposure (ref 6)
|
83
|
+
0x600 => 8, # timestamps (ref 6)
|
79
84
|
0x700 => 53, # GPS
|
80
85
|
);
|
81
86
|
|
@@ -91,7 +96,7 @@ my %insvLimit = (
|
|
91
96
|
NOTES => q{
|
92
97
|
Timed metadata extracted from QuickTime media data and some AVI videos when
|
93
98
|
the ExtractEmbedded option is used. Although most of these tags are
|
94
|
-
combined into the single table below, ExifTool currently reads
|
99
|
+
combined into the single table below, ExifTool currently reads 46 different
|
95
100
|
formats of timed GPS metadata from video files.
|
96
101
|
},
|
97
102
|
VARS => { NO_ID => 1 },
|
@@ -118,6 +123,7 @@ my %insvLimit = (
|
|
118
123
|
ExposureCompensation => { PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', Groups => { 2 => 'Camera' } },
|
119
124
|
ISO => { Groups => { 2 => 'Camera' } },
|
120
125
|
CameraDateTime=>{ PrintConv => '$self->ConvertDateTime($val)', Groups => { 2 => 'Time' } },
|
126
|
+
VideoTimeStamp => { Groups => { 2 => 'Video' } },
|
121
127
|
Accelerometer=> { Notes => '3-axis acceleration in units of g' },
|
122
128
|
AccelerometerData => { },
|
123
129
|
AngularVelocity => { },
|
@@ -159,10 +165,17 @@ my %insvLimit = (
|
|
159
165
|
ProcessProc => \&Process_mebx,
|
160
166
|
},
|
161
167
|
},
|
162
|
-
gpmd => {
|
163
|
-
Name => '
|
168
|
+
gpmd => [{
|
169
|
+
Name => 'gpmd_GoPro',
|
170
|
+
Condition => '$$valPt !~ /^\0\0\xf2\xe1\xf0\xeeTT/',
|
164
171
|
SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
|
165
|
-
},
|
172
|
+
},{
|
173
|
+
Name => 'gpmd_Rove', # Rove Stealth 4K encrypted text
|
174
|
+
SubDirectory => {
|
175
|
+
TagTable => 'Image::ExifTool::QuickTime::Stream',
|
176
|
+
ProcessProc => \&Process_text,
|
177
|
+
},
|
178
|
+
}],
|
166
179
|
fdsc => {
|
167
180
|
Name => 'fdsc',
|
168
181
|
Condition => '$$valPt =~ /^GPRO/',
|
@@ -173,6 +186,10 @@ my %insvLimit = (
|
|
173
186
|
Name => 'rtmd',
|
174
187
|
SubDirectory => { TagTable => 'Image::ExifTool::Sony::rtmd' },
|
175
188
|
},
|
189
|
+
marl => {
|
190
|
+
Name => 'marl',
|
191
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::marl' },
|
192
|
+
},
|
176
193
|
CTMD => { # (Canon Timed MetaData)
|
177
194
|
Name => 'CTMD',
|
178
195
|
SubDirectory => { TagTable => 'Image::ExifTool::Canon::CTMD' },
|
@@ -548,6 +565,137 @@ my %insvLimit = (
|
|
548
565
|
},
|
549
566
|
);
|
550
567
|
|
568
|
+
%Image::ExifTool::QuickTime::Tags360Fly = (
|
569
|
+
PROCESS_PROC => \&Process360Fly,
|
570
|
+
NOTES => 'Timed metadata found in MP4 videos from the 360Fly.',
|
571
|
+
1 => {
|
572
|
+
Name => 'Accel360Fly',
|
573
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Accel360Fly' },
|
574
|
+
},
|
575
|
+
2 => {
|
576
|
+
Name => 'Gyro360Fly',
|
577
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Gyro360Fly' },
|
578
|
+
},
|
579
|
+
3 => {
|
580
|
+
Name => 'Mag360Fly',
|
581
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Mag360Fly' },
|
582
|
+
},
|
583
|
+
5 => {
|
584
|
+
Name => 'GPS360Fly',
|
585
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GPS360Fly' },
|
586
|
+
},
|
587
|
+
6 => {
|
588
|
+
Name => 'Rot360Fly',
|
589
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Rot360Fly' },
|
590
|
+
},
|
591
|
+
250 => {
|
592
|
+
Name => 'Fusion360Fly',
|
593
|
+
SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Fusion360Fly' },
|
594
|
+
},
|
595
|
+
);
|
596
|
+
|
597
|
+
%Image::ExifTool::QuickTime::Accel360Fly = (
|
598
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
599
|
+
GROUPS => { 2 => 'Location' },
|
600
|
+
1 => { Name => 'AccelMode', Unknown => 1 }, # (always 2 in my sample)
|
601
|
+
2 => {
|
602
|
+
Name => 'SampleTime',
|
603
|
+
Groups => { 2 => 'Video' },
|
604
|
+
Format => 'int64u',
|
605
|
+
ValueConv => '$val / 1e6',
|
606
|
+
PrintConv => 'ConvertDuration($val)',
|
607
|
+
},
|
608
|
+
10 => { Name => 'AccelYPR', Format => 'float[3]' },
|
609
|
+
);
|
610
|
+
|
611
|
+
%Image::ExifTool::QuickTime::Gyro360Fly = (
|
612
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
613
|
+
GROUPS => { 2 => 'Location' },
|
614
|
+
1 => { Name => 'GyroMode', Unknown => 1 }, # (always 1 in my sample)
|
615
|
+
2 => {
|
616
|
+
Name => 'SampleTime',
|
617
|
+
Groups => { 2 => 'Video' },
|
618
|
+
Format => 'int64u',
|
619
|
+
ValueConv => '$val / 1e6',
|
620
|
+
PrintConv => 'ConvertDuration($val)',
|
621
|
+
},
|
622
|
+
10 => { Name => 'GyroYPR', Format => 'float[3]' },
|
623
|
+
);
|
624
|
+
|
625
|
+
%Image::ExifTool::QuickTime::Mag360Fly = (
|
626
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
627
|
+
GROUPS => { 2 => 'Location' },
|
628
|
+
1 => { Name => 'MagMode', Unknown => 1 }, # (always 1 in my sample)
|
629
|
+
2 => {
|
630
|
+
Name => 'SampleTime',
|
631
|
+
Groups => { 2 => 'Video' },
|
632
|
+
Format => 'int64u',
|
633
|
+
ValueConv => '$val / 1e6',
|
634
|
+
PrintConv => 'ConvertDuration($val)',
|
635
|
+
},
|
636
|
+
10 => { Name => 'MagnetometerXYZ', Format => 'float[3]' },
|
637
|
+
);
|
638
|
+
|
639
|
+
%Image::ExifTool::QuickTime::GPS360Fly = (
|
640
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
641
|
+
GROUPS => { 2 => 'Location' },
|
642
|
+
1 => { Name => 'GPSMode', Unknown => 1 }, # (always 16 in my sample)
|
643
|
+
2 => {
|
644
|
+
Name => 'SampleTime',
|
645
|
+
Groups => { 2 => 'Video' },
|
646
|
+
Format => 'int64u',
|
647
|
+
ValueConv => '$val / 1e6',
|
648
|
+
PrintConv => 'ConvertDuration($val)',
|
649
|
+
},
|
650
|
+
10 => { Name => 'GPSLatitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")' },
|
651
|
+
14 => { Name => 'GPSLongitude', Format => 'float', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
|
652
|
+
18 => { Name => 'GPSAltitude', Format => 'float', PrintConv => '"$val m"' }, # (questionable accuracy)
|
653
|
+
22 => {
|
654
|
+
Name => 'GPSSpeed',
|
655
|
+
Notes => 'converted to km/hr',
|
656
|
+
Format => 'int16u',
|
657
|
+
ValueConv => '$val * 0.036',
|
658
|
+
PrintConv => 'sprintf("%.1f",$val)',
|
659
|
+
},
|
660
|
+
24 => { Name => 'GPSTrack', Format => 'int16u', ValueConv => '$val / 100' },
|
661
|
+
26 => { Name => 'Acceleration', Format => 'int16u', ValueConv => '$val / 1000' },
|
662
|
+
);
|
663
|
+
|
664
|
+
%Image::ExifTool::QuickTime::Rot360Fly = (
|
665
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
666
|
+
GROUPS => { 2 => 'Location' },
|
667
|
+
1 => { Name => 'RotMode', Unknown => 1 }, # (always 1 in my sample)
|
668
|
+
2 => {
|
669
|
+
Name => 'SampleTime',
|
670
|
+
Groups => { 2 => 'Video' },
|
671
|
+
Format => 'int64u',
|
672
|
+
ValueConv => '$val / 1e6',
|
673
|
+
PrintConv => 'ConvertDuration($val)',
|
674
|
+
},
|
675
|
+
10 => { Name => 'RotationXYZ', Format => 'float[3]' },
|
676
|
+
);
|
677
|
+
|
678
|
+
%Image::ExifTool::QuickTime::Fusion360Fly = (
|
679
|
+
PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
|
680
|
+
GROUPS => { 2 => 'Location' },
|
681
|
+
1 => { Name => 'FusionMode', Unknown => 1 }, # (always 0 in my sample)
|
682
|
+
2 => {
|
683
|
+
Name => 'SampleTime',
|
684
|
+
Groups => { 2 => 'Video' },
|
685
|
+
Format => 'int64u',
|
686
|
+
ValueConv => '$val / 1e6',
|
687
|
+
PrintConv => 'ConvertDuration($val)',
|
688
|
+
},
|
689
|
+
10 => { Name => 'FusionYPR', Format => 'float[3]' },
|
690
|
+
);
|
691
|
+
|
692
|
+
# tags found in 'marl' ctbx timed metadata (ref PH)
|
693
|
+
%Image::ExifTool::QuickTime::marl = (
|
694
|
+
PROCESS_PROC => \&Process_marl,
|
695
|
+
GROUPS => { 2 => 'Other' },
|
696
|
+
NOTES => 'Tags extracted from the marl ctbx timed metadata of GM cars.',
|
697
|
+
);
|
698
|
+
|
551
699
|
#------------------------------------------------------------------------------
|
552
700
|
# Save information from keys in OtherSampleDesc directory for processing timed metadata
|
553
701
|
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
@@ -648,7 +796,7 @@ sub FoundSomething($$;$$)
|
|
648
796
|
|
649
797
|
#------------------------------------------------------------------------------
|
650
798
|
# Approximate GPSDateTime value from sample time and CreateDate
|
651
|
-
# Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (
|
799
|
+
# Inputs: 0) ExifTool ref, 1) tag table ptr, 2) sample time (s)
|
652
800
|
# 3) true if CreateDate is at end of video
|
653
801
|
# Notes: Uses ExifTool CreateDateAtEnd as flag to subtract video duration
|
654
802
|
sub SetGPSDateTime($$$)
|
@@ -696,15 +844,21 @@ sub HandleTextTags($$$)
|
|
696
844
|
|
697
845
|
#------------------------------------------------------------------------------
|
698
846
|
# Process subtitle 'text'
|
699
|
-
# Inputs: 0) ExifTool ref, 1)
|
847
|
+
# Inputs: 0) ExifTool ref, 1) data ref or dirInfo ref, 2) tag table ref
|
700
848
|
sub Process_text($$$)
|
701
849
|
{
|
702
|
-
my ($et, $
|
850
|
+
my ($et, $dataPt, $tagTbl) = @_;
|
703
851
|
my %tags;
|
704
852
|
|
705
853
|
return if $$et{NoMoreTextDecoding};
|
706
854
|
|
707
|
-
|
855
|
+
if (ref $dataPt eq 'HASH') {
|
856
|
+
my $dirName = $$dataPt{DirName};
|
857
|
+
$dataPt = $$dataPt{DataPt};
|
858
|
+
$et->VerboseDir($dirName, undef, length($$dataPt));
|
859
|
+
}
|
860
|
+
|
861
|
+
while ($$dataPt =~ /\$(\w+)([^\$]*)/g) {
|
708
862
|
my ($tag, $dat) = ($1, $2);
|
709
863
|
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+)/) {
|
710
864
|
my $time = "$1:$2:$3";
|
@@ -795,31 +949,31 @@ sub Process_text($$$)
|
|
795
949
|
# 0110: 31 30 38 30 30 30 58 00 58 00 58 00 58 00 58 00 [108000X.X.X.X.X.]
|
796
950
|
# 0120: 58 00 58 00 58 00 58 00 00 00 00 00 00 00 00 00 [X.X.X.X.........]
|
797
951
|
# 0130: 00 00 00 00 00 00 00 [.......]
|
798
|
-
if ($$
|
799
|
-
my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
952
|
+
if ($$dataPt =~ /^\0\0(..\xaa\xaa|\xf2\xe1\xf0\xee)/s and length $$dataPt >= 282) {
|
953
|
+
my $val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 8, 14)));
|
800
954
|
if ($val =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/) {
|
801
955
|
$tags{GPSDateTime} = "$1:$2:$3 $4:$5:$6";
|
802
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
956
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 38, 9)));
|
803
957
|
if ($val =~ /^([NS])(\d{2})(\d+$)$/) {
|
804
958
|
$tags{GPSLatitude} = ($2 + $3 / 600000) * ($1 eq 'S' ? -1 : 1);
|
805
959
|
}
|
806
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
960
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 47, 10)));
|
807
961
|
if ($val =~ /^([EW])(\d{3})(\d+$)$/) {
|
808
962
|
$tags{GPSLongitude} = ($2 + $3 / 600000) * ($1 eq 'W' ? -1 : 1);
|
809
963
|
}
|
810
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
964
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x39, 5)));
|
811
965
|
$tags{GPSAltitude} = $val + 0 if $val =~ /^[-+]\d+$/;
|
812
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
966
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x3e, 3)));
|
813
967
|
if ($val =~ /^\d+$/) {
|
814
968
|
$tags{GPSSpeed} = $val + 0;
|
815
969
|
$tags{GPSSpeedRef} = 'K';
|
816
970
|
}
|
817
|
-
if ($$
|
818
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
971
|
+
if ($$dataPt =~ /^\0\0..\xaa\xaa/s) { # (BlueSkySea)
|
972
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xad, 12)));
|
819
973
|
# the first X,Y,Z accelerometer readings from the AccelerometerData
|
820
974
|
if ($val =~ /^([-+]\d{3})([-+]\d{3})([-+]\d{3})$/) {
|
821
975
|
$tags{Accelerometer} = "$1 $2 $3";
|
822
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
976
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0xba, 96)));
|
823
977
|
my $order = GetByteOrder();
|
824
978
|
SetByteOrder('II');
|
825
979
|
$val = ReadValue(\$val, 0, 'float');
|
@@ -828,7 +982,7 @@ sub Process_text($$$)
|
|
828
982
|
}
|
829
983
|
} else { # (Ambarella)
|
830
984
|
my @acc;
|
831
|
-
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$
|
985
|
+
$val = pack('C*', map { $_ ^ 0xaa } unpack('C*', substr($$dataPt, 0x41, 195)));
|
832
986
|
push @acc, $1, $2, $3 while $val =~ /\G([-+]\d{3})([-+]\d{3})([-+]\d{3})/g;
|
833
987
|
$tags{Accelerometer} = "@acc" if @acc;
|
834
988
|
}
|
@@ -839,36 +993,36 @@ sub Process_text($$$)
|
|
839
993
|
# check for DJI telemetry data, eg:
|
840
994
|
# "F/3.5, SS 1000, ISO 100, EV 0, GPS (8.6499, 53.1665, 18), D 24.26m,
|
841
995
|
# H 6.00m, H.S 2.10m/s, V.S 0.00m/s \n"
|
842
|
-
if ($$
|
996
|
+
if ($$dataPt =~ /GPS \(([-+]?\d*\.\d+),\s*([-+]?\d*\.\d+)/) {
|
843
997
|
$$et{CreateDateAtEnd} = 1; # set flag indicating the file creation date is at the end
|
844
998
|
$tags{GPSLatitude} = $2;
|
845
999
|
$tags{GPSLongitude} = $1;
|
846
|
-
$tags{GPSAltitude} = $1 if $$
|
847
|
-
if ($$
|
1000
|
+
$tags{GPSAltitude} = $1 if $$dataPt =~ /,\s*H\s+([-+]?\d+\.?\d*)m/;
|
1001
|
+
if ($$dataPt =~ /,\s*H.S\s+([-+]?\d+\.?\d*)/) {
|
848
1002
|
$tags{GPSSpeed} = $1 * $mpsToKph;
|
849
1003
|
$tags{GPSSpeedRef} = 'K';
|
850
1004
|
}
|
851
|
-
$tags{Distance} = $1 * $mpsToKph if $$
|
852
|
-
$tags{VerticalSpeed} = $1 if $$
|
853
|
-
$tags{FNumber} = $1 if $$
|
854
|
-
$tags{ExposureTime} = 1 / $1 if $$
|
855
|
-
$tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$
|
856
|
-
$tags{ISO} = $1 if $$
|
1005
|
+
$tags{Distance} = $1 * $mpsToKph if $$dataPt =~ /,\s*D\s+(\d+\.?\d*)m/;
|
1006
|
+
$tags{VerticalSpeed} = $1 if $$dataPt =~ /,\s*V.S\s+([-+]?\d+\.?\d*)/;
|
1007
|
+
$tags{FNumber} = $1 if $$dataPt =~ /\bF\/(\d+\.?\d*)/;
|
1008
|
+
$tags{ExposureTime} = 1 / $1 if $$dataPt =~ /\bSS\s+(\d+\.?\d*)/;
|
1009
|
+
$tags{ExposureCompensation} = ($1 / ($2 || 1)) if $$dataPt =~ /\bEV\s+([-+]?\d+\.?\d*)(\/\d+)?/;
|
1010
|
+
$tags{ISO} = $1 if $$dataPt =~ /\bISO\s+(\d+\.?\d*)/;
|
857
1011
|
HandleTextTags($et, $tagTbl, \%tags);
|
858
1012
|
return;
|
859
1013
|
}
|
860
1014
|
|
861
1015
|
# check for Mini 0806 dashcam GPS, eg:
|
862
1016
|
# "A,270519,201555.000,3356.8925,N,08420.2071,W,000.0,331.0M,+01.84,-09.80,-00.61;\n"
|
863
|
-
if ($$
|
1017
|
+
if ($$dataPt =~ /^A,(\d{2})(\d{2})(\d{2}),(\d{2})(\d{2})(\d{2}(\.\d+)?)/) {
|
864
1018
|
$tags{GPSDateTime} = "20$3:$2:$1 $4:$5:$6Z";
|
865
|
-
if ($$
|
1019
|
+
if ($$dataPt =~ /^A,.*?,.*?,(\d{2})(\d+\.\d+),([NS])/) {
|
866
1020
|
$tags{GPSLatitude} = ($1 + $2/60) * ($3 eq 'S' ? -1 : 1);
|
867
1021
|
}
|
868
|
-
if ($$
|
1022
|
+
if ($$dataPt =~ /^A,.*?,.*?,.*?,.*?,(\d{3})(\d+\.\d+),([EW])/) {
|
869
1023
|
$tags{GPSLongitude} = ($1 + $2/60) * ($3 eq 'W' ? -1 : 1);
|
870
1024
|
}
|
871
|
-
my @a = split ',', $$
|
1025
|
+
my @a = split ',', $$dataPt;
|
872
1026
|
$tags{GPSAltitude} = $a[8] if $a[8] and $a[8] =~ s/M$//;
|
873
1027
|
$tags{GPSSpeed} = $a[7] if $a[7] and $a[7] =~ /^\d+\.\d+$/; # (NC)
|
874
1028
|
$tags{Accelerometer} = "$a[9] $a[10] $a[11]" if $a[11] and $a[11] =~ s/;\s*$//;
|
@@ -876,10 +1030,31 @@ sub Process_text($$$)
|
|
876
1030
|
return;
|
877
1031
|
}
|
878
1032
|
|
879
|
-
# check for
|
1033
|
+
# check for Roadhawk dashcam text
|
1034
|
+
# ".;;;;D?JL;6+;;;D;R?;4;;;;DBB;;O;;;=D;L;;HO71G>F;-?=J-F:FNJJ;DPP-JF3F;;PL=DBRLBF0F;=?DNF-RD-PF;N;?=JF;;?D=F:*6F~"
|
1035
|
+
# decoded:
|
1036
|
+
# "X0000.2340Y-000.0720Z0000.9900G0001.0400$GPRMC,082138,A,5330.6683,N,00641.9749,W,012.5,87.86,050213,002.1,A"
|
1037
|
+
# (note: "002.1" is magnetic variation and is not decoded; it should have ",E" or ",W" afterward for direction)
|
1038
|
+
if ($$dataPt =~ /\*[0-9A-F]{2}~$/) {
|
1039
|
+
# (ref https://reverseengineering.stackexchange.com/questions/11582/how-to-reverse-engineer-dash-cam-metadata)
|
1040
|
+
my @decode = unpack 'C*', '-I8XQWRVNZOYPUTA0B1C2SJ9K.L,M$D3E4F5G6H7';
|
1041
|
+
my @chars = unpack 'C*', substr($$dataPt, 0, -4);
|
1042
|
+
foreach (@chars) {
|
1043
|
+
my $n = $_ - 43;
|
1044
|
+
$_ = $decode[$n] if $n >= 0 and defined $decode[$n];
|
1045
|
+
}
|
1046
|
+
my $buff = pack 'C*', @chars;
|
1047
|
+
if ($buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/) {
|
1048
|
+
# yup. the decoding worked out
|
1049
|
+
$tags{Accelerometer} = "$1 $2 $3 $4";
|
1050
|
+
$$dataPt = $buff; # (process GPRMC below)
|
1051
|
+
}
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
# check for Thinkware format (and other NMEA RMC), eg:
|
880
1055
|
# "gsensori,4,512,-67,-12,100;GNRMC,161313.00,A,4529.87489,N,07337.01215,W,6.225,35.34,310819,,,A*52..;
|
881
1056
|
# CAR,0,0,0,0.0,0,0,0,0,0,0,0,0"
|
882
|
-
if ($$
|
1057
|
+
if ($$dataPt =~ /[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
|
883
1058
|
# do some basic sanity checks on the date
|
884
1059
|
$13 <= 31 and $14 <= 12 and $15 <= 99)
|
885
1060
|
{
|
@@ -896,8 +1071,8 @@ sub Process_text($$$)
|
|
896
1071
|
$tags{GPSTrackRef} = 'T';
|
897
1072
|
}
|
898
1073
|
}
|
899
|
-
$tags{GSensor} = $1 if $$
|
900
|
-
$tags{Car} = $1 if $$
|
1074
|
+
$tags{GSensor} = $1 if $$dataPt =~ /\bgsensori,(.*?)(;|$)/;
|
1075
|
+
$tags{Car} = $1 if $$dataPt =~ /\bCAR,(.*?)(;|$)/;
|
901
1076
|
|
902
1077
|
if (%tags) {
|
903
1078
|
HandleTextTags($et, $tagTbl, \%tags);
|
@@ -953,7 +1128,7 @@ sub ProcessSamples($)
|
|
953
1128
|
($startChunk, $samplesPerChunk, $descIdx) = @{shift @$stsc};
|
954
1129
|
$nextChunk = $$stsc[0][0] if @$stsc;
|
955
1130
|
}
|
956
|
-
@$size < @$start + $samplesPerChunk and $et->WarnOnce('Sample size error'),
|
1131
|
+
@$size < @$start + $samplesPerChunk and $et->WarnOnce('Sample size error'), last;
|
957
1132
|
my $sampleStart = $chunkStart;
|
958
1133
|
for ($i=0; ; ) {
|
959
1134
|
push @$start, $sampleStart;
|
@@ -1056,11 +1231,13 @@ sub ProcessSamples($)
|
|
1056
1231
|
$val =~ tr/\t/ /;
|
1057
1232
|
$et->HandleTag($tagTbl, RawGSensor => $val) if length $val;
|
1058
1233
|
}
|
1059
|
-
} elsif ($buff =~ /^PNDM/
|
1234
|
+
} elsif ($buff =~ /^(\0.{3})?PNDM/s) {
|
1060
1235
|
# Garmin Dashcam format (actually binary, not text)
|
1061
|
-
$
|
1062
|
-
|
1063
|
-
$et->HandleTag($tagTbl,
|
1236
|
+
my $n = $1 ? 4 : 0; # skip leading 4-byte size word if it exists
|
1237
|
+
next if length($buff) < 20 + $n;
|
1238
|
+
$et->HandleTag($tagTbl, GPSLatitude => Get32s(\$buff, 12+$n) * 180/0x80000000);
|
1239
|
+
$et->HandleTag($tagTbl, GPSLongitude => Get32s(\$buff, 16+$n) * 180/0x80000000);
|
1240
|
+
$et->HandleTag($tagTbl, GPSSpeed => Get16u(\$buff, 8+$n));
|
1064
1241
|
$et->HandleTag($tagTbl, GPSSpeedRef => 'M');
|
1065
1242
|
SetGPSDateTime($et, $tagTbl, $time[$i]);
|
1066
1243
|
next; # all done (don't store/process as text)
|
@@ -1069,7 +1246,7 @@ sub ProcessSamples($)
|
|
1069
1246
|
$et->HandleTag($tagTbl, Text => $buff); # just store any other text
|
1070
1247
|
}
|
1071
1248
|
}
|
1072
|
-
Process_text($et, $tagTbl
|
1249
|
+
Process_text($et, \$buff, $tagTbl);
|
1073
1250
|
|
1074
1251
|
} elsif ($processByMetaFormat{$type}) {
|
1075
1252
|
|
@@ -1080,8 +1257,8 @@ sub ProcessSamples($)
|
|
1080
1257
|
$$et{ee} = $ee; # need ee information for 'keys'
|
1081
1258
|
$et->HandleTag($tagTbl, $metaFormat, undef,
|
1082
1259
|
DataPt => \$buff,
|
1083
|
-
DataPos =>
|
1084
|
-
Base => $$start[$i],
|
1260
|
+
DataPos => 0,
|
1261
|
+
Base => $$start[$i], # (Base must be set for CR3 files)
|
1085
1262
|
TagInfo => $tagInfo,
|
1086
1263
|
);
|
1087
1264
|
delete $$et{ee};
|
@@ -1090,10 +1267,10 @@ sub ProcessSamples($)
|
|
1090
1267
|
# "X0000.0000Y0000.0000Z0000.0000G0000.0000$GPRMC,000125,V,,,,,000.0,,280908,002.1,N*71~, 794021 \x0a"
|
1091
1268
|
FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
|
1092
1269
|
$et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3 $4") if $buff =~ /X(.*?)Y(.*?)Z(.*?)G(.*?)\$/;
|
1093
|
-
Process_text($et, $tagTbl
|
1270
|
+
Process_text($et, \$buff, $tagTbl);
|
1094
1271
|
}
|
1095
1272
|
} elsif ($verbose) {
|
1096
|
-
$et->VPrint(0, "Unknown
|
1273
|
+
$et->VPrint(0, "Unknown $type format ($metaFormat)");
|
1097
1274
|
}
|
1098
1275
|
|
1099
1276
|
} elsif ($type eq 'gps ') { # (ie. GPSDataList tag)
|
@@ -1115,8 +1292,8 @@ sub ProcessSamples($)
|
|
1115
1292
|
FoundSomething($et, $tagTbl, $time[$i], $dur[$i]);
|
1116
1293
|
$et->HandleTag($tagTbl, $type, undef,
|
1117
1294
|
DataPt => \$buff,
|
1118
|
-
DataPos =>
|
1119
|
-
Base => $$start[$i],
|
1295
|
+
DataPos => 0,
|
1296
|
+
Base => $$start[$i], # (Base must be set for CR3 files)
|
1120
1297
|
TagInfo => $tagInfo,
|
1121
1298
|
);
|
1122
1299
|
}
|
@@ -1150,13 +1327,35 @@ sub ProcessFreeGPS($$$)
|
|
1150
1327
|
|
1151
1328
|
return 0 if $dirLen < 92;
|
1152
1329
|
|
1153
|
-
if (substr($$dataPt,
|
1330
|
+
if (substr($$dataPt,18,8) eq "\xaa\xaa\xf2\xe1\xf0\xee\x54\x54") {
|
1154
1331
|
|
1332
|
+
# (this is very similar to the encrypted text format)
|
1155
1333
|
# decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
|
1156
1334
|
# header looks like this in my sample:
|
1157
1335
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 05 01 00 00 [....freeGPS ....]
|
1158
1336
|
# 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 9b 92 9a 93 [........TT......]
|
1159
1337
|
# 0020: 98 9e 98 98 9e 93 98 92 a6 9f 9f 9c 9d ed fa 8a [................]
|
1338
|
+
# decrypted (from byte 18):
|
1339
|
+
# 0000: 00 00 58 4b 5a 44 fe fe 32 30 31 38 30 39 32 34 [..XKZD..20180924]
|
1340
|
+
# 0010: 32 32 34 39 32 38 0c 35 35 36 37 47 50 20 20 20 [224928.5567GP ]
|
1341
|
+
# 0020: 00 00 00 00 00 03 4e 34 30 34 36 34 33 35 30 57 [......N40464350W]
|
1342
|
+
# 0030: 30 30 37 30 34 30 33 30 38 30 30 30 30 30 30 30 [0070403080000000]
|
1343
|
+
# 0040: 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [7...............]
|
1344
|
+
# [...]
|
1345
|
+
# 00a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 2b 30 39 [.............+09]
|
1346
|
+
# 00b0: 33 2d 30 30 33 2d 30 30 35 00 00 00 00 00 00 00 [3-003-005.......]
|
1347
|
+
# header looks like this for EEEkit gps:
|
1348
|
+
# 0000: 00 00 04 00 66 72 65 65 47 50 53 20 f0 03 00 00 [....freeGPS ....]
|
1349
|
+
# 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 98 9a 9a 9f [........TT......]
|
1350
|
+
# 0020: 9b 93 9b 9c 98 99 99 9f a6 9a 9a 98 9a 9a 9f 9b [................]
|
1351
|
+
# 0030: 93 9b 9c 98 99 99 9c a9 e4 99 9d 9e 9f 98 9e 9b [................]
|
1352
|
+
# 0040: 9c fd 9b 98 98 98 9f 9f 9a 9a 93 81 9a 9b 9d 9f [................]
|
1353
|
+
# decrypted (from byte 18):
|
1354
|
+
# 0000: 00 00 58 4b 5a 44 fe fe 32 30 32 30 30 35 31 39 [..XKZD..20200519]
|
1355
|
+
# 0010: 31 36 32 33 33 35 0c 30 30 32 30 30 35 31 39 31 [162335.002005191]
|
1356
|
+
# 0020: 36 32 33 33 36 03 4e 33 37 34 35 32 34 31 36 57 [62336.N37452416W]
|
1357
|
+
# 0030: 31 32 32 32 35 35 30 30 39 2b 30 31 37 35 30 31 [122255009+017501]
|
1358
|
+
# 0040: 31 2b 30 31 34 2b 30 30 32 2b 30 32 36 2b 30 31 [1+014+002+026+01]
|
1160
1359
|
my $n = $dirLen - 18;
|
1161
1360
|
$n = 0x101 if $n > 0x101;
|
1162
1361
|
my $buf2 = pack 'C*', map { $_ ^ 0xaa } unpack 'C*', substr($$dataPt,18,$n);
|
@@ -1165,13 +1364,25 @@ sub ProcessFreeGPS($$$)
|
|
1165
1364
|
$et->VerboseDump(\$buf2);
|
1166
1365
|
}
|
1167
1366
|
# (extract longitude as 9 digits, not 8, ref PH)
|
1168
|
-
return 0 unless $buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})([NS])(\d{8})([EW])(\d{9})(\d{8})
|
1367
|
+
return 0 unless $buf2 =~ /^.{8}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).(.{15})([NS])(\d{8})([EW])(\d{9})(\d{8})?/s;
|
1169
1368
|
($yr,$mon,$day,$hr,$min,$sec,$lbl,$latRef,$lat,$lonRef,$lon,$spd) = ($1,$2,$3,$4,$5,$6,$7,$8,$9/1e4,$10,$11/1e4,$12);
|
1170
|
-
$spd
|
1369
|
+
if (defined $spd) { # (Azdome)
|
1370
|
+
$spd += 0; # remove leading 0's
|
1371
|
+
} elsif ($buf2 =~ /^.{57}([-+]\d{4})(\d{3})/s) { # (EEEkit)
|
1372
|
+
# $alt = $1 + 0; (doesn't look right for my sample, but the Ambarella A12 text has this)
|
1373
|
+
$spd = $2 + 0;
|
1374
|
+
}
|
1171
1375
|
$lbl =~ s/\0.*//s; $lbl =~ s/\s+$//; # truncate at null and remove trailing spaces
|
1172
1376
|
push @xtra, UserLabel => $lbl if length $lbl;
|
1173
1377
|
# extract accelerometer data (ref PH)
|
1174
|
-
|
1378
|
+
if ($buf2 =~ /^.{65}(([-+]\d{3})([-+]\d{3})([-+]\d{3})([-+]\d{3})*)/s) {
|
1379
|
+
$_ = $1;
|
1380
|
+
@acc = ($2/100, $3/100, $4/100);
|
1381
|
+
s/([-+])/ $1/g; s/^ //;
|
1382
|
+
push @xtra, AccelerometerData => $_;
|
1383
|
+
} elsif ($buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s) { # (Azdome)
|
1384
|
+
@acc = ($1/100, $2/100, $3/100);
|
1385
|
+
}
|
1175
1386
|
|
1176
1387
|
} elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/s) {
|
1177
1388
|
|
@@ -1222,12 +1433,12 @@ sub ProcessFreeGPS($$$)
|
|
1222
1433
|
|
1223
1434
|
# decode freeGPS from Akaso dashcam
|
1224
1435
|
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 60 00 00 00 [....freeGPS `...]
|
1225
|
-
#
|
1226
|
-
#
|
1227
|
-
#
|
1228
|
-
#
|
1229
|
-
#
|
1230
|
-
#
|
1436
|
+
# 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
|
1437
|
+
# 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
|
1438
|
+
# 0030: 12 00 00 00 2f 00 00 00 19 00 00 00 41 00 00 00 [..../.......A...]
|
1439
|
+
# 0040: 13 b3 ca 44 4e 00 00 00 29 92 fb 45 45 00 00 00 [...DN...)..EE...]
|
1440
|
+
# 0050: d9 ee b4 41 ec d1 d3 42 e4 07 00 00 01 00 00 00 [...A...B........]
|
1441
|
+
# 0060: 0c 00 00 00 01 00 00 00 05 00 00 00 00 00 00 00 [................]
|
1231
1442
|
($latRef, $lonRef) = ($1, $2);
|
1232
1443
|
($hr, $min, $sec, $yr, $mon, $day) = unpack('x48V3x28V3', $$dataPt);
|
1233
1444
|
SetByteOrder('II');
|
@@ -1238,6 +1449,56 @@ sub ProcessFreeGPS($$$)
|
|
1238
1449
|
$trk -= 360 if $trk >= 360;
|
1239
1450
|
SetByteOrder('MM');
|
1240
1451
|
|
1452
|
+
} elsif ($$dataPt =~ /^.{16}YndAkasoCar/s) {
|
1453
|
+
|
1454
|
+
# Akaso V1 dascham
|
1455
|
+
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 00 00 00 [....freeGPS x...]
|
1456
|
+
# 0010: 59 6e 64 41 6b 61 73 6f 43 61 72 00 00 00 00 00 [YndAkasoCar.....]
|
1457
|
+
# 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
|
1458
|
+
# 0030: 0e 00 00 00 27 00 00 00 2c 00 00 00 e3 07 00 00 [....'...,.......]
|
1459
|
+
# 0040: 05 00 00 00 1d 00 00 00 41 4e 45 00 00 00 00 00 [........ANE.....]
|
1460
|
+
# 0050: f1 4e 3e 3d 90 df ca 40 e3 50 bf 0b 0b 31 a0 40 [.N>=...@.P...1.@]
|
1461
|
+
# 0060: 4b dc c8 41 9a 79 a7 43 34 58 43 31 4f 37 31 35 [K..A.y.C4XC1O715]
|
1462
|
+
# 0070: 35 31 32 36 36 35 37 35 59 4e 44 53 0d e7 cc f9 [51266575YNDS....]
|
1463
|
+
# 0080: 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 [................]
|
1464
|
+
($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef) =
|
1465
|
+
unpack('x48V6a1a1a1x1', $$dataPt);
|
1466
|
+
# ignore invalid fixes
|
1467
|
+
return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
|
1468
|
+
($lonRef eq 'E' or $lonRef eq 'W');
|
1469
|
+
|
1470
|
+
$et->WarnOnce("Can't yet decrypt Akaso V1 timed GPS", 1);
|
1471
|
+
# (see https://exiftool.org/forum/index.php?topic=11320.0)
|
1472
|
+
return 1;
|
1473
|
+
|
1474
|
+
SetByteOrder('II');
|
1475
|
+
$lat = GetDouble($dataPt, 0x50); # latitude is here, but encrypted somehow
|
1476
|
+
$lon = GetDouble($dataPt, 0x58); # longitude is here, but encrypted somehow
|
1477
|
+
SetByteOrder('MM');
|
1478
|
+
#my $serialNum = substr($$dataPt, 0x68, 20);
|
1479
|
+
|
1480
|
+
} elsif ($$dataPt =~ /^.{12}\xac\0\0\0.{44}(.{72})/s) {
|
1481
|
+
|
1482
|
+
# EACHPAI dash cam
|
1483
|
+
# 0000: 00 00 80 00 66 72 65 65 47 50 53 20 ac 00 00 00 [....freeGPS ....]
|
1484
|
+
# 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
1485
|
+
# 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
|
1486
|
+
# 0030: 00 00 00 00 00 00 00 00 00 00 00 00 34 57 60 62 [............4W`b]
|
1487
|
+
# 0040: 5d 53 3c 41 47 45 45 42 42 3e 40 40 40 3c 51 3c []S<AGEEBB>@@@<Q<]
|
1488
|
+
# 0050: 44 42 44 40 3e 48 46 43 45 3c 5e 3c 40 48 43 41 [DBD@>HFCE<^<@HCA]
|
1489
|
+
# 0060: 42 3e 46 42 47 48 3c 67 3c 40 3e 40 42 3c 43 3e [B>FBGH<g<@>@B<C>]
|
1490
|
+
# 0070: 43 41 3c 40 42 40 46 42 40 3c 3c 3c 51 3a 47 46 [CA<@B@FB@<<<Q:GF]
|
1491
|
+
# 0080: 00 2a 36 35 00 00 00 00 00 00 00 00 00 00 00 00 [.*65............]
|
1492
|
+
|
1493
|
+
$et->WarnOnce("Can't yet decrypt EACHPAI timed GPS", 1);
|
1494
|
+
# (see https://exiftool.org/forum/index.php?topic=5095.msg61266#msg61266)
|
1495
|
+
return 1;
|
1496
|
+
|
1497
|
+
my $time = pack 'C*', map { $_ ^= 0 } unpack 'C*', $1;
|
1498
|
+
# bytes 7-12 are the timestamp in ASCII HHMMSS after xor-ing with 0x70
|
1499
|
+
substr($time,7,6) = pack 'C*', map { $_ ^= 0x70 } unpack 'C*', substr($time,7,6);
|
1500
|
+
# (other values are currently unknown)
|
1501
|
+
|
1241
1502
|
} else {
|
1242
1503
|
|
1243
1504
|
# decode binary GPS format (Viofo A119S, ref 2)
|
@@ -1252,6 +1513,21 @@ sub ProcessFreeGPS($$$)
|
|
1252
1513
|
return 0 unless $stat eq 'A' and ($latRef eq 'N' or $latRef eq 'S') and
|
1253
1514
|
($lonRef eq 'E' or $lonRef eq 'W');
|
1254
1515
|
($lat,$lon,$spd,$trk) = unpack 'f*', pack 'L*', $lat, $lon, $spd, $trk;
|
1516
|
+
# lat/lon also stored as doubles by Transcend Driver Pro 230 (ref PH)
|
1517
|
+
SetByteOrder('II');
|
1518
|
+
my ($lat2, $lon2, $alt2) = (
|
1519
|
+
GetDouble($dataPt, 0x70),
|
1520
|
+
GetDouble($dataPt, 0x80),
|
1521
|
+
# GetDouble($dataPt, 0x98), # (don't know what this is)
|
1522
|
+
GetDouble($dataPt,0xa0),
|
1523
|
+
# GetDouble($dataPt,0xa8)) # (don't know what this is)
|
1524
|
+
);
|
1525
|
+
if (abs($lat2-$lat) < 0.001 and abs($lon2-$lon) < 0.001) {
|
1526
|
+
$lat = $lat2;
|
1527
|
+
$lon = $lon2;
|
1528
|
+
$alt = $alt2;
|
1529
|
+
}
|
1530
|
+
SetByteOrder('MM');
|
1255
1531
|
$yr += 2000 if $yr < 2000;
|
1256
1532
|
$spd *= $knotsToKph; # convert speed to km/h
|
1257
1533
|
# ($trk is not confirmed; may be GPSImageDirection, ref PH)
|
@@ -1644,6 +1920,33 @@ sub ParseTag($$$)
|
|
1644
1920
|
}
|
1645
1921
|
$$et{HandlerType} = $tag; # fake handler type
|
1646
1922
|
ProcessSamples($et); # we have all we need to process sample data now
|
1923
|
+
} elsif ($tag eq 'GPS ') {
|
1924
|
+
my $pos = 0;
|
1925
|
+
my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
1926
|
+
SetByteOrder('II');
|
1927
|
+
while ($pos + 36 < $dataLen) {
|
1928
|
+
my $dat = substr($$dataPt, $pos, 36);
|
1929
|
+
last if $dat eq "\x0" x 36;
|
1930
|
+
my @a = unpack 'VVVVCVCV', $dat;
|
1931
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
1932
|
+
# 0=1, 1=1, 2=secs, 3=?
|
1933
|
+
SetGPSDateTime($et, $tagTbl, $a[2]);
|
1934
|
+
my $lat = $a[5] / 1e3;
|
1935
|
+
my $lon = $a[7] / 1e3;
|
1936
|
+
my $deg = int($lat / 100);
|
1937
|
+
$lat = $deg + ($lat - $deg * 100) / 60;
|
1938
|
+
$deg = int($lon / 100);
|
1939
|
+
$lon = $deg + ($lon - $deg * 100) / 60;
|
1940
|
+
$lat = -$lat if $a[4] eq 'S';
|
1941
|
+
$lon = -$lon if $a[6] eq 'W';
|
1942
|
+
$et->HandleTag($tagTbl, GPSLatitude => $lat);
|
1943
|
+
$et->HandleTag($tagTbl, GPSLongitude => $lon);
|
1944
|
+
$et->HandleTag($tagTbl, GPSSpeed => $a[3] / 1e3);
|
1945
|
+
$et->HandleTag($tagTbl, GPSSpeedRef => 'K');
|
1946
|
+
$pos += 36;
|
1947
|
+
}
|
1948
|
+
SetByteOrder('MM');
|
1949
|
+
delete $$et{DOC_NUM};
|
1647
1950
|
}
|
1648
1951
|
}
|
1649
1952
|
|
@@ -1661,6 +1964,26 @@ sub Process_tx3g($$$)
|
|
1661
1964
|
return 1;
|
1662
1965
|
}
|
1663
1966
|
|
1967
|
+
#------------------------------------------------------------------------------
|
1968
|
+
# Process GM 'marl' ctbx metadata (ref PH)
|
1969
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
1970
|
+
# Returns: 1 on success
|
1971
|
+
sub Process_marl($$$)
|
1972
|
+
{
|
1973
|
+
my ($et, $dirInfo, $tagTablePtr) = @_;
|
1974
|
+
my $dataPt = $$dirInfo{DataPt};
|
1975
|
+
return 0 if length $$dataPt < 2;
|
1976
|
+
|
1977
|
+
# 8-byte records:
|
1978
|
+
# byte 0 seems to be tag ID (0=timestamp in sec * 1e7)
|
1979
|
+
# bytes 1-3 seem to be 24-bit signed integer (unknown meaning)
|
1980
|
+
# bytes 4-7 are an int32u value, usually a multiple of 10000
|
1981
|
+
|
1982
|
+
$et->WarnOnce("Can't yet decode timed GM data", 1);
|
1983
|
+
# (see https://exiftool.org/forum/index.php?topic=11335.msg61393#msg61393)
|
1984
|
+
return 1;
|
1985
|
+
}
|
1986
|
+
|
1664
1987
|
#------------------------------------------------------------------------------
|
1665
1988
|
# Process QuickTime 'mebx' timed metadata
|
1666
1989
|
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
@@ -2139,11 +2462,16 @@ sub ProcessInsta360($;$)
|
|
2139
2462
|
$et->HandleTag($tagTbl, Accelerometer => "@a[0..2]"); # (NC)
|
2140
2463
|
$et->HandleTag($tagTbl, AngularVelocity => "@a[3..5]"); # (NC)
|
2141
2464
|
}
|
2142
|
-
} elsif ($id == 0x400
|
2465
|
+
} elsif ($id == 0x400) {
|
2143
2466
|
for ($p=0; $p<$len; $p+=$dlen) {
|
2144
2467
|
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
2145
2468
|
$et->HandleTag($tagTbl, TimeCode => sprintf('%.3f', Get64u(\$buff, $p) / 1000));
|
2146
|
-
$et->HandleTag($tagTbl,
|
2469
|
+
$et->HandleTag($tagTbl, ExposureTime => GetDouble(\$buff, $p + 8)); #6
|
2470
|
+
}
|
2471
|
+
} elsif ($id == 0x600) { #6
|
2472
|
+
for ($p=0; $p<$len; $p+=$dlen) {
|
2473
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
2474
|
+
$et->HandleTag($tagTbl, VideoTimeStamp => sprintf('%.3f', Get64u(\$buff, $p) / 1000));
|
2147
2475
|
}
|
2148
2476
|
} elsif ($id == 0x700) {
|
2149
2477
|
for ($p=0; $p<$len; $p+=$dlen) {
|
@@ -2189,6 +2517,37 @@ sub ProcessInsta360($;$)
|
|
2189
2517
|
return 1;
|
2190
2518
|
}
|
2191
2519
|
|
2520
|
+
#------------------------------------------------------------------------------
|
2521
|
+
# Process 360Fly 'uuid' atom containing sensor data
|
2522
|
+
# (ref https://github.com/JamesHeinrich/getID3/blob/master/getid3/module.audio-video.quicktime.php)
|
2523
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
2524
|
+
# Returns: 1 on success
|
2525
|
+
sub Process360Fly($$$)
|
2526
|
+
{
|
2527
|
+
my ($et, $dirInfo, $tagTbl) = @_;
|
2528
|
+
my $dataPt = $$dirInfo{DataPt};
|
2529
|
+
my $dataLen = length $$dataPt;
|
2530
|
+
my $pos = 16;
|
2531
|
+
my $lastTime = -1;
|
2532
|
+
my $streamTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
2533
|
+
while ($pos + 32 <= $dataLen) {
|
2534
|
+
my $type = ord substr $$dataPt, $pos, 1;
|
2535
|
+
my $time = Get64u($dataPt, $pos + 2); # (only valid for some types)
|
2536
|
+
if ($$tagTbl{$type}) {
|
2537
|
+
if ($time != $lastTime) {
|
2538
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
2539
|
+
$lastTime = $time;
|
2540
|
+
}
|
2541
|
+
}
|
2542
|
+
$et->HandleTag($tagTbl, $type, undef, DataPt => $dataPt, Start => $pos, Size => 32);
|
2543
|
+
# synthesize GPSDateTime from the timestamp for GPS records
|
2544
|
+
SetGPSDateTime($et, $streamTbl, $time / 1e6) if $type == 5;
|
2545
|
+
$pos += 32;
|
2546
|
+
}
|
2547
|
+
delete $$et{DOC_NUM};
|
2548
|
+
return 1;
|
2549
|
+
}
|
2550
|
+
|
2192
2551
|
#------------------------------------------------------------------------------
|
2193
2552
|
# Scan media data for "freeGPS" metadata if not found already (ref PH)
|
2194
2553
|
# Inputs: 0) ExifTool ref
|