exiftool_vendored 13.36.0 → 13.37.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +23 -0
  3. data/bin/MANIFEST +5 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +2 -2
  7. data/bin/build_geolocation +7 -3
  8. data/bin/exiftool +2 -2
  9. data/bin/lib/Image/ExifTool/Audible.pm +1 -1
  10. data/bin/lib/Image/ExifTool/BMP.pm +1 -1
  11. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +15 -8
  12. data/bin/lib/Image/ExifTool/CBOR.pm +1 -1
  13. data/bin/lib/Image/ExifTool/Canon.pm +59 -5
  14. data/bin/lib/Image/ExifTool/CanonVRD.pm +1 -1
  15. data/bin/lib/Image/ExifTool/CaptureOne.pm +1 -1
  16. data/bin/lib/Image/ExifTool/DJI.pm +5 -5
  17. data/bin/lib/Image/ExifTool/DV.pm +1 -1
  18. data/bin/lib/Image/ExifTool/EXE.pm +3 -2
  19. data/bin/lib/Image/ExifTool/FLIF.pm +1 -1
  20. data/bin/lib/Image/ExifTool/FLIR.pm +3 -3
  21. data/bin/lib/Image/ExifTool/FlashPix.pm +1 -1
  22. data/bin/lib/Image/ExifTool/FujiFilm.pm +5 -4
  23. data/bin/lib/Image/ExifTool/GIF.pm +1 -1
  24. data/bin/lib/Image/ExifTool/GM.pm +1 -1
  25. data/bin/lib/Image/ExifTool/Geolocation.pm +3 -1
  26. data/bin/lib/Image/ExifTool/Geotag.pm +10 -2
  27. data/bin/lib/Image/ExifTool/GoPro.pm +5 -5
  28. data/bin/lib/Image/ExifTool/Google.pm +804 -0
  29. data/bin/lib/Image/ExifTool/H264.pm +1 -1
  30. data/bin/lib/Image/ExifTool/ICC_Profile.pm +1 -1
  31. data/bin/lib/Image/ExifTool/ID3.pm +3 -3
  32. data/bin/lib/Image/ExifTool/JPEG.pm +1 -1
  33. data/bin/lib/Image/ExifTool/JSON.pm +1 -1
  34. data/bin/lib/Image/ExifTool/LIF.pm +1 -1
  35. data/bin/lib/Image/ExifTool/LNK.pm +2 -2
  36. data/bin/lib/Image/ExifTool/Lytro.pm +1 -1
  37. data/bin/lib/Image/ExifTool/M2TS.pm +2 -2
  38. data/bin/lib/Image/ExifTool/MPEG.pm +1 -1
  39. data/bin/lib/Image/ExifTool/MWG.pm +1 -1
  40. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  41. data/bin/lib/Image/ExifTool/MacOS.pm +2 -2
  42. data/bin/lib/Image/ExifTool/MakerNotes.pm +30 -7
  43. data/bin/lib/Image/ExifTool/Microsoft.pm +4 -4
  44. data/bin/lib/Image/ExifTool/Nikon.pm +4 -4
  45. data/bin/lib/Image/ExifTool/OOXML.pm +1 -1
  46. data/bin/lib/Image/ExifTool/Ogg.pm +1 -1
  47. data/bin/lib/Image/ExifTool/Olympus.pm +2 -1
  48. data/bin/lib/Image/ExifTool/Other.pm +1 -1
  49. data/bin/lib/Image/ExifTool/Panasonic.pm +1 -1
  50. data/bin/lib/Image/ExifTool/Pentax.pm +18 -7
  51. data/bin/lib/Image/ExifTool/Protobuf.pm +12 -6
  52. data/bin/lib/Image/ExifTool/Qualcomm.pm +2 -2
  53. data/bin/lib/Image/ExifTool/QuickTime.pm +30 -8
  54. data/bin/lib/Image/ExifTool/README +7 -6
  55. data/bin/lib/Image/ExifTool/Rawzor.pm +1 -1
  56. data/bin/lib/Image/ExifTool/Reconyx.pm +375 -91
  57. data/bin/lib/Image/ExifTool/Samsung.pm +1 -1
  58. data/bin/lib/Image/ExifTool/Shortcuts.pm +8 -5
  59. data/bin/lib/Image/ExifTool/Sony.pm +18 -3
  60. data/bin/lib/Image/ExifTool/TagInfoXML.pm +3 -2
  61. data/bin/lib/Image/ExifTool/TagLookup.pm +5745 -5695
  62. data/bin/lib/Image/ExifTool/TagNames.pod +662 -509
  63. data/bin/lib/Image/ExifTool/Text.pm +1 -1
  64. data/bin/lib/Image/ExifTool/Trailer.pm +1 -1
  65. data/bin/lib/Image/ExifTool/WPG.pm +1 -1
  66. data/bin/lib/Image/ExifTool/WTV.pm +1 -1
  67. data/bin/lib/Image/ExifTool/XMP.pm +31 -30
  68. data/bin/lib/Image/ExifTool/XMP2.pl +0 -482
  69. data/bin/lib/Image/ExifTool/ZIP.pm +1 -1
  70. data/bin/lib/Image/ExifTool/iWork.pm +1 -1
  71. data/bin/lib/Image/ExifTool.pm +4 -4
  72. data/bin/lib/Image/ExifTool.pod +3 -3
  73. data/bin/perl-Image-ExifTool.spec +1 -1
  74. data/lib/exiftool_vendored/version.rb +1 -1
  75. metadata +4 -6
