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.
- 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
|
}
|