exiftool_vendored 13.02.0 → 13.04.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }