exiftool_vendored 11.99.0 → 12.11.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.
- checksums.yaml +4 -4
- data/bin/Changes +201 -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 +172 -99
- data/bin/lib/Image/ExifTool.pm +170 -117
- data/bin/lib/Image/ExifTool.pod +132 -97
- 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 +202 -13
- 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/ID3.pm +86 -12
- data/bin/lib/Image/ExifTool/IPTC.pm +1 -0
- data/bin/lib/Image/ExifTool/Import.pm +12 -9
- 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 +1 -1
- data/bin/lib/Image/ExifTool/MPF.pm +2 -2
- data/bin/lib/Image/ExifTool/MacOS.pm +154 -38
- data/bin/lib/Image/ExifTool/Matroska.pm +3 -1
- data/bin/lib/Image/ExifTool/Minolta.pm +7 -2
- data/bin/lib/Image/ExifTool/Nikon.pm +143 -17
- data/bin/lib/Image/ExifTool/Olympus.pm +40 -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 +11 -3
- data/bin/lib/Image/ExifTool/Photoshop.pm +2 -1
- data/bin/lib/Image/ExifTool/QuickTime.pm +240 -37
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +419 -60
- data/bin/lib/Image/ExifTool/README +25 -21
- data/bin/lib/Image/ExifTool/RSRC.pm +17 -11
- 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 +423 -39
- data/bin/lib/Image/ExifTool/Stim.pm +2 -2
- data/bin/lib/Image/ExifTool/TagLookup.pm +5798 -5675
- data/bin/lib/Image/ExifTool/TagNames.pod +575 -100
- data/bin/lib/Image/ExifTool/Validate.pm +4 -4
- data/bin/lib/Image/ExifTool/WriteExif.pl +1 -0
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +30 -21
- data/bin/lib/Image/ExifTool/Writer.pl +49 -24
- data/bin/lib/Image/ExifTool/XMP.pm +99 -17
- data/bin/lib/Image/ExifTool/XMP2.pl +1 -0
- 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 +9 -8
|
@@ -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
|