exiftool_vendored 13.02.0 → 13.04.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.
@@ -20,7 +20,7 @@ use strict;
20
20
  use vars qw($VERSION);
21
21
  use Image::ExifTool qw(:DataAccess :Utils);
22
22
 
23
- $VERSION = '1.20';
23
+ $VERSION = '1.21';
24
24
 
25
25
  # road map of directory locations in GIF images
26
26
  my %gifMap = (
@@ -28,6 +28,9 @@ my %gifMap = (
28
28
  ICC_Profile => 'GIF',
29
29
  );
30
30
 
31
+ # application extensions that we can write, and the order they are written
32
+ my @appExtensions = ( 'XMP Data/XMP', 'ICCRGBG1/012' );
33
+
31
34
  %Image::ExifTool::GIF::Main = (
32
35
  GROUPS => { 2 => 'Image' },
33
36
  VARS => { NO_ID => 1 },
@@ -61,19 +64,26 @@ my %gifMap = (
61
64
  %Image::ExifTool::GIF::Extensions = (
62
65
  GROUPS => { 2 => 'Image' },
63
66
  NOTES => 'Tags extracted from GIF89a application extensions.',
67
+ WRITE_PROC => sub { return 1 }, # (dummy proc to facilitate writable directories)
64
68
  'NETSCAPE/2.0' => { #3
65
69
  Name => 'Animation',
66
70
  SubDirectory => { TagTable => 'Image::ExifTool::GIF::Animation' },
67
71
  },
68
72
  'XMP Data/XMP' => { #2
69
73
  Name => 'XMP',
70
- IncludeLengthBytes => 1, # length bytes are included in the data
71
- Writable => 2,
74
+ # IncludeLengthBytes indicates the length bytes are part of the data value...
75
+ # undef = data may contain nulls and is split into 255-byte blocks
76
+ # 1 = data may not contain nulls and is not split; NULL padding is added as necessary
77
+ # 2 = data is not split and may be edited in place; 257-byte landing zone is added
78
+ # (Terminator may be specified for a value of 1 above, but must be specified for 2)
79
+ IncludeLengthBytes => 2,
80
+ Terminator => q(<\\?xpacket end=['"][wr]['"]\\?>), # (regex to match end of valid data)
81
+ Writable => 2, # (writable directory!)
72
82
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
73
83
  },
74
84
  'ICCRGBG1/012' => { #4
75
85
  Name => 'ICC_Profile',
76
- Writable => 2,
86
+ Writable => 2, # (writable directory!)
77
87
  SubDirectory => { TagTable => 'Image::ExifTool::ICC_Profile::Main' },
78
88
  },
79
89
  'MIDICTRL/Jon' => { #5
@@ -162,7 +172,7 @@ my %gifMap = (
162
172
  );
163
173
 
164
174
  #------------------------------------------------------------------------------
165
- # Process meta information in GIF image
175
+ # Read/write meta information in GIF image
166
176
  # Inputs: 0) ExifTool object reference, 1) Directory information ref
167
177
  # Returns: 1 on success, 0 if this wasn't a valid GIF file, or -1 if
168
178
  # an output file was specified and a write error occurred
@@ -174,7 +184,7 @@ sub ProcessGIF($$)
174
184
  my $verbose = $et->Options('Verbose');
175
185
  my $out = $et->Options('TextOut');
176
186
  my ($a, $s, $ch, $length, $buff);
177
- my ($err, $newComment, $setComment, $nvComment);
187
+ my ($err, $newComment, $setComment, $nvComment, $newExt);
178
188
  my ($addDirs, %doneDir);
179
189
  my ($frameCount, $delayTime) = (0, 0);
180
190
 
@@ -186,9 +196,19 @@ sub ProcessGIF($$)
186
196
  my $ver = $1;
187
197
  my $rtnVal = 0;
188
198
  my $tagTablePtr = GetTagTable('Image::ExifTool::GIF::Main');
199
+ my $extTable = GetTagTable('Image::ExifTool::GIF::Extensions');
189
200
  SetByteOrder('II');
190
201
 
191
202
  if ($outfile) {
203
+ # add any user-defined writable app extensions to the list
204
+ my $ext;
205
+ foreach $ext (sort keys %$extTable) {
206
+ next unless ref $$extTable{$ext} eq 'HASH';
207
+ my $extInfo = $$extTable{$ext};
208
+ next unless $$extInfo{SubDirectory} and $$extInfo{Writable} and not $gifMap{$$extInfo{Name}};
209
+ $gifMap{$$extInfo{Name}} = 'GIF';
210
+ push @appExtensions, $ext;
211
+ }
192
212
  $et->InitWriteDirs(\%gifMap, 'XMP'); # make XMP the preferred group for GIF
193
213
  $addDirs = $$et{ADD_DIRS};
194
214
  # determine if we are editing the File:Comment tag
@@ -196,8 +216,9 @@ sub ProcessGIF($$)
196
216
  $newComment = $et->GetNewValue('Comment', \$nvComment);
197
217
  $setComment = 1 if $nvComment or $$delGroup{File};
198
218
  # change to GIF 89a if adding comment, XMP or ICC_Profile
199
- $buff = 'GIF89a' if $$addDirs{XMP} or $$addDirs{ICC_Profile} or defined $newComment;
219
+ $buff = 'GIF89a' if %$addDirs or defined $newComment;
200
220
  Write($outfile, $buff, $s) or $err = 1;
221
+ $newExt = $et->GetNewTagInfoHash($extTable);
201
222
  } else {
202
223
  $et->SetFileType(); # set file type
203
224
  $et->HandleTag($tagTablePtr, 'GIFVersion', $ver);
@@ -238,45 +259,50 @@ Block:
238
259
  undef $nvComment; # delete any other extraneous comments
239
260
  ++$$et{CHANGED}; # increment file changed flag
240
261
  }
241
- # add application extension containing XMP block if necessary
242
- # (this will place XMP before the first non-extension block)
243
- if (exists $$addDirs{XMP} and not defined $doneDir{XMP}) {
244
- $doneDir{XMP} = 1;
245
- # write new XMP data
246
- my $xmpTable = GetTagTable('Image::ExifTool::XMP::Main');
247
- my %dirInfo = ( Parent => 'GIF' );
248
- $verbose and print $out "Creating XMP application extension block:\n";
249
- $buff = $et->WriteDirectory(\%dirInfo, $xmpTable);
250
- if (defined $buff and length $buff) {
251
- my $lz = pack('C*',1,reverse(0..255),0);
252
- Write($outfile, "\x21\xff\x0bXMP DataXMP", $buff, $lz) or $err = 1;
253
- ++$doneDir{XMP}; # set to 2 to indicate we added XMP
262
+ # add application extensions if necessary
263
+ my $ext;
264
+ my @new = sort keys %$newExt;
265
+ foreach $ext (@appExtensions, @new) {
266
+ my $extInfo = $$extTable{$ext};
267
+ my $name = $$extInfo{Name};
268
+ if ($$newExt{$ext}) {
269
+ delete $$newExt{$ext};
270
+ $doneDir{$name} = 1; # (we wrote this as a block instead)
271
+ $buff = $et->GetNewValue($extInfo);
272
+ $et->VerboseValue("+ GIF:$name", $buff);
273
+ } elsif (exists $$addDirs{$name} and not defined $doneDir{$name}) {
274
+ $doneDir{$name} = 1;
275
+ my $tbl = GetTagTable($$extInfo{SubDirectory}{TagTable});
276
+ my %dirInfo = ( Parent => 'GIF' );
277
+ $verbose and print $out "Creating $name application extension block:\n";
278
+ $buff = $et->WriteDirectory(\%dirInfo, $tbl);
254
279
  } else {
255
- $verbose and print $out " -> no XMP to add\n";
280
+ next;
256
281
  }
257
- }
258
- # add application extension containing ICC_Profile if necessary
259
- if (exists $$addDirs{ICC_Profile} and not defined $doneDir{ICC_Profile}) {
260
- $doneDir{ICC_Profile} = 1;
261
- # write new ICC_Profile
262
- my $iccTable = GetTagTable('Image::ExifTool::ICC_Profile::Main');
263
- my %dirInfo = ( Parent => 'GIF' );
264
- $verbose and print $out "Creating ICC_Profile application extension block:\n";
265
- $buff = $et->WriteDirectory(\%dirInfo, $iccTable);
266
282
  if (defined $buff and length $buff) {
283
+ ++$$et{CHANGED};
284
+ Write($outfile, "\x21\xff\x0b", substr($ext,0,8), substr($ext,9,3)) or $err = 1;
267
285
  my $pos = 0;
268
- Write($outfile, "\x21\xff\x0bICCRGBG1012") or $err = 1;
269
- my $len = length $buff;
270
- while ($pos < $len) {
271
- my $n = $len - $pos;
272
- $n = 255 if $n > 255;
273
- Write($outfile, chr($n), substr($buff, $pos, $n)) or $err = 1;
274
- $pos += $n;
286
+ if (not $$extTable{$ext}{IncludeLengthBytes}) {
287
+ my $len = length $buff;
288
+ while ($pos < length $buff) {
289
+ my $n = length($buff) - $pos;
290
+ $n = 255 if $n > 255;
291
+ Write($outfile, chr($n), substr($buff, $pos, $n)) or $err = 1;
292
+ $pos += $n;
293
+ }
294
+ Write($outfile, "\0") or $err = 1; # write null terminator
295
+ } elsif ($$extTable{$ext}{IncludeLengthBytes} < 2) {
296
+ $pos += ord(substr($buff,$pos,1)) + 1 while $pos < length $buff;
297
+ # write data, null padding and terminator
298
+ Write($outfile, $buff, "\0" x ($pos - length($buff) + 1)) or $err = 1;
299
+ } else {
300
+ # write data, landing zone and null terminator
301
+ Write($outfile, $buff, pack('C*',1,reverse(0..255),0)) or $err = 1;
275
302
  }
276
- Write($outfile, "\0") or $err = 1; # write null terminator
277
- ++$doneDir{ICC_Profile}; # set to 2 to indicate we added a new profile
303
+ ++$doneDir{$name}; # set to 2 to indicate we added it
278
304
  } else {
279
- $verbose and print $out " -> no ICC_Profile to add\n";
305
+ $verbose and print $out " -> no $name to add\n";
280
306
  }
281
307
  }
282
308
  }
@@ -286,7 +312,7 @@ Block:
286
312
  # image descriptor
287
313
  last unless $raf->Read($buff, 8) == 8 and $raf->Read($ch, 1);
288
314
  Write($outfile, $buff, $ch) or $err = 1 if $outfile;
289
- if ($verbose) {
315
+ if ($verbose and not $outfile) {
290
316
  my ($left, $top, $w, $h) = unpack('v*', $buff);
291
317
  print $out "Image: left=$left top=$top width=$w height=$h\n";
292
318
  }
@@ -352,9 +378,9 @@ Block:
352
378
  }
353
379
  if ($isOverwriting) {
354
380
  ++$$et{CHANGED}; # increment file changed flag
355
- $et->VerboseValue('- Comment', $comment);
381
+ $et->VerboseValue('- GIF:Comment', $comment);
356
382
  $comment = $newComment;
357
- $et->VerboseValue('+ Comment', $comment) if defined $comment;
383
+ $et->VerboseValue('+ GIF:Comment', $comment) if defined $comment;
358
384
  undef $nvComment; # just delete remaining comments
359
385
  } else {
360
386
  undef $setComment; # leave remaining comments alone
@@ -393,14 +419,19 @@ Block:
393
419
  $tag =~ tr/\0-\x1f//d; # remove nulls and control characters
394
420
  $verbose and print $out "Application Extension: $tag\n";
395
421
 
396
- my $extTable = GetTagTable('Image::ExifTool::GIF::Extensions');
397
422
  my $extInfo = $$extTable{$tag};
398
- my ($subdir, $inclLen, $justCopy);
423
+ my ($subdir, $inclLen, $justCopy, $name);
399
424
  if ($extInfo) {
400
- $subdir = $$extInfo{SubDirectory};
425
+ if ($outfile and $$newExt{$$extInfo{TagID}}) {
426
+ delete $$newExt{$$extInfo{TagID}}; # don't create again
427
+ # (write as a block -- don't define $subdir)
428
+ } else {
429
+ $subdir = $$extInfo{SubDirectory};
430
+ }
401
431
  $inclLen = $$extInfo{IncludeLengthBytes};
402
- # rewrite as-is unless this is a writable subdirectory
403
- $justCopy = 1 if $outfile and (not $subdir or not $$extInfo{Writable});
432
+ $name = $$extInfo{Name};
433
+ # rewrite as-is unless this is a writable
434
+ $justCopy = 1 if $outfile and not $$extInfo{Writable};
404
435
  } else {
405
436
  $justCopy = 1 if $outfile;
406
437
  }
@@ -415,62 +446,82 @@ Block:
415
446
  Write($outfile, $ch, $buff) or $err = 1 if $justCopy;
416
447
  $dat .= $inclLen ? $ch . $buff : $buff;
417
448
  }
418
- Write($outfile, "\0") if $justCopy;
419
-
420
- if ($subdir) {
421
- my $dirLen = length $dat;
422
- my $name = $$extInfo{Name};
423
- if ($name eq 'XMP') {
424
- # get length of XMP without landing zone data
425
- # (note that LZ data may not be exactly the same as what we use)
426
- $dirLen = pos($dat) if $dat =~ /<\?xpacket end=['"][wr]['"]\?>/g;
449
+ if ($justCopy) {
450
+ Write($outfile, "\0") or $err = 1;
451
+ next;
452
+ } elsif ($inclLen) {
453
+ # remove landing zone or padding
454
+ if ($$extInfo{Terminator} and $dat =~ /$$extInfo{Terminator}/g) {
455
+ $dat = substr($dat, 0, pos($dat));
456
+ } elsif ($dat =~ /\0/g) {
457
+ $dat = substr($dat, 0, pos($dat) - 1);
427
458
  }
459
+ }
460
+ if ($subdir) {
428
461
  my %dirInfo = (
429
462
  DataPt => \$dat,
430
463
  DataLen => length $dat,
431
- DirLen => $dirLen,
464
+ DirLen => length $dat,
432
465
  DirName => $name,
433
466
  Parent => 'GIF',
434
467
  );
435
468
  my $subTable = GetTagTable($$subdir{TagTable});
436
- if (not $outfile) {
469
+ unless ($outfile) {
437
470
  $et->ProcessDirectory(\%dirInfo, $subTable);
438
- } elsif ($$extInfo{Writable}) {
439
- if ($doneDir{$name} and $doneDir{$name} > 1) {
440
- $et->Warn("Duplicate $name block created");
441
- }
442
- $buff = $et->WriteDirectory(\%dirInfo, $subTable);
443
- if (defined $buff) {
444
- next unless length $buff; # delete this extension if length is zero
445
- # check for null just to be safe
446
- $et->Error("$name contained NULL character") if $buff =~ /\0/;
447
- $dat = $buff;
448
- # add landing zone (without terminator, which will be added later)
449
- $dat .= pack('C*',1,reverse(0..255)) if $$extInfo{IncludeLengthBytes};
450
- } # (else rewrite original data)
451
-
452
- $doneDir{$name} = 1;
453
-
454
- if ($$extInfo{IncludeLengthBytes}) {
455
- # write data and landing zone
456
- Write($outfile, $hdr, $dat) or $err = 1;
457
- } else {
458
- # write as sub-blocks
459
- Write($outfile, $hdr) or $err = 1;
460
- my $pos = 0;
461
- my $len = length $dat;
462
- while ($pos < $len) {
463
- my $n = $len - $pos;
464
- $n = 255 if $n > 255;
465
- Write($outfile, chr($n), substr($dat, $pos, $n)) or $err = 1;
466
- $pos += $n;
467
- }
468
- }
469
- Write($outfile, "\0") or $err = 1; # write null terminator
471
+ next;
472
+ }
473
+ next if $justCopy;
474
+ if ($doneDir{$name} and $doneDir{$name} > 1) {
475
+ $et->Warn("Duplicate $name block created");
476
+ }
477
+ $buff = $et->WriteDirectory(\%dirInfo, $subTable);
478
+ if (defined $buff) {
479
+ next unless length $buff; # delete this extension if length is zero
480
+ $dat = $buff;
481
+ }
482
+ $doneDir{$name} = 1;
483
+ } elsif ($outfile and not $justCopy) {
484
+ my $nvHash = $et->GetNewValueHash($extInfo);
485
+ if ($nvHash and $et->IsOverwriting($nvHash, $dat)) {
486
+ ++$$et{CHANGED};
487
+ my $val = $et->GetNewValue($extInfo);
488
+ $et->VerboseValue("- GIF:$name", $dat);
489
+ next unless defined $val and length $val;
490
+ $dat = $val;
491
+ $et->VerboseValue("+ GIF:$name", $dat);
492
+ $doneDir{$name} = 1; # (possibly wrote dir as a block)
470
493
  }
471
494
  } elsif (not $outfile) {
472
495
  $et->HandleTag($extTable, $tag, $dat);
496
+ next;
497
+ }
498
+ Write($outfile, $hdr) or $err = 1; # write extension header
499
+ if ($inclLen) {
500
+ # check for null just to be safe
501
+ $et->Error("$name contained NULL character") if $inclLen and $dat =~ /\0/;
502
+ if ($inclLen > 1) {
503
+ # add landing zone (without terminator, which will be added later)
504
+ $dat .= pack('C*',1,reverse(0..255)) if $inclLen;
505
+ } else {
506
+ # pad with nulls as required
507
+ my $pos = 0;
508
+ $pos += ord(substr($dat,$pos,1)) + 1 while $pos < length $dat;
509
+ $dat .= "\0" x ($pos - length($dat));
510
+ }
511
+ # write data and landing zone
512
+ Write($outfile, $dat) or $err = 1;
513
+ } else {
514
+ # write as sub-blocks
515
+ my $pos = 0;
516
+ my $len = length $dat;
517
+ while ($pos < $len) {
518
+ my $n = $len - $pos;
519
+ $n = 255 if $n > 255;
520
+ Write($outfile, chr($n), substr($dat, $pos, $n)) or $err = 1;
521
+ $pos += $n;
522
+ }
473
523
  }
524
+ Write($outfile, "\0") or $err = 1; # write null terminator
474
525
  next;
475
526
 
476
527
  } elsif ($a == 0xf9 and $length == 4) { # graphic control extension
@@ -489,7 +540,7 @@ Block:
489
540
 
490
541
  last unless $raf->Read($buff, $length) == $length;
491
542
  Write($outfile, $ch, $s, $buff) or $err = 1 if $outfile;
492
- if ($verbose) {
543
+ if ($verbose and not $outfile) {
493
544
  my ($left, $top, $w, $h) = unpack('v4', $buff);
494
545
  print $out "Text: left=$left top=$top width=$w height=$h\n";
495
546
  }
@@ -12,7 +12,7 @@ use strict;
12
12
  use vars qw($VERSION);
13
13
  use Image::ExifTool::Exif;
14
14
 
15
- $VERSION = '1.56';
15
+ $VERSION = '1.57';
16
16
 
17
17
  my %coordConv = (
18
18
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)',
@@ -20,6 +20,34 @@ my %coordConv = (
20
20
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)',
21
21
  );
22
22
 
23
+ my %printConvLatRef = (
24
+ # extract N/S if written from Composite:GPSLatitude
25
+ # (also allow writing from a signed number)
26
+ OTHER => sub {
27
+ my ($val, $inv) = @_;
28
+ return undef unless $inv;
29
+ return uc $2 if $val =~ /(^|[^A-Z])([NS])(orth|outh)?\b/i;
30
+ return $1 eq '-' ? 'S' : 'N' if $val =~ /([-+]?)\d+/;
31
+ return undef;
32
+ },
33
+ N => 'North',
34
+ S => 'South',
35
+ );
36
+
37
+ my %printConvLonRef = (
38
+ # extract E/W if written from Composite:GPSLongitude
39
+ # (also allow writing from a signed number)
40
+ OTHER => sub {
41
+ my ($val, $inv) = @_;
42
+ return undef unless $inv;
43
+ return uc $2 if $val =~ /(^|[^A-Z])([EW])(ast|est)?\b/i;
44
+ return $1 eq '-' ? 'W' : 'E' if $val =~ /([-+]?)\d+/;
45
+ return undef;
46
+ },
47
+ E => 'East',
48
+ W => 'West',
49
+ );
50
+
23
51
  %Image::ExifTool::GPS::Main = (
24
52
  GROUPS => { 0 => 'EXIF', 1 => 'GPS', 2 => 'Location' },
25
53
  WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
@@ -43,19 +71,7 @@ my %coordConv = (
43
71
  latitudes or negative for south, or a string containing N, North, S or South
44
72
  },
45
73
  Count => 2,
46
- PrintConv => {
47
- # extract N/S if written from Composite:GPSLatitude
48
- # (also allow writing from a signed number)
49
- OTHER => sub {
50
- my ($val, $inv) = @_;
51
- return undef unless $inv;
52
- return uc $2 if $val =~ /(^|[^A-Z])([NS])(orth|outh)?\b/i;
53
- return $1 eq '-' ? 'S' : 'N' if $val =~ /([-+]?)\d+/;
54
- return undef;
55
- },
56
- N => 'North',
57
- S => 'South',
58
- },
74
+ PrintConv => \%printConvLatRef,
59
75
  },
60
76
  0x0002 => {
61
77
  Name => 'GPSLatitude',
@@ -72,19 +88,7 @@ my %coordConv = (
72
88
  ExifTool will also accept a number when writing this tag, positive for east
73
89
  longitudes or negative for west, or a string containing E, East, W or West
74
90
  },
75
- PrintConv => {
76
- # extract E/W if written from Composite:GPSLongitude
77
- # (also allow writing from a signed number)
78
- OTHER => sub {
79
- my ($val, $inv) = @_;
80
- return undef unless $inv;
81
- return uc $2 if $val =~ /(^|[^A-Z])([EW])(ast|est)?\b/i;
82
- return $1 eq '-' ? 'W' : 'E' if $val =~ /([-+]?)\d+/;
83
- return undef;
84
- },
85
- E => 'East',
86
- W => 'West',
87
- },
91
+ PrintConv => \%printConvLonRef,
88
92
  },
89
93
  0x0004 => {
90
94
  Name => 'GPSLongitude',
@@ -238,7 +242,7 @@ my %coordConv = (
238
242
  Writable => 'string',
239
243
  Notes => 'tags 0x0013-0x001a used for subject location according to MWG 2.0',
240
244
  Count => 2,
241
- PrintConv => { N => 'North', S => 'South' },
245
+ PrintConv => \%printConvLatRef,
242
246
  },
243
247
  0x0014 => {
244
248
  Name => 'GPSDestLatitude',
@@ -251,7 +255,7 @@ my %coordConv = (
251
255
  Name => 'GPSDestLongitudeRef',
252
256
  Writable => 'string',
253
257
  Count => 2,
254
- PrintConv => { E => 'East', W => 'West' },
258
+ PrintConv => \%printConvLonRef,
255
259
  },
256
260
  0x0016 => {
257
261
  Name => 'GPSDestLongitude',
@@ -17,6 +17,7 @@
17
17
  # 2022/06/21 - PH Added ability to read Google Takeout JSON files
18
18
  # 2024/04/23 - PH Added ability to read more OpenTracks GPS tags
19
19
  # 2024/08/28 - PH Added support for new Google Takeout JSON format
20
+ # 2024/11/26 - PH Also write GPSMeasureMode and GPSDOP
20
21
  #
21
22
  # References: 1) http://www.topografix.com/GPX/1/1/
22
23
  # 2) http://www.gpsinformation.org/dale/nmea.htm#GSA
@@ -31,7 +32,7 @@ use vars qw($VERSION);
31
32
  use Image::ExifTool qw(:Public);
32
33
  use Image::ExifTool::GPS;
33
34
 
34
- $VERSION = '1.80';
35
+ $VERSION = '1.81';
35
36
 
36
37
  sub JITTER() { return 2 } # maximum time jitter
37
38
 
@@ -90,12 +91,25 @@ my %fixInfoKeys = (
90
91
  orient => [ 'dir', 'pitch', 'roll' ],
91
92
  atemp => [ 'atemp' ],
92
93
  err => [ 'err' ],
94
+ dop => [ 'hdop', 'vdop', 'pdop' ],
93
95
  );
94
96
 
95
- my %isOrient = ( dir => 1, pitch => 1, roll => 1 ); # test for orientation key
97
+ # category for select keys
98
+ my %keyCategory = (
99
+ dir => 'orient',
100
+ pitch => 'orient',
101
+ roll => 'orient',
102
+ hdop => 'dop',
103
+ pdop => 'dop',
104
+ vdop => 'dop',
105
+ );
96
106
 
97
107
  # tags which may exist separately in some formats (eg. CSV)
98
- my %sepTags = ( dir => 1, pitch => 1, roll => 1, track => 1, speed => 1 );
108
+ my %sepTags = (
109
+ dir => 1, pitch => 1, roll => 1, track => 1, speed => 1,
110
+ # (plus other tags we don't want to scan outwards for)
111
+ hdop => 1, pdop => 1, vdop => 1,
112
+ );
99
113
 
100
114
  # conversion factors for GPSSpeed (standard EXIF units only)
101
115
  my %speedConv = (
@@ -348,8 +362,8 @@ sub LoadTrackLog($$;$)
348
362
  my $tag = $xmlTag{lc $2};
349
363
  if ($tag) {
350
364
  $$fix{$tag} = $4;
351
- if ($isOrient{$tag}) {
352
- $$has{orient} = 1;
365
+ if ($keyCategory{$tag}) {
366
+ $$has{$keyCategory{$tag}} = 1;
353
367
  } elsif ($tag eq 'alt') {
354
368
  # validate altitude
355
369
  undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/;
@@ -394,8 +408,8 @@ sub LoadTrackLog($$;$)
394
408
  } else {
395
409
  $$fix{$tag} = $1;
396
410
  }
397
- if ($isOrient{$tag}) {
398
- $$has{orient} = 1;
411
+ if ($keyCategory{$tag}) {
412
+ $$has{$keyCategory{$tag}} = 1;
399
413
  } elsif ($tag eq 'alt') {
400
414
  # validate altitude
401
415
  undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/;
@@ -1145,7 +1159,7 @@ sub SetGeoValues($$;$)
1145
1159
  # loop through available fix information categories
1146
1160
  # (pos, track, alt, orient)
1147
1161
  my ($category, $key);
1148
- Category: foreach $category (qw{pos track alt orient atemp err}) {
1162
+ Category: foreach $category (qw{pos track alt orient atemp err dop}) {
1149
1163
  next unless $$has{$category};
1150
1164
  my ($f, $p0b, $p1b, $f0b);
1151
1165
  # loop through specific fix information keys
@@ -1303,6 +1317,25 @@ Category: foreach $category (qw{pos track alt orient atemp err}) {
1303
1317
  if ($$has{err}) {
1304
1318
  @r = $et->SetNewValue(GPSHPositioningError => $$fix{err}, %opts);
1305
1319
  }
1320
+ if ($$has{dop}) {
1321
+ my ($dop, $mm);
1322
+ if (defined $$fix{pdop}) {
1323
+ $dop = $$fix{pdop};
1324
+ $mm = 3;
1325
+ } elsif (defined $$fix{hdop}) {
1326
+ if (defined $$fix{vdop}) {
1327
+ $dop = sqrt($$fix{hdop} * $$fix{hdop} + $$fix{vdop} * $$fix{vdop});
1328
+ $mm = 3;
1329
+ } else {
1330
+ $dop = $$fix{hdop};
1331
+ $mm = 2;
1332
+ }
1333
+ }
1334
+ if (defined $dop) {
1335
+ $et->SetNewValue(GPSMeasureMode => $mm, %opts);
1336
+ $et->SetNewValue(GPSDOP => $dop, %opts);
1337
+ }
1338
+ }
1306
1339
  unless ($xmp) {
1307
1340
  my ($latRef, $lonRef);
1308
1341
  $latRef = ($$fix{lat} > 0 ? 'N' : 'S') if defined $$fix{lat};
@@ -1328,7 +1361,8 @@ Category: foreach $category (qw{pos track alt orient atemp err}) {
1328
1361
  GPSAltitude GPSAltitudeRef GPSDateStamp GPSTimeStamp GPSDateTime
1329
1362
  GPSTrack GPSTrackRef GPSSpeed GPSSpeedRef GPSImgDirection
1330
1363
  GPSImgDirectionRef GPSPitch GPSRoll CameraElevationAngle
1331
- AmbientTemperature GPSHPositioningError GPSCoordinates))
1364
+ AmbientTemperature GPSHPositioningError GPSCoordinates
1365
+ GPSMeasureMode GPSDOP))
1332
1366
  {
1333
1367
  my @r = $et->SetNewValue($_, undef, %opts);
1334
1368
  }