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.
- checksums.yaml +4 -4
- data/bin/Changes +40 -3
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +2 -2
- data/bin/exiftool +76 -43
- data/bin/lib/Image/ExifTool/Apple.pm +2 -2
- data/bin/lib/Image/ExifTool/Canon.pm +7 -1
- data/bin/lib/Image/ExifTool/Exif.pm +15 -4
- data/bin/lib/Image/ExifTool/GIF.pm +143 -92
- data/bin/lib/Image/ExifTool/GPS.pm +33 -29
- data/bin/lib/Image/ExifTool/Geotag.pm +43 -9
- data/bin/lib/Image/ExifTool/GoPro.pm +118 -6
- data/bin/lib/Image/ExifTool/JPEG.pm +9 -1
- data/bin/lib/Image/ExifTool/Matroska.pm +10 -2
- data/bin/lib/Image/ExifTool/OOXML.pm +7 -7
- data/bin/lib/Image/ExifTool/PDF.pm +29 -2
- data/bin/lib/Image/ExifTool/PNG.pm +14 -3
- data/bin/lib/Image/ExifTool/PPM.pm +11 -2
- data/bin/lib/Image/ExifTool/Photoshop.pm +2 -2
- data/bin/lib/Image/ExifTool/QuickTime.pm +6 -1
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +1 -1
- data/bin/lib/Image/ExifTool/RIFF.pm +7 -2
- data/bin/lib/Image/ExifTool/TagLookup.pm +5598 -5583
- data/bin/lib/Image/ExifTool/TagNames.pod +64 -6
- data/bin/lib/Image/ExifTool/WriteRIFF.pl +13 -4
- data/bin/lib/Image/ExifTool/Writer.pl +16 -11
- data/bin/lib/Image/ExifTool/XMP.pm +7 -2
- data/bin/lib/Image/ExifTool/XMP2.pl +64 -0
- data/bin/lib/Image/ExifTool.pm +101 -72
- data/bin/lib/Image/ExifTool.pod +16 -3
- data/bin/perl-Image-ExifTool.spec +1 -1
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +2 -2
@@ -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.
|
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
|
71
|
-
|
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
|
-
#
|
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
|
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
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
my $
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|
-
|
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
|
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
|
-
$
|
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
|
-
|
403
|
-
|
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
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
if ($
|
424
|
-
|
425
|
-
|
426
|
-
$
|
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 => $
|
464
|
+
DirLen => length $dat,
|
432
465
|
DirName => $name,
|
433
466
|
Parent => 'GIF',
|
434
467
|
);
|
435
468
|
my $subTable = GetTagTable($$subdir{TagTable});
|
436
|
-
|
469
|
+
unless ($outfile) {
|
437
470
|
$et->ProcessDirectory(\%dirInfo, $subTable);
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
$
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
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.
|
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 =>
|
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 =>
|
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.
|
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
|
-
|
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 = (
|
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 ($
|
352
|
-
$$has{
|
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 ($
|
398
|
-
$$has{
|
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
|
}
|