source: main/trunk/greenstone2/perllib/cpan/Image/ExifTool/GoPro.pm@ 34921

Last change on this file since 34921 was 34921, checked in by anupama, 3 years ago

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

File size: 27.6 KB
Line 
1#------------------------------------------------------------------------------
2# File: GoPro.pm
3#
4# Description: Read information from GoPro videos
5#
6# Revisions: 2018/01/12 - P. Harvey Created
7#
8# References: 1) https://github.com/gopro/gpmf-parser
9# 2) https://github.com/stilldavid/gopro-utils
10#------------------------------------------------------------------------------
11
12package Image::ExifTool::GoPro;
13
14use strict;
15use vars qw($VERSION);
16use Image::ExifTool qw(:DataAccess :Utils);
17use Image::ExifTool::QuickTime;
18
19$VERSION = '1.06';
20
21sub ProcessGoPro($$$);
22sub ProcessString($$$);
23sub ScaleValues($$);
24sub AddUnits($$$);
25sub ConvertSystemTime($$);
26
27# GoPro data types that have ExifTool equivalents (ref 1)
28my %goProFmt = ( # format codes
29 # 0x00 - container (subdirectory)
30 0x62 => 'int8s', # 'b'
31 0x42 => 'int8u', # 'B'
32 0x63 => 'string', # 'c' (possibly null terminated)
33 0x73 => 'int16s', # 's'
34 0x53 => 'int16u', # 'S'
35 0x6c => 'int32s', # 'l'
36 0x4c => 'int32u', # 'L'
37 0x66 => 'float', # 'f'
38 0x64 => 'double', # 'd'
39 0x46 => 'undef', # 'F' (4-char ID)
40 0x47 => 'undef', # 'G' (16-byte uuid)
41 0x6a => 'int64s', # 'j'
42 0x4a => 'int64u', # 'J'
43 0x71 => 'fixed32s', # 'q'
44 0x51 => 'fixed64s', # 'Q'
45 0x55 => 'undef', # 'U' (16-byte date)
46 0x3f => 'undef', # '?' (complex structure)
47);
48
49# sizes of format codes if different than what FormatSize() would return
50my %goProSize = (
51 0x46 => 4,
52 0x47 => 16,
53 0x55 => 16,
54);
55
56# tagInfo elements to add units to PrintConv value
57my %addUnits = (
58 AddUnits => 1,
59 PrintConv => 'Image::ExifTool::GoPro::AddUnits($self, $val, $tag)',
60);
61
62# Tags found in the GPMF box of Hero6 mp4 videos (ref PH), and
63# the gpmd-format timed metadata of Hero5 and Hero6 videos (ref 1)
64%Image::ExifTool::GoPro::GPMF = (
65 PROCESS_PROC => \&ProcessGoPro,
66 GROUPS => { 2 => 'Camera' },
67 NOTES => q{
68 Tags extracted from the GPMF box of GoPro MP4 videos, the APP6 "GoPro"
69 segment of JPEG files, and from the "gpmd" timed metadata if the
70 L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded> (-ee) option is enabled. Many more tags exist, but are
71 currently unknown and extracted only with the L<Unknown|../ExifTool.html#Unknown> (-u) option. Please
72 let me know if you discover the meaning of any of these unknown tags. See
73 L<https://github.com/gopro/gpmf-parser> for details about this format.
74 },
75 ACCL => { #2 (gpmd)
76 Name => 'Accelerometer',
77 Notes => 'accelerator readings in m/s2',
78 Binary => 1,
79 },
80 # ANGX (GPMF-GEOC) - seen -0.05 (fmt d, Max)
81 # ANGY (GPMF-GEOC) - seen 179.9 (fmt d, Max)
82 # ANGZ (GPMF-GEOC) - seen 0.152 (fmt d, Max)
83 ALLD => 'AutoLowLightDuration', #1 (gpmd) (untested)
84 # APTO (GPMF) - seen: 'RAW', 'DYNM' (fmt c)
85 ATTD => { #PH (Karma)
86 Name => 'Attitude',
87 # UNIT=s,rad,rad,rad,rad/s,rad/s,rad/s,
88 # TYPE=LffffffB
89 # SCAL=1000 1 1 1 1 1 1 1
90 Binary => 1,
91 },
92 ATTR => { #PH (Karma)
93 Name => 'AttitudeTarget',
94 # UNIT=s,rad,rad,rad,
95 # TYPE=Jffff
96 # SCAL=1000 1 1 1 1
97 Binary => 1,
98 },
99 AUDO => 'AudioSetting', #PH (GPMF - seen: 'WIND', fmt c)
100 # AUPT (GPMF) - seen: 'N','Y' (fmt c)
101 BPOS => { #PH (Karma)
102 Name => 'Controller',
103 Unknown => 1,
104 # UNIT=deg,deg,m,deg,deg,m,m,m
105 # TYPE=lllfffff
106 # SCAL=10000000 10000000 1000 1 1 1 1 1
107 %addUnits,
108 },
109 # BRID (GPMF) - seen: 0 (fmt B)
110 # BROD (GPMF) - seen: 'ASK','' (fmt c)
111 # CALH (GPMF-GEOC) - seen 3040 (fmt L, Max)
112 # CALW (GPMF-GEOC) - seen 4056 (fmt L, Max)
113 CASN => 'CameraSerialNumber', #PH (GPMF - seen: 'C3221324545448', fmt c)
114 # CINF (GPMF) - seen: 0x67376be7709bc8876a8baf3940908618, 0xe230988539b30cf5f016627ae8fc5395,
115 # 0x8bcbe424acc5b37d7d77001635198b3b (fmt B) (Camera INFormation?)
116 # CMOD (GPMF) - seen: 12,13,17 [12 360 video, 13 time-laps video, 17 JPEG] (fmt B)
117 # CRTX (GPMF-BACK/FRNT) - double[1]
118 # CRTY (GPMF-BACK/FRNT) - double[1]
119 CSEN => { #PH (Karma)
120 Name => 'CoyoteSense',
121 # UNIT=s,rad/s,rad/s,rad/s,g,g,g,,,,
122 # TYPE=LffffffLLLL
123 # SCAL=1000 1 1 1 1 1 1 1 1 1 1
124 Binary => 1,
125 },
126 CYTS => { #PH (Karma)
127 Name => 'CoyoteStatus',
128 # UNIT=s,,,,,rad,rad,rad,,
129 # TYPE=LLLLLfffBB
130 # SCAL=1000 1 1 1 1 1 1 1 1 1
131 Binary => 1,
132 },
133 DEVC => { #PH (gpmd,GPMF, fmt \0)
134 Name => 'DeviceContainer',
135 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
136 # (Max) DVID=1,DVNM='Global Settings',VERS,FMWR,LINF,CINF,CASN,MINF,MUID,CMOD,MTYP,OREN,
137 # DZOM,DZST,SMTR,PRTN,PTWB,PTSH,PTCL,EXPT,PIMX,PIMN,PTEV,RATE,SROT,ZFOV,VLTE,VLTA,
138 # EISE,EISA,AUPT,AUDO,BROD,BRID,PVUL,PRJT,SOFF
139 # (Max) DVID='GEOC',DVNM='Geometry Calibrations',SHFX,SHFY,SHFZ,ANGX,ANGY,ANGZ,CALW,CALH
140 # (Max) DVID='BACK',DVNM='Back Lens',KLNS,CTRX,CTRY,MFOV,SFTR
141 # (Max) DVID='FRNT',DVNM='Front Lens',KLNS,CTRX,CTRY,MFOV,SFTR
142 # (Max) DVID='HLMT',DVNM='Highlights'
143 },
144 # DVID (GPMF) - DeviceID; seen: 1 (fmt L), HLMT (fmt F), GEOC (fmt F), 'BACK' (fmt F, Max)
145 DVID => { Name => 'DeviceID', Unknown => 1 }, #2 (gpmd)
146 # DVNM (GPMF) seen: 'Video Global Settings' (fmt c), 'Highlights' (fmt c), 'Geometry Calibrations' (Max)
147 # DVNM (gpmd) seen: 'Camera' (Hero5), 'Hero6 Black' (Hero6), 'GoPro Karma v1.0' (Karma)
148 DVNM => 'DeviceName', #PH (n/c)
149 DZOM => { #PH (GPMF - seen: 'Y', fmt c)
150 Name => 'DigitalZoom',
151 PrintConv => { N => 'No', Y => 'Yes' },
152 },
153 # DZST (GPMF) - seen: 0 (fmt L) (something to do with digital zoom maybe?)
154 EISA => { #PH (GPMF) - seen: 'Y','N','HS EIS','N/A' (fmt c) [N was for a time-lapse video]
155 Name => 'ElectronicImageStabilization',
156 },
157 # EISE (GPMF) - seen: 'Y','N' (fmt c)
158 EMPT => { Name => 'Empty', Unknown => 1 }, #2 (gpmd)
159 ESCS => { #PH (Karma)
160 Name => 'EscapeStatus',
161 # UNIT=s,rpm,rpm,rpm,rpm,rpm,rpm,rpm,rpm,degC,degC,degC,degC,V,V,V,V,A,A,A,A,,,,,,,,,
162 # TYPE=JSSSSSSSSssssSSSSSSSSSSSSSSSSB
163 # (no SCAL!)
164 Unknown => 1,
165 %addUnits,
166 },
167 # EXPT (GPMF) - seen: '', 'AUTO' (fmt c)
168 FACE => 'FaceDetected', #PH (gpmd)
169 FCNM => 'FaceNumbers', #PH (gpmd) (faces counted per frame, ref 1)
170 FMWR => 'FirmwareVersion', #PH (GPMF - seen: HD6.01.01.51.00, fmt c)
171 FWVS => 'OtherFirmware', #PH (NC) (gpmd - seen: '1.1.11.0', Karma)
172 GLPI => { #PH (gpmd, Karma)
173 Name => 'GPSPos',
174 # UNIT=s,deg,deg,m,m,m/s,m/s,m/s,deg
175 # TYPE=LllllsssS
176 # SCAL=1000 10000000 10000000 1000 1000 100 100 100 100
177 RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
178 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GLPI' },
179 },
180 GPRI => { #PH (gpmd, Karma)
181 Name => 'GPSRaw',
182 # UNIT=s,deg,deg,m,m,m,m/s,deg,,
183 # TYPE=JlllSSSSBB
184 # SCAL=1000000,10000000,10000000,1000,100,100,100,100,1,1
185 Unknown => 1,
186 RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
187 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPRI' },
188 },
189 GPS5 => { #2 (gpmd)
190 Name => 'GPSInfo',
191 # SCAL=10000000,10000000,1000,1000,100
192 RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
193 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPS5' },
194 },
195 GPSF => { #2 (gpmd)
196 Name => 'GPSMeasureMode',
197 PrintConv => {
198 2 => '2-Dimensional Measurement',
199 3 => '3-Dimensional Measurement',
200 },
201 },
202 GPSP => { #2 (gpmd)
203 Name => 'GPSHPositioningError',
204 Description => 'GPS Horizontal Positioning Error',
205 ValueConv => '$val / 100', # convert from cm to m
206 },
207 GPSU => { #2 (gpmd)
208 Name => 'GPSDateTime',
209 Groups => { 2 => 'Time' },
210 # (HERO5 writes this in 'c' format, HERO6 writes 'U')
211 ValueConv => '$val =~ s/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:/; $val',
212 PrintConv => '$self->ConvertDateTime($val)',
213 },
214 GYRO => { #2 (gpmd)
215 Name => 'Gyroscope',
216 Notes => 'gyroscope readings in rad/s',
217 Binary => 1,
218 },
219 # HFLG (APP6) - seen: 0
220 ISOE => 'ISOSpeeds', #PH (gpmd)
221 ISOG => { #2 (gpmd)
222 Name => 'ImageSensorGain',
223 Binary => 1,
224 },
225 KBAT => { #PH (gpmd) (Karma)
226 Name => 'BatteryStatus',
227 # UNIT=A,Ah,J,degC,V,V,V,V,s,%,,,,,%
228 # TYPE=lLlsSSSSSSSBBBb
229 # SCAL=1000,1000,0.00999999977648258,100,1000,1000,1000,1000,0.0166666675359011,1,1,1,1,1,1
230 RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
231 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::KBAT' },
232 },
233 # KLNS (GPMF-BACK/FRNT) - double[5] (fmt d, Max)
234 # LINF (GPMF) - seen: LAJ7061916601668,C3341326002180,C33632245450981 (fmt c) (Lens INFormation?)
235 LNED => { #PH (Karma)
236 Name => 'LocalPositionNED',
237 # UNIT=s,m,m,m,m/s,m/s,m/s
238 # TYPE=Lffffff
239 # SCAL=1000 1 1 1 1 1 1
240 Binary => 1,
241 },
242 MAGN => 'Magnetometer', #1 (gpmd) (units of uT)
243 # MFOV (GPMF-BACK/FRNT) - seen: 100 (fmt d, Max)
244 MINF => { #PH (GPMF - seen: HERO6 Black, fmt c)
245 Name => 'Model',
246 Groups => { 2 => 'Camera' },
247 Description => 'Camera Model Name',
248 },
249 # MTYP (GPMF) - seen: 0,1,5,11 [1 for time-lapse video, 5 for 360 video, 11 for JPEG] (fmt B)
250 # MUID (GPMF) - seen: 3882563431 2278071152 967805802 411471936 0 0 0 0 (fmt L)
251 OREN => { #PH (GPMF - seen: 'U', fmt c)
252 Name => 'AutoRotation',
253 PrintConv => {
254 U => 'Up',
255 D => 'Down', # (NC)
256 A => 'Auto', # (NC)
257 },
258 },
259 # (most of the "P" tags are ProTune settings - PH)
260 PHDR => 'HDRSetting', #PH (APP6 - seen: 0)
261 PIMN => 'AutoISOMin', #PH (GPMF - seen: 100, fmt L)
262 PIMX => 'AutoISOMax', #PH (GPMF - seen: 1600, fmt L)
263 # PRAW (APP6) - seen: 0, 'N', 'Y' (fmt c)
264 PRES => 'PhotoResolution', #PH (APP6 - seen: '12MP_W')
265 # PRJT (APP6) - seen: 'GPRO','EACO' (fmt F, Hero8, Max)
266 PRTN => { #PH (GPMF - seen: 'N', fmt c)
267 Name => 'ProTune',
268 PrintConv => {
269 N => 'Off',
270 Y => 'On', # (NC)
271 },
272 },
273 PTCL => 'ColorMode', #PH (GPMF - seen: 'GOPRO', fmt c' APP6: 'FLAT')
274 PTEV => 'ExposureCompensation', #PH (GPMF - seen: '0.0', fmt c)
275 PTSH => 'Sharpness', #PH (GPMF - seen: 'HIGH', fmt c)
276 PTWB => 'WhiteBalance', #PH (GPMF - seen: 'AUTO', fmt c)
277 # PVUL (APP6) - seen: 'F' (fmt c, Hero8, Max)
278 RATE => 'Rate', #PH (GPMF - seen: '0_5SEC', fmt c; APP6 - seen: '4_1SEC')
279 RMRK => { #2 (gpmd)
280 Name => 'Comments',
281 ValueConv => '$self->Decode($val, "Latin")',
282 },
283 SCAL => { #2 (gpmd) scale factor for subsequent data
284 Name => 'ScaleFactor',
285 Unknown => 1,
286 },
287 SCPR => { #PH (Karma) [stream was empty]
288 Name => 'ScaledPressure',
289 # UNIT=s,Pa,Pa,degC
290 # TYPE=Lffs
291 # SCAL=1000 0.00999999977648258 0.00999999977648258 100
292 %addUnits,
293 },
294 # SFTR (GPMF-BACK/FRNT) - seen 0.999,1.00004 (fmt d, Max)
295 # SHFX (GPMF-GEOC) - seen 22.92 (fmt d, Max)
296 # SHFY (GPMF-GEOC) - seen 0.123 (fmt d, Max)
297 # SHFZ (GPMF-GEOC) - seen 36.06 (fmt d, Max)
298 SHUT => { #2 (gpmd)
299 Name => 'ExposureTimes',
300 PrintConv => q{
301 my @a = split ' ', $val;
302 $_ = Image::ExifTool::Exif::PrintExposureTime($_) foreach @a;
303 return join ' ', @a;
304 },
305 },
306 SIMU => { #PH (Karma)
307 Name => 'ScaledIMU',
308 # UNIT=s,g,g,g,rad/s,rad/s,rad/s,T,T,T
309 # TYPE=Lsssssssss
310 # SCAL=1000 1000 1000 1000 1000 1000 1000 1000 1000 1000
311 %addUnits,
312 },
313 SIUN => { #2 (gpmd - seen : 'm/s2','rad/s')
314 Name => 'SIUnits',
315 Unknown => 1,
316 ValueConv => '$self->Decode($val, "Latin")',
317 },
318 # SMTR (GPMF) - seen: 'N' (fmt c)
319 # SOFF (APP6) - seen: 0 (fmt L, Hero8, Max)
320 # SROT (GPMF) - seen 20.60 (fmt f, Max)
321 STMP => { #1 (gpmd)
322 Name => 'TimeStamp',
323 ValueConv => '$val / 1e6',
324 },
325 STRM => { #2 (gpmd,GPMF, fmt \0)
326 Name => 'NestedSignalStream',
327 SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
328 },
329 STNM => { #2 (gpmd)
330 Name => 'StreamName',
331 Unknown => 1,
332 ValueConv => '$self->Decode($val, "Latin")',
333 },
334 SYST => { #PH (Karma)
335 Name => 'SystemTime',
336 # UNIT=s,s
337 # TYPE=JJ
338 # SCAL=1000000 1000
339 # save system time calibrations for later
340 RawConv => q{
341 my @v = split ' ', $val;
342 if (@v == 2) {
343 my $s = $$self{SystemTimeList};
344 $s or $s = $$self{SystemTimeList} = [ ];
345 push @$s, \@v;
346 }
347 return $val;
348 },
349 },
350 # TICK => { Name => 'InTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd)
351 TMPC => { #2 (gpmd)
352 Name => 'CameraTemperature',
353 PrintConv => '"$val C"',
354 },
355 # TOCK => { Name => 'OutTime', Unknown => 1, ValueConv => '$val/1000' }, #1 (gpmd)
356 TSMP => { Name => 'TotalSamples', Unknown => 1 }, #2 (gpmd)
357 TYPE => { Name => 'StructureType', Unknown => 1 }, #2 (gpmd,GPMF - eg 'LLLllfFff', fmt c)
358 UNIT => { #2 (gpmd) alternative units
359 Name => 'Units',
360 Unknown => 1,
361 ValueConv => '$self->Decode($val, "Latin")',
362 },
363 VERS => {
364 Name => 'MetadataVersion',
365 PrintConv => '$val =~ tr/ /./; $val',
366 },
367 VFOV => { #PH (GPMF - seen: 'W', fmt c)
368 Name => 'FieldOfView',
369 PrintConv => {
370 W => 'Wide',
371 S => 'Super View', # (NC, not seen)
372 L => 'Linear', # (NC, not seen)
373 },
374 },
375 # VLTA (GPMF) - seen: 78 ('N') (fmt B -- wrong format?)
376 VFRH => { #PH (Karma)
377 Name => 'VisualFlightRulesHUD',
378 BinaryData => 1,
379 # UNIT=m/s,m/s,m,m/s,deg,%
380 # TYPE=ffffsS
381 },
382 # VLTE (GPMF) - seen: 'Y','N' (fmt c)
383 WBAL => 'ColorTemperatures', #PH (gpmd)
384 WRGB => { #PH (gpmd)
385 Name => 'WhiteBalanceRGB',
386 Binary => 1,
387 },
388 # ZFOV (APP6,GPMF) - seen: 148.34, 0 (fmt f, Hero8, Max)
389);
390
391# GoPro GPS5 tags (ref 2) (Hero5,Hero6)
392%Image::ExifTool::GoPro::GPS5 = (
393 PROCESS_PROC => \&ProcessString,
394 GROUPS => { 1 => 'GoPro', 2 => 'Location' },
395 VARS => { HEX_ID => 0, ID_LABEL => 'Index' },
396 0 => { # (unit='deg')
397 Name => 'GPSLatitude',
398 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
399 },
400 1 => { # (unit='deg')
401 Name => 'GPSLongitude',
402 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
403 },
404 2 => { # (unit='m')
405 Name => 'GPSAltitude',
406 PrintConv => '"$val m"',
407 },
408 3 => 'GPSSpeed', # (unit='m/s')
409 4 => 'GPSSpeed3D', # (unit='m/s')
410);
411
412# GoPro GPRI tags (ref PH) (Karma)
413%Image::ExifTool::GoPro::GPRI = (
414 PROCESS_PROC => \&ProcessString,
415 GROUPS => { 1 => 'GoPro', 2 => 'Location' },
416 VARS => { HEX_ID => 0, ID_LABEL => 'Index' },
417 0 => { # (unit='s')
418 Name => 'GPSDateTimeRaw',
419 Groups => { 2 => 'Time' },
420 ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock
421 PrintConv => '$self->ConvertDateTime($val)',
422 },
423 1 => { # (unit='deg')
424 Name => 'GPSLatitudeRaw',
425 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
426 },
427 2 => { # (unit='deg')
428 Name => 'GPSLongitudeRaw',
429 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
430 },
431 3 => {
432 Name => 'GPSAltitudeRaw', # (NC)
433 PrintConv => '"$val m"',
434 },
435 # (unknown tags must be defined so that ProcessString() will iterate through all values)
436 4 => { Name => 'GPRI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
437 5 => { Name => 'GPRI_Unknown5', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
438 6 => 'GPSSpeedRaw', # (NC) # (unit='m/s' -- should convert to other units?)
439 7 => 'GPSTrackRaw', # (NC) # (unit='deg')
440 8 => { Name => 'GPRI_Unknown8', Unknown => 1, Hidden => 1 }, # (no units)
441 9 => { Name => 'GPRI_Unknown9', Unknown => 1, Hidden => 1 }, # (no units)
442);
443
444# GoPro GLPI tags (ref PH) (Karma)
445%Image::ExifTool::GoPro::GLPI = (
446 PROCESS_PROC => \&ProcessString,
447 GROUPS => { 1 => 'GoPro', 2 => 'Location' },
448 VARS => { HEX_ID => 0, ID_LABEL => 'Index' },
449 0 => { # (unit='s')
450 Name => 'GPSDateTime',
451 Groups => { 2 => 'Time' },
452 ValueConv => \&ConvertSystemTime, # convert to date/time based on SystemTime clock
453 PrintConv => '$self->ConvertDateTime($val)',
454 },
455 1 => { # (unit='deg')
456 Name => 'GPSLatitude',
457 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
458 },
459 2 => { # (unit='deg')
460 Name => 'GPSLongitude',
461 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
462 },
463 3 => { # (unit='m')
464 Name => 'GPSAltitude', # (NC)
465 PrintConv => '"$val m"',
466 },
467 # (unknown tags must be defined so that ProcessString() will iterate through all values)
468 4 => { Name => 'GLPI_Unknown4', Unknown => 1, Hidden => 1, PrintConv => '"$val m"' },
469 5 => { Name => 'GPSSpeedX', PrintConv => '"$val m/s"' }, # (NC)
470 6 => { Name => 'GPSSpeedY', PrintConv => '"$val m/s"' }, # (NC)
471 7 => { Name => 'GPSSpeedZ', PrintConv => '"$val m/s"' }, # (NC)
472 8 => { Name => 'GPSTrack' }, # (unit='deg')
473);
474
475# GoPro KBAT tags (ref PH)
476%Image::ExifTool::GoPro::KBAT = (
477 PROCESS_PROC => \&ProcessString,
478 GROUPS => { 1 => 'GoPro', 2 => 'Camera' },
479 VARS => { HEX_ID => 0, ID_LABEL => 'Index' },
480 NOTES => 'Battery status information found in GoPro Karma videos.',
481 0 => { Name => 'BatteryCurrent', PrintConv => '"$val A"' },
482 1 => { Name => 'BatteryCapacity', PrintConv => '"$val Ah"' },
483 2 => { Name => 'KBAT_Unknown2', PrintConv => '"$val J"', Unknown => 1, Hidden => 1 },
484 3 => { Name => 'BatteryTemperature', PrintConv => '"$val C"' },
485 4 => { Name => 'BatteryVoltage1', PrintConv => '"$val V"' },
486 5 => { Name => 'BatteryVoltage2', PrintConv => '"$val V"' },
487 6 => { Name => 'BatteryVoltage3', PrintConv => '"$val V"' },
488 7 => { Name => 'BatteryVoltage4', PrintConv => '"$val V"' },
489 8 => { Name => 'BatteryTime', PrintConv => 'ConvertDuration(int($val + 0.5))' }, # (NC)
490 9 => { Name => 'KBAT_Unknown9', PrintConv => '"$val %"', Unknown => 1, Hidden => 1, },
491 10 => { Name => 'KBAT_Unknown10', Unknown => 1, Hidden => 1 }, # (no units)
492 11 => { Name => 'KBAT_Unknown11', Unknown => 1, Hidden => 1 }, # (no units)
493 12 => { Name => 'KBAT_Unknown12', Unknown => 1, Hidden => 1 }, # (no units)
494 13 => { Name => 'KBAT_Unknown13', Unknown => 1, Hidden => 1 }, # (no units)
495 14 => { Name => 'BatteryLevel', PrintConv => '"$val %"' },
496);
497
498# GoPro fdsc tags written by the Hero5 and Hero6 (ref PH)
499%Image::ExifTool::GoPro::fdsc = (
500 GROUPS => { 2 => 'Camera' },
501 PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
502 NOTES => q{
503 Tags extracted from the MP4 "fdsc" timed metadata when the L<ExtractEmbedded|../ExifTool.html#ExtractEmbedded>
504 (-ee) option is used.
505 },
506 0x08 => { Name => 'FirmwareVersion', Format => 'string[15]' },
507 0x17 => { Name => 'SerialNumber', Format => 'string[16]' },
508 0x57 => { Name => 'OtherSerialNumber', Format => 'string[15]' }, # (NC)
509 0x66 => {
510 Name => 'Model',
511 Description => 'Camera Model Name',
512 Format => 'string[16]',
513 },
514 # ...
515 # after this there are lots of interesting values also found in the GPMF box,
516 # but this block is lacking tag ID's and any directory structure, so the
517 # value offsets are therefore presumably firmware dependent :(
518);
519
520#------------------------------------------------------------------------------
521# Convert system time to date/time string
522# Inputs: 0) system time value, 1) ExifTool ref
523# Returns: EXIF-format date/time string with milliseconds
524sub ConvertSystemTime($$)
525{
526 my ($val, $et) = @_;
527 my $s = $$et{SystemTimeList} or return '<uncalibrated>';
528 unless ($$et{SystemTimeListSorted}) {
529 $s = $$et{SystemTimeList} = [ sort { $$a[0] <=> $$b[0] } @$s ];
530 $$et{SystemTimeListSorted} = 1;
531 }
532 my ($i, $j) = (0, $#$s);
533 # perform binary search to find this system time value
534 while ($j - $i > 1) {
535 my $t = int(($i + $j) / 2);
536 ($val < $$s[$t][0] ? $j : $i) = $t;
537 }
538 if ($i == $j or $$s[$j][0] == $$s[$i][0]) {
539 $val = $$s[$i][1];
540 } else {
541 # interpolate between values
542 $val = $$s[$i][1] + ($$s[$j][1] - $$s[$i][1]) * ($val - $$s[$i][0]) / ($$s[$j][0] - $$s[$i][0]);
543 }
544 # (a bit tricky to remove fractional seconds then add them back again after
545 # the date/time conversion while avoiding round-off errors which could
546 # put the seconds out by 1...)
547 my ($t, $f) = ("$val" =~ /^(\d+)(\.\d+)/);
548 return Image::ExifTool::ConvertUnixTime($t, $$et{OPTIONS}{QuickTimeUTC}) . $f;
549}
550
551#------------------------------------------------------------------------------
552# Scale values by last 'SCAL' constants
553# Inputs: 0) value or list of values, 1) string of scale factors
554# Returns: nothing, but updates values
555sub ScaleValues($$)
556{
557 my ($val, $scl) = @_;
558 return unless $val and $scl;
559 my @scl = split ' ', $scl or return;
560 my @scaled;
561 my $v = (ref $val eq 'ARRAY') ? $val : [ $val ];
562 foreach $val (@$v) {
563 my @a = split ' ', $val;
564 $a[$_] /= $scl[$_ % @scl] foreach 0..$#a;
565 push @scaled, join(' ', @a);
566 }
567 $_[0] = @scaled > 1 ? \@scaled : $scaled[0];
568}
569
570#------------------------------------------------------------------------------
571# Add units to values for human-readable output
572# Inputs: 0) ExifTool ref, 1) value, 2) tag key
573# Returns: converted value
574sub AddUnits($$$)
575{
576 my ($et, $val, $tag) = @_;
577 if ($et and $$et{TAG_EXTRA}{$tag} and $$et{TAG_EXTRA}{$tag}{Units}) {
578 my $u = $$et{TAG_EXTRA}{$tag}{Units};
579 $u = [ $u ] unless ref $u eq 'ARRAY';
580 my @a = split ' ', $val;
581 if (@$u == @a) {
582 my $i;
583 for ($i=0; $i<@a; ++$i) {
584 $a[$i] .= ' ' . $$u[$i] if $$u[$i];
585 }
586 $val = join ' ', @a;
587 }
588 }
589 return $val;
590}
591
592#------------------------------------------------------------------------------
593# Process string of values (or array of strings) to extract as separate tags
594# Inputs: 0) ExifTool object ref, 1) directory information ref, 2) tag table ref
595# Returns: 1 on success
596sub ProcessString($$$)
597{
598 my ($et, $dirInfo, $tagTablePtr) = @_;
599 my $dataPt = $$dirInfo{DataPt};
600 my @list = ref $$dataPt eq 'ARRAY' ? @{$$dataPt} : ( $$dataPt );
601 my ($string, $val);
602 $et->VerboseDir('GoPro structure');
603 foreach $string (@list) {
604 my @val = split ' ', $string;
605 my $i = 0;
606 foreach $val (@val) {
607 $et->HandleTag($tagTablePtr, $i, $val);
608 $$tagTablePtr{++$i} or $i = 0;
609 }
610 }
611 return 1;
612}
613
614#------------------------------------------------------------------------------
615# Process GoPro metadata (gpmd samples, GPMF box, or APP6) (ref PH/1/2)
616# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
617# Returns: 1 on success
618# - with hack to check for encrypted text in gpmd data (Rove Stealth 4K)
619sub ProcessGoPro($$$)
620{
621 my ($et, $dirInfo, $tagTablePtr) = @_;
622 my $dataPt = $$dirInfo{DataPt};
623 my $base = $$dirInfo{Base};
624 my $pos = $$dirInfo{DirStart} || 0;
625 my $dirEnd = $pos + ($$dirInfo{DirLen} || (length($$dataPt) - $pos));
626 my $verbose = $et->Options('Verbose');
627 my $unknown = $verbose || $et->Options('Unknown');
628 my ($size, $type, $unit, $scal, $setGroup0);
629
630 $et->VerboseDir($$dirInfo{DirName} || 'GPMF', undef, $dirEnd-$pos) if $verbose;
631 if ($pos) {
632 my $parent = $$dirInfo{Parent};
633 $setGroup0 = $$et{SET_GROUP0} = 'APP6' if $parent and $parent eq 'APP6';
634 } else {
635 # set group0 to "QuickTime" unless group1 is being changed (to Track#)
636 $setGroup0 = $$et{SET_GROUP0} = 'QuickTime' unless $$et{SET_GROUP1};
637 }
638
639 for (; $pos+8<=$dirEnd; $pos+=($size+3)&0xfffffffc) {
640 my ($tag,$fmt,$len,$count) = unpack("x${pos}a4CCn", $$dataPt);
641 $size = $len * $count;
642 $pos += 8;
643 last if $pos + $size > $dirEnd;
644 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
645 last if $tag eq "\0\0\0\0"; # stop at null tag
646 next unless $size or $verbose; # don't save empty values unless verbose
647 my $format = $goProFmt{$fmt} || 'undef';
648 my ($val, $i, $j, $p, @v);
649 if ($fmt == 0x3f and defined $type) {
650 # decode structure with format given by previous 'TYPE'
651 for ($i=0; $i<$count; ++$i) {
652 my (@s, $l);
653 for ($j=0, $p=0; $j<length($type); ++$j, $p+=$l) {
654 my $b = Get8u(\$type, $j);
655 my $f = $goProFmt{$b} or last;
656 $l = $goProSize{$b} || Image::ExifTool::FormatSize($f) or last;
657 last if $p + $l > $len;
658 my $s = ReadValue($dataPt, $pos+$i*$len+$p, $f, undef, $l);
659 last unless defined $s;
660 push @s, $s;
661 }
662 push @v, join ' ', @s if @s;
663 }
664 $val = @v > 1 ? \@v : $v[0];
665 } elsif (($format eq 'undef' or $format eq 'string') and $count > 1 and $len > 1) {
666 # unpack multiple undef/string values as a list
667 my $a = $format eq 'undef' ? 'a' : 'A';
668 $val = [ unpack("x${pos}".("$a$len" x $count), $$dataPt) ];
669 } else {
670 $val = ReadValue($dataPt, $pos, $format, undef, $size);
671 }
672 # save TYPE, UNIT/SIUN and SCAL values for later
673 $type = $val if $tag eq 'TYPE';
674 $unit = $val if $tag eq 'UNIT' or $tag eq 'SIUN';
675 $scal = $val if $tag eq 'SCAL';
676
677 unless ($tagInfo) {
678 next unless $unknown;
679 my $name = Image::ExifTool::QuickTime::PrintableTagID($tag);
680 $tagInfo = { Name => "GoPro_$name", Description => "GoPro $name", Unknown => 1 };
681 $$tagInfo{SubDirectory} = { TagTable => 'Image::ExifTool::GoPro::GPMF' } if not $fmt;
682 AddTagToTable($tagTablePtr, $tag, $tagInfo);
683 }
684 # apply scaling if available to last tag in this container
685 ScaleValues($val, $scal) if $scal and $tag ne 'SCAL' and $pos+$size+3>=$dirEnd;
686 my $key = $et->HandleTag($tagTablePtr, $tag, $val,
687 DataPt => $dataPt,
688 Base => $base,
689 Start => $pos,
690 Size => $size,
691 TagInfo => $tagInfo,
692 Format => $format,
693 Extra => $verbose ? ", type='".($fmt ? chr($fmt) : '\0')."' size=$len count=$count" : undef,
694 );
695 # save units for adding in print conversion if specified
696 $$et{TAG_EXTRA}{$key}{Units} = $unit if $$tagInfo{AddUnits} and $key;
697 }
698 delete $$et{SET_GROUP0} if $setGroup0;
699 return 1;
700}
701
7021; # end
703
704__END__
705
706=head1 NAME
707
708Image::ExifTool::GoPro - Read information from GoPro videos
709
710=head1 SYNOPSIS
711
712This module is used by Image::ExifTool
713
714=head1 DESCRIPTION
715
716This module contains definitions required by Image::ExifTool to decode
717metadata from GoPro MP4 videos.
718
719=head1 AUTHOR
720
721Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
722
723This library is free software; you can redistribute it and/or modify it
724under the same terms as Perl itself.
725
726=head1 REFERENCES
727
728=over 4
729
730=item L<https://github.com/gopro/gpmf-parser>
731
732=item L<https://github.com/stilldavid/gopro-utils>
733
734=back
735
736=head1 SEE ALSO
737
738L<Image::ExifTool::TagNames/GoPro Tags>,
739L<Image::ExifTool(3pm)|Image::ExifTool>
740
741=cut
742
Note: See TracBrowser for help on using the repository browser.