@@ -0,0 +1,804 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: Google.pm
3
+ #
4
+ # Description: Google maker notes and XMP tags
5
+ #
6
+ # Revisions: 2025-09-17 - P. Harvey Created
7
+ #
8
+ # References: 1) https://github.com/jakiki6/ruminant/blob/master/ruminant/modules/images.py
9
+ #------------------------------------------------------------------------------
10
+
11
+ package Image::ExifTool::Google;
12
+
13
+ use strict;
14
+ use vars qw($VERSION);
15
+ use Image::ExifTool qw(:DataAccess :Utils);
16
+ use Image::ExifTool::XMP;
17
+
18
+ $VERSION = '1.00';
19
+
20
+ sub ProcessHDRP($$$);
21
+
22
+ # default formats based on Google format size
23
+ my @formatName = ( undef, 'string', 'int16s', undef, 'int32s' );
24
+
25
+ my %sPose = (
26
+ STRUCT_NAME => 'Google Pose',
27
+ NAMESPACE => { Pose => 'http://ns.google.com/photos/dd/1.0/pose/' },
28
+ PositionX => { Writable => 'real', Groups => { 2 => 'Location' } },
29
+ PositionY => { Writable => 'real', Groups => { 2 => 'Location' } },
30
+ PositionZ => { Writable => 'real', Groups => { 2 => 'Location' } },
31
+ RotationX => { Writable => 'real', Groups => { 2 => 'Location' } },
32
+ RotationY => { Writable => 'real', Groups => { 2 => 'Location' } },
33
+ RotationZ => { Writable => 'real', Groups => { 2 => 'Location' } },
34
+ RotationW => { Writable => 'real', Groups => { 2 => 'Location' } },
35
+ Timestamp => {
36
+ Writable => 'integer',
37
+ Shift => 'Time',
38
+ Groups => { 2 => 'Time' },
39
+ ValueConv => 'ConvertUnixTime($val / 1000, 1, 3)',
40
+ ValueConvInv => 'int(GetUnixTime($val, 1) * 1000)',
41
+ PrintConv => '$self->ConvertDateTime($val)',
42
+ PrintConvInv => '$self->InverseDateTime($val,undef,1)',
43
+ },
44
+ );
45
+ my %sEarthPose = (
46
+ STRUCT_NAME => 'Google EarthPose',
47
+ NAMESPACE => { EarthPose => 'http://ns.google.com/photos/dd/1.0/earthpose/' },
48
+ Latitude => {
49
+ Writable => 'real',
50
+ Groups => { 2 => 'Location' },
51
+ ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
52
+ ValueConvInv => '$val',
53
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
54
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
55
+ },
56
+ Longitude => {
57
+ Writable => 'real',
58
+ Groups => { 2 => 'Location' },
59
+ ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
60
+ ValueConvInv => '$val',
61
+ PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
62
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
63
+ },
64
+ Altitude => {
65
+ Writable => 'real',
66
+ Groups => { 2 => 'Location' },
67
+ PrintConv => '"$val m"',
68
+ PrintConvInv => '$val=~s/\s*m$//;$val',
69
+ },
70
+ RotationX => { Writable => 'real', Groups => { 2 => 'Location' } },
71
+ RotationY => { Writable => 'real', Groups => { 2 => 'Location' } },
72
+ RotationZ => { Writable => 'real', Groups => { 2 => 'Location' } },
73
+ RotationW => { Writable => 'real', Groups => { 2 => 'Location' } },
74
+ Timestamp => {
75
+ Writable => 'integer',
76
+ Shift => 'Time',
77
+ Groups => { 2 => 'Time' },
78
+ ValueConv => 'ConvertUnixTime($val / 1000, 1, 3)',
79
+ ValueConvInv => 'int(GetUnixTime($val, 1) * 1000)',
80
+ PrintConv => '$self->ConvertDateTime($val)',
81
+ PrintConvInv => '$self->InverseDateTime($val,undef,1)',
82
+ },
83
+ );
84
+ my %sVendorInfo = (
85
+ STRUCT_NAME => 'Google VendorInfo',
86
+ NAMESPACE => { VendorInfo => 'http://ns.google.com/photos/dd/1.0/vendorinfo/' },
87
+ Model => { },
88
+ Manufacturer => { },
89
+ Notes => { },
90
+ );
91
+ my %sAppInfo = (
92
+ STRUCT_NAME => 'Google AppInfo',
93
+ NAMESPACE => { AppInfo => 'http://ns.google.com/photos/dd/1.0/appinfo/' },
94
+ Application => { },
95
+ Version => { },
96
+ ItemURI => { },
97
+ );
98
+
99
+ # Google audio namespace
100
+ %Image::ExifTool::Google::GAudio = (
101
+ %Image::ExifTool::XMP::xmpTableDefaults,
102
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GAudio', 2 => 'Audio' },
103
+ NAMESPACE => 'GAudio',
104
+ Data => {
105
+ Name => 'AudioData',
106
+ ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
107
+ ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
108
+ },
109
+ Mime => { Name => 'AudioMimeType' },
110
+ );
111
+
112
+ # Google image namespace
113
+ %Image::ExifTool::Google::GImage = (
114
+ %Image::ExifTool::XMP::xmpTableDefaults,
115
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GImage', 2 => 'Image' },
116
+ NAMESPACE => 'GImage',
117
+ Data => {
118
+ Name => 'ImageData',
119
+ ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
120
+ ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
121
+ },
122
+ Mime => { Name => 'ImageMimeType' },
123
+ );
124
+
125
+ # Google panorama namespace properties
126
+ # (ref https://exiftool.org/forum/index.php/topic,4569.0.html)
127
+ %Image::ExifTool::Google::GPano = (
128
+ %Image::ExifTool::XMP::xmpTableDefaults,
129
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GPano', 2 => 'Image' },
130
+ NAMESPACE => 'GPano',
131
+ NOTES => q{
132
+ Panorama tags written by Google Photosphere. See
133
+ L<https://developers.google.com/panorama/metadata/> for the specification.
134
+ },
135
+ UsePanoramaViewer => { Writable => 'boolean' },
136
+ CaptureSoftware => { },
137
+ StitchingSoftware => { },
138
+ ProjectionType => { },
139
+ PoseHeadingDegrees => { Writable => 'real' },
140
+ PosePitchDegrees => { Writable => 'real' },
141
+ PoseRollDegrees => { Writable => 'real' },
142
+ InitialViewHeadingDegrees => { Writable => 'real' },
143
+ InitialViewPitchDegrees => { Writable => 'real' },
144
+ InitialViewRollDegrees => { Writable => 'real' },
145
+ InitialHorizontalFOVDegrees => { Writable => 'real' },
146
+ InitialVerticalFOVDegrees => { Writable => 'real' },
147
+ FirstPhotoDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time' } },
148
+ LastPhotoDate => { %Image::ExifTool::XMP::dateTimeInfo, Groups => { 2 => 'Time' } },
149
+ SourcePhotosCount => { Writable => 'integer' },
150
+ ExposureLockUsed => { Writable => 'boolean' },
151
+ CroppedAreaImageWidthPixels => { Writable => 'real' },
152
+ CroppedAreaImageHeightPixels => { Writable => 'real' },
153
+ FullPanoWidthPixels => { Writable => 'real' },
154
+ FullPanoHeightPixels => { Writable => 'real' },
155
+ CroppedAreaLeftPixels => { Writable => 'real' },
156
+ CroppedAreaTopPixels => { Writable => 'real' },
157
+ InitialCameraDolly => { Writable => 'real' },
158
+ # (the following have been observed, but are not in the specification)
159
+ LargestValidInteriorRectLeft => { Writable => 'real' },
160
+ LargestValidInteriorRectTop => { Writable => 'real' },
161
+ LargestValidInteriorRectWidth => { Writable => 'real' },
162
+ LargestValidInteriorRectHeight => { Writable => 'real' },
163
+ );
164
+
165
+ # Google Spherical Images namespace (ref https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md)
166
+ %Image::ExifTool::Google::GSpherical = (
167
+ %Image::ExifTool::XMP::xmpTableDefaults,
168
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GSpherical', 2 => 'Image' },
169
+ WRITE_GROUP => 'GSpherical', # write in special location for video files
170
+ NAMESPACE => 'GSpherical',
171
+ AVOID => 1,
172
+ NOTES => q{
173
+ Not actually XMP. These RDF/XML tags are used in Google spherical MP4
174
+ videos. These tags are written into the video track of MOV/MP4 files, and
175
+ not at the top level like other XMP tags. See
176
+ L<https://github.com/google/spatial-media/blob/master/docs/spherical-video-rfc.md>
177
+ for the specification.
178
+ },
179
+ # (avoid due to conflicts with XMP-GPano tags)
180
+ Spherical => { Writable => 'boolean' },
181
+ Stitched => { Writable => 'boolean' },
182
+ StitchingSoftware => { },
183
+ ProjectionType => { },
184
+ StereoMode => { },
185
+ SourceCount => { Writable => 'integer' },
186
+ InitialViewHeadingDegrees => { Writable => 'real' },
187
+ InitialViewPitchDegrees => { Writable => 'real' },
188
+ InitialViewRollDegrees => { Writable => 'real' },
189
+ Timestamp => {
190
+ Name => 'TimeStamp',
191
+ Groups => { 2 => 'Time' },
192
+ Writable => 'integer',
193
+ Shift => 'Time',
194
+ ValueConv => 'ConvertUnixTime($val)', #(NC)
195
+ ValueConvInv => 'GetUnixTime($val)',
196
+ PrintConv => '$self->ConvertDateTime($val)',
197
+ PrintConvInv => '$self->InverseDateTime($val)',
198
+ },
199
+ FullPanoWidthPixels => { Writable => 'integer' },
200
+ FullPanoHeightPixels => { Writable => 'integer' },
201
+ CroppedAreaImageWidthPixels => { Writable => 'integer' },
202
+ CroppedAreaImageHeightPixels=> { Writable => 'integer' },
203
+ CroppedAreaLeftPixels => { Writable => 'integer' },
204
+ CroppedAreaTopPixels => { Writable => 'integer' },
205
+ );
206
+
207
+ # Google depthmap information (ref https://developers.google.com/depthmap-metadata/reference)
208
+ %Image::ExifTool::Google::GDepth = (
209
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GDepth', 2 => 'Image' },
210
+ NAMESPACE => 'GDepth',
211
+ AVOID => 1, # (too many potential tag name conflicts)
212
+ NOTES => q{
213
+ Google depthmap information. See
214
+ L<https://developers.google.com/depthmap-metadata/> for the specification.
215
+ },
216
+ WRITABLE => 'string', # (default to string-type tags)
217
+ PRIORITY => 0,
218
+ Format => {
219
+ PrintConv => {
220
+ RangeInverse => 'RangeInverse',
221
+ RangeLinear => 'RangeLinear',
222
+ },
223
+ },
224
+ Near => { Writable => 'real' },
225
+ Far => { Writable => 'real' },
226
+ Mime => { },
227
+ Data => {
228
+ Name => 'DepthImage',
229
+ ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
230
+ ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
231
+ },
232
+ Units => { },
233
+ MeasureType => {
234
+ PrintConv => {
235
+ OpticalAxis => 'OpticalAxis',
236
+ OpticalRay => 'OpticalRay',
237
+ },
238
+ },
239
+ ConfidenceMime => { },
240
+ Confidence => {
241
+ ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
242
+ ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
243
+ },
244
+ Manufacturer=> { },
245
+ Model => { },
246
+ Software => { },
247
+ ImageWidth => { Writable => 'real' },
248
+ ImageHeight => { Writable => 'real' },
249
+ );
250
+
251
+ # Google focus namespace
252
+ %Image::ExifTool::Google::GFocus = (
253
+ %Image::ExifTool::XMP::xmpTableDefaults,
254
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GFocus', 2 => 'Image' },
255
+ NAMESPACE => 'GFocus',
256
+ NOTES => 'Focus information found in Google depthmap images.',
257
+ BlurAtInfinity => { Writable => 'real' },
258
+ FocalDistance => { Writable => 'real' },
259
+ FocalPointX => { Writable => 'real' },
260
+ FocalPointY => { Writable => 'real' },
261
+ );
262
+
263
+ # Google camera namespace (ref PH)
264
+ %Image::ExifTool::Google::GCamera = (
265
+ %Image::ExifTool::XMP::xmpTableDefaults,
266
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GCamera', 2 => 'Camera' },
267
+ NAMESPACE => 'GCamera',
268
+ NOTES => 'Camera information found in Google panorama images.',
269
+ BurstID => { },
270
+ BurstPrimary => { },
271
+ PortraitNote => { },
272
+ PortraitRequest => {
273
+ Writable => 'string', # (writable in encoded format)
274
+ Binary => 1,
275
+ SubDirectory => { TagTable => 'Image::ExifTool::Google::HDRPMakerNote' },
276
+ },
277
+ PortraitVersion => { },
278
+ SpecialTypeID => { List => 'Bag' },
279
+ PortraitNote => { },
280
+ DisableAutoCreation => { List => 'Bag' },
281
+ DisableSuggestedAction => { List => 'Bag' }, #forum16147
282
+ hdrp_makernote => {
283
+ Name => 'HDRPMakerNote',
284
+ Writable => 'string', # (writable in encoded format)
285
+ Binary => 1,
286
+ SubDirectory => { TagTable => 'Image::ExifTool::Google::HDRPMakerNote' },
287
+ },
288
+ MicroVideo => { Writable => 'integer' },
289
+ MicroVideoVersion => { Writable => 'integer' },
290
+ MicroVideoOffset => { Writable => 'integer' },
291
+ MicroVideoPresentationTimestampUs => { Writable => 'integer' },
292
+ shot_log_data => { #forum14108
293
+ Name => 'ShotLogData',
294
+ Writable => 'string', # (writable in encoded format)
295
+ IsProtobuf => 1,
296
+ Binary => 1,
297
+ SubDirectory => { TagTable => 'Image::ExifTool::Google::ShotLogData' },
298
+ },
299
+ HdrPlusMakernote => {
300
+ Name => 'HDRPlusMakerNote',
301
+ Writable => 'string', # (writable in encoded format)
302
+ Binary => 1,
303
+ SubDirectory => { TagTable => 'Image::ExifTool::Google::HDRPlusMakerNote' },
304
+ },
305
+ MotionPhoto => { Writable => 'integer' },
306
+ MotionPhotoVersion => { Writable => 'integer' },
307
+ MotionPhotoPresentationTimestampUs => { Writable => 'integer' },
308
+ );
309
+
310
+ # Google creations namespace (ref PH)
311
+ %Image::ExifTool::Google::GCreations = (
312
+ %Image::ExifTool::XMP::xmpTableDefaults,
313
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GCreations', 2 => 'Camera' },
314
+ NAMESPACE => 'GCreations',
315
+ NOTES => 'Google creations tags.',
316
+ CameraBurstID => { },
317
+ Type => { Avoid => 1 },
318
+ );
319
+
320
+ # Google depth-map Device namespace (ref 13)
321
+ %Image::ExifTool::Google::Device = (
322
+ %Image::ExifTool::XMP::xmpTableDefaults,
323
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-Device', 2 => 'Camera' },
324
+ NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' },
325
+ NOTES => q{
326
+ Google depth-map Device tags. See
327
+ L<https://developer.android.com/training/camera2/Dynamic-depth-v1.0.pdf> for
328
+ the specification.
329
+ },
330
+ Container => {
331
+ Struct => {
332
+ STRUCT_NAME => 'Google DeviceContainer',
333
+ NAMESPACE => { Container => 'http://ns.google.com/photos/dd/1.0/container/' },
334
+ Directory => {
335
+ List => 'Seq',
336
+ Struct => {
337
+ STRUCT_NAME => 'Google DeviceDirectory',
338
+ NAMESPACE => { Container => 'http://ns.google.com/photos/dd/1.0/container/' },
339
+ Item => {
340
+ Struct => {
341
+ STRUCT_NAME => 'Google DeviceItem',
342
+ NAMESPACE => { Item => 'http://ns.google.com/photos/dd/1.0/item/' },
343
+ # use this as a key to process Google trailer
344
+ Mime => { RawConv => '$$self{ProcessGoogleTrailer} = $val' },
345
+ Length => { Writable => 'integer' },
346
+ Padding => { Writable => 'integer' },
347
+ DataURI => { },
348
+ },
349
+ },
350
+ },
351
+ }
352
+ },
353
+ },
354
+ Profiles => {
355
+ List => 'Seq',
356
+ FlatName => '',
357
+ Struct => {
358
+ STRUCT_NAME => 'Google DeviceProfiles',
359
+ NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' },
360
+ Profile => {
361
+ Struct => {
362
+ STRUCT_NAME => 'Google DeviceProfile',
363
+ NAMESPACE => { Profile => 'http://ns.google.com/photos/dd/1.0/profile/' },
364
+ CameraIndices => { List => 'Seq', Writable => 'integer' },
365
+ Type => { },
366
+ },
367
+ },
368
+ },
369
+ },
370
+ Cameras => {
371
+ List => 'Seq',
372
+ FlatName => '',
373
+ Struct => {
374
+ STRUCT_NAME => 'Google DeviceCameras',
375
+ NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' },
376
+ Camera => {
377
+ Struct => {
378
+ STRUCT_NAME => 'Google DeviceCamera',
379
+ NAMESPACE => { Camera => 'http://ns.google.com/photos/dd/1.0/camera/' },
380
+ DepthMap => {
381
+ Struct => {
382
+ STRUCT_NAME => 'Google DeviceDepthMap',
383
+ NAMESPACE => { DepthMap => 'http://ns.google.com/photos/dd/1.0/depthmap/' },
384
+ ConfidenceURI => { },
385
+ DepthURI => { },
386
+ Far => { Writable => 'real' },
387
+ Format => { },
388
+ ItemSemantic=> { },
389
+ MeasureType => { },
390
+ Near => { Writable => 'real' },
391
+ Units => { },
392
+ Software => { },
393
+ FocalTableEntryCount => { Writable => 'integer' },
394
+ FocalTable => { }, # (base64)
395
+ },
396
+ },
397
+ Image => {
398
+ Struct => {
399
+ STRUCT_NAME => 'Google DeviceImage',
400
+ NAMESPACE => { Image => 'http://ns.google.com/photos/dd/1.0/image/' },
401
+ ItemSemantic=> { },
402
+ ItemURI => { },
403
+ },
404
+ },
405
+ ImagingModel => {
406
+ Struct => {
407
+ STRUCT_NAME => 'Google DeviceImagingModel',
408
+ NAMESPACE => { ImagingModel => 'http://ns.google.com/photos/dd/1.0/imagingmodel/' },
409
+ Distortion => { }, # (base64)
410
+ DistortionCount => { Writable => 'integer' },
411
+ FocalLengthX => { Writable => 'real' },
412
+ FocalLengthY => { Writable => 'real' },
413
+ ImageHeight => { Writable => 'integer' },
414
+ ImageWidth => { Writable => 'integer' },
415
+ PixelAspectRatio=> { Writable => 'real' },
416
+ PrincipalPointX => { Writable => 'real' },
417
+ PrincipalPointY => { Writable => 'real' },
418
+ Skew => { Writable => 'real' },
419
+ },
420
+ },
421
+ PointCloud => {
422
+ Struct => {
423
+ STRUCT_NAME => 'Google DevicePointCloud',
424
+ NAMESPACE => { PointCloud => 'http://ns.google.com/photos/dd/1.0/pointcloud/' },
425
+ PointCloud => { Writable => 'integer' },
426
+ Points => { },
427
+ Metric => { Writable => 'boolean' },
428
+ },
429
+ },
430
+ Pose => { Struct => \%sPose },
431
+ LightEstimate => {
432
+ Struct => {
433
+ STRUCT_NAME => 'Google DeviceLightEstimate',
434
+ NAMESPACE => { LightEstimate => 'http://ns.google.com/photos/dd/1.0/lightestimate/' },
435
+ ColorCorrectionR => { Writable => 'real' },
436
+ ColorCorrectionG => { Writable => 'real' },
437
+ ColorCorrectionB => { Writable => 'real' },
438
+ PixelIntensity => { Writable => 'real' },
439
+ },
440
+ },
441
+ VendorInfo => { Struct => \%sVendorInfo },
442
+ AppInfo => { Struct => \%sAppInfo },
443
+ Trait => { },
444
+ },
445
+ },
446
+ },
447
+ },
448
+ VendorInfo => { Struct => \%sVendorInfo },
449
+ AppInfo => { Struct => \%sAppInfo },
450
+ EarthPos => { Struct => \%sEarthPose },
451
+ Pose => { Struct => \%sPose },
452
+ Planes => {
453
+ List => 'Seq',
454
+ FlatName => '',
455
+ Struct => {
456
+ STRUCT_NAME => 'Google DevicePlanes',
457
+ NAMESPACE => { Device => 'http://ns.google.com/photos/dd/1.0/device/' },
458
+ Plane => {
459
+ Struct => {
460
+ STRUCT_NAME => 'Google DevicePlane',
461
+ NAMESPACE => { Plane => 'http://ns.google.com/photos/dd/1.0/plane/' },
462
+ Pose => { Struct => \%sPose },
463
+ ExtentX => { Writable => 'real' },
464
+ ExtentZ => { Writable => 'real' },
465
+ BoundaryVertexCount => { Writable => 'integer' },
466
+ Boundary => { },
467
+ },
468
+ },
469
+ },
470
+ },
471
+ );
472
+
473
+ # Google container tags (ref https://developer.android.com/guide/topics/media/platform/hdr-image-format)
474
+ # NOTE: The namespace prefix used by ExifTool is 'GContainer' instead of 'Container'
475
+ # dueo to a conflict with Google's depth-map Device 'Container' namespace!
476
+ # (see ../pics/GooglePixel8Pro.jpg sample image)
477
+ %Image::ExifTool::Google::GContainer = (
478
+ %Image::ExifTool::XMP::xmpTableDefaults,
479
+ GROUPS => { 0 => 'XMP', 1 => 'XMP-GContainer', 2 => 'Image' },
480
+ NAMESPACE => 'GContainer',
481
+ NOTES => q{
482
+ Google Container namespace. ExifTool uses the prefix 'GContainer' instead
483
+ of 'Container' to avoid a conflict with the Google Device Container
484
+ namespace.
485
+ },
486
+ Directory => {
487
+ Name => 'ContainerDirectory',
488
+ FlatName => 'Directory',
489
+ List => 'Seq',
490
+ Struct => {
491
+ STRUCT_NAME => 'Google Directory',
492
+ Item => {
493
+ Namespace => 'GContainer',
494
+ Struct => {
495
+ STRUCT_NAME => 'Google Item',
496
+ # (use 'GItem' to avoid conflict with Google Device Container Item)
497
+ NAMESPACE => { GItem => 'http://ns.google.com/photos/1.0/container/item/'},
498
+ Mime => { RawConv => '$$self{ProcessGoogleTrailer} = $val' },
499
+ Semantic => { },
500
+ Length => { Writable => 'integer' },
501
+ Label => { },
502
+ Padding => { Writable => 'integer' },
503
+ URI => { },
504
+ },
505
+ },
506
+ },
507
+ },
508
+ );
509
+
510
+ # DHRP maker notes (ref PH)
511
+ %Image::ExifTool::Google::HDRPlusMakerNote = (
512
+ GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
513
+ TAG_PREFIX => 'HDRPlusMakerNote',
514
+ PROCESS_PROC => \&ProcessHDRP,
515
+ VARS => { ID_FMT => 'str' },
516
+ NOTES => q{
517
+ Google protobuf-format HDR-Plus maker notes. Tag ID's are hierarchical
518
+ protobuf field numbers. Stored as base64-encoded, encrypted and gzipped
519
+ Protobuf data.
520
+ },
521
+ '1-1' => 'ImageName',
522
+ '1-2' => { Name => 'ImageData', Format => 'undef', Binary => 1 },
523
+ '2' => { Name => 'TimeLogText', Binary => 1 },
524
+ '3' => { Name => 'SummaryText', Binary => 1 },
525
+ '9-3' => { Name => 'FrameCount', Format => 'unsigned' },
526
+ # 9-4 - smaller for larger focal lengths
527
+ '9-36-1' => {
528
+ Name => 'CreateDate',
529
+ Groups => { 2 => 'Time' },
530
+ Format => 'unsigned',
531
+ Priority => 0, # (to give EXIF priority)
532
+ ValueConv => 'ConvertUnixTime($val, 1)',
533
+ PrintConv => '$self->ConvertDateTime($val)',
534
+ },
535
+ '12-1' => { Name => 'DeviceMake', Groups => { 2 => 'Device' } },
536
+ '12-2' => { Name => 'DeviceModel', Groups => { 2 => 'Device' } },
537
+ '12-3' => { Name => 'DeviceCodename', Groups => { 2 => 'Device' } },
538
+ '12-4' => { Name => 'DeviceHardwareRevision', Groups => { 2 => 'Device' } },
539
+ '12-6' => { Name => 'HDRPSoftware', Groups => { 2 => 'Device' } },
540
+ '12-7' => { Name => 'AndroidRelease', Groups => { 2 => 'Device' } },
541
+ '12-8' => {
542
+ Name => 'SoftwareDate',
543
+ Groups => { 2 => 'Time' },
544
+ Format => 'unsigned',
545
+ ValueConv => 'ConvertUnixTime($val / 1000, 1, 3)',
546
+ PrintConv => '$self->ConvertDateTime($val)',
547
+ },
548
+ '12-9' => { Name => 'Application', Groups => { 2 => 'Device' } },
549
+ '12-10' => { Name => 'AppVersion', Groups => { 2 => 'Device' } },
550
+ '12-12-1' => {
551
+ Name => 'ExposureTimeMin',
552
+ Groups => { 2 => 'Camera' },
553
+ Format => 'float',
554
+ ValueConv => '$val / 1000',
555
+ },
556
+ '12-12-2' => {
557
+ Name => 'ExposureTimeMax',
558
+ Groups => { 2 => 'Camera' },
559
+ Format => 'float',
560
+ ValueConv => '$val / 1000',
561
+ },
562
+ '12-13-1' => { Name => 'ISOMin', Format => 'float', Groups => { 2 => 'Camera' } }, # (NC)
563
+ '12-13-2' => { Name => 'ISOMax', Format => 'float', Groups => { 2 => 'Camera' } }, # (NC)
564
+ '12-14' => { Name => 'MaxAnalogISO', Format => 'float', Groups => { 2 => 'Camera' } }, # (NC)
565
+ );
566
+
567
+ %Image::ExifTool::Google::ShotLogData = (
568
+ GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
569
+ TAG_PREFIX => 'ShotLogData',
570
+ PROCESS_PROC => \&ProcessHDRP,
571
+ VARS => { ID_FMT => 'str' },
572
+ NOTES => 'Stored as base64-encoded, encrypted and gzipped Protobuf data.',
573
+ 2 => { Name => 'MeteringFrameCount', Format => 'unsigned' }, # (NC)
574
+ 3 => { Name => 'OriginalPayloadFrameCount', Format => 'unsigned' }, # (NC)
575
+ # 1-6 - pure_fraction_of_pixels_from_long_exposure?
576
+ # 1-7 - weighted_fraction_of_pixels_from_long_exposure?
577
+ );
578
+
579
+ %Image::ExifTool::Google::HDRPMakerNote = (
580
+ GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
581
+ TAG_PREFIX => '',
582
+ PROCESS_PROC => \&ProcessHDRP,
583
+ VARS => { LONG_TAGS => 1 },
584
+ NOTES => q{
585
+ Google text-based HDRP maker note tags. Stored as base64-encoded,
586
+ encrypted and gzipped text.
587
+ },
588
+ 'InitParams' => { Name => 'InitParamsText', Binary => 1 },
589
+ 'Logging metadata' => { Name => 'LoggingMetadataText', Binary => 1 },
590
+ 'Merged image' => { Name => 'MergedImage', Binary => 1 },
591
+ 'Finished image' => { Name => 'FinishedImage', Binary => 1 },
592
+ 'Payload frame' => { Name => 'PayloadFrame', Binary => 1 },
593
+ 'Payload metadata' => { Name => 'PayloadMetadataText', Binary => 1 },
594
+ 'ShotLogData' => { Name => 'ShotLogDataText', Binary => 1 },
595
+ 'ShotParams' => { Name => 'ShotParamsText', Binary => 1 },
596
+ 'StaticMetadata' => { Name => 'StaticMetadataText', Binary => 1 },
597
+ 'Summary' => { Name => 'SummaryText', Binary => 1 },
598
+ 'Time log' => { Name => 'TimeLogText', Binary => 1 },
599
+ 'Unused logging metadata' => { Name => 'UnusedLoggingMetadata', Binary => 1 },
600
+ 'Rectiface' => { Name => 'RectifaceText', Binary => 1 },
601
+ 'GoudaRequest' => { Name => 'GoudaRequestText', Binary => 1 },
602
+ ProcessingNotes => { },
603
+ );
604
+
605
+ %Image::ExifTool::Google::PortraitReq = (
606
+ GROUPS => { 0 => 'MakerNotes', 2 => 'Image' },
607
+ TAG_PREFIX => '',
608
+ PROCESS_PROC => \&ProcessHDRP,
609
+ NOTES => q{
610
+ Google text-based PortraitRequest information. Stored as base64-encoded,
611
+ encrypted and gzipped text.
612
+ },
613
+ );
614
+
615
+ #------------------------------------------------------------------------------
616
+ # Read HDRP text-format maker note version 2
617
+ # Inputs: 0) ExifTool ref, 1) data ref
618
+ sub ProcessHDRPMakerNote($$)
619
+ {
620
+ my ($et, $dataPt) = @_;
621
+ my ($tag, $dat, $pos);
622
+ my $tagTbl = GetTagTable('Image::ExifTool::Google::HDRPMakerNote');
623
+ for (;;) {
624
+ my ($end, $last);
625
+ if ($$dataPt =~ /^ ?([A-Z].*)$/mg) {
626
+ $end = pos($$dataPt) - length($1);
627
+ } else {
628
+ $end = length($$dataPt);
629
+ $last = 1;
630
+ }
631
+ if ($tag) {
632
+ my $len = $end - ($pos + 1);
633
+ last if $len <= 0; # (just to be safe)
634
+ $et->HandleTag($tagTbl, $tag, substr($$dataPt, $pos + 1, $len), MakeTagInfo => 1);
635
+ }
636
+ last if $last;
637
+ $tag = $1;
638
+ unless ($tag =~ /:/ or $tag =~ /^\w+$/) {
639
+ $et->HandleTag($tagTbl, 'ProcessingNotes', $tag);
640
+ undef $tag;
641
+ next;
642
+ }
643
+ $pos = pos $$dataPt;
644
+ if ($tag =~ s/( \(base64\))?: ?(.*)// and $2) {
645
+ my $dat = $2;
646
+ $dat = Image::ExifTool::XMP::DecodeBase64($dat) if $1;
647
+ $et->HandleTag($tagTbl, $tag, $dat, MakeTagInfo => 1);
648
+ undef $tag;
649
+ }
650
+ }
651
+ }
652
+
653
+ #------------------------------------------------------------------------------
654
+ # Decode HDRP maker notes (ref https://github.com/jakiki6/ruminant/blob/master/ruminant/modules/images.py)
655
+ # Inputs: 0) ExifTool ref, 1) base64-encoded string, 2) tagInfo ref
656
+ # Returns: reference to decoded+decrypted+gunzipped data
657
+ # - also extracts protobuf info as separate tags when Unknown used if $$tagInfo{IsProtobuf} is set
658
+ sub ProcessHDRP($$$)
659
+ {
660
+ my ($et, $dirInfo, $tagTbl) = @_;
661
+ my $dataPt = $$dirInfo{DataPt};
662
+ my $tagInfo = $$dirInfo{TagInfo};
663
+ my $tagName = $tagInfo ? $$tagInfo{Name} : '';
664
+ my $verbose = $et->Options('Verbose');
665
+ my ($ver, $valPt);
666
+
667
+ if ($$dirInfo{DirStart}) {
668
+ my $dat = substr($$dataPt, $$dirInfo{DirStart}, $$dirInfo{DirLen});
669
+ $dataPt = \$dat;
670
+ }
671
+ if ($$dataPt =~ /^HDRP[\x02\x03]/) {
672
+ $valPt = $dataPt;
673
+ } else {
674
+ $et->VerboseDir($tagName, undef, length($$dataPt));
675
+ $et->VerboseDump($dataPt) if $verbose > 2;
676
+ $valPt = Image::ExifTool::XMP::DecodeBase64($$dataPt);
677
+ if ($verbose > 2) {
678
+ $et->VerboseDir("Base64-decoded $tagName", undef, length($$valPt));
679
+ $et->VerboseDump($valPt);
680
+ }
681
+ }
682
+ if ($$valPt =~ s/^HDRP([\x02\x03])//) {
683
+ $ver = ord($1);
684
+ } else {
685
+ $et->Warn('Unrecognized HDRP format');
686
+ return undef;
687
+ }
688
+ my $pad = (8 - (length($$valPt) % 8)) & 0x07;
689
+ $$valPt .= "\0" x $pad if $pad; # pad to an even 8 bytes
690
+ my @words = unpack('V*', $$valPt);
691
+ # my $key = 0x2515606b4a7791cd;
692
+ my ($hi, $lo) = ( 0x2515606b, 0x4a7791cd );
693
+ my $i = 0;
694
+ while ($i < @words) {
695
+ # (messy, but handle all 64-bit arithmetic with 32-bit backward
696
+ # compatibility -- no bit operations on any number > 0xffffffff)
697
+ # rotate the key
698
+ # $key ^= $key >> 12;
699
+ $lo ^= $lo >> 12 | ($hi & 0xfff) << 20;
700
+ $hi ^= $hi >> 12;
701
+ # $key ^= ($key << 25) & 0xffffffffffffffff;
702
+ $hi ^= ($hi & 0x7f) << 25 | $lo >> 7;
703
+ $lo ^= ($lo & 0x7f) << 25;
704
+ # $key ^= ($key >> 27) & 0xffffffffffffffff;
705
+ $lo ^= $lo >> 27 | ($hi & 0x7ffffff) << 5;
706
+ $hi ^= $hi >> 27;
707
+ # $key = ($key * 0x2545f4914f6cdd1d) & 0xffffffffffffffff;
708
+ # (multiply using 16-bit math to avoid overflowing 32-bit integers)
709
+ my @a = unpack('n*', pack('N*', $hi, $lo));
710
+ my @b = (0x2545, 0xf491, 0x4f6c, 0xdd1d);
711
+ my @c = (0) x 7;
712
+ my ($j, $k);
713
+ for ($j=0; $j<4; ++$j) {
714
+ for ($k=0; $k<4; ++$k) {
715
+ $c[$j+$k] += $a[$j] * $b[$k];
716
+ }
717
+ }
718
+ # (we only care about the low 32-bits, so don't bother with the upper 32)
719
+ for ($j=6; $j>=3; --$j) {
720
+ while ($c[$j] > 0xffffffff) {
721
+ ++$c[$j-2];
722
+ $c[$j] -= 4294967296;
723
+ }
724
+ $c[$j-1] += $c[$j] >> 16;
725
+ $c[$j] &= 0xffff;
726
+ }
727
+ $hi = ($c[3] << 16) + $c[4];
728
+ $lo = ($c[5] << 16) + $c[6];
729
+ # apply the xor
730
+ $words[$i++] ^= $lo;
731
+ $words[$i++] ^= $hi;
732
+ }
733
+ my $result;
734
+ my $val = pack('V*', @words);
735
+ $val = substr($val,0,-$pad) if $pad; # remove padding
736
+ if ($verbose > 2) {
737
+ $et->VerboseDir("Decrypted $tagName", undef, length($val));
738
+ $et->VerboseDump(\$val);
739
+ }
740
+ if (eval { require IO::Uncompress::Gunzip }) {
741
+ my $buff;
742
+ if (IO::Uncompress::Gunzip::gunzip(\$val, \$buff)) {
743
+ if ($verbose > 2) {
744
+ $et->VerboseDir("Gunzipped $tagName", undef, length($buff));
745
+ $et->VerboseDump(\$buff);
746
+ }
747
+ if ($ver == 3 or ($tagInfo and $$tagInfo{IsProtobuf})) {
748
+ my %dirInfo = (
749
+ DataPt => \$buff,
750
+ DirName => $tagName,
751
+ );
752
+ require Image::ExifTool::Protobuf;
753
+ Image::ExifTool::Protobuf::ProcessProtobuf($et, \%dirInfo, $tagTbl);
754
+ } else {
755
+ ProcessHDRPMakerNote($et, \$buff);
756
+ }
757
+ $result = \$buff;
758
+ } else {
759
+ $et->Warn("Error inflating stream: $IO::Uncompress::Gunzip::GunzipError");
760
+ }
761
+ } else {
762
+ $et->Warn('Install IO::Uncompress::Gunzip to decode HDRP makernote');
763
+ }
764
+ return $result;
765
+ }
766
+
767
+ 1; # end
768
+
769
+ __END__
770
+
771
+ =head1 NAME
772
+
773
+ Image::ExifTool::Google - Google maker notes and XMP tags
774
+
775
+ =head1 SYNOPSIS
776
+
777
+ This module is loaded automatically by Image::ExifTool when required.
778
+
779
+ =head1 DESCRIPTION
780
+
781
+ This module contains definitions required by Image::ExifTool to decode
782
+ Google maker notes and write Google XMP tags.
783
+
784
+ =head1 AUTHOR
785
+
786
+ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
787
+
788
+ This library is free software; you can redistribute it and/or modify it
789
+ under the same terms as Perl itself.
790
+
791
+ =head1 REFERENCES
792
+
793
+ =over 4
794
+
795
+ =item L<https://github.com/jakiki6/ruminant/blob/master/ruminant/modules/images.py>
796
+
797
+ =back
798
+
799
+ =head1 SEE ALSO
800
+
801
+ L<Image::ExifTool::TagNames/Google Tags>,
802
+ L<Image::ExifTool(3pm)|Image::ExifTool>
803
+
804
+ =cut