exiftool-vendored.exe 12.99.0 → 13.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/bin/exiftool.exe +0 -0
  2. package/bin/exiftool_files/exiftool.pl +183 -70
  3. package/bin/exiftool_files/lib/File/RandomAccess.pm +1 -1
  4. package/bin/exiftool_files/lib/File/RandomAccess.pod +2 -2
  5. package/bin/exiftool_files/lib/Image/ExifTool/AAC.pm +1 -1
  6. package/bin/exiftool_files/lib/Image/ExifTool/AES.pm +1 -1
  7. package/bin/exiftool_files/lib/Image/ExifTool/AFCP.pm +6 -6
  8. package/bin/exiftool_files/lib/Image/ExifTool/AIFF.pm +2 -2
  9. package/bin/exiftool_files/lib/Image/ExifTool/APE.pm +2 -2
  10. package/bin/exiftool_files/lib/Image/ExifTool/APP12.pm +1 -1
  11. package/bin/exiftool_files/lib/Image/ExifTool/ASF.pm +2 -2
  12. package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +11 -9
  13. package/bin/exiftool_files/lib/Image/ExifTool/Audible.pm +1 -1
  14. package/bin/exiftool_files/lib/Image/ExifTool/BMP.pm +1 -1
  15. package/bin/exiftool_files/lib/Image/ExifTool/BPG.pm +1 -1
  16. package/bin/exiftool_files/lib/Image/ExifTool/BZZ.pm +1 -1
  17. package/bin/exiftool_files/lib/Image/ExifTool/BigTIFF.pm +1 -1
  18. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +36 -22
  19. package/bin/exiftool_files/lib/Image/ExifTool/CBOR.pm +5 -2
  20. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +66 -27
  21. package/bin/exiftool_files/lib/Image/ExifTool/CanonCustom.pm +1 -1
  22. package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +1 -1
  23. package/bin/exiftool_files/lib/Image/ExifTool/CanonVRD.pm +1 -1
  24. package/bin/exiftool_files/lib/Image/ExifTool/CaptureOne.pm +1 -1
  25. package/bin/exiftool_files/lib/Image/ExifTool/Casio.pm +1 -1
  26. package/bin/exiftool_files/lib/Image/ExifTool/Charset.pm +1 -1
  27. package/bin/exiftool_files/lib/Image/ExifTool/DICOM.pm +1 -1
  28. package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +196 -30
  29. package/bin/exiftool_files/lib/Image/ExifTool/DNG.pm +1 -1
  30. package/bin/exiftool_files/lib/Image/ExifTool/DPX.pm +1 -1
  31. package/bin/exiftool_files/lib/Image/ExifTool/DV.pm +1 -1
  32. package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +1 -1
  33. package/bin/exiftool_files/lib/Image/ExifTool/DjVu.pm +1 -1
  34. package/bin/exiftool_files/lib/Image/ExifTool/EXE.pm +138 -33
  35. package/bin/exiftool_files/lib/Image/ExifTool/Exif.pm +30 -17
  36. package/bin/exiftool_files/lib/Image/ExifTool/FITS.pm +3 -3
  37. package/bin/exiftool_files/lib/Image/ExifTool/FLAC.pm +1 -1
  38. package/bin/exiftool_files/lib/Image/ExifTool/FLIF.pm +3 -3
  39. package/bin/exiftool_files/lib/Image/ExifTool/FLIR.pm +1 -1
  40. package/bin/exiftool_files/lib/Image/ExifTool/Fixup.pm +1 -1
  41. package/bin/exiftool_files/lib/Image/ExifTool/Flash.pm +1 -1
  42. package/bin/exiftool_files/lib/Image/ExifTool/FlashPix.pm +17 -21
  43. package/bin/exiftool_files/lib/Image/ExifTool/Font.pm +2 -2
  44. package/bin/exiftool_files/lib/Image/ExifTool/FotoStation.pm +1 -1
  45. package/bin/exiftool_files/lib/Image/ExifTool/FujiFilm.pm +1 -1
  46. package/bin/exiftool_files/lib/Image/ExifTool/GE.pm +1 -1
  47. package/bin/exiftool_files/lib/Image/ExifTool/GIF.pm +144 -93
  48. package/bin/exiftool_files/lib/Image/ExifTool/GIMP.pm +1 -1
  49. package/bin/exiftool_files/lib/Image/ExifTool/GM.pm +1 -1
  50. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +34 -30
  51. package/bin/exiftool_files/lib/Image/ExifTool/GeoTiff.pm +1 -1
  52. package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.dat +0 -0
  53. package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.pm +19 -9
  54. package/bin/exiftool_files/lib/Image/ExifTool/Geotag.pm +49 -14
  55. package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +120 -8
  56. package/bin/exiftool_files/lib/Image/ExifTool/H264.pm +1 -1
  57. package/bin/exiftool_files/lib/Image/ExifTool/HP.pm +2 -2
  58. package/bin/exiftool_files/lib/Image/ExifTool/HTML.pm +1 -1
  59. package/bin/exiftool_files/lib/Image/ExifTool/HtmlDump.pm +1 -1
  60. package/bin/exiftool_files/lib/Image/ExifTool/ICC_Profile.pm +81 -2
  61. package/bin/exiftool_files/lib/Image/ExifTool/ICO.pm +1 -1
  62. package/bin/exiftool_files/lib/Image/ExifTool/ID3.pm +8 -8
  63. package/bin/exiftool_files/lib/Image/ExifTool/IPTC.pm +10 -7
  64. package/bin/exiftool_files/lib/Image/ExifTool/ISO.pm +1 -1
  65. package/bin/exiftool_files/lib/Image/ExifTool/ITC.pm +1 -1
  66. package/bin/exiftool_files/lib/Image/ExifTool/Import.pm +5 -4
  67. package/bin/exiftool_files/lib/Image/ExifTool/InDesign.pm +2 -2
  68. package/bin/exiftool_files/lib/Image/ExifTool/InfiRay.pm +1 -1
  69. package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +32 -5
  70. package/bin/exiftool_files/lib/Image/ExifTool/JPEGDigest.pm +1 -1
  71. package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +1 -1
  72. package/bin/exiftool_files/lib/Image/ExifTool/JVC.pm +1 -1
  73. package/bin/exiftool_files/lib/Image/ExifTool/Jpeg2000.pm +10 -9
  74. package/bin/exiftool_files/lib/Image/ExifTool/Kodak.pm +1 -1
  75. package/bin/exiftool_files/lib/Image/ExifTool/KyoceraRaw.pm +1 -1
  76. package/bin/exiftool_files/lib/Image/ExifTool/LIF.pm +1 -1
  77. package/bin/exiftool_files/lib/Image/ExifTool/LNK.pm +2 -2
  78. package/bin/exiftool_files/lib/Image/ExifTool/Lang/cs.pm +1 -1
  79. package/bin/exiftool_files/lib/Image/ExifTool/Lang/de.pm +1 -1
  80. package/bin/exiftool_files/lib/Image/ExifTool/Lang/en_ca.pm +1 -1
  81. package/bin/exiftool_files/lib/Image/ExifTool/Lang/en_gb.pm +1 -1
  82. package/bin/exiftool_files/lib/Image/ExifTool/Lang/es.pm +1 -1
  83. package/bin/exiftool_files/lib/Image/ExifTool/Lang/fi.pm +1 -1
  84. package/bin/exiftool_files/lib/Image/ExifTool/Lang/fr.pm +1 -1
  85. package/bin/exiftool_files/lib/Image/ExifTool/Lang/it.pm +1 -1
  86. package/bin/exiftool_files/lib/Image/ExifTool/Lang/ja.pm +1 -1
  87. package/bin/exiftool_files/lib/Image/ExifTool/Lang/ko.pm +1 -1
  88. package/bin/exiftool_files/lib/Image/ExifTool/Lang/nl.pm +1 -1
  89. package/bin/exiftool_files/lib/Image/ExifTool/Lang/pl.pm +1 -1
  90. package/bin/exiftool_files/lib/Image/ExifTool/Lang/ru.pm +1 -1
  91. package/bin/exiftool_files/lib/Image/ExifTool/Lang/sk.pm +1 -1
  92. package/bin/exiftool_files/lib/Image/ExifTool/Lang/sv.pm +1 -1
  93. package/bin/exiftool_files/lib/Image/ExifTool/Lang/tr.pm +1 -1
  94. package/bin/exiftool_files/lib/Image/ExifTool/Lang/zh_cn.pm +1 -1
  95. package/bin/exiftool_files/lib/Image/ExifTool/Lang/zh_tw.pm +1 -1
  96. package/bin/exiftool_files/lib/Image/ExifTool/Leaf.pm +1 -1
  97. package/bin/exiftool_files/lib/Image/ExifTool/LigoGPS.pm +409 -0
  98. package/bin/exiftool_files/lib/Image/ExifTool/Lytro.pm +1 -1
  99. package/bin/exiftool_files/lib/Image/ExifTool/M2TS.pm +58 -19
  100. package/bin/exiftool_files/lib/Image/ExifTool/MIE.pm +15 -6
  101. package/bin/exiftool_files/lib/Image/ExifTool/MIEUnits.pod +1 -1
  102. package/bin/exiftool_files/lib/Image/ExifTool/MIFF.pm +1 -1
  103. package/bin/exiftool_files/lib/Image/ExifTool/MISB.pm +1 -1
  104. package/bin/exiftool_files/lib/Image/ExifTool/MNG.pm +1 -1
  105. package/bin/exiftool_files/lib/Image/ExifTool/MOI.pm +1 -1
  106. package/bin/exiftool_files/lib/Image/ExifTool/MPC.pm +1 -1
  107. package/bin/exiftool_files/lib/Image/ExifTool/MPEG.pm +1 -1
  108. package/bin/exiftool_files/lib/Image/ExifTool/MPF.pm +1 -1
  109. package/bin/exiftool_files/lib/Image/ExifTool/MRC.pm +1 -1
  110. package/bin/exiftool_files/lib/Image/ExifTool/MWG.pm +1 -1
  111. package/bin/exiftool_files/lib/Image/ExifTool/MXF.pm +3 -3
  112. package/bin/exiftool_files/lib/Image/ExifTool/MacOS.pm +3 -2
  113. package/bin/exiftool_files/lib/Image/ExifTool/MakerNotes.pm +1 -1
  114. package/bin/exiftool_files/lib/Image/ExifTool/Matroska.pm +22 -6
  115. package/bin/exiftool_files/lib/Image/ExifTool/Microsoft.pm +2 -2
  116. package/bin/exiftool_files/lib/Image/ExifTool/Minolta.pm +1 -1
  117. package/bin/exiftool_files/lib/Image/ExifTool/MinoltaRaw.pm +1 -1
  118. package/bin/exiftool_files/lib/Image/ExifTool/Motorola.pm +1 -1
  119. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +495 -39
  120. package/bin/exiftool_files/lib/Image/ExifTool/NikonCapture.pm +1 -1
  121. package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +2 -2
  122. package/bin/exiftool_files/lib/Image/ExifTool/NikonSettings.pm +1 -1
  123. package/bin/exiftool_files/lib/Image/ExifTool/Nintendo.pm +1 -1
  124. package/bin/exiftool_files/lib/Image/ExifTool/OOXML.pm +8 -8
  125. package/bin/exiftool_files/lib/Image/ExifTool/Ogg.pm +1 -1
  126. package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +1 -1
  127. package/bin/exiftool_files/lib/Image/ExifTool/OpenEXR.pm +1 -1
  128. package/bin/exiftool_files/lib/Image/ExifTool/Opus.pm +1 -1
  129. package/bin/exiftool_files/lib/Image/ExifTool/Other.pm +1 -1
  130. package/bin/exiftool_files/lib/Image/ExifTool/PCX.pm +1 -1
  131. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +49 -18
  132. package/bin/exiftool_files/lib/Image/ExifTool/PGF.pm +1 -1
  133. package/bin/exiftool_files/lib/Image/ExifTool/PICT.pm +1 -1
  134. package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +4 -4
  135. package/bin/exiftool_files/lib/Image/ExifTool/PLUS.pm +1 -1
  136. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +20 -8
  137. package/bin/exiftool_files/lib/Image/ExifTool/PPM.pm +12 -3
  138. package/bin/exiftool_files/lib/Image/ExifTool/PSP.pm +1 -1
  139. package/bin/exiftool_files/lib/Image/ExifTool/Palm.pm +1 -1
  140. package/bin/exiftool_files/lib/Image/ExifTool/Panasonic.pm +27 -3
  141. package/bin/exiftool_files/lib/Image/ExifTool/PanasonicRaw.pm +1 -1
  142. package/bin/exiftool_files/lib/Image/ExifTool/Parrot.pm +1 -1
  143. package/bin/exiftool_files/lib/Image/ExifTool/Pentax.pm +1 -1
  144. package/bin/exiftool_files/lib/Image/ExifTool/PhaseOne.pm +6 -5
  145. package/bin/exiftool_files/lib/Image/ExifTool/PhotoCD.pm +1 -1
  146. package/bin/exiftool_files/lib/Image/ExifTool/PhotoMechanic.pm +1 -1
  147. package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +65 -4
  148. package/bin/exiftool_files/lib/Image/ExifTool/PostScript.pm +1 -1
  149. package/bin/exiftool_files/lib/Image/ExifTool/PrintIM.pm +1 -1
  150. package/bin/exiftool_files/lib/Image/ExifTool/Protobuf.pm +270 -0
  151. package/bin/exiftool_files/lib/Image/ExifTool/Qualcomm.pm +1 -1
  152. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +326 -88
  153. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +266 -200
  154. package/bin/exiftool_files/lib/Image/ExifTool/README +12 -2
  155. package/bin/exiftool_files/lib/Image/ExifTool/RIFF.pm +21 -6
  156. package/bin/exiftool_files/lib/Image/ExifTool/RSRC.pm +1 -1
  157. package/bin/exiftool_files/lib/Image/ExifTool/RTF.pm +2 -2
  158. package/bin/exiftool_files/lib/Image/ExifTool/Radiance.pm +1 -1
  159. package/bin/exiftool_files/lib/Image/ExifTool/Rawzor.pm +1 -1
  160. package/bin/exiftool_files/lib/Image/ExifTool/Real.pm +1 -1
  161. package/bin/exiftool_files/lib/Image/ExifTool/Reconyx.pm +1 -1
  162. package/bin/exiftool_files/lib/Image/ExifTool/Red.pm +1 -1
  163. package/bin/exiftool_files/lib/Image/ExifTool/Ricoh.pm +4 -4
  164. package/bin/exiftool_files/lib/Image/ExifTool/Samsung.pm +2 -2
  165. package/bin/exiftool_files/lib/Image/ExifTool/Sanyo.pm +1 -1
  166. package/bin/exiftool_files/lib/Image/ExifTool/Scalado.pm +1 -1
  167. package/bin/exiftool_files/lib/Image/ExifTool/Shift.pl +1 -1
  168. package/bin/exiftool_files/lib/Image/ExifTool/Shortcuts.pm +1 -1
  169. package/bin/exiftool_files/lib/Image/ExifTool/Sigma.pm +1 -1
  170. package/bin/exiftool_files/lib/Image/ExifTool/SigmaRaw.pm +1 -1
  171. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +5 -4
  172. package/bin/exiftool_files/lib/Image/ExifTool/SonyIDC.pm +1 -1
  173. package/bin/exiftool_files/lib/Image/ExifTool/Stim.pm +1 -1
  174. package/bin/exiftool_files/lib/Image/ExifTool/TagInfoXML.pm +6 -5
  175. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +7025 -6967
  176. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +477 -46
  177. package/bin/exiftool_files/lib/Image/ExifTool/Text.pm +4 -3
  178. package/bin/exiftool_files/lib/Image/ExifTool/Theora.pm +1 -1
  179. package/bin/exiftool_files/lib/Image/ExifTool/Torrent.pm +3 -3
  180. package/bin/exiftool_files/lib/Image/ExifTool/Unknown.pm +1 -1
  181. package/bin/exiftool_files/lib/Image/ExifTool/VCard.pm +3 -3
  182. package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +6 -6
  183. package/bin/exiftool_files/lib/Image/ExifTool/Vivo.pm +124 -0
  184. package/bin/exiftool_files/lib/Image/ExifTool/Vorbis.pm +1 -1
  185. package/bin/exiftool_files/lib/Image/ExifTool/WPG.pm +1 -1
  186. package/bin/exiftool_files/lib/Image/ExifTool/WTV.pm +1 -1
  187. package/bin/exiftool_files/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  188. package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +3 -3
  189. package/bin/exiftool_files/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  190. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -1
  191. package/bin/exiftool_files/lib/Image/ExifTool/WritePNG.pl +1 -1
  192. package/bin/exiftool_files/lib/Image/ExifTool/WritePhotoshop.pl +1 -1
  193. package/bin/exiftool_files/lib/Image/ExifTool/WritePostScript.pl +1 -1
  194. package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +166 -79
  195. package/bin/exiftool_files/lib/Image/ExifTool/WriteRIFF.pl +17 -6
  196. package/bin/exiftool_files/lib/Image/ExifTool/WriteXMP.pl +3 -3
  197. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +89 -96
  198. package/bin/exiftool_files/lib/Image/ExifTool/XISF.pm +1 -1
  199. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +39 -14
  200. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +103 -1
  201. package/bin/exiftool_files/lib/Image/ExifTool/XMPStruct.pl +2 -3
  202. package/bin/exiftool_files/lib/Image/ExifTool/ZIP.pm +2 -2
  203. package/bin/exiftool_files/lib/Image/ExifTool/ZISRAW.pm +1 -1
  204. package/bin/exiftool_files/lib/Image/ExifTool/iWork.pm +1 -1
  205. package/bin/exiftool_files/lib/Image/ExifTool.pm +338 -166
  206. package/bin/exiftool_files/lib/Image/ExifTool.pod +119 -73
  207. package/bin/exiftool_files/readme_windows.txt +8 -13
  208. package/bin/exiftool_files/windows_exiftool.txt +102 -53
  209. package/package.json +11 -11
