exiftool-vendored.exe 13.0.0 → 13.17.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.
- package/bin/exiftool.exe +0 -0
- package/bin/exiftool_files/exiftool.pl +169 -65
- package/bin/exiftool_files/lib/File/RandomAccess.pm +1 -1
- package/bin/exiftool_files/lib/File/RandomAccess.pod +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/AAC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/AES.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/AFCP.pm +6 -6
- package/bin/exiftool_files/lib/Image/ExifTool/AIFF.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/APE.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/APP12.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/ASF.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +11 -9
- package/bin/exiftool_files/lib/Image/ExifTool/Audible.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/BMP.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/BPG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/BZZ.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/BigTIFF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +46 -26
- package/bin/exiftool_files/lib/Image/ExifTool/CBOR.pm +5 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +68 -28
- package/bin/exiftool_files/lib/Image/ExifTool/CanonCustom.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/CanonVRD.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/CaptureOne.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Casio.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Charset.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DICOM.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +196 -30
- package/bin/exiftool_files/lib/Image/ExifTool/DNG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DPX.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DV.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DjVu.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/EXE.pm +138 -33
- package/bin/exiftool_files/lib/Image/ExifTool/Exif.pm +29 -16
- package/bin/exiftool_files/lib/Image/ExifTool/FITS.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/FLAC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/FLIF.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/FLIR.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Fixup.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Flash.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/FlashPix.pm +17 -21
- package/bin/exiftool_files/lib/Image/ExifTool/Font.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/FotoStation.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/FujiFilm.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/GE.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/GIF.pm +144 -93
- package/bin/exiftool_files/lib/Image/ExifTool/GIMP.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/GM.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +34 -30
- package/bin/exiftool_files/lib/Image/ExifTool/GeoTiff.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.dat +0 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.pm +19 -9
- package/bin/exiftool_files/lib/Image/ExifTool/Geotag.pm +46 -12
- package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +120 -8
- package/bin/exiftool_files/lib/Image/ExifTool/H264.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/HP.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/HTML.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/HtmlDump.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/ICC_Profile.pm +81 -2
- package/bin/exiftool_files/lib/Image/ExifTool/ICO.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/ID3.pm +8 -8
- package/bin/exiftool_files/lib/Image/ExifTool/IPTC.pm +10 -7
- package/bin/exiftool_files/lib/Image/ExifTool/ISO.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/ITC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Import.pm +5 -4
- package/bin/exiftool_files/lib/Image/ExifTool/InDesign.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/InfiRay.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +38 -5
- package/bin/exiftool_files/lib/Image/ExifTool/JPEGDigest.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/JVC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Jpeg2000.pm +10 -9
- package/bin/exiftool_files/lib/Image/ExifTool/Kodak.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/KyoceraRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/LIF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/LNK.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/cs.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/de.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/en_ca.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/en_gb.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/es.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/fi.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/fr.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/it.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/ja.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/ko.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/nl.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/pl.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/ru.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/sk.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/sv.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/tr.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/zh_cn.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Lang/zh_tw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Leaf.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/LigoGPS.pm +409 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Lytro.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/M2TS.pm +57 -18
- package/bin/exiftool_files/lib/Image/ExifTool/MIE.pm +15 -6
- package/bin/exiftool_files/lib/Image/ExifTool/MIEUnits.pod +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MIFF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MISB.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MNG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MOI.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MPC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MPEG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MPF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MRC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MWG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MXF.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/MacOS.pm +3 -2
- package/bin/exiftool_files/lib/Image/ExifTool/MakerNotes.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Matroska.pm +22 -6
- package/bin/exiftool_files/lib/Image/ExifTool/Microsoft.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Minolta.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/MinoltaRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Motorola.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +457 -103
- package/bin/exiftool_files/lib/Image/ExifTool/NikonCapture.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +6 -6
- package/bin/exiftool_files/lib/Image/ExifTool/NikonSettings.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Nintendo.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/OOXML.pm +8 -8
- package/bin/exiftool_files/lib/Image/ExifTool/Ogg.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/OpenEXR.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Opus.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Other.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PCX.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +49 -18
- package/bin/exiftool_files/lib/Image/ExifTool/PGF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PICT.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +4 -4
- package/bin/exiftool_files/lib/Image/ExifTool/PLUS.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +20 -8
- package/bin/exiftool_files/lib/Image/ExifTool/PPM.pm +12 -3
- package/bin/exiftool_files/lib/Image/ExifTool/PSP.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Palm.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Panasonic.pm +27 -3
- package/bin/exiftool_files/lib/Image/ExifTool/PanasonicRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Parrot.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Pentax.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PhaseOne.pm +4 -4
- package/bin/exiftool_files/lib/Image/ExifTool/PhotoCD.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PhotoMechanic.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +65 -4
- package/bin/exiftool_files/lib/Image/ExifTool/PostScript.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PrintIM.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Protobuf.pm +270 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Qualcomm.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +327 -88
- package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +199 -195
- package/bin/exiftool_files/lib/Image/ExifTool/README +12 -2
- package/bin/exiftool_files/lib/Image/ExifTool/RIFF.pm +21 -6
- package/bin/exiftool_files/lib/Image/ExifTool/RSRC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/RTF.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Radiance.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Rawzor.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Real.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Reconyx.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Red.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Ricoh.pm +4 -4
- package/bin/exiftool_files/lib/Image/ExifTool/Samsung.pm +6 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Sanyo.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Scalado.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Shift.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Shortcuts.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Sigma.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/SigmaRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +6 -5
- package/bin/exiftool_files/lib/Image/ExifTool/SonyIDC.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Stim.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/TagInfoXML.pm +6 -5
- package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +7028 -6968
- package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +12079 -11630
- package/bin/exiftool_files/lib/Image/ExifTool/Text.pm +4 -3
- package/bin/exiftool_files/lib/Image/ExifTool/Theora.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Torrent.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/Trailer.pm +318 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Unknown.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/VCard.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +6 -6
- package/bin/exiftool_files/lib/Image/ExifTool/Vorbis.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WPG.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WTV.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/WriteIPTC.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WritePNG.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WritePhotoshop.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WritePostScript.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +170 -79
- package/bin/exiftool_files/lib/Image/ExifTool/WriteRIFF.pl +17 -6
- package/bin/exiftool_files/lib/Image/ExifTool/WriteXMP.pl +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +89 -96
- package/bin/exiftool_files/lib/Image/ExifTool/XISF.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +28 -13
- package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +106 -3
- package/bin/exiftool_files/lib/Image/ExifTool/XMPStruct.pl +2 -3
- package/bin/exiftool_files/lib/Image/ExifTool/ZIP.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/ZISRAW.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/iWork.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool.pm +467 -228
- package/bin/exiftool_files/lib/Image/ExifTool.pod +118 -72
- package/bin/exiftool_files/windows_exiftool.txt +96 -51
- package/package.json +8 -8
|
@@ -784,7 +784,7 @@ and values.
|
|
|
784
784
|
|
|
785
785
|
=head1 AUTHOR
|
|
786
786
|
|
|
787
|
-
Copyright 2003-
|
|
787
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
788
788
|
|
|
789
789
|
This library is free software; you can redistribute it and/or modify it
|
|
790
790
|
under the same terms as Perl itself.
|
|
@@ -504,7 +504,7 @@ Capture.
|
|
|
504
504
|
|
|
505
505
|
=head1 AUTHOR
|
|
506
506
|
|
|
507
|
-
Copyright 2003-
|
|
507
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
508
508
|
|
|
509
509
|
This library is free software; you can redistribute it and/or modify it
|
|
510
510
|
under the same terms as Perl itself.
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#------------------------------------------------------------------------------
|
|
2
|
+
# File: LigoGPS.pm
|
|
3
|
+
#
|
|
4
|
+
# Description: Read LIGOGPSINFO timed GPS records
|
|
5
|
+
#
|
|
6
|
+
# Revisions: 2024-12-30 - P. Harvey Created
|
|
7
|
+
#------------------------------------------------------------------------------
|
|
8
|
+
package Image::ExifTool::LigoGPS;
|
|
9
|
+
|
|
10
|
+
use strict;
|
|
11
|
+
use vars qw($VERSION);
|
|
12
|
+
use Image::ExifTool;
|
|
13
|
+
|
|
14
|
+
$VERSION = '1.02';
|
|
15
|
+
|
|
16
|
+
sub ProcessLigoGPS($$$;$);
|
|
17
|
+
sub ProcessLigoJSON($$$);
|
|
18
|
+
sub OrderCipherDigits($$$;$);
|
|
19
|
+
|
|
20
|
+
my $knotsToKph = 1.852; # knots --> km/h
|
|
21
|
+
|
|
22
|
+
#------------------------------------------------------------------------------
|
|
23
|
+
# Clean up cipher variables and print warning if deciphering was unsuccessful
|
|
24
|
+
# Inputs: 0) ExifTool ref
|
|
25
|
+
sub CleanupCipher($)
|
|
26
|
+
{
|
|
27
|
+
my $et = shift;
|
|
28
|
+
if ($$et{LigoCipher} and $$et{LigoCipher}{'next'}) {
|
|
29
|
+
$et->Warn('Not enough GPS points to determine cipher for decoding LIGOGPSINFO');
|
|
30
|
+
}
|
|
31
|
+
delete $$et{LigoCipher};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#------------------------------------------------------------------------------
|
|
35
|
+
# Un-do LIGOGPS fuzzing
|
|
36
|
+
# Inputs: 0) fuzzed latitude, 1) fuzzed longitude, 2) scale factor
|
|
37
|
+
# Returns: 0) latitude, 1) longitude
|
|
38
|
+
sub UnfuzzLigoGPS($$$)
|
|
39
|
+
{
|
|
40
|
+
my ($lat, $lon, $scl) = @_;
|
|
41
|
+
my $lat2 = int($lat / 10) * 10;
|
|
42
|
+
my $lon2 = int($lon / 10) * 10;
|
|
43
|
+
return($lat2 + ($lon - $lon2) * $scl, $lon2 + ($lat - $lat2) * $scl);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#------------------------------------------------------------------------------
|
|
47
|
+
# Decrypt LIGOGPSINFO record (starting with "####")
|
|
48
|
+
# Inputs: 0) encrypted GPS record incuding 8-byte header
|
|
49
|
+
# Returns: decrypted record including 4-byte uint32 header, or undef on error
|
|
50
|
+
sub DecryptLigoGPS($)
|
|
51
|
+
{
|
|
52
|
+
my $str = shift;
|
|
53
|
+
my $num = unpack('x4V',$str);
|
|
54
|
+
return undef if $num < 4;
|
|
55
|
+
$num = 0x84 if $num > 0x84; # (be safe)
|
|
56
|
+
my @in = unpack("x8C$num",$str);
|
|
57
|
+
my @out;
|
|
58
|
+
while (@in) {
|
|
59
|
+
my $b = shift @in; # get next byte in data
|
|
60
|
+
# upper 3 bits steer the decryption for this round
|
|
61
|
+
my $steeringBits = $b & 0xe0;
|
|
62
|
+
if ($steeringBits >= 0xc0) {
|
|
63
|
+
return undef if @in < 4; # next 4 bytes are encrypted data
|
|
64
|
+
push @out, (shift(@in) | $b & 0x01) ^ 0x20,
|
|
65
|
+
(shift(@in) | $b & 0x02) ^ 0x20,
|
|
66
|
+
(shift(@in) | $b & 0x0c) ^ 0x20,
|
|
67
|
+
shift(@in) ^ 0x20 | $b & 0x30;
|
|
68
|
+
} elsif ($steeringBits >= 0x40) {
|
|
69
|
+
return undef if @in < 3; # next 3 bytes are encrypted data
|
|
70
|
+
if ($steeringBits == 0x40) {
|
|
71
|
+
push @out, 0x20,
|
|
72
|
+
(shift(@in) | $b & 0x01) ^ 0x20,
|
|
73
|
+
(shift(@in) | $b & 0x06) ^ 0x20,
|
|
74
|
+
(shift(@in) | $b & 0x18) ^ 0x20;
|
|
75
|
+
} elsif ($steeringBits == 0x60) {
|
|
76
|
+
push @out, (shift(@in) | $b & 0x03) ^ 0x20,
|
|
77
|
+
0x20,
|
|
78
|
+
(shift(@in) | $b & 0x04) ^ 0x20,
|
|
79
|
+
(shift(@in) | $b & 0x18) ^ 0x20;
|
|
80
|
+
} elsif ($steeringBits == 0x80) {
|
|
81
|
+
push @out, (shift(@in) | $b & 0x03) ^ 0x20,
|
|
82
|
+
(shift(@in) | $b & 0x0c) ^ 0x20,
|
|
83
|
+
0x20,
|
|
84
|
+
(shift(@in) | $b & 0x10) ^ 0x20;
|
|
85
|
+
} else {
|
|
86
|
+
push @out, (shift(@in) | $b & 0x01) ^ 0x20,
|
|
87
|
+
(shift(@in) | $b & 0x06) ^ 0x20,
|
|
88
|
+
(shift(@in) | $b & 0x18) ^ 0x20,
|
|
89
|
+
0x20;
|
|
90
|
+
}
|
|
91
|
+
} elsif ($steeringBits == 0x00) {
|
|
92
|
+
return undef if @in < 1; # next byte is encrypted data
|
|
93
|
+
push @out, shift(@in) | $b & 0x13;
|
|
94
|
+
} else {
|
|
95
|
+
return undef; # (shouldn't happen)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return pack 'C*', @out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#------------------------------------------------------------------------------
|
|
102
|
+
# Determine correct ordering of enciphered digits (unit digits of seconds)
|
|
103
|
+
# Inputs: 0) starting character code, 1) lookup for next character(s) in sequence
|
|
104
|
+
# 2) i/o list of ordered characters, 3) hash of used characters
|
|
105
|
+
# Returns: true if a consistent ordering was found
|
|
106
|
+
# - loops through all possible orders based on $next sequence until a complete
|
|
107
|
+
# cycle is established
|
|
108
|
+
# - this complexity is necessary because GPS may skip some seconds
|
|
109
|
+
sub OrderCipherDigits($$$;$)
|
|
110
|
+
{
|
|
111
|
+
my ($ch, $next, $order, $did) = @_;
|
|
112
|
+
$did or $did = { };
|
|
113
|
+
while ($$next{$ch}) {
|
|
114
|
+
if (@$order < 10) {
|
|
115
|
+
last if $$did{$ch};
|
|
116
|
+
} else {
|
|
117
|
+
# success if we have cycled through all 10 digits and back to the first
|
|
118
|
+
return 1 if @$order == 10 and $ch eq $$order[0];
|
|
119
|
+
last;
|
|
120
|
+
}
|
|
121
|
+
push @$order, $ch;
|
|
122
|
+
$$did{$ch} = 1;
|
|
123
|
+
# continue with next character if there is only one possibility
|
|
124
|
+
@{$$next{$ch}} == 1 and $ch = $$next{$ch}[0], next;
|
|
125
|
+
# otherwise, test all possibilities
|
|
126
|
+
my $n = $#$order;
|
|
127
|
+
foreach (@{$$next{$ch}}) {
|
|
128
|
+
my %did = %$did; # make a copy of the used-character lookup
|
|
129
|
+
return 1 if OrderCipherDigits($_, $next, $order, \%did);
|
|
130
|
+
$#$order = $n; # restore order and try next possibility
|
|
131
|
+
}
|
|
132
|
+
last;
|
|
133
|
+
}
|
|
134
|
+
return 0; # failure
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#------------------------------------------------------------------------------
|
|
138
|
+
# Decipher and parse LIGOGPSINFO record (starting with "####")
|
|
139
|
+
# Inputs: 0) ExifTool ref, 1) enciphered string, 2) tag table ref
|
|
140
|
+
# 3) true if GPS coordinates don't need de-fuzzing
|
|
141
|
+
# Returns: true if this looked like an enciphered string
|
|
142
|
+
# Notes: handles contained tags, but may defer handling until full cipher is known
|
|
143
|
+
sub DecipherLigoGPS($$$;$)
|
|
144
|
+
{
|
|
145
|
+
my ($et, $str, $tagTbl, $noFuzz) = @_;
|
|
146
|
+
|
|
147
|
+
# (enciphered characters must be in the range 0x30-0x5f ('0' - '_'))
|
|
148
|
+
$str =~ m[^####.{4}([0-_])[0-_]{3}/[0-_]{2}/[0-_]{2} ..([0-_])..([0-_]).([0-_]) ]s or return undef;
|
|
149
|
+
return undef unless $2 eq $3; # (colons in time string must be the same)
|
|
150
|
+
|
|
151
|
+
my $cipherInfo = $$et{LigoCipher};
|
|
152
|
+
unless ($cipherInfo) {
|
|
153
|
+
$cipherInfo = $$et{LigoCipher} = { cache => [ ], 'next' => { } };
|
|
154
|
+
$et->AddCleanup(\&CleanupCipher);
|
|
155
|
+
};
|
|
156
|
+
my $decipher = $$cipherInfo{decipher};
|
|
157
|
+
my $cache = $$cipherInfo{cache};
|
|
158
|
+
|
|
159
|
+
# determine the cipher code table based on the advancing 1's digit of seconds
|
|
160
|
+
unless ($decipher) {
|
|
161
|
+
push @$cache, $str; # cache records until we can decipher them
|
|
162
|
+
my $next = $$cipherInfo{next};
|
|
163
|
+
my ($millennium, $colon, $ch2) = ($1, $2, $4);
|
|
164
|
+
# determine the cipher lookup table
|
|
165
|
+
# (only characters in range 0x30-0x5f are encrypted)
|
|
166
|
+
my $ch1 = $$cipherInfo{ch1};
|
|
167
|
+
$$cipherInfo{ch1} = $ch2;
|
|
168
|
+
return 1 if not defined $ch1 or $ch1 eq $ch2; # ignore duplicate sequential digits
|
|
169
|
+
if ($$next{$ch1}) {
|
|
170
|
+
return 1 if grep /\Q$ch2\E/, @{$$next{$ch1}}; # don't add twice
|
|
171
|
+
push @{$$next{$ch1}}, $ch2;
|
|
172
|
+
} else {
|
|
173
|
+
$$next{$ch1} = [ $ch2 ];
|
|
174
|
+
}
|
|
175
|
+
# must wait until the lookup contains all 10 digits
|
|
176
|
+
scalar(keys %$next) < 10 and return 1;
|
|
177
|
+
# protect against trying to decipher bad data
|
|
178
|
+
scalar(keys %$next) > 10 and $$cipherInfo{'next'} = { }, return 1;
|
|
179
|
+
my (@order, $two);
|
|
180
|
+
return 1 unless OrderCipherDigits($ch1, $next, \@order);
|
|
181
|
+
# get index of enciphered "2" in ordered array
|
|
182
|
+
$order[$_] eq $millennium and $two = $_, last foreach 0..9;
|
|
183
|
+
defined $two or $et->Warn('Problem deciphering LIGOGPSINFO'), return 1;
|
|
184
|
+
delete $$cipherInfo{'next'}; # all done with 'next' lookup
|
|
185
|
+
my %decipher = ( $colon => ':' ); # (':' is the time separator)
|
|
186
|
+
foreach (0..9) {
|
|
187
|
+
my $ch = $order[($_ + $two - 2 + 10) % 10];
|
|
188
|
+
$decipher{$ch} = chr($_ + 0x30);
|
|
189
|
+
}
|
|
190
|
+
# may also know the lat/lon quadrant from the signs of the coordinates
|
|
191
|
+
if ($str =~ / ([0-_])$colon(-?).*? ([0-_])$colon(-?)/) {
|
|
192
|
+
@decipher{$1,$3} = ($2 ? 'S' : 'N', $4 ? 'W' : 'E');
|
|
193
|
+
unless ($2 or $4) {
|
|
194
|
+
my ($ns, $ew) = ($1, $3);
|
|
195
|
+
if ($$et{OPTIONS}{GPSQuadrant} and $$et{OPTIONS}{GPSQuadrant} =~ /^([NS])([EW])$/i) {
|
|
196
|
+
@decipher{$ns,$ew} = (uc($1), uc($2));
|
|
197
|
+
} else {
|
|
198
|
+
$et->Warn('May need to set API GPSQuadrant option (eg. "NW")');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
# fill in unknown entries with '?' (only chars 0x30-0x5f are enciphered)
|
|
203
|
+
defined $decipher{$_} or $decipher{$_} = '?' foreach map(chr, 0x30..0x5f);
|
|
204
|
+
$decipher = $$cipherInfo{decipher} = \%decipher;
|
|
205
|
+
$str = shift @$cache; # start deciphering at oldest cache entry
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# apply reverse cipher and extract GPS information
|
|
209
|
+
do {
|
|
210
|
+
my $pre = substr($str, 4, 4); # save second 4 bytes of header
|
|
211
|
+
($str = substr($str,8)) =~ s/\0+$//; # remove 8-byte header and null padding
|
|
212
|
+
$str =~ s/([0-_])/$$decipher{$1}/g; # decipher
|
|
213
|
+
if ($$et{OPTIONS}{Verbose} > 1) {
|
|
214
|
+
$et->VPrint(1, "$$et{INDENT}\(Deciphered: ".unpack('H8',$pre)." $str)\n");
|
|
215
|
+
}
|
|
216
|
+
# add back leading 4 bytes (int16u counter plus 2 unknown bytes), and parse
|
|
217
|
+
ParseLigoGPS($et, "$pre$str", $tagTbl, $noFuzz);
|
|
218
|
+
} while $str = shift @$cache;
|
|
219
|
+
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#------------------------------------------------------------------------------
|
|
224
|
+
# Parse decrypted/deciphered (but not defuzzed) LIGOGPSINFO record
|
|
225
|
+
# (record starts with 4-byte int32u counter followed by date/time, etc)
|
|
226
|
+
# Inputs: 0) ExifTool ref, 1) GPS string, 2) tag table ref, 3) not fuzzed
|
|
227
|
+
# Returns: nothing
|
|
228
|
+
sub ParseLigoGPS($$$;$)
|
|
229
|
+
{
|
|
230
|
+
my ($et, $str, $tagTbl, $noFuzz) = @_;
|
|
231
|
+
|
|
232
|
+
# example string input
|
|
233
|
+
# "....2022/09/19 12:45:24 N:31.285065 W:124.759483 46.93 km/h x:-0.000 y:-0.000 z:-0.000"
|
|
234
|
+
unless ($str=~ /^.{4}(\S+ \S+)\s+([NS?]):(-?)([.\d]+)\s+([EW?]):(-?)([\.\d]+)\s+([.\d]+)/s) {
|
|
235
|
+
$et->Warn('LIGOGPSINFO format error');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
my ($time,$latRef,$latNeg,$lat,$lonRef,$lonNeg,$lon,$spd) = ($1,$2,$3,$4,$5,$6,$7,$8);
|
|
239
|
+
my %gpsScl = ( 1 => 1.524855137, 2 => 1.456027985, 3 => 1.15368 );
|
|
240
|
+
my $spdScl = $noFuzz ? $knotsToKph : 1.85407333;
|
|
241
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
|
242
|
+
$time =~ tr(/)(:);
|
|
243
|
+
# convert from DDMM.MMMMMM to DD.DDDDDD if necessary
|
|
244
|
+
# (speed wasn't scaled in my 1 sample with this format)
|
|
245
|
+
$lat =~ /^\d{3}/ and Image::ExifTool::QuickTime::ConvertLatLon($lat,$lon), $spdScl = 1;
|
|
246
|
+
unless ($noFuzz) { # unfuzz the coordinates if necessary
|
|
247
|
+
my $scl = $$et{OPTIONS}{LigoGPSScale} || $$et{LigoGPSScale} || 1;
|
|
248
|
+
$scl = $gpsScl{$scl} if $gpsScl{$scl};
|
|
249
|
+
($lat, $lon) = UnfuzzLigoGPS($lat, $lon, $scl);
|
|
250
|
+
}
|
|
251
|
+
# a final sanity check
|
|
252
|
+
($lat > 90 or $lon > 180) and $et->Warn('LIGOGPSINFO coordinates out of range'), return;
|
|
253
|
+
$$et{SET_GROUP1} = 'LIGO';
|
|
254
|
+
$et->HandleTag($tagTbl, 'GPSDateTime', $time);
|
|
255
|
+
# (ignore N/S/E/W if coordinate is signed)
|
|
256
|
+
$et->HandleTag($tagTbl, 'GPSLatitude', $lat * (($latNeg or $latRef eq 'S') ? -1 : 1));
|
|
257
|
+
$et->HandleTag($tagTbl, 'GPSLongitude', $lon * (($lonNeg or $lonRef eq 'W') ? -1 : 1));
|
|
258
|
+
$et->HandleTag($tagTbl, 'GPSSpeed', $spd * $spdScl);
|
|
259
|
+
$et->HandleTag($tagTbl, 'GPSTrack', $1) if $str =~ /\bA:(\S+)/;
|
|
260
|
+
$et->HandleTag($tagTbl, 'GPSAltitude', $1) if $str =~ /\bH:(\S+)/;
|
|
261
|
+
$et->HandleTag($tagTbl, 'MagneticVariation', $1) if $str =~ /\bM:(\S+)/;
|
|
262
|
+
# (have a sample where tab is used to separate acc components)
|
|
263
|
+
$et->HandleTag($tagTbl, 'Accelerometer',"$1 $2 $3") if $str =~ /x:(\S+)\sy:(\S+)\sz:(\S+)/;
|
|
264
|
+
delete $$et{SET_GROUP1};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#------------------------------------------------------------------------------
|
|
268
|
+
# Process LIGOGPSINFO data (non-JSON format)
|
|
269
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
|
|
270
|
+
# 3) 1=LIGOGPS lat/lon/spd weren't fuzzed
|
|
271
|
+
# Returns: 1 on success
|
|
272
|
+
# Notes: The directory data should start with the string "LIGOGPSINFO\0"
|
|
273
|
+
sub ProcessLigoGPS($$$;$)
|
|
274
|
+
{
|
|
275
|
+
my ($et, $dirInfo, $tagTbl, $noFuzz) = @_;
|
|
276
|
+
my $dataPt = $$dirInfo{DataPt};
|
|
277
|
+
my $pos = ($$dirInfo{DirStart} || 0) + 0x14;
|
|
278
|
+
return undef if $pos > length $$dataPt;
|
|
279
|
+
my $cipherInfo = $$et{LigoCipher};
|
|
280
|
+
my $dirName = $$dirInfo{DirName} || 'LigoGPS';
|
|
281
|
+
push @{$$et{PATH}}, $dirName unless $$dirInfo{DirID};
|
|
282
|
+
# not fuzzed if header =~ /LIGOGPSINFO\0\0\0\0[\x01\x14]/ (\x01=BlueSkySeaDV688)
|
|
283
|
+
$noFuzz = 1 if substr($$dataPt, $pos-8, 4) =~ /^\0\0\0[\x01\x14]/;
|
|
284
|
+
$et->VerboseDir($dirName);
|
|
285
|
+
for (; $pos + 0x84 <= length($$dataPt); $pos+=0x84) {
|
|
286
|
+
my $dat = substr($$dataPt, $pos, 0x84);
|
|
287
|
+
$dat =~ /^####/ or next; # (have seen blank records filled with zeros, so keep trying)
|
|
288
|
+
# decipher if we already know the encryption
|
|
289
|
+
$cipherInfo and $$cipherInfo{decipher} and DecipherLigoGPS($et, $dat, $tagTbl, $noFuzz) and next;
|
|
290
|
+
my $str = DecryptLigoGPS($dat);
|
|
291
|
+
defined $str or DecipherLigoGPS($et, $dat, $tagTbl, $noFuzz), next; # try to decipher
|
|
292
|
+
$et->VPrint(1, "$$et{INDENT}\(Decrypted: ",unpack('V',$str),' ',substr($str,4),")\n") if $$et{OPTIONS}{Verbose} > 1;
|
|
293
|
+
ParseLigoGPS($et, $str, $tagTbl, $noFuzz);
|
|
294
|
+
}
|
|
295
|
+
pop @{$$et{PATH}} unless $$dirInfo{DirID};
|
|
296
|
+
delete $$et{DOC_NUM};
|
|
297
|
+
return 1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#------------------------------------------------------------------------------
|
|
301
|
+
# Process LIGOGPSINFO JSON-format GPS (Yada RoadCam Pro 4K BT58189)
|
|
302
|
+
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
|
303
|
+
# Returns: 1 on success
|
|
304
|
+
# Sample data (chained 512-byte records starting like this):
|
|
305
|
+
# 0000: 4c 49 47 4f 47 50 53 49 4e 46 4f 20 7b 22 48 6f [LIGOGPSINFO {"Ho]
|
|
306
|
+
# 0010: 75 72 22 3a 20 22 32 33 22 2c 20 22 4d 69 6e 75 [ur": "23", "Minu]
|
|
307
|
+
# 0020: 74 65 22 3a 20 22 31 30 22 2c 20 22 53 65 63 6f [te": "10", "Seco]
|
|
308
|
+
# 0030: 6e 64 22 3a 20 22 32 32 22 2c 20 22 59 65 61 72 [nd": "22", "Year]
|
|
309
|
+
# 0040: 22 3a 20 22 32 30 32 33 22 2c 20 22 4d 6f 6e 74 [": "2023", "Mont]
|
|
310
|
+
# 0050: 68 22 3a 20 22 31 32 22 2c 20 22 44 61 79 22 3a [h": "12", "Day":]
|
|
311
|
+
# 0060: 20 22 32 38 22 2c 20 22 73 74 61 74 75 73 22 3a [ "28", "status":]
|
|
312
|
+
sub ProcessLigoJSON($$$)
|
|
313
|
+
{
|
|
314
|
+
my ($et, $dirInfo, $tagTbl) = @_;
|
|
315
|
+
my $dataPt = $$dirInfo{DataPt};
|
|
316
|
+
my $dirLen = $$dirInfo{DirLen};
|
|
317
|
+
require Image::ExifTool::Import;
|
|
318
|
+
$et->VerboseDir('LIGO_JSON', undef, length($$dataPt));
|
|
319
|
+
$$et{SET_GROUP1} = 'LIGO';
|
|
320
|
+
while ($$dataPt =~ /LIGOGPSINFO (\{.*?\})/g) {
|
|
321
|
+
my $json = $1;
|
|
322
|
+
my %dbase;
|
|
323
|
+
Image::ExifTool::Import::ReadJSON(\$json, \%dbase);
|
|
324
|
+
my $info = $dbase{'*'} or next;
|
|
325
|
+
# my sample contains the following JSON fields (in this order):
|
|
326
|
+
# Hour Minute Second Year Month Day (GPS UTC time)
|
|
327
|
+
# status NS EW Latitude Longitude Speed (speed in knots)
|
|
328
|
+
# GsensorX GsensorY GsensorZ (units? - only seen "000" for all)
|
|
329
|
+
# MHour MMinute MSecond MYear MMonth MDay (local dashcam clock time)
|
|
330
|
+
# OLatitude OLongitude (? same values as Latitude/Longitude)
|
|
331
|
+
next unless defined $$info{status} and $$info{status} eq 'A'; # only read if GPS is active
|
|
332
|
+
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
|
333
|
+
my $num = 0;
|
|
334
|
+
defined $$info{$_} and ++$num foreach qw(Year Month Day Hour Minute Second);
|
|
335
|
+
if ($num == 6) {
|
|
336
|
+
# this is the GPS time in UTC
|
|
337
|
+
my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ',@$info{qw{Year Month Day Hour Minute Second}});
|
|
338
|
+
$et->HandleTag($tagTbl, GPSDateTime => $time);
|
|
339
|
+
}
|
|
340
|
+
if ($$info{Latitude} and $$info{Longitude}) {
|
|
341
|
+
my $lat = $$info{Latitude};
|
|
342
|
+
$lat = -$lat if $$info{NS} and $$info{NS} eq 'S';
|
|
343
|
+
my $lon = $$info{Longitude};
|
|
344
|
+
$lon = -$lon if $$info{EW} and $$info{EW} eq 'W';
|
|
345
|
+
$et->HandleTag($tagTbl, GPSLatitude => $lat);
|
|
346
|
+
$et->HandleTag($tagTbl, GPSLongitude => $lon);
|
|
347
|
+
}
|
|
348
|
+
$et->HandleTag($tagTbl, GPSSpeed => $$info{Speed} * $knotsToKph) if defined $$info{Speed};
|
|
349
|
+
if (defined $$info{GsensorX} and defined $$info{GsensorY} and defined $$info{GsensorZ}) {
|
|
350
|
+
# (don't know conversion factor for accel data, so leave it raw for now)
|
|
351
|
+
$et->HandleTag($tagTbl, Accelerometer => "$$info{GsensorX} $$info{GsensorY} $$info{GsensorZ}");
|
|
352
|
+
}
|
|
353
|
+
$num = 0;
|
|
354
|
+
defined $$info{$_} and ++$num foreach qw(MYear MMonth MDay MHour MMinute MSecond);
|
|
355
|
+
if ($num == 6) {
|
|
356
|
+
# this is the dashcam clock time (local time zone)
|
|
357
|
+
my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d',@$info{qw{MYear MMonth MDay MHour MMinute MSecond}});
|
|
358
|
+
$et->HandleTag($tagTbl, DateTimeOriginal => $time);
|
|
359
|
+
}
|
|
360
|
+
if (defined $$info{OLatitude} and defined $$info{OLongitude}) {
|
|
361
|
+
my $lat = $$info{OLatitude};
|
|
362
|
+
$lat = -$lat if $$info{NS} and $$info{NS} eq 'S';
|
|
363
|
+
my $lon = $$info{OLongitude};
|
|
364
|
+
$lon = -$lon if $$info{EW} and $$info{EW} eq 'W';
|
|
365
|
+
$et->HandleTag($tagTbl, GPSLatitude2 => $lat);
|
|
366
|
+
$et->HandleTag($tagTbl, GPSLongitude2 => $lon);
|
|
367
|
+
}
|
|
368
|
+
unless ($et->Options('ExtractEmbedded')) {
|
|
369
|
+
$et->Warn('Use the ExtractEmbedded option to extract all timed GPS',3);
|
|
370
|
+
last;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
delete $$et{DOC_NUM};
|
|
374
|
+
delete $$et{SET_GROUP1};
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
1; #end
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
__END__
|
|
382
|
+
|
|
383
|
+
=head1 NAME
|
|
384
|
+
|
|
385
|
+
Image::ExifTool::LigoGPS - Read LIGOGPSINFO timed GPS records
|
|
386
|
+
|
|
387
|
+
=head1 SYNOPSIS
|
|
388
|
+
|
|
389
|
+
This module is loaded automatically by Image::ExifTool when required.
|
|
390
|
+
|
|
391
|
+
=head1 DESCRIPTION
|
|
392
|
+
|
|
393
|
+
This module decrypts, deciphers and decodes timed GPS metadata from
|
|
394
|
+
LIGOGPSINFO records found in various locations of MP4 and M2TS videos from a
|
|
395
|
+
variety of dashcam makes and models.
|
|
396
|
+
|
|
397
|
+
=head1 AUTHOR
|
|
398
|
+
|
|
399
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
400
|
+
|
|
401
|
+
This library is free software; you can redistribute it and/or modify it
|
|
402
|
+
under the same terms as Perl itself.
|
|
403
|
+
|
|
404
|
+
=head1 SEE ALSO
|
|
405
|
+
|
|
406
|
+
L<Image::ExifTool::TagNames/QuickTime Stream Tags>,
|
|
407
|
+
L<Image::ExifTool(3pm)|Image::ExifTool>
|
|
408
|
+
|
|
409
|
+
=cut
|
|
@@ -192,7 +192,7 @@ from Lytro Light Field Picture (LFP) files.
|
|
|
192
192
|
|
|
193
193
|
=head1 AUTHOR
|
|
194
194
|
|
|
195
|
-
Copyright 2003-
|
|
195
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
196
196
|
|
|
197
197
|
This library is free software; you can redistribute it and/or modify it
|
|
198
198
|
under the same terms as Perl itself.
|
|
@@ -32,7 +32,7 @@ use strict;
|
|
|
32
32
|
use vars qw($VERSION);
|
|
33
33
|
use Image::ExifTool qw(:DataAccess :Utils);
|
|
34
34
|
|
|
35
|
-
$VERSION = '1.
|
|
35
|
+
$VERSION = '1.29';
|
|
36
36
|
|
|
37
37
|
# program map table "stream_type" lookup (ref 6/1/9)
|
|
38
38
|
my %streamType = (
|
|
@@ -305,6 +305,15 @@ sub ParsePID($$$$$)
|
|
|
305
305
|
# MPEG-1/MPEG-2 Audio
|
|
306
306
|
require Image::ExifTool::MPEG;
|
|
307
307
|
Image::ExifTool::MPEG::ParseMPEGAudio($et, $dataPt);
|
|
308
|
+
} elsif ($type == 6 and $pid == 0x0300) {
|
|
309
|
+
# LIGOGPSINFO from unknown dashcam (../testpics/gps_video/Wrong Way pass.ts)
|
|
310
|
+
if ($$dataPt =~ /^LIGOGPSINFO/s) {
|
|
311
|
+
my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
|
312
|
+
my %dirInfo = ( DataPt => $dataPt, DirName => 'Ligo0x0300' );
|
|
313
|
+
Image::ExifTool::LigoGPS::ProcessLigoGPS($et, \%dirInfo, $tbl, 1);
|
|
314
|
+
$$et{FoundGoodGPS} = 1;
|
|
315
|
+
$more = 1;
|
|
316
|
+
}
|
|
308
317
|
} elsif ($type == 0x1b) {
|
|
309
318
|
# H.264 Video
|
|
310
319
|
require Image::ExifTool::H264;
|
|
@@ -313,7 +322,7 @@ sub ParsePID($$$$$)
|
|
|
313
322
|
if ($$et{OPTIONS}{ExtractEmbedded}) {
|
|
314
323
|
$more = 1;
|
|
315
324
|
} elsif (not $$et{OPTIONS}{Validate}) {
|
|
316
|
-
$et->
|
|
325
|
+
$et->Warn('The ExtractEmbedded option may find more tags in the video data',3);
|
|
317
326
|
}
|
|
318
327
|
} elsif ($type == 0x81 or $type == 0x87 or $type == 0x91) {
|
|
319
328
|
# AC-3 audio
|
|
@@ -428,13 +437,16 @@ sub ParsePID($$$$$)
|
|
|
428
437
|
$more = 1;
|
|
429
438
|
} elsif ($$dataPt =~ /\$GPRMC,/) {
|
|
430
439
|
# Jomise T860S-GM dashcam
|
|
431
|
-
# $GPRMC,hhmmss.ss,A,ddmm.mmmmm,N,dddmm.mmmmm,W,spd-kts,dir-dg,DDMMYY
|
|
432
|
-
# $GPRMC,172255.00,A,:985.95194,N,17170.14674,W,029.678,170.68,240822,,,D*7B
|
|
433
|
-
# $GPRMC,
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
440
|
+
# $GPRMC,hhmmss.ss,A,ddmm.mmmmm,N,dddmm.mmmmm,W,spd-kts,dir-dg,DDMMYY,,M*cs - lat,lon,spd from video
|
|
441
|
+
# $GPRMC,172255.00,A,:985.95194,N,17170.14674,W,029.678,170.68,240822,,,D*7B - N47.70428,W122.15338,35mph
|
|
442
|
+
# $GPRMC,192643.00,A,:987.94979,N,17171.07268,W,010.059,079.61,111122,,,A*73 - N47.71862,W122.16437,12mph
|
|
443
|
+
# $GPRMC,192743.00,A,:988.72110,N,17171.04873,W,017.477,001.03,111122,,,A*78 - N47.72421,W122.16408,20mph
|
|
444
|
+
# $GPRMC,192844.00,A,:989.43771,N,17171.03538,W,016.889,001.20,111122,,,A*7B - N47.72932,W122.16393,19mph
|
|
445
|
+
# $GPRMC,005241.00,A,:987.70873,N,17171.81293,W,000.284,354.78,141122,,,A*7F - N47.71687,W122.17318,0mph
|
|
446
|
+
# $GPRMC,005341.00,A,:987.90851,N,17171.85380,W,000.080,349.36,141122,,,A*7C - N47.71832,W122.17367,0mph
|
|
447
|
+
# $GPRMC,005441.00,A,:987.94538,N,17171.21783,W,029.686,091.09,141122,,,A*7A - N47.71859,W122.16630,35mph
|
|
448
|
+
# $GPRMC,002816.00,A,6820.67273,N,13424.26599,W,000.045,000.00,261122,,,A*79 - N29.52096,W95.55953,0mph (seattle)
|
|
449
|
+
# $GPRMC,035136.00,A,:981.47322,N,17170.14105,W,024.594,180.50,291122,,,D*79 - N47.67180,W122.15328,28mph
|
|
438
450
|
my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
|
439
451
|
while ($$dataPt =~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(.{2})(\d{2}\.\d+),([NS]),(.{3})(\d{2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/g and
|
|
440
452
|
# do some basic sanity checks on the date
|
|
@@ -459,15 +471,20 @@ sub ParsePID($$$$$)
|
|
|
459
471
|
$bad = 1 if $_ < 0x30 or $_ > 0x39;
|
|
460
472
|
}
|
|
461
473
|
if ($bad) {
|
|
462
|
-
$et->
|
|
474
|
+
$et->Warn('Error decrypting GPS degrees');
|
|
463
475
|
} else {
|
|
464
476
|
my $la = pack('C*', @chars[0,1]);
|
|
465
477
|
my $lo = pack('C*', @chars[2,3,4]);
|
|
466
|
-
$et->
|
|
478
|
+
$et->Warn('Decryption of this GPS is highly experimental. More testing samples are required');
|
|
467
479
|
$et->HandleTag($tagTbl, GPSLatitude => (($la || 0) + (($6-85.95194)/2.43051724137931+42.2568)/60) * ($7 eq 'N' ? 1 : -1));
|
|
468
480
|
$et->HandleTag($tagTbl, GPSLongitude => (($lo || 0) + (($9-70.14674)/1.460987654320988+9.2028)/60) * ($10 eq 'E' ? 1 : -1));
|
|
469
481
|
}
|
|
470
482
|
}
|
|
483
|
+
} elsif ($$dataPt =~ /\$GSENSORD,\s*(\d+),\s*(\d+),\s*(\d+),/) {
|
|
484
|
+
# Jomise T860S-GM dashcam
|
|
485
|
+
my $tagTbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
|
486
|
+
$$et{DOC_NUM} = $$et{DOC_COUNT};
|
|
487
|
+
$et->HandleTag($tagTbl, Accelerometer => "$1 $2 $3"); # (NC - values range from 0 to 6)
|
|
471
488
|
} elsif ($$dataPt =~ /^.{44}A\0{3}.{4}([NS])\0{3}.{4}([EW])\0{3}/s and length($$dataPt) >= 84) {
|
|
472
489
|
#forum11320
|
|
473
490
|
SetByteOrder('II');
|
|
@@ -476,7 +493,7 @@ sub ParsePID($$$$$)
|
|
|
476
493
|
my $lon = abs(GetFloat($dataPt, 56)); # (abs just to be safe)
|
|
477
494
|
my $spd = GetFloat($dataPt, 64);
|
|
478
495
|
my $trk = GetFloat($dataPt, 68);
|
|
479
|
-
$et->
|
|
496
|
+
$et->Warn('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
|
|
480
497
|
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
|
|
481
498
|
my @date = unpack('x32V3x28V3', $$dataPt);
|
|
482
499
|
$date[3] += 2000;
|
|
@@ -514,9 +531,16 @@ sub ParsePID($$$$$)
|
|
|
514
531
|
$et->HandleTag($tagTbl, GPSTrack => $a[2] / 100);
|
|
515
532
|
}
|
|
516
533
|
# Note: 10 bytes after last GPS record look like a single 3-axis accelerometer reading:
|
|
517
|
-
# eg. fd ff 00 00 ff ff 00 00 01 00
|
|
534
|
+
# eg. fd ff 00 00 ff ff 00 00 01 00
|
|
518
535
|
$$et{FoundGoodGPS} = 1; # so we skip over unrecognized packets
|
|
519
536
|
$more = 1;
|
|
537
|
+
} elsif ($$dataPt =~ /^skip.{4}LIGOGPSINFO\0/s) {
|
|
538
|
+
# (this record contains 2 copies of the same 'skip' atom in my sample --
|
|
539
|
+
# only extract data from the first one)
|
|
540
|
+
my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
|
541
|
+
my %dirInfo = ( DataPt => $dataPt, DirStart => 8, DirName => sprintf('Ligo0x%.4x',$pid));
|
|
542
|
+
Image::ExifTool::LigoGPS::ProcessLigoGPS($et, \%dirInfo, $tbl, 1);
|
|
543
|
+
$$et{FoundGoodGPS} = 1;
|
|
520
544
|
} elsif ($$et{FoundGoodGPS}) {
|
|
521
545
|
$more = 1;
|
|
522
546
|
}
|
|
@@ -576,7 +600,8 @@ sub ProcessM2TS($$)
|
|
|
576
600
|
my %gpsPID = (
|
|
577
601
|
0x0300 => 1, # Novatek INNOVV, DOD_LS600W
|
|
578
602
|
0x01e4 => 1, # vsys a6l dashcam
|
|
579
|
-
0x0e1b => 1, # Jomise T860S-GM dashcam
|
|
603
|
+
0x0e1b => 1, # Jomise T860S-GM dashcam GPS
|
|
604
|
+
0x0e1a => 1, # Jomise T860S-GM dashcam accelerometer
|
|
580
605
|
);
|
|
581
606
|
my $pEnd = 0;
|
|
582
607
|
|
|
@@ -694,7 +719,7 @@ sub ProcessM2TS($$)
|
|
|
694
719
|
# or if we are just looking for the last timestamp
|
|
695
720
|
next unless $payload_data_exists and not defined $backScan;
|
|
696
721
|
|
|
697
|
-
|
|
722
|
+
# decode payload data
|
|
698
723
|
if ($pid == 0 or # program association table
|
|
699
724
|
defined $pmt{$pid}) # program map table(s)
|
|
700
725
|
{
|
|
@@ -787,7 +812,7 @@ sub ProcessM2TS($$)
|
|
|
787
812
|
last if $j + $descriptor_length > $program_info_length;
|
|
788
813
|
my $desc = substr($buf2, $pos+$j, $descriptor_length);
|
|
789
814
|
$j += $descriptor_length;
|
|
790
|
-
$desc =~ s/([\x00-\x1f\
|
|
815
|
+
$desc =~ s/([\x00-\x1f\x7f-\xff])/sprintf("\\x%.2x",ord $1)/eg;
|
|
791
816
|
printf $out " Program Descriptor: Type=0x%.2x \"$desc\"\n", $descriptor_tag;
|
|
792
817
|
}}
|
|
793
818
|
$pos += $program_info_length; # skip descriptors (for now)
|
|
@@ -822,7 +847,7 @@ sub ProcessM2TS($$)
|
|
|
822
847
|
$j += $descriptor_length;
|
|
823
848
|
if ($verbose > 1) {
|
|
824
849
|
my $dstr = $desc;
|
|
825
|
-
$dstr =~ s/([\x00-\x1f\
|
|
850
|
+
$dstr =~ s/([\x00-\x1f\x7f-\xff])/sprintf("\\x%.2x",ord $1)/eg;
|
|
826
851
|
printf $out " ES Descriptor: Type=0x%.2x \"$dstr\"\n", $descriptor_tag;
|
|
827
852
|
}
|
|
828
853
|
# parse type-specific descriptor information (once)
|
|
@@ -954,6 +979,20 @@ sub ProcessM2TS($$)
|
|
|
954
979
|
ParsePID($et, $pid, $pidType{$pid}, $pidName{$pid}, \$data{$pid});
|
|
955
980
|
delete $data{$pid};
|
|
956
981
|
}
|
|
982
|
+
|
|
983
|
+
# look for LIGOGPSINFO trailer
|
|
984
|
+
if ($et->Options('ExtractEmbedded') and
|
|
985
|
+
$raf->Seek(-8, 2) and $raf->Read($buff, 8) == 8 and
|
|
986
|
+
$buff =~ /^&&&&/)
|
|
987
|
+
{
|
|
988
|
+
my $len = unpack('x4N', $buff);
|
|
989
|
+
if ($len < $raf->Tell() and $raf->Seek(-$len, 2) and $raf->Read($buff,$len) == $len) {
|
|
990
|
+
my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
|
|
991
|
+
my %dirInfo = ( DataPt => \$buff, DirStart => 8, DirName => 'LigoTrailer' );
|
|
992
|
+
Image::ExifTool::LigoGPS::ProcessLigoGPS($et, \%dirInfo, $tbl);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
957
996
|
return 1;
|
|
958
997
|
}
|
|
959
998
|
|
|
@@ -977,7 +1016,7 @@ video.
|
|
|
977
1016
|
|
|
978
1017
|
=head1 AUTHOR
|
|
979
1018
|
|
|
980
|
-
Copyright 2003-
|
|
1019
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
981
1020
|
|
|
982
1021
|
This library is free software; you can redistribute it and/or modify it
|
|
983
1022
|
under the same terms as Perl itself.
|
|
@@ -14,7 +14,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
|
|
|
14
14
|
use Image::ExifTool::Exif;
|
|
15
15
|
use Image::ExifTool::GPS;
|
|
16
16
|
|
|
17
|
-
$VERSION = '1.
|
|
17
|
+
$VERSION = '1.56';
|
|
18
18
|
|
|
19
19
|
sub ProcessMIE($$);
|
|
20
20
|
sub ProcessMIEGroup($$$);
|
|
@@ -1376,8 +1376,9 @@ sub WriteMIEGroup($$$)
|
|
|
1376
1376
|
my $term = "~\0\0\0";
|
|
1377
1377
|
unless ($$dirInfo{Parent}) {
|
|
1378
1378
|
# write extended terminator for file-level group
|
|
1379
|
-
my $len = ref $outfile eq 'SCALAR' ? length($$outfile) : tell $outfile;
|
|
1380
|
-
|
|
1379
|
+
my $len = ref $outfile eq 'SCALAR' ? length($$outfile) || 0 : tell $outfile;
|
|
1380
|
+
# include length of terminator itself minus original $outfile position
|
|
1381
|
+
$len += 10 - ($$dirInfo{OutPos} || 0);
|
|
1381
1382
|
if ($len and $len <= 0x7fffffff) {
|
|
1382
1383
|
$term = "~\0\0\x06" . Set32u($len) . MIEGroupFormat(1) . "\x04";
|
|
1383
1384
|
}
|
|
@@ -1596,9 +1597,10 @@ sub ProcessMIEGroup($$$)
|
|
|
1596
1597
|
} else {
|
|
1597
1598
|
# process MIE data format types
|
|
1598
1599
|
if ($tagInfo) {
|
|
1599
|
-
my $rational;
|
|
1600
|
+
my ($rational, $binVal);
|
|
1600
1601
|
# extract tag value
|
|
1601
1602
|
my $val = ReadMIEValue(\$value, 0, $formatStr, undef, $valLen, \$rational);
|
|
1603
|
+
$binVal = substr($value, 0, $valLen) if $$et{OPTIONS}{SaveBin};
|
|
1602
1604
|
unless (defined $val) {
|
|
1603
1605
|
$et->Warn("Error reading $tag value");
|
|
1604
1606
|
$val = '<err>';
|
|
@@ -1661,7 +1663,12 @@ sub ProcessMIEGroup($$$)
|
|
|
1661
1663
|
$val .= "($units)" if defined $units;
|
|
1662
1664
|
}
|
|
1663
1665
|
my $key = $et->FoundTag($tagInfo, $val);
|
|
1664
|
-
|
|
1666
|
+
if (defined $key) {
|
|
1667
|
+
my $ex = $$et{TAG_EXTRA}{$key};
|
|
1668
|
+
$$ex{Rational} = $rational if defined $rational;
|
|
1669
|
+
$$ex{BinVal} = $binVal if defined $binVal;
|
|
1670
|
+
$$ex{G6} = $formatStr if $$et{OPTIONS}{SaveFormat};
|
|
1671
|
+
}
|
|
1665
1672
|
}
|
|
1666
1673
|
} else {
|
|
1667
1674
|
# skip over unknown information or free bytes
|
|
@@ -1796,6 +1803,8 @@ sub ProcessMIE($$)
|
|
|
1796
1803
|
# don't define Parent so WriteMIEGroup() writes extended terminator
|
|
1797
1804
|
);
|
|
1798
1805
|
if ($outfile) {
|
|
1806
|
+
# save start position in $outfile
|
|
1807
|
+
$subdirInfo{OutPos} = ref $outfile eq 'SCALAR' ? length($$outfile) || 0 : tell $outfile;
|
|
1799
1808
|
# generate lookup for MIE format codes if not done already
|
|
1800
1809
|
unless (%mieCode) {
|
|
1801
1810
|
foreach (keys %mieFormat) {
|
|
@@ -2551,7 +2560,7 @@ tag name. For example:
|
|
|
2551
2560
|
|
|
2552
2561
|
=head1 AUTHOR
|
|
2553
2562
|
|
|
2554
|
-
Copyright 2003-
|
|
2563
|
+
Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
|
|
2555
2564
|
|
|
2556
2565
|
This library is free software; you can redistribute it and/or modify it
|
|
2557
2566
|
under the same terms as Perl itself. The MIE format itself is also
|