@@ -504,7 +504,7 @@ Capture.
504
504
 
505
505
  =head1 AUTHOR
506
506
 
507
- Copyright 2003-2024, Phil Harvey (philharvey66 at gmail.com)
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-2024, Phil Harvey (philharvey66 at gmail.com)
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.25';
35
+ $VERSION = '1.29';
36
36
 
37
37
  # program map table "stream_type" lookup (ref 6/1/9)
38
38
  my %streamType = (
@@ -82,7 +82,7 @@ my %streamType = (
82
82
  0x86 => 'DTS-HD Audio',
83
83
  0x87 => 'E-AC-3 Audio',
84
84
  0x8a => 'DTS Audio',
85
- 0x90 => 'PGS Audio', #https://www.avsforum.com/threads/bass-eq-for-filtered-movies.2995212/page-399
85
+ 0x90 => 'Presentation Graphic Stream (subtitle)', #https://en.wikipedia.org/wiki/Program-specific_information
86
86
  0x91 => 'A52b/AC-3 Audio',
87
87
  0x92 => 'DVD_SPU vls Subtitle',
88
88
  0x94 => 'SDDS Audio',
@@ -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->WarnOnce('The ExtractEmbedded option may find more tags in the video data',3);
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,,*cs
432
- # $GPRMC,172255.00,A,:985.95194,N,17170.14674,W,029.678,170.68,240822,,,D*7B
433
- # $GPRMC,172355.00,A,:984.76779,N,17170.00473,W,032.219,172.04,240822,,,D*7B
434
- # ddmm.mmmm: from 4742.2568 12209.2028 (should be)
435
- # to 4741.7696 12209.1056
436
- # stamped on video: 47.70428N, 122.15338W, 35mph (dd.ddddd)
437
- # to 47.69616N, 122.15176W, 37mph
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->WarnOnce('Error decrypting GPS degrees');
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->WarnOnce('Decryption of this GPS is highly experimental. More testing samples are required');
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->WarnOnce('GPSLatitude/Longitude encryption is not yet known, so these will be wrong');
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
- # decode payload data
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\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg;
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\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg;
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-2024, Phil Harvey (philharvey66 at gmail.com)
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.54';
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
- $len += 10; # include length of terminator itself
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
- $$et{RATIONAL}{$key} = $rational if defined $rational and defined $key;
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-2024, Phil Harvey (philharvey66 at gmail.com)
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