- Timestamp:
- 2021-02-26T19:39:51+13:00 (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
main/trunk/greenstone2/perllib/cpan/Image/ExifTool/Writer.pl
r24107 r34921 6 6 # Notes: Also contains some less used ExifTool functions 7 7 # 8 # URL: http ://owl.phy.queensu.ca/~phil/exiftool/8 # URL: https://exiftool.org/ 9 9 # 10 10 # Revisions: 12/16/2004 - P. Harvey Created … … 20 20 sub AssembleRational($$@); 21 21 sub LastInList($); 22 sub CreateDirectory($); 22 sub CreateDirectory($$); 23 sub NextFreeTagKey($$); 23 24 sub RemoveNewValueHash($$$); 24 25 sub RemoveNewValuesForGroup($$); … … 28 29 29 30 my $loadedAllTables; # flag indicating we loaded all tables 31 my $advFmtSelf; # ExifTool object during evaluation of advanced formatting expr 30 32 31 33 # the following is a road map of where we write each directory … … 47 49 CanonVRD => 'MakerNotes', # (so VRDOffset will get updated) 48 50 NikonCapture => 'MakerNotes', # (to allow delete by group) 51 PhaseOne => 'MakerNotes', # (for editing PhaseOne SensorCalibration tags) 49 52 ); 50 53 my %exifMap = ( … … 69 72 ICC_Profile => 'APP2', 70 73 FlashPix => 'APP2', 74 MPF => 'APP2', 71 75 Meta => 'APP3', 72 76 MetaIFD => 'Meta', … … 74 78 Ducky => 'APP12', 75 79 Photoshop => 'APP13', 80 Adobe => 'APP14', 76 81 IPTC => 'Photoshop', 77 82 MakerNotes => ['ExifIFD', 'CIFF'], # (first parent is the default) … … 82 87 my %dirMap = ( 83 88 JPEG => \%jpegMap, 89 EXV => \%jpegMap, 84 90 TIFF => \%tiffMap, 85 91 ORF => \%tiffMap, … … 88 94 ); 89 95 96 # module names and write functions for each writable file type 97 # (defaults to "$type" and "Process$type" if not defined) 98 # - types that are handled specially will not appear in this list 99 my %writableType = ( 100 CRW => [ 'CanonRaw', 'WriteCRW' ], 101 DR4 => 'CanonVRD', 102 EPS => [ 'PostScript', 'WritePS' ], 103 FLIF=> [ undef, 'WriteFLIF'], 104 GIF => undef, 105 ICC => [ 'ICC_Profile', 'WriteICC' ], 106 IND => 'InDesign', 107 JP2 => 'Jpeg2000', 108 MIE => undef, 109 MOV => [ 'QuickTime', 'WriteMOV' ], 110 MRW => 'MinoltaRaw', 111 PDF => [ undef, 'WritePDF' ], 112 PNG => undef, 113 PPM => undef, 114 PS => [ 'PostScript', 'WritePS' ], 115 PSD => 'Photoshop', 116 RAF => [ 'FujiFilm', 'WriteRAF' ], 117 VRD => 'CanonVRD', 118 X3F => 'SigmaRaw', 119 XMP => [ undef, 'WriteXMP' ], 120 ); 121 122 # RAW file types 123 my %rawType = ( 124 '3FR'=> 1, CR3 => 1, IIQ => 1, NEF => 1, RW2 => 1, 125 ARQ => 1, CRW => 1, K25 => 1, NRW => 1, RWL => 1, 126 ARW => 1, DCR => 1, KDC => 1, ORF => 1, SR2 => 1, 127 ARW => 1, ERF => 1, MEF => 1, PEF => 1, SRF => 1, 128 CR2 => 1, FFF => 1, MOS => 1, RAW => 1, SRW => 1, 129 ); 130 90 131 # groups we are allowed to delete 91 132 # Notes: … … 93 134 # 2) any dependencies must be added to %excludeGroups 94 135 my @delGroups = qw( 95 AFCP CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix FotoStation GlobParamIFD 96 GPS ICC_Profile IFD0 IFD1 InteropIFD IPTC JFIF MakerNotes Meta MetaIFD MIE 97 NikonCapture PDF PDF-update PhotoMechanic Photoshop PNG PrintIM RMETA RSRC 98 SubIFD Trailer XML XML-* XMP XMP-* 136 Adobe AFCP APP0 APP1 APP2 APP3 APP4 APP5 APP6 APP7 APP8 APP9 APP10 APP11 137 APP12 APP13 APP14 APP15 CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix 138 FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD IPTC 139 ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD MIE MPF NikonCapture PDF 140 PDF-update PhotoMechanic Photoshop PNG PNG-pHYs PrintIM QuickTime RMETA RSRC 141 SubIFD Trailer UserData XML XML-* XMP XMP-* 99 142 ); 143 # family 2 group names that we can delete 144 my @delGroup2 = qw( 145 Audio Author Camera Document ExifTool Image Location Other Preview Printing 146 Time Video 147 ); 148 # Extra groups to delete when deleting another group 149 my %delMore = ( 150 QuickTime => [ qw(ItemList UserData Keys) ], 151 XMP => [ 'XMP-*' ], 152 XML => [ 'XML-*' ], 153 ); 154 155 # family 0 groups where directories should never be deleted 156 my %permanentDir = ( QuickTime => 1 ); 157 158 # lookup for all valid family 2 groups (lower case) 159 my %family2groups = map { lc $_ => 1 } @delGroup2, 'Unknown'; 160 161 # groups we don't delete when deleting all information 162 my $protectedGroups = '(IFD1|SubIFD|InteropIFD|GlobParamIFD|PDF-update|Adobe)'; 163 100 164 # other group names of new tag values to remove when deleting an entire group 101 165 my %removeGroups = ( … … 125 189 PhotoMechanic=> [ 'Trailer' ], 126 190 MIE => [ 'Trailer' ], 191 QuickTime => [ qw(ItemList UserData Keys) ], 192 ); 193 # translate (lower case) wanted group when writing for tags where group name may change 194 my %translateWantGroup = ( 195 ciff => 'canonraw', 127 196 ); 128 197 # group names to translate for writing 129 198 my %translateWriteGroup = ( 130 EXIF => 'ExifIFD', 131 Meta => 'MetaIFD', 132 File => 'Comment', 133 MIE => 'MIE', 199 EXIF => 'ExifIFD', 200 Meta => 'MetaIFD', 201 File => 'Comment', 202 # any entry in this table causes the write group to be set from the 203 # tag information instead of whatever the user specified... 204 MIE => 'MIE', 205 APP14 => 'APP14', 134 206 ); 135 # names of valid EXIF and Meta directories :207 # names of valid EXIF and Meta directories (lower case keys): 136 208 my %exifDirs = ( 137 209 gps => 'GPS', … … 140 212 globparamifd => 'GlobParamIFD', 141 213 interopifd => 'InteropIFD', 142 makernotes => 'MakerNotes',143 214 previewifd => 'PreviewIFD', # (in MakerNotes) 144 215 metaifd => 'MetaIFD', # Kodak APP3 Meta 216 makernotes => 'MakerNotes', 145 217 ); 218 # valid family 0 groups when WriteGroup is set to "All" 219 my %allFam0 = ( 220 exif => 1, 221 makernotes => 1, 222 ); 223 224 my @writableMacOSTags = qw( 225 FileCreateDate MDItemFinderComment MDItemFSCreationDate MDItemFSLabel MDItemUserTags 226 XAttrQuarantine 227 ); 228 146 229 # min/max values for integer formats 147 230 my %intRange = ( … … 153 236 'int32u' => [0, 0xffffffff], 154 237 'int32s' => [-0x80000000, 0x7fffffff], 238 'int64u' => [0, 18446744073709551615], 239 'int64s' => [-9223372036854775808, 9223372036854775807], 155 240 ); 156 241 # lookup for file types with block-writable EXIF 157 my %blockExifTypes = ( JPEG=>1, PNG=>1, JP2=>1, MIE=>1, EXIF=>1);242 my %blockExifTypes = map { $_ => 1 } qw(JPEG PNG JP2 MIE EXIF FLIF MOV MP4); 158 243 159 244 my $maxSegmentLen = 0xfffd; # maximum length of data in a JPEG segment … … 164 249 165 250 # printConv hash keys to ignore when doing reverse lookup 166 my %ignorePrintConv = ( OTHER => 1, BITMASK => 1, Notes => 1);251 my %ignorePrintConv = map { $_ => 1 } qw(OTHER BITMASK Notes); 167 252 168 253 #------------------------------------------------------------------------------ … … 175 260 # Type => PrintConv, ValueConv or Raw - specifies value type 176 261 # AddValue => true to add to list of existing values instead of overwriting 177 # DelValue => true to delete this existing value value from a list 262 # DelValue => true to delete this existing value value from a list, or 263 # or doing a conditional delete, or to shift a time value 178 264 # Group => family 0 or 1 group name (case insensitive) 179 265 # Replace => 0, 1 or 2 - overwrite previous new values (2=reset) … … 182 268 # EditGroup => true to only edit existing groups (don't create new group) 183 269 # Shift => undef, 0, +1 or -1 - shift value if possible 270 # NoFlat => treat flattened tags as 'unsafe' 184 271 # NoShortcut => true to prevent looking up shortcut tags 272 # ProtectSaved => protect existing new values with a save count greater than this 273 # IgnorePermanent => ignore attempts to delete a permanent tag 185 274 # CreateGroups => [internal use] createGroups hash ref from related tags 186 275 # ListOnly => [internal use] set only list or non-list tags 187 276 # SetTags => [internal use] hash ref to return tagInfo refs of set tags 277 # Sanitized => [internal use] set to avoid double-sanitizing the value 188 278 # Returns: number of tags set (plus error string in list context) 189 279 # Notes: For tag lists (like Keywords), call repeatedly with the same tag name for 190 280 # each value in the list. Internally, the new information is stored in 191 # the following members of the $ self->{NEW_VALUE}{$tagInfo} hash:281 # the following members of the $$self{NEW_VALUE}{$tagInfo} hash: 192 282 # TagInfo - tag info ref 193 # DelValue - list ref for values to delete 194 # Value - list ref for values to add 195 # IsCreating - must be set for the tag to be added, otherwise just 196 # changed if it already exists. Set to 2 to not create group 283 # DelValue - list ref for raw values to delete 284 # Value - list ref for raw values to add (not defined if deleting the tag) 285 # IsCreating - must be set for the tag to be added for the standard file types, 286 # otherwise just changed if it already exists. This may be 287 # overridden for file types with a PREFERRED metadata type. 288 # Set to 2 to create individual tags but not new groups 289 # EditOnly - flag set if tag should never be created (regardless of file type). 290 # If this is set, then IsCreating must be false 291 # CreateOnly - flag set if creating only (never edit existing tag) 197 292 # CreateGroups - hash of all family 0 group names where tag may be created 198 293 # WriteGroup - group name where information is being written (correct case) 199 294 # WantGroup - group name as specified in call to function (case insensitive) 200 295 # Next - pointer to next new value hash (if more than one) 201 # Self - ExifTool object reference 296 # NoReplace - set if value was created with Replace=0 297 # AddBefore - number of list items added by a subsequent Replace=0 call 298 # IsNVH - Flag indicating this is a new value hash 202 299 # Shift - shift value 300 # Save - counter used by SaveNewValues()/RestoreNewValues() 203 301 # MAKER_NOTE_FIXUP - pointer to fixup if necessary for a maker note value 204 302 sub SetNewValue($;$$%) … … 206 304 local $_; 207 305 my ($self, $tag, $value, %options) = @_; 208 my ($err, $tagInfo );209 my $verbose = $ self->{OPTIONS}{Verbose};210 my $out = $ self->{OPTIONS}{TextOut};306 my ($err, $tagInfo, $family); 307 my $verbose = $$self{OPTIONS}{Verbose}; 308 my $out = $$self{OPTIONS}{TextOut}; 211 309 my $protected = $options{Protected} || 0; 212 310 my $listOnly = $options{ListOnly}; 213 311 my $setTags = $options{SetTags}; 312 my $noFlat = $options{NoFlat}; 214 313 my $numSet = 0; 215 314 216 315 unless (defined $tag) { 217 # remove any existing set values 218 delete $self->{NEW_VALUE}; 219 $self->{DEL_GROUP} = { }; 220 $verbose > 1 and print $out "Cleared new values\n"; 316 delete $$self{NEW_VALUE}; 317 $$self{SAVE_COUNT} = 0; 318 $$self{DEL_GROUP} = { }; 221 319 return 1; 222 320 } … … 224 322 if (ref $value) { 225 323 if (ref $value eq 'ARRAY') { 226 # (since value is an ARRAY, it will have more than one entry) 227 # set all list-type tags first 228 my $replace = $options{Replace}; 229 foreach (@$value) { 230 my ($n, $e) = SetNewValue($self, $tag, $_, %options, ListOnly => 1); 231 $err = $e if $e; 232 $numSet += $n; 233 delete $options{Replace}; # don't replace earlier values in list 234 } 235 # and now set only non-list tags 236 $value = join $self->{OPTIONS}{ListSep}, @$value; 237 $options{Replace} = $replace; 238 $listOnly = $options{ListOnly} = 0; 324 # value is an ARRAY so it may have more than one entry 325 # - set values both separately and as a combined string if there are more than one 326 if (@$value > 1) { 327 # set all list-type tags first 328 my $replace = $options{Replace}; 329 my $noJoin; 330 foreach (@$value) { 331 $noJoin = 1 if ref $_; 332 my ($n, $e) = SetNewValue($self, $tag, $_, %options, ListOnly => 1); 333 $err = $e if $e; 334 $numSet += $n; 335 delete $options{Replace}; # don't replace earlier values in list 336 } 337 return $numSet if $noJoin; # don't join if list contains objects 338 # and now set only non-list tags 339 $value = join $$self{OPTIONS}{ListSep}, @$value; 340 $options{Replace} = $replace; 341 $listOnly = $options{ListOnly} = 0; 342 } else { 343 $value = $$value[0]; 344 $value = $$value if ref $value eq 'SCALAR'; # (handle single scalar ref in a list) 345 } 239 346 } elsif (ref $value eq 'SCALAR') { 240 347 $value = $$value; … … 243 350 # un-escape as necessary and make sure the Perl UTF-8 flag is OFF for the value 244 351 # if perl is 5.6 or greater (otherwise our byte manipulations get corrupted!!) 245 $self->Sanitize(\$value) if defined $value and not ref $value ;352 $self->Sanitize(\$value) if defined $value and not ref $value and not $options{Sanitized}; 246 353 247 354 # set group name in options if specified 248 if ($tag =~ /(.*):(.+)/) { 249 $options{Group} = $1 if $1 ne '*' and lc($1) ne 'all'; 250 $tag = $2; 251 } 355 ($options{Group}, $tag) = ($1, $2) if $tag =~ /(.*):(.+)/; 356 252 357 # allow trailing '#' for ValueConv value 253 358 $options{Type} = 'ValueConv' if $tag =~ s/#$//; 254 # ignore leading family number if 0 or 1 specified 255 if ($options{Group} and $options{Group} =~ /^(\d+)(.*)/ and $1 < 2) { 256 $options{Group} = $2; 359 my $convType = $options{Type} || ($$self{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv'); 360 361 # filter value if necessary 362 $self->Filter($$self{OPTIONS}{FilterW}, \$value) or return 0 if $convType eq 'PrintConv'; 363 364 my (@wantGroup, $family2); 365 my $wantGroup = $options{Group}; 366 if ($wantGroup) { 367 foreach (split /:/, $wantGroup) { 368 next unless length($_) and /^(\d+)?(.*)/; # separate family number and group name 369 my ($f, $g) = ($1, $2); 370 my $lcg = lc $g; 371 # save group/family unless '*' or 'all' 372 push @wantGroup, [ $f, $lcg ] unless $lcg eq '*' or $lcg eq 'all'; 373 if ($g =~ s/^ID-//i) { # family 7 is a tag ID 374 return 0 if defined $f and $f ne 7; 375 $wantGroup[-1] = [ 7, $g ]; # group name with 'ID-' removed and case preserved 376 } elsif (defined $f) { 377 $f > 2 and return 0; # only allow family 0, 1 or 2 378 $family2 = 1 if $f == 2; # set flag indicating family 2 was used 379 } else { 380 $family2 = 1 if $family2groups{$lcg}; 381 } 382 } 383 undef $wantGroup unless @wantGroup; 384 } 385 386 $tag =~ s/ .*//; # convert from tag key to tag name if necessary 387 $tag = '*' if lc($tag) eq 'all'; # use '*' instead of 'all' 388 # 389 # handle group delete 390 # 391 while ($tag eq '*' and not defined $value and not $family2 and @wantGroup < 2) { 392 # set groups to delete 393 my (@del, $grp); 394 my $remove = ($options{Replace} and $options{Replace} > 1); 395 if ($wantGroup) { 396 @del = grep /^$wantGroup$/i, @delGroups unless $wantGroup =~ /^XM[LP]-\*$/i; 397 # remove associated groups when excluding from mass delete 398 if (@del and $remove) { 399 # remove associated groups in other family 400 push @del, @{$excludeGroups{$del[0]}} if $excludeGroups{$del[0]}; 401 # remove upstream groups according to JPEG map 402 my $dirName = $del[0]; 403 my @dirNames; 404 for (;;) { 405 my $parent = $jpegMap{$dirName}; 406 if (ref $parent) { 407 push @dirNames, @$parent; 408 $parent = pop @dirNames; 409 } 410 $dirName = $parent || shift @dirNames or last; 411 push @del, $dirName; # exclude this too 412 } 413 } 414 # allow MIE groups to be deleted by number, 415 # and allow any XMP family 1 group to be deleted 416 push @del, uc($wantGroup) if $wantGroup =~ /^(MIE\d+|XM[LP]-[-\w]*\w)$/i; 417 } else { 418 # push all groups plus '*', except the protected groups 419 push @del, (grep !/^$protectedGroups$/, @delGroups), '*'; 420 } 421 if (@del) { 422 ++$numSet; 423 my @donegrps; 424 my $delGroup = $$self{DEL_GROUP}; 425 foreach $grp (@del) { 426 if ($remove) { 427 my $didExcl; 428 if ($grp =~ /^(XM[LP])(-.*)?$/) { 429 my $x = $1; 430 if ($grp eq $x) { 431 # exclude all related family 1 groups too 432 foreach (keys %$delGroup) { 433 next unless /^(-?)$x-/; 434 push @donegrps, $_ unless $1; 435 delete $$delGroup{$_}; 436 } 437 } elsif ($$delGroup{"$x-*"} and not $$delGroup{"-$grp"}) { 438 # must also exclude XMP or XML to prevent bulk delete 439 if ($$delGroup{$x}) { 440 push @donegrps, $x; 441 delete $$delGroup{$x}; 442 } 443 # flag XMP/XML family 1 group for exclusion with leading '-' 444 $$delGroup{"-$grp"} = 1; 445 $didExcl = 1; 446 } 447 } 448 if (exists $$delGroup{$grp}) { 449 delete $$delGroup{$grp}; 450 } else { 451 next unless $didExcl; 452 } 453 } else { 454 $$delGroup{$grp} = 1; 455 # add extra groups to delete if necessary 456 if ($delMore{$grp}) { 457 $$delGroup{$_} = 1, push @donegrps, $_ foreach @{$delMore{$grp}}; 458 } 459 # remove all of this group from previous new values 460 $self->RemoveNewValuesForGroup($grp); 461 } 462 push @donegrps, $grp; 463 } 464 if ($verbose > 1 and @donegrps) { 465 @donegrps = sort @donegrps; 466 my $msg = $remove ? 'Excluding from deletion' : 'Deleting tags in'; 467 print $out " $msg: @donegrps\n"; 468 } 469 } elsif (grep /^$wantGroup$/i, @delGroup2) { 470 last; # allow tags to be deleted by group2 name 471 } else { 472 $err = "Not a deletable group: $wantGroup"; 473 } 474 # all done 475 return ($numSet, $err) if wantarray; 476 $err and warn "$err\n"; 477 return $numSet; 478 } 479 480 # initialize write/create flags 481 my $createOnly; 482 my $editOnly = $options{EditOnly}; 483 my $editGroup = $options{EditGroup}; 484 my $writeMode = $$self{OPTIONS}{WriteMode}; 485 if ($writeMode ne 'wcg') { 486 $createOnly = 1 if $writeMode !~ /w/i; # don't write existing tags 487 if ($writeMode !~ /c/i) { 488 return 0 if $createOnly; # nothing to do unless writing existing tags 489 $editOnly = 1; # don't create new tags 490 } elsif ($writeMode !~ /g/i) { 491 $editGroup = 1; # don't create new groups 492 } 493 } 494 my ($ifdName, $mieGroup, $movGroup, $fg); 495 # set family 1 group names 496 foreach $fg (@wantGroup) { 497 next if defined $$fg[0] and $$fg[0] != 1; 498 $_ = $$fg[1]; 499 # set $ifdName if this group is a valid IFD or SubIFD name 500 my $grpName; 501 if (/^IFD(\d+)$/i) { 502 $grpName = $ifdName = "IFD$1"; 503 } elsif (/^SubIFD(\d+)$/i) { 504 $grpName = $ifdName = "SubIFD$1"; 505 } elsif (/^Version(\d+)$/i) { 506 $grpName = $ifdName = "Version$1"; # Sony IDC VersionIFD 507 } elsif ($exifDirs{$_}) { 508 $grpName = $exifDirs{$_}; 509 $ifdName = $grpName unless $ifdName and $allFam0{$_}; 510 } elsif ($allFam0{$_}) { 511 $grpName = $allFam0{$_}; 512 } elsif (/^Track(\d+)$/i) { 513 $grpName = $movGroup = "Track$1"; # QuickTime track 514 } elsif (/^MIE(\d*-?)(\w+)$/i) { 515 $grpName = $mieGroup = "MIE$1" . ucfirst(lc($2)); 516 } elsif (not $ifdName and /^XMP\b/i) { 517 # must load XMP table to set group1 names 518 my $table = GetTagTable('Image::ExifTool::XMP::Main'); 519 my $writeProc = $$table{WRITE_PROC}; 520 if ($writeProc) { 521 no strict 'refs'; 522 &$writeProc(); 523 } 524 } 525 # fix case for known groups 526 $wantGroup =~ s/$grpName/$grpName/i if $grpName and $grpName ne $_; 257 527 } 258 528 # 259 529 # get list of tags we want to set 260 530 # 261 my $wantGroup = $options{Group}; 262 $tag =~ s/ .*//; # convert from tag key to tag name if necessary 531 my $origTag = $tag; 263 532 my @matchingTags = FindTagInfo($tag); 264 533 until (@matchingTags) { 265 if ($tag eq '*' or lc($tag) eq 'all') { 266 # set groups to delete 267 if (defined $value) { 268 $err = "Can't set value for all tags"; 269 } else { 270 my (@del, $grp); 271 my $remove = ($options{Replace} and $options{Replace} > 1); 272 if ($wantGroup) { 273 @del = grep /^$wantGroup$/i, @delGroups unless $wantGroup =~ /^XM[LP]-\*$/i; 274 # remove associated groups when excluding from mass delete 275 if (@del and $remove) { 276 # remove associated groups in other family 277 push @del, @{$excludeGroups{$del[0]}} if $excludeGroups{$del[0]}; 278 # remove upstream groups according to JPEG map 279 my $dirName = $del[0]; 280 my @dirNames; 281 for (;;) { 282 my $parent = $jpegMap{$dirName}; 283 if (ref $parent) { 284 push @dirNames, @$parent; 285 $parent = pop @dirNames; 286 } 287 $dirName = $parent || shift @dirNames or last; 288 push @del, $dirName; # exclude this too 289 } 290 } 291 # allow MIE groups to be deleted by number, 292 # and allow any XMP family 1 group to be deleted 293 push @del, uc($wantGroup) if $wantGroup =~ /^(MIE\d+|XM[LP]-[-\w]+)$/i; 534 my $langCode; 535 # allow language suffix of form "-en_CA" or "-<rfc3066>" on tag name 536 if ($tag =~ /^([?*\w]+)-([a-z]{2})(_[a-z]{2})$/i or # MIE 537 $tag =~ /^([?*\w]+)-([a-z]{2,3}|[xi])(-[a-z\d]{2,8}(-[a-z\d]{1,8})*)?$/i) # XMP/PNG/QuickTime 538 { 539 $tag = $1; 540 # normalize case of language codes 541 $langCode = lc($2); 542 $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3; 543 my @newMatches = FindTagInfo($tag); 544 foreach $tagInfo (@newMatches) { 545 # only allow language codes in tables which support them 546 next unless $$tagInfo{Table}; 547 my $langInfoProc = $$tagInfo{Table}{LANG_INFO} or next; 548 my $langInfo = &$langInfoProc($tagInfo, $langCode); 549 push @matchingTags, $langInfo if $langInfo; 550 } 551 last if @matchingTags; 552 } elsif (not $options{NoShortcut}) { 553 # look for a shortcut or alias 554 require Image::ExifTool::Shortcuts; 555 my ($match) = grep /^\Q$tag\E$/i, keys %Image::ExifTool::Shortcuts::Main; 556 undef $err; 557 if ($match) { 558 $options{NoShortcut} = $options{Sanitized} = 1; 559 foreach $tag (@{$Image::ExifTool::Shortcuts::Main{$match}}) { 560 my ($n, $e) = $self->SetNewValue($tag, $value, %options); 561 $numSet += $n; 562 $e and $err = $e; 563 } 564 undef $err if $numSet; # no error if any set successfully 565 return ($numSet, $err) if wantarray; 566 $err and warn "$err\n"; 567 return $numSet; 568 } 569 } 570 unless ($listOnly) { 571 if (not TagExists($tag)) { 572 if ($tag =~ /^[-\w*?]+$/) { 573 my $pre = $wantGroup ? $wantGroup . ':' : ''; 574 $err = "Tag '$pre${origTag}' is not defined"; 575 $err .= ' or has a bad language code' if $origTag =~ /-/; 294 576 } else { 295 # push all groups plus '*', except IFD1 and a few others 296 push @del, (grep !/^(IFD1|SubIFD|InteropIFD|GlobParamIFD|PDF-update)$/, @delGroups), '*'; 297 } 298 if (@del) { 299 ++$numSet; 300 my @donegrps; 301 my $delGroup = $self->{DEL_GROUP}; 302 foreach $grp (@del) { 303 if ($remove) { 304 my $didExcl; 305 if ($grp =~ /^(XM[LP])(-.*)?$/) { 306 my $x = $1; 307 if ($grp eq $x) { 308 # exclude all related family 1 groups too 309 foreach (keys %$delGroup) { 310 next unless /^-?$x-/; 311 push @donegrps, $_ if /^$x/; 312 delete $$delGroup{$_}; 313 } 314 } elsif ($$delGroup{"$x-*"} and not $$delGroup{"-$grp"}) { 315 # must also exclude XMP or XML to prevent bulk delete 316 if ($$delGroup{$x}) { 317 push @donegrps, $x; 318 delete $$delGroup{$x}; 319 } 320 # flag XMP/XML family 1 group for exclusion with leading '-' 321 $$delGroup{"-$grp"} = 1; 322 $didExcl = 1; 323 } 324 } 325 if (exists $$delGroup{$grp}) { 326 delete $$delGroup{$grp}; 327 } else { 328 next unless $didExcl; 329 } 330 } else { 331 $$delGroup{$grp} = 1; 332 # add flag for XMP/XML family 1 groups if deleting all XMP 333 if ($grp =~ /^XM[LP]$/) { 334 $$delGroup{"$grp-*"} = 1; 335 push @donegrps, "$grp-*"; 336 } 337 # remove all of this group from previous new values 338 $self->RemoveNewValuesForGroup($grp); 339 } 340 push @donegrps, $grp; 341 } 342 if ($verbose > 1 and @donegrps) { 343 @donegrps = sort @donegrps; 344 my $msg = $remove ? 'Excluding from deletion' : 'Deleting tags in'; 345 print $out " $msg: @donegrps\n"; 346 } 347 } else { 348 $err = "Not a deletable group: $wantGroup"; 349 } 350 } 351 } else { 352 my $origTag = $tag; 353 my $langCode; 354 # allow language suffix of form "-en_CA" or "-<rfc3066>" on tag name 355 if ($tag =~ /^(\w+)-([a-z]{2})(_[a-z]{2})$/i or # MIE 356 $tag =~ /^(\w+)-([a-z]{2,3}|[xi])(-[a-z\d]{2,8}(-[a-z\d]{1,8})*)?$/i) # XMP/PNG 357 { 358 $tag = $1; 359 # normalize case of language codes 360 $langCode = lc($2); 361 $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3; 362 my @newMatches = FindTagInfo($tag); 363 foreach $tagInfo (@newMatches) { 364 # only allow language codes in tables which support them 365 next unless $$tagInfo{Table}; 366 my $langInfoProc = $tagInfo->{Table}{LANG_INFO} or next; 367 my $langInfo = &$langInfoProc($tagInfo, $langCode); 368 push @matchingTags, $langInfo if $langInfo; 369 } 370 last if @matchingTags; 371 } else { 372 # look for a shortcut or alias 373 require Image::ExifTool::Shortcuts; 374 my ($match) = grep /^\Q$tag\E$/i, keys %Image::ExifTool::Shortcuts::Main; 375 undef $err; 376 if ($match and not $options{NoShortcut}) { 377 if (@{$Image::ExifTool::Shortcuts::Main{$match}} == 1) { 378 $tag = $Image::ExifTool::Shortcuts::Main{$match}[0]; 379 @matchingTags = FindTagInfo($tag); 380 last if @matchingTags; 381 } else { 382 $options{NoShortcut} = 1; 383 foreach $tag (@{$Image::ExifTool::Shortcuts::Main{$match}}) { 384 my ($n, $e) = $self->SetNewValue($tag, $value, %options); 385 $numSet += $n; 386 $e and $err = $e; 387 } 388 undef $err if $numSet; # no error if any set successfully 389 return ($numSet, $err) if wantarray; 390 $err and warn "$err\n"; 391 return $numSet; 392 } 393 } 394 } 395 if (not TagExists($tag)) { 396 $err = "Tag '$origTag' does not exist"; 397 $err .= ' or has a bad language code' if $origTag =~ /-/; 577 $err = "Invalid tag name '${tag}'"; 578 $err .= " (remove the leading '\$')" if $tag =~ /^\$/; 579 } 398 580 } elsif ($langCode) { 399 $err = "Tag '$ tag' does not support alternate languages";581 $err = "Tag '${tag}' does not support alternate languages"; 400 582 } elsif ($wantGroup) { 401 583 $err = "Sorry, $wantGroup:$origTag doesn't exist or isn't writable"; … … 412 594 # get group name that we're looking for 413 595 my $foundMatch = 0; 414 my ($ifdName, $mieGroup);415 if ($wantGroup) {416 # set $ifdName if this group is a valid IFD or SubIFD name417 if ($wantGroup =~ /^IFD(\d+)$/i) {418 $ifdName = "IFD$1";419 } elsif ($wantGroup =~ /^SubIFD(\d+)$/i) {420 $ifdName = "SubIFD$1";421 } elsif ($wantGroup =~ /^Version(\d+)$/i) {422 $ifdName = "Version$1"; # Sony IDC VersionIFD423 } elsif ($wantGroup =~ /^MIE(\d*-?)(\w+)$/i) {424 $mieGroup = "MIE$1" . ucfirst(lc($2));425 } else {426 $ifdName = $exifDirs{lc($wantGroup)};427 if (not $ifdName and $wantGroup =~ /^XMP\b/i) {428 # must load XMP table to set group1 names429 my $table = GetTagTable('Image::ExifTool::XMP::Main');430 my $writeProc = $table->{WRITE_PROC};431 $writeProc and &$writeProc();432 }433 }434 }435 596 # 436 597 # determine the groups for all tags found, and the tag with 437 598 # the highest priority group 438 599 # 439 my (@tagInfoList, @writeAlsoList, %writeGroup, %preferred, %tagPriority, $avoid, $wasProtected); 440 my $highestPriority = -1; 441 foreach $tagInfo (@matchingTags) { 442 $tag = $tagInfo->{Name}; # set tag so warnings will use proper case 443 my ($writeGroup, $priority); 600 my (@tagInfoList, @writeAlsoList, %writeGroup, %preferred, %tagPriority); 601 my (%avoid, $wasProtected, $noCreate, %highestPriority, %highestQT); 602 603 TAG: foreach $tagInfo (@matchingTags) { 604 $tag = $$tagInfo{Name}; # get tag name for warnings 605 my $lcTag = lc $tag; # get lower-case tag name for use in variables 606 # initialize highest priority if we are starting a new tag 607 $highestPriority{$lcTag} = -999 unless defined $highestPriority{$lcTag}; 608 my ($priority, $writeGroup); 609 my $prfTag = defined $$tagInfo{Preferred} ? $$tagInfo{Preferred} : $$tagInfo{Table}{PREFERRED}; 444 610 if ($wantGroup) { 445 my $lcWant = lc($wantGroup); 446 # only set tag in specified group 447 $writeGroup = $self->GetGroup($tagInfo, 0); 448 unless (lc($writeGroup) eq $lcWant) { 449 if ($writeGroup eq 'EXIF' or $writeGroup eq 'SonyIDC') { 450 next unless $ifdName; 451 # can't yet write PreviewIFD tags 452 $ifdName eq 'PreviewIFD' and ++$foundMatch, next; 453 $writeGroup = $ifdName; # write to the specified IFD 454 } elsif ($writeGroup eq 'MIE') { 455 next unless $mieGroup; 611 # a WriteGroup of All is special 612 my $wgAll = ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All'); 613 my @grp = $self->GetGroup($tagInfo); 614 my $hiPri = 1000; 615 foreach $fg (@wantGroup) { 616 my ($fam, $lcWant) = @$fg; 617 $lcWant = $translateWantGroup{$lcWant} if $translateWantGroup{$lcWant}; 618 # only set tag in specified group 619 # bump priority of preferred tag 620 $hiPri += $prfTag if $prfTag; 621 if (not defined $fam) { 622 if ($lcWant eq lc $grp[0]) { 623 # don't go to more general write group of "All" 624 # if something more specific was wanted 625 $writeGroup = $grp[0] if $wgAll and not $writeGroup; 626 next; 627 } 628 next if $lcWant eq lc $grp[2]; 629 } elsif ($fam == 7) { 630 next if IsSameID($$tagInfo{TagID}, $lcWant); 631 } elsif ($fam != 1 and not $$tagInfo{AllowGroup}) { 632 next if $lcWant eq lc $grp[$fam]; 633 if ($wgAll and not $fam and $allFam0{$lcWant}) { 634 $writeGroup or $writeGroup = $allFam0{$lcWant}; 635 next; 636 } 637 next TAG; # wrong group 638 } 639 # handle family 1 groups specially 640 if ($grp[0] eq 'EXIF' or $grp[0] eq 'SonyIDC' or $wgAll) { 641 unless ($ifdName and $lcWant eq lc $ifdName) { 642 next TAG unless $wgAll and not $fam and $allFam0{$lcWant}; 643 $writeGroup = $allFam0{$lcWant} unless $writeGroup; 644 next; 645 } 646 next TAG if $wgAll and $allFam0{$lcWant} and $fam; 647 # can't yet write PreviewIFD tags (except for image) 648 $lcWant eq 'PreviewIFD' and ++$foundMatch, next TAG; 649 $writeGroup = $ifdName; # write to the specified IFD 650 } elsif ($grp[0] eq 'QuickTime') { 651 if ($grp[1] eq 'Track#') { 652 next TAG unless $movGroup and $lcWant eq lc($movGroup); 653 $writeGroup = $movGroup; 654 } else { 655 my $grp = $$tagInfo{Table}{WRITE_GROUP}; 656 next TAG unless $grp and $lcWant eq lc $grp; 657 $writeGroup = $grp; 658 } 659 } elsif ($grp[0] eq 'MIE') { 660 next TAG unless $mieGroup and $lcWant eq lc($mieGroup); 456 661 $writeGroup = $mieGroup; # write to specific MIE group 457 662 # set specific write group with document number if specified 458 if ($writeGroup =~ /^MIE\d+$/ and $ tagInfo->{Table}{WRITE_GROUP}) {459 $writeGroup = $ tagInfo->{Table}{WRITE_GROUP};663 if ($writeGroup =~ /^MIE\d+$/ and $$tagInfo{Table}{WRITE_GROUP}) { 664 $writeGroup = $$tagInfo{Table}{WRITE_GROUP}; 460 665 $writeGroup =~ s/^MIE/$mieGroup/; 461 666 } 462 } elsif (not $$tagInfo{AllowGroup} or $ wantGroup!~ /^$$tagInfo{AllowGroup}$/i) {667 } elsif (not $$tagInfo{AllowGroup} or $lcWant !~ /^$$tagInfo{AllowGroup}$/i) { 463 668 # allow group1 name to be specified 464 my $grp1 = $self->GetGroup($tagInfo, 1); 465 unless ($grp1 and lc($grp1) eq $lcWant) { 466 # must also check group1 name directly in case it is different 467 $grp1 = $tagInfo->{Groups}{1}; 468 next unless $grp1 and lc($grp1) eq $lcWant; 469 } 470 } 471 } 472 $priority = 1000; # highest priority since group was specified 669 next TAG unless $lcWant eq lc $grp[1]; 670 } 671 } 672 $writeGroup or $writeGroup = ($$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP} || $grp[0]); 673 $priority = $hiPri; # highest priority since group was specified 473 674 } 474 675 ++$foundMatch; 475 676 # must do a dummy call to the write proc to autoload write package 476 677 # before checking Writable flag 477 my $table = $ tagInfo->{Table};478 my $writeProc = $ table->{WRITE_PROC};678 my $table = $$tagInfo{Table}; 679 my $writeProc = $$table{WRITE_PROC}; 479 680 # load source table if this was a user-defined table 480 681 if ($$table{SRC_TABLE}) { … … 482 683 $writeProc = $$src{WRITE_PROC} unless $writeProc; 483 684 } 484 next unless $writeProc and &$writeProc(); 685 { 686 no strict 'refs'; 687 next unless $writeProc and &$writeProc(); 688 } 485 689 # must still check writable flags in case of UserDefined tags 486 my $writable = $ tagInfo->{Writable};487 next unless $writable or ($ table->{WRITABLE} and690 my $writable = $$tagInfo{Writable}; 691 next unless $writable or ($$table{WRITABLE} and 488 692 not defined $writable and not $$tagInfo{SubDirectory}); 489 693 # set specific write group (if we didn't already) 490 if (not $writeGroup or $translateWriteGroup{$writeGroup}) { 694 if (not $writeGroup or ($translateWriteGroup{$writeGroup} and 695 (not $$tagInfo{WriteGroup} or $$tagInfo{WriteGroup} ne 'All'))) 696 { 491 697 # use default write group 492 $writeGroup = $ tagInfo->{WriteGroup} || $tagInfo->{Table}{WRITE_GROUP};698 $writeGroup = $$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP}; 493 699 # use group 0 name if no WriteGroup specified 494 700 my $group0 = $self->GetGroup($tagInfo, 0); … … 496 702 # get priority for this group 497 703 unless ($priority) { 498 $priority = $ self->{WRITE_PRIORITY}{lc($writeGroup)};704 $priority = $$self{WRITE_PRIORITY}{lc($writeGroup)}; 499 705 unless ($priority) { 500 $priority = $self->{WRITE_PRIORITY}{lc($group0)} || 0; 501 } 502 } 706 $priority = $$self{WRITE_PRIORITY}{lc($group0)} || 0; 707 } 708 } 709 # adjust priority based on Preferred level for this tag 710 $priority += $prfTag if $prfTag; 503 711 } 504 712 # don't write tag if protected 505 if ($tagInfo->{Protected}) { 506 my $prot = $tagInfo->{Protected} & ~$protected; 713 my $prot = $$tagInfo{Protected}; 714 $prot = 1 if $noFlat and defined $$tagInfo{Flat}; 715 if ($prot) { 716 $prot &= ~$protected; 507 717 if ($prot) { 508 718 my %lkup = ( 1=>'unsafe', 2=>'protected', 3=>'unsafe and protected'); … … 510 720 if ($verbose > 1) { 511 721 my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); 512 print $out " Not writing $wgrp1:$tag ($wasProtected)\n";722 print $out "Sorry, $wgrp1:$tag is $wasProtected for writing\n"; 513 723 } 514 724 next; … … 517 727 # set priority for this tag 518 728 $tagPriority{$tagInfo} = $priority; 519 if ($priority > $highestPriority) { 520 $highestPriority = $priority; 521 %preferred = ( $tagInfo => 1 ); 522 $avoid = 0; 523 ++$avoid if $$tagInfo{Avoid}; 524 } elsif ($priority == $highestPriority) { 729 # keep track of highest priority QuickTime tag 730 $highestQT{$lcTag} = $priority if $$table{GROUPS}{0} eq 'QuickTime' and 731 (not defined $highestQT{$lcTag} or $highestQT{$lcTag} < $priority); 732 if ($priority > $highestPriority{$lcTag}) { 733 $highestPriority{$lcTag} = $priority; 734 $preferred{$lcTag} = { $tagInfo => 1 }; 735 $avoid{$lcTag} = $$tagInfo{Avoid} ? 1 : 0; 736 } elsif ($priority == $highestPriority{$lcTag}) { 525 737 # create all tags with highest priority 526 $preferred{$ tagInfo} = 1;527 ++$avoid if $$tagInfo{Avoid};738 $preferred{$lcTag}{$tagInfo} = 1; 739 ++$avoid{$lcTag} if $$tagInfo{Avoid}; 528 740 } 529 741 if ($$tagInfo{WriteAlso}) { … … 533 745 push @tagInfoList, $tagInfo; 534 746 } 747 # special case to allow override of XMP WriteGroup 748 if ($writeGroup eq 'XMP') { 749 my $wg = $$tagInfo{WriteGroup} || $$table{WRITE_GROUP}; 750 $writeGroup = $wg if $wg; 751 } 535 752 $writeGroup{$tagInfo} = $writeGroup; 536 753 } 537 # sort tag info list in reverse order of priority (hig est number last)754 # sort tag info list in reverse order of priority (highest number last) 538 755 # so we get the highest priority error message in the end 539 756 @tagInfoList = sort { $tagPriority{$a} <=> $tagPriority{$b} } @tagInfoList; … … 541 758 unshift @tagInfoList, @writeAlsoList if @writeAlsoList; 542 759 543 # don't create tags with priority 0 if group priorities are set 544 if ($highestPriority == 0 and %{$self->{WRITE_PRIORITY}}) { 545 undef %preferred; 546 } 547 # avoid creating tags with 'Avoid' flag set if there are other alternatives 548 if ($avoid and %preferred) { 549 if ($avoid < scalar(keys %preferred)) { 550 # just remove the 'Avoid' tags since there are other preferred tags 551 foreach $tagInfo (@tagInfoList) { 552 delete $preferred{$tagInfo} if $$tagInfo{Avoid}; 553 } 554 } elsif ($highestPriority < 1000) { 555 # look for another priority tag to create instead 556 my $nextHighest = 0; 557 my @nextBestTags; 558 foreach $tagInfo (@tagInfoList) { 559 my $priority = $tagPriority{$tagInfo} or next; 560 next if $priority == $highestPriority; 561 next if $priority < $nextHighest; 562 next if $$tagInfo{Avoid} or $$tagInfo{Permanent}; 563 next if $writeGroup{$tagInfo} eq 'MakerNotes'; 564 if ($nextHighest < $priority) { 565 $nextHighest = $priority; 566 undef @nextBestTags; 567 } 568 push @nextBestTags, $tagInfo; 569 } 570 if (@nextBestTags) { 571 # change our preferred tags to the next best tags 572 undef %preferred; 573 foreach $tagInfo (@nextBestTags) { 574 $preferred{$tagInfo} = 1; 760 # check priorities for each set of tags we are writing 761 my $lcTag; 762 foreach $lcTag (keys %preferred) { 763 # don't create tags with priority 0 if group priorities are set 764 if ($preferred{$lcTag} and $highestPriority{$lcTag} == 0 and 765 %{$$self{WRITE_PRIORITY}}) 766 { 767 delete $preferred{$lcTag} 768 } 769 # avoid creating tags with 'Avoid' flag set if there are other alternatives 770 if ($avoid{$lcTag} and $preferred{$lcTag}) { 771 if ($avoid{$lcTag} < scalar(keys %{$preferred{$lcTag}})) { 772 # just remove the 'Avoid' tags since there are other preferred tags 773 foreach $tagInfo (@tagInfoList) { 774 next unless $lcTag eq lc $$tagInfo{Name}; 775 delete $preferred{$lcTag}{$tagInfo} if $$tagInfo{Avoid}; 776 } 777 } elsif ($highestPriority{$lcTag} < 1000) { 778 # look for another priority tag to create instead 779 my $nextHighest = 0; 780 my @nextBestTags; 781 foreach $tagInfo (@tagInfoList) { 782 next unless $lcTag eq lc $$tagInfo{Name}; 783 my $priority = $tagPriority{$tagInfo} or next; 784 next if $priority == $highestPriority{$lcTag}; 785 next if $priority < $nextHighest; 786 my $permanent = $$tagInfo{Permanent}; 787 $permanent = $$tagInfo{Table}{PERMANENT} unless defined $permanent; 788 next if $$tagInfo{Avoid} or $permanent; 789 next if $writeGroup{$tagInfo} eq 'MakerNotes'; 790 if ($nextHighest < $priority) { 791 $nextHighest = $priority; 792 undef @nextBestTags; 793 } 794 push @nextBestTags, $tagInfo; 795 } 796 if (@nextBestTags) { 797 # change our preferred tags to the next best tags 798 delete $preferred{$lcTag}; 799 foreach $tagInfo (@nextBestTags) { 800 $preferred{$lcTag}{$tagInfo} = 1; 801 } 575 802 } 576 803 } … … 592 819 my $writeGroup = $writeGroup{$tagInfo}; 593 820 my $permanent = $$tagInfo{Permanent}; 821 $permanent = $$tagInfo{Table}{PERMANENT} unless defined $permanent; 594 822 $writeGroup eq 'MakerNotes' and $permanent = 1 unless defined $permanent; 595 823 my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); 596 $tag = $tagInfo->{Name}; # get proper case for tag name 824 $tag = $$tagInfo{Name}; # get tag name for warnings 825 my $lcTag = lc $tag; 826 my $pref = $preferred{$lcTag} || { }; 597 827 my $shift = $options{Shift}; 828 my $addValue = $options{AddValue}; 598 829 if (defined $shift) { 599 # (can't currently shift List-type tags) 600 if ($tagInfo->{Shift} and not $tagInfo->{List}) { 830 # (can't currently shift list-type tags) 831 my $shiftable; 832 if ($$tagInfo{List}) { 833 $shiftable = ''; # can add/delete but not shift 834 } else { 835 $shiftable = $$tagInfo{Shift}; 601 836 unless ($shift) { 602 837 # set shift according to AddValue/DelValue 603 $shift = 1 if $options{AddValue}; 604 $shift = -1 if $options{DelValue}; 838 $shift = 1 if $addValue; 839 # can shift a date/time with -=, but this is 840 # a conditional delete operation for other tags 841 $shift = -1 if $options{DelValue} and defined $shiftable and $shiftable eq 'Time'; 605 842 } 606 843 if ($shift and (not defined $value or not length $value)) { … … 611 848 undef $shift; 612 849 } 613 } elsif ($shift) { 850 } 851 # can't shift List-type tag 852 if ((defined $shiftable and not $shiftable) and 853 # and don't try to conditionally delete if Shift is "0" 854 ($shift or ($shiftable eq '0' and $options{DelValue}))) 855 { 614 856 $err = "$wgrp1:$tag is not shiftable"; 615 857 $verbose > 2 and print $out "$err\n"; … … 620 862 if (defined $val) { 621 863 # check to make sure this is a List or Shift tag if adding 622 if ($options{AddValue} and not ($shift or $tagInfo->{List})) { 623 $err = "Can't add $wgrp1:$tag (not a List type)"; 624 $verbose > 2 and print $out "$err\n"; 625 next; 864 if ($addValue and not ($shift or $$tagInfo{List})) { 865 if ($addValue eq '2') { 866 undef $addValue; # quietly reset this option 867 } else { 868 $err = "Can't add $wgrp1:$tag (not a List type)"; 869 $verbose > 2 and print $out "$err\n"; 870 next; 871 } 626 872 } 627 873 if ($shift) { 628 # add '+' or '-' prefix to indicate shift direction 629 $val = ($shift > 0 ? '+' : '-') . $val; 630 # check the shift for validity 631 require 'Image/ExifTool/Shift.pl'; 632 my $err2 = CheckShift($tagInfo->{Shift}, $val); 633 if ($err2) { 634 $err = "$err2 for $wgrp1:$tag"; 874 if ($$tagInfo{Shift} and $$tagInfo{Shift} eq 'Time') { 875 # add '+' or '-' prefix to indicate shift direction 876 $val = ($shift > 0 ? '+' : '-') . $val; 877 # check the shift for validity 878 require 'Image/ExifTool/Shift.pl'; 879 my $err2 = CheckShift($$tagInfo{Shift}, $val); 880 if ($err2) { 881 $err = "$err2 for $wgrp1:$tag"; 882 $verbose > 2 and print $out "$err\n"; 883 next; 884 } 885 } elsif (IsFloat($val)) { 886 $val *= $shift; 887 } else { 888 $err = "Shift value for $wgrp1:$tag is not a number"; 635 889 $verbose > 2 and print $out "$err\n"; 636 890 next; … … 645 899 } 646 900 } elsif ($permanent) { 901 return 0 if $options{IgnorePermanent}; 647 902 # can't delete permanent tags, so set them to DelValue or empty string instead 648 903 if (defined $$tagInfo{DelValue}) { … … 652 907 $val = ''; 653 908 } 654 } elsif ($ options{AddValue}or $options{DelValue}) {909 } elsif ($addValue or $options{DelValue}) { 655 910 $err = "No value to add or delete in $wgrp1:$tag"; 656 911 $verbose > 2 and print $out "$err\n"; 657 912 next; 658 913 } else { 659 if ($ tagInfo->{DelCheck}) {914 if ($$tagInfo{DelCheck}) { 660 915 #### eval DelCheck ($self, $tagInfo, $wantGroup) 661 my $err2 = eval $ tagInfo->{DelCheck};916 my $err2 = eval $$tagInfo{DelCheck}; 662 917 $@ and warn($@), $err2 = 'Error evaluating DelCheck'; 663 if ($err2) { 918 if (defined $err2) { 919 # (allow other tags to be set using DelCheck as a hook) 920 $err2 or goto WriteAlso; # GOTO! 664 921 $err2 .= ' for' unless $err2 =~ /delete$/; 665 922 $err = "$err2 $wgrp1:$tag"; 666 923 $verbose > 2 and print $out "$err\n"; 667 924 next; 668 } elsif (defined $err2) { 669 ++$numSet; # (allow other tags to be set using DelCheck as a hook) 670 goto WriteAlso; 671 } 925 } 926 } 927 # set group delete flag if this tag represents an entire group 928 if ($$tagInfo{DelGroup} and not $options{DelValue}) { 929 my @del = ( $tag ); 930 $$self{DEL_GROUP}{$tag} = 1; 931 # delete extra groups if necessary 932 if ($delMore{$tag}) { 933 $$self{DEL_GROUP}{$_} = 1, push(@del,$_) foreach @{$delMore{$tag}}; 934 } 935 # remove all of this group from previous new values 936 $self->RemoveNewValuesForGroup($tag); 937 $verbose and print $out " Deleting tags in: @del\n"; 938 ++$numSet; 939 next; 672 940 } 673 941 $noConv = 1; # value is not defined, so don't do conversion … … 677 945 unless ($noConv) { 678 946 # set default conversion type used by ConvInv() and CHECK_PROC routines 679 $$self{ConvType} = $ options{Type} || ($self->{OPTIONS}{PrintConv} ? 'PrintConv' : 'ValueConv');947 $$self{ConvType} = $convType; 680 948 my $e; 681 ($val,$e) = $self->ConvInv($val, $tagInfo, $tag, $wgrp1, $$self{ConvType},$wantGroup);949 ($val,$e) = $self->ConvInv($val,$tagInfo,$tag,$wgrp1,$$self{ConvType},$wantGroup); 682 950 if (defined $e) { 683 if ($e) { 684 ($err = $e) =~ s/\$wgrp1/$wgrp1/g; 685 } else { 686 ++$numSet; # an empty error string causes error to be ignored 687 } 951 # empty error string causes error to be ignored without setting the value 952 $e or goto WriteAlso; # GOTO! 953 $err = $e; 688 954 } 689 955 } … … 694 960 $val = 'xxx never delete xxx'; 695 961 } 696 $ self->{NEW_VALUE} or $self->{NEW_VALUE} = { };962 $$self{NEW_VALUE} or $$self{NEW_VALUE} = { }; 697 963 if ($options{Replace}) { 698 964 # delete the previous new value 699 $self->GetNewValueHash($tagInfo, $writeGroup, 'delete' );965 $self->GetNewValueHash($tagInfo, $writeGroup, 'delete', $options{ProtectSaved}); 700 966 # also delete related tag previous new values 701 967 if ($$tagInfo{WriteAlso}) { 702 my $wtag; 968 my ($wgrp, $wtag); 969 if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { 970 $wgrp = $writeGroup . ':'; 971 } else { 972 $wgrp = ''; 973 } 703 974 foreach $wtag (keys %{$$tagInfo{WriteAlso}}) { 704 my ($n,$e) = $self->SetNewValue($w tag, undef, Replace=>2);975 my ($n,$e) = $self->SetNewValue($wgrp . $wtag, undef, Replace=>2); 705 976 $numSet += $n; 706 977 } … … 711 982 if (defined $val) { 712 983 # we are editing this tag, so create a NEW_VALUE hash entry 713 my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create'); 714 $nvHash->{WantGroup} = $wantGroup; 984 my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create', 985 $options{ProtectSaved}, ($options{DelValue} and not $shift)); 986 # ignore new values protected with ProtectSaved 987 $nvHash or ++$numSet, next; # (increment $numSet to avoid warning) 988 $$nvHash{NoReplace} = 1 if $$tagInfo{List} and not $options{Replace}; 989 $$nvHash{WantGroup} = $wantGroup; 990 $$nvHash{EditOnly} = 1 if $editOnly; 715 991 # save maker note information if writing maker notes 716 992 if ($$tagInfo{MakerNotes}) { 717 $nvHash->{MAKER_NOTE_FIXUP} = $self->{MAKER_NOTE_FIXUP}; 718 } 719 if ($options{DelValue} or $options{AddValue} or $shift) { 993 $$nvHash{MAKER_NOTE_FIXUP} = $$self{MAKER_NOTE_FIXUP}; 994 } 995 if ($createOnly) { # create only (never edit) 996 # empty item in DelValue list to never edit existing value 997 $$nvHash{DelValue} = [ '' ]; 998 $$nvHash{CreateOnly} = 1; 999 } elsif ($options{DelValue} or $addValue or $shift) { 720 1000 # flag any AddValue or DelValue by creating the DelValue list 721 $ nvHash->{DelValue} or $nvHash->{DelValue} = [ ];1001 $$nvHash{DelValue} or $$nvHash{DelValue} = [ ]; 722 1002 if ($shift) { 723 1003 # add shift value to list 724 $ nvHash->{Shift} = $val;1004 $$nvHash{Shift} = $val; 725 1005 } elsif ($options{DelValue}) { 726 1006 # don't create if we are replacing a specific value 727 $ nvHash->{IsCreating} = 0 unless $val eq '' or $tagInfo->{List};1007 $$nvHash{IsCreating} = 0 unless $val eq '' or $$tagInfo{List}; 728 1008 # add delete value to list 729 push @{$ nvHash->{DelValue}}, ref $val eq 'ARRAY' ? @$val : $val;1009 push @{$$nvHash{DelValue}}, ref $val eq 'ARRAY' ? @$val : $val; 730 1010 if ($verbose > 1) { 731 1011 my $verb = $permanent ? 'Replacing' : 'Deleting'; 732 my $fromList = $ tagInfo->{List} ? ' from list' : '';1012 my $fromList = $$tagInfo{List} ? ' from list' : ''; 733 1013 my @vals = (ref $val eq 'ARRAY' ? @$val : $val); 734 1014 foreach (@vals) { … … 737 1017 $_ = Image::ExifTool::XMP::SerializeStruct($_); 738 1018 } 739 print $out "$verb $wgrp1:$tag$fromList if value is '$ _'\n";1019 print $out "$verb $wgrp1:$tag$fromList if value is '${_}'\n"; 740 1020 } 741 1021 } … … 745 1025 # (will only create the priority tag if it doesn't exist, 746 1026 # others get changed only if they already exist) 747 if ($preferred{$tagInfo} or $tagInfo->{Table}{PREFERRED}) { 1027 my $prf = defined $$tagInfo{Preferred} ? $$tagInfo{Preferred} : $$tagInfo{Table}{PREFERRED}; 1028 # hack to prefer only a single tag in the QuickTime group 1029 if ($$tagInfo{Table}{GROUPS}{0} eq 'QuickTime') { 1030 $prf = 0 if $tagPriority{$tagInfo} < $highestQT{$lcTag}; 1031 } 1032 if ($$pref{$tagInfo} or $prf) { 748 1033 if ($permanent or $shift) { 749 1034 # don't create permanent or Shift-ed tag but define IsCreating 750 1035 # so we know that it is the preferred tag 751 $ nvHash->{IsCreating} = 0;752 } elsif (($ tagInfo->{List} and not $options{DelValue}) or753 not ($ nvHash->{DelValue} and @{$nvHash->{DelValue}}) or1036 $$nvHash{IsCreating} = 0; 1037 } elsif (($$tagInfo{List} and not $options{DelValue}) or 1038 not ($$nvHash{DelValue} and @{$$nvHash{DelValue}}) or 754 1039 # also create tag if any DelValue value is empty ('') 755 grep(/^$/,@{$ nvHash->{DelValue}}))1040 grep(/^$/,@{$$nvHash{DelValue}})) 756 1041 { 757 $ nvHash->{IsCreating} = $options{EditOnly} ? 0 : ($options{EditGroup}? 2 : 1);1042 $$nvHash{IsCreating} = $editOnly ? 0 : ($editGroup ? 2 : 1); 758 1043 # add to hash of groups where this tag is being created 759 1044 $createGroups or $createGroups = $options{CreateGroups} || { }; 760 1045 $$createGroups{$self->GetGroup($tagInfo, 0)} = 1; 761 $nvHash->{CreateGroups} = $createGroups; 762 } 763 } 764 if (%{$self->{DEL_GROUP}} and $nvHash->{IsCreating}) { 765 my ($grp, @grps); 766 foreach $grp (keys %{$self->{DEL_GROUP}}) { 767 next if $self->{DEL_GROUP}{$grp} == 2; 768 # set flag indicating tags were written after this group was deleted 769 $self->{DEL_GROUP}{$grp} = 2; 770 push @grps, $grp; 771 } 772 if ($verbose > 1 and @grps) { 773 @grps = sort @grps; 774 print $out " Writing new tags after deleting groups: @grps\n"; 775 } 1046 $$nvHash{CreateGroups} = $createGroups; 1047 } 1048 } 1049 if ($$nvHash{IsCreating}) { 1050 if (%{$$self{DEL_GROUP}}) { 1051 my ($grp, @grps); 1052 foreach $grp (keys %{$$self{DEL_GROUP}}) { 1053 next if $$self{DEL_GROUP}{$grp} == 2; 1054 # set flag indicating tags were written after this group was deleted 1055 $$self{DEL_GROUP}{$grp} = 2; 1056 push @grps, $grp; 1057 } 1058 if ($verbose > 1 and @grps) { 1059 @grps = sort @grps; 1060 print $out " Writing new tags after deleting groups: @grps\n"; 1061 } 1062 } 1063 } elsif ($createOnly) { 1064 $noCreate = $permanent ? 'permanent' : ($$tagInfo{Avoid} ? 'avoided' : ''); 1065 $noCreate or $noCreate = $shift ? 'shifting' : 'not preferred'; 1066 $verbose > 2 and print $out "Not creating $wgrp1:$tag ($noCreate)\n"; 1067 next; # nothing to do (not creating and not editing) 776 1068 } 777 1069 if ($shift or not $options{DelValue}) { 778 $ nvHash->{Value} or $nvHash->{Value} = [ ];779 if (not $ tagInfo->{List}) {1070 $$nvHash{Value} or $$nvHash{Value} = [ ]; 1071 if (not $$tagInfo{List}) { 780 1072 # not a List tag -- overwrite existing value 781 $nvHash->{Value}[0] = $val; 1073 $$nvHash{Value}[0] = $val; 1074 } elsif (defined $$nvHash{AddBefore} and @{$$nvHash{Value}} >= $$nvHash{AddBefore}) { 1075 # values from a later argument have been added (ie. Replace=0) 1076 # to this list, so the new values should come before these 1077 splice @{$$nvHash{Value}}, -$$nvHash{AddBefore}, 0, ref $val eq 'ARRAY' ? @$val : $val; 782 1078 } else { 783 # add toexisting list784 push @{$ nvHash->{Value}}, ref $val eq 'ARRAY' ? @$val : $val;1079 # add at end of existing list 1080 push @{$$nvHash{Value}}, ref $val eq 'ARRAY' ? @$val : $val; 785 1081 } 786 1082 if ($verbose > 1) { 787 my $ifExists = $nvHash->{IsCreating} ? 788 ($nvHash->{IsCreating} == 2 ? " if $writeGroup exists" : '') : 789 (($nvHash->{DelValue} and @{$nvHash->{DelValue}}) ? 790 ' if tag was deleted' : ' if tag exists'); 791 my $verb = ($shift ? 'Shifting' : ($options{AddValue} ? 'Adding' : 'Writing')); 1083 my $ifExists = $$nvHash{IsCreating} ? ( $createOnly ? 1084 ($$nvHash{IsCreating} == 2 ? 1085 " if $writeGroup exists and tag doesn't" : 1086 " if tag doesn't exist") : 1087 ($$nvHash{IsCreating} == 2 ? " if $writeGroup exists" : '')) : 1088 (($$nvHash{DelValue} and @{$$nvHash{DelValue}}) ? 1089 ' if tag was deleted' : ' if tag exists'); 1090 my $verb = ($shift ? 'Shifting' : ($addValue ? 'Adding' : 'Writing')); 792 1091 print $out "$verb $wgrp1:$tag$ifExists\n"; 793 1092 } 794 1093 } 795 1094 } elsif ($permanent) { 796 $err = "Can't delete $wgrp1:$tag";1095 $err = "Can't delete Permanent tag $wgrp1:$tag"; 797 1096 $verbose > 1 and print $out "$err\n"; 798 1097 next; 799 } elsif ($ options{AddValue}or $options{DelValue}) {1098 } elsif ($addValue or $options{DelValue}) { 800 1099 $verbose > 1 and print $out "Adding/Deleting nothing does nothing\n"; 801 1100 next; … … 804 1103 $self->GetNewValueHash($tagInfo, $writeGroup, 'delete'); 805 1104 my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create'); 806 $ nvHash->{WantGroup} = $wantGroup;1105 $$nvHash{WantGroup} = $wantGroup; 807 1106 $verbose > 1 and print $out "Deleting $wgrp1:$tag\n"; 808 1107 } 1108 $$setTags{$tagInfo} = 1 if $setTags; 1109 $prioritySet = 1 if $$pref{$tagInfo}; 1110 WriteAlso: 809 1111 ++$numSet; 810 $$setTags{$tagInfo} = 1 if $setTags;811 $prioritySet = 1 if $preferred{$tagInfo};812 WriteAlso:813 1112 # also write related tags 814 1113 my $writeAlso = $$tagInfo{WriteAlso}; 815 1114 if ($writeAlso) { 816 my ($wtag, $n); 1115 my ($wgrp, $wtag, $n); 1116 if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { 1117 $wgrp = $writeGroup . ':'; 1118 } else { 1119 $wgrp = ''; 1120 } 817 1121 local $SIG{'__WARN__'} = \&SetWarning; 818 1122 foreach $wtag (keys %$writeAlso) { 819 1123 my %opts = ( 820 1124 Type => 'ValueConv', 821 Protected => $protected | 0x02, 822 AddValue => $options{AddValue}, 823 DelValue => $options{DelValue}, 824 CreateGroups => $createGroups, 825 SetTags => \%alsoWrote, # remember tags already written 1125 Protected => $protected | 0x02, 1126 AddValue => $addValue, 1127 DelValue => $options{DelValue}, 1128 Shift => $options{Shift}, 1129 Replace => $options{Replace}, # handle lists properly 1130 CreateGroups=> $createGroups, 1131 SetTags => \%alsoWrote, # remember tags already written 826 1132 ); 827 1133 undef $evalWarning; 828 1134 #### eval WriteAlso ($val) 829 my $v = eval $writeAlso->{$wtag}; 1135 my $v = eval $$writeAlso{$wtag}; 1136 # we wanted to do the eval in case there are side effect, but we 1137 # don't want to write a value for a tag that is being deleted: 1138 undef $v unless defined $val; 830 1139 $@ and $evalWarning = $@; 831 1140 unless ($evalWarning) { 832 ($n,$evalWarning) = $self->SetNewValue($w tag, $v, %opts);1141 ($n,$evalWarning) = $self->SetNewValue($wgrp . $wtag, $v, %opts); 833 1142 $numSet += $n; 834 1143 # count this as being set if any related tag is set 835 $prioritySet = 1 if $n and $ preferred{$tagInfo};1144 $prioritySet = 1 if $n and $$pref{$tagInfo}; 836 1145 } 837 1146 if ($evalWarning and (not $err or $verbose > 2)) { … … 851 1160 warn "$err\n" if $err and not wantarray; 852 1161 } elsif (not $numSet) { 853 my $pre = $wantGroup ? ($ifdName || $wantGroup). ':' : '';1162 my $pre = $wantGroup ? $wantGroup . ':' : ''; 854 1163 if ($wasProtected) { 855 $err = "Tag '$pre$tag' is $wasProtected for writing"; 856 } elsif ($foundMatch) { 857 $err = "Sorry, $pre$tag is not writable"; 858 } else { 859 $err = "Tag '$pre$tag' does not exist"; 860 } 861 $verbose > 2 and print $out "$err\n"; 862 warn "$err\n" unless wantarray; 1164 $verbose = 0; # we already printed this verbose message 1165 unless ($options{Replace} and $options{Replace} == 2) { 1166 $err = "Sorry, $pre$tag is $wasProtected for writing"; 1167 } 1168 } elsif (not $listOnly) { 1169 if ($origTag =~ /[?*]/) { 1170 if ($noCreate) { 1171 $err = "No tags matching 'pre${origTag}' will be created"; 1172 $verbose = 0; # (already printed) 1173 } elsif ($foundMatch) { 1174 $err = "Sorry, no writable tags matching '$pre${origTag}'"; 1175 } else { 1176 $err = "No matching tags for '$pre${origTag}'"; 1177 } 1178 } elsif ($noCreate) { 1179 $err = "Not creating $pre$tag"; 1180 $verbose = 0; # (already printed) 1181 } elsif ($foundMatch) { 1182 $err = "Sorry, $pre$tag is not writable"; 1183 } elsif ($wantGroup and @matchingTags) { 1184 $err = "Sorry, $pre$tag doesn't exist or isn't writable"; 1185 } else { 1186 $err = "Tag '$pre${tag}' is not defined"; 1187 } 1188 } 1189 if ($err) { 1190 $verbose > 2 and print $out "$err\n"; 1191 warn "$err\n" unless wantarray; 1192 } 863 1193 } elsif ($$self{CHECK_WARN}) { 864 1194 $err = $$self{CHECK_WARN}; … … 888 1218 local $_; 889 1219 my ($self, $srcFile, @setTags) = @_; 890 my $key;1220 my ($key, $tag, @exclude, @reqTags); 891 1221 892 1222 # get initial SetNewValuesFromFile options … … 895 1225 $_ = shift @setTags; 896 1226 foreach $key (keys %$_) { 897 $opts{$key} = $ _->{$key};1227 $opts{$key} = $$_{$key}; 898 1228 } 899 1229 } … … 901 1231 @setTags and ExpandShortcuts(\@setTags); 902 1232 my $srcExifTool = new Image::ExifTool; 903 my $options = $self->{OPTIONS}; 1233 # set flag to indicate we are being called from inside SetNewValuesFromFile() 1234 $$srcExifTool{TAGS_FROM_FILE} = 1; 1235 # synchronize and increment the file sequence number 1236 $$srcExifTool{FILE_SEQUENCE} = $$self{FILE_SEQUENCE}++; 904 1237 # set options for our extraction tool 905 $srcExifTool->{TAGS_FROM_FILE} = 1; 1238 my $options = $$self{OPTIONS}; 1239 # copy both structured and flattened tags by default (but flattened tags are "unsafe") 1240 my $structOpt = defined $$options{Struct} ? $$options{Struct} : 2; 1241 # copy structures only if no tags specified (since flattened tags are "unsafe") 1242 $structOpt = 1 if $structOpt eq '2' and not @setTags; 906 1243 # +------------------------------------------+ 907 1244 # ! DON'T FORGET!! Must consider each new ! … … 909 1246 # +------------------------------------------+ 910 1247 $srcExifTool->Options( 911 Binary => 1, 912 Charset => $$options{Charset}, 913 CharsetID3 => $$options{CharsetID3}, 914 CharsetIPTC => $$options{CharsetIPTC}, 915 CharsetPhotoshop => $$options{CharsetPhotoshop}, 916 Composite => $$options{Composite}, 917 CoordFormat => $$options{CoordFormat} || '%d %d %.8f', # copy coordinates at high resolution unless otherwise specified 918 DateFormat => $$options{DateFormat}, 919 Duplicates => 1, 920 Escape => $$options{Escape}, 1248 Binary => 1, 1249 Charset => $$options{Charset}, 1250 CharsetEXIF => $$options{CharsetEXIF}, 1251 CharsetFileName => $$options{CharsetFileName}, 1252 CharsetID3 => $$options{CharsetID3}, 1253 CharsetIPTC => $$options{CharsetIPTC}, 1254 CharsetPhotoshop=> $$options{CharsetPhotoshop}, 1255 Composite => $$options{Composite}, 1256 CoordFormat => $$options{CoordFormat} || '%d %d %.8f', # copy coordinates at high resolution unless otherwise specified 1257 DateFormat => $$options{DateFormat}, 1258 Duplicates => 1, 1259 Escape => $$options{Escape}, 1260 # Exclude (set below) 1261 ExtendedXMP => $$options{ExtendedXMP}, 921 1262 ExtractEmbedded => $$options{ExtractEmbedded}, 922 FastScan => $$options{FastScan}, 923 FixBase => $$options{FixBase}, 924 IgnoreMinorErrors => $$options{IgnoreMinorErrors}, 925 Lang => $$options{Lang}, 926 LargeFileSupport => $$options{LargeFileSupport}, 927 List => 1, 928 MakerNotes => 1, 1263 FastScan => $$options{FastScan}, 1264 Filter => $$options{Filter}, 1265 FixBase => $$options{FixBase}, 1266 GlobalTimeShift => $$options{GlobalTimeShift}, 1267 HexTagIDs => $$options{HexTagIDs}, 1268 IgnoreMinorErrors=>$$options{IgnoreMinorErrors}, 1269 Lang => $$options{Lang}, 1270 LargeFileSupport=> $$options{LargeFileSupport}, 1271 List => 1, 1272 ListItem => $$options{ListItem}, 1273 ListSep => $$options{ListSep}, 1274 MakerNotes => $$options{FastScan} && $$options{FastScan} > 1 ? undef : 1, 1275 MDItemTags => $$options{MDItemTags}, 929 1276 MissingTagValue => $$options{MissingTagValue}, 930 Password => $$options{Password}, 931 PrintConv => $$options{PrintConv}, 932 ScanForXMP => $$options{ScanForXMP}, 933 StrictDate => 1, 934 Struct => ($$options{Struct} or not defined $$options{Struct}) ? 1 : 0, 935 Unknown => $$options{Unknown}, 1277 NoPDFList => $$options{NoPDFList}, 1278 Password => $$options{Password}, 1279 PrintConv => $$options{PrintConv}, 1280 QuickTimeUTC => $$options{QuickTimeUTC}, 1281 RequestAll => $$options{RequestAll} || 1, # (is this still necessary now that RequestTags are being set?) 1282 RequestTags => $$options{RequestTags}, 1283 SaveFormat => $$options{SaveFormat}, 1284 SavePath => $$options{SavePath}, 1285 ScanForXMP => $$options{ScanForXMP}, 1286 StrictDate => defined $$options{StrictDate} ? $$options{StrictDate} : 1, 1287 Struct => $structOpt, 1288 SystemTags => $$options{SystemTags}, 1289 TimeZone => $$options{TimeZone}, 1290 Unknown => $$options{Unknown}, 1291 UserParam => $$options{UserParam}, 1292 Validate => $$options{Validate}, 1293 XAttrTags => $$options{XAttrTags}, 1294 XMPAutoConv => $$options{XMPAutoConv}, 936 1295 ); 1296 $$srcExifTool{GLOBAL_TIME_OFFSET} = $$self{GLOBAL_TIME_OFFSET}; 1297 foreach $tag (@setTags) { 1298 next if ref $tag; 1299 if ($tag =~ /^-(.*)/) { 1300 # avoid extracting tags that are excluded 1301 push @exclude, $1; 1302 next; 1303 } 1304 # add specified tags to list of requested tags 1305 $_ = $tag; 1306 if (/(.+?)\s*(>|<)\s*(.+)/) { 1307 if ($2 eq '>') { 1308 $_ = $1; 1309 } else { 1310 $_ = $3; 1311 /\$/ and push(@reqTags, /\$\{?(?:[-\w]+:)*([-\w?*]+)/g), next; 1312 } 1313 } 1314 push @reqTags, $2 if /(^|:)([-\w?*]+)#?$/; 1315 } 1316 if (@exclude) { 1317 ExpandShortcuts(\@exclude, 1); 1318 $srcExifTool->Options(Exclude => \@exclude); 1319 } 1320 $srcExifTool->Options(RequestTags => \@reqTags) if @reqTags; 937 1321 my $printConv = $$options{PrintConv}; 938 1322 if ($opts{Type}) { … … 948 1332 my $info = $srcExifTool->ImageInfo($srcFile); 949 1333 return $info if $$info{Error} and $$info{Error} eq 'Error opening file'; 950 delete $ srcExifTool->{VALUE}{Error};# delete so we can check this later1334 delete $$srcExifTool{VALUE}{Error}; # delete so we can check this later 951 1335 952 1336 # sort tags in reverse order so we get priority tag last 953 1337 my @tags = reverse sort keys %$info; 954 my $tag;955 1338 # 956 1339 # simply transfer all tags from source image if no tags specified … … 958 1341 unless (@setTags) { 959 1342 # transfer maker note information to this object 960 $ self->{MAKER_NOTE_FIXUP} = $srcExifTool->{MAKER_NOTE_FIXUP};961 $ self->{MAKER_NOTE_BYTE_ORDER} = $srcExifTool->{MAKER_NOTE_BYTE_ORDER};1343 $$self{MAKER_NOTE_FIXUP} = $$srcExifTool{MAKER_NOTE_FIXUP}; 1344 $$self{MAKER_NOTE_BYTE_ORDER} = $$srcExifTool{MAKER_NOTE_BYTE_ORDER}; 962 1345 foreach $tag (@tags) { 963 1346 # don't try to set errors or warnings 964 1347 next if $tag =~ /^(Error|Warning)\b/; 965 # get appropri te value type if necessary1348 # get appropriate value type if necessary 966 1349 if ($opts{SrcType} and $opts{SrcType} ne $srcType) { 967 1350 $$info{$tag} = $srcExifTool->GetValue($tag, $opts{SrcType}); … … 969 1352 # set value for this tag 970 1353 my ($n, $e) = $self->SetNewValue($tag, $$info{$tag}, %opts); 971 # delete this tag if we could 't set it1354 # delete this tag if we couldn't set it 972 1355 $n or delete $$info{$tag}; 973 1356 } … … 978 1361 # 979 1362 # 1) loop through input list of tags to set, and build @setList 980 my (@setList, $set, %setMatches );981 foreach (@setTags) {982 if (ref $ _eq 'HASH') {1363 my (@setList, $set, %setMatches, $t); 1364 foreach $t (@setTags) { 1365 if (ref $t eq 'HASH') { 983 1366 # update current options 984 foreach $key (keys %$ _) {985 $opts{$key} = $ _->{$key};1367 foreach $key (keys %$t) { 1368 $opts{$key} = $$t{$key}; 986 1369 } 987 1370 next; … … 990 1373 # (also use this hash to store expression and wildcard flags, EXPR and WILD) 991 1374 my $opts = { %opts }; 992 $tag = lc ($_);# change tag/group names to all lower case993 my ( $fam, $grp, $dst, $dstGrp, $dstTag, $isExclude);1375 $tag = lc $t; # change tag/group names to all lower case 1376 my (@fg, $grp, $dst, $dstGrp, $dstTag, $isExclude); 994 1377 # handle redirection to another tag 995 1378 if ($tag =~ /(.+?)\s*(>|<)\s*(.+)/) { … … 998 1381 if ($2 eq '>') { 999 1382 ($tag, $dstTag) = ($1, $3); 1000 # flag add and delete ( ie. '+<' and '-<') redirections1383 # flag add and delete (eg. '+<' and '-<') redirections 1001 1384 $opt = $1 if $tag =~ s/\s*([-+])$// or $dstTag =~ s/^([-+])\s*//; 1002 1385 } else { … … 1005 1388 # handle expressions 1006 1389 if ($tag =~ /\$/) { 1007 $tag = $ _; # restore original case1390 $tag = $t; # restore original case 1008 1391 # recover leading whitespace (except for initial single space) 1009 1392 $tag =~ s/(.+?)\s*(>|<) ?//; 1010 1393 $$opts{EXPR} = 1; # flag this expression 1011 $grp = '';1012 1394 } else { 1013 1395 $opt = $1 if $tag =~ s/^([-+])\s*//; 1014 1396 } 1015 1397 } 1398 # validate tag name(s) 1399 $$opts{EXPR} or ValidTagName($tag) or $self->Warn("Invalid tag name '${tag}'"), next; 1400 ValidTagName($dstTag) or $self->Warn("Invalid tag name '${dstTag}'"), next; 1016 1401 # translate '+' and '-' to appropriate SetNewValue option 1017 1402 if ($opt) { 1018 1403 $$opts{{ '+' => 'AddValue', '-' => 'DelValue' }->{$opt}} = 1; 1019 $$opts{Shift} = 0; # shift if this is a date/time tag1404 $$opts{Shift} = 0; # shift if shiftable 1020 1405 } 1021 1406 ($dstGrp, $dstTag) = ($1, $2) if $dstTag =~ /(.*):(.+)/; 1022 1407 # ValueConv may be specified separately on the destination with '#' 1023 1408 $$opts{Type} = 'ValueConv' if $dstTag =~ s/#$//; 1024 # ignore leading family number 1025 $dstGrp = $2 if $dstGrp =~ /^(\d+)(.*)/ and $1 < 2; 1026 # replace 'all' with '*' in tag and group names 1409 # replace tag name of 'all' with '*' 1027 1410 $dstTag = '*' if $dstTag eq 'all'; 1028 $dstGrp = '*' if $dstGrp eq 'all';1029 1411 } 1030 1412 unless ($$opts{EXPR}) { 1031 1413 $isExclude = ($tag =~ s/^-//); 1032 if ($tag =~ / ^([-\w]*?|\*):(.+)/) {1414 if ($tag =~ /(.*):(.+)/) { 1033 1415 ($grp, $tag) = ($1, $2); 1034 # separate leading family number 1035 ($fam, $grp) = ($1, $2) if $grp =~ /^(\d+)(.*)/; 1036 } else { 1037 $grp = ''; # flag for don't care about group 1416 foreach (split /:/, $grp) { 1417 # save family/groups in list (ignoring 'all' and '*') 1418 next unless length($_) and /^(\d+)?(.*)/; 1419 my ($f, $g) = ($1, $2); 1420 $f = 7 if $g =~ s/^ID-//i; 1421 push @fg, [ $f, $g ] unless $g eq '*' or $g eq 'all'; 1422 } 1038 1423 } 1039 1424 # allow ValueConv to be specified by a '#' on the tag name … … 1044 1429 # replace 'all' with '*' in tag and group names 1045 1430 $tag = '*' if $tag eq 'all'; 1046 $grp = '*' if $grp eq 'all'; 1047 # allow wildcards in tag names 1431 # allow wildcards in tag names (handle differently from all tags: '*') 1048 1432 if ($tag =~ /[?*]/ and $tag ne '*') { 1049 $$opts{WILD} = 1; # set flag indicating wildcards were used 1433 $$opts{WILD} = 1; # set flag indicating wildcards were used in source tag 1050 1434 $tag =~ s/\*/[-\\w]*/g; 1051 1435 $tag =~ s/\?/[-\\w]/g; 1052 1436 } 1053 1437 } 1054 # redirect, exclude or set this tag (Note: $grp is '' if we don't care)1438 # redirect, exclude or set this tag (Note: @fg is empty if we don't care about the group) 1055 1439 if ($dstTag) { 1056 1440 # redirect this tag 1057 1441 $isExclude and return { Error => "Can't redirect excluded tag" }; 1058 if ($dstTag ne '*') {1059 if ($dstTag =~ /[?*]/) {1060 if ($dstTag eq $tag) {1061 $dstTag = '*';1062 } else {1063 return { Error => "Invalid use of wildcards in destination tag" };1064 }1065 } elsif ($tag eq '*') {1066 return { Error => "Can't redirect from all tags to one tag" };1067 }1068 }1069 1442 # set destination group the same as source if necessary 1070 # (removed in 7.72 so '- xmp:*>*:*' will preserve XMP family 1 groups)1443 # (removed in 7.72 so '-*:*<xmp:*' will preserve XMP family 1 groups) 1071 1444 # $dstGrp = $grp if $dstGrp eq '*' and $grp; 1072 1445 # write to specified destination group/tag … … 1074 1447 } elsif ($isExclude) { 1075 1448 # implicitly assume '*' if first entry is an exclusion 1076 unshift @setList, [ undef, '*', '*', [ '', '*' ], $opts ] unless @setList;1449 unshift @setList, [ [ ], '*', [ '', '*' ], $opts ] unless @setList; 1077 1450 # exclude this tag by leaving $dst undefined 1078 1451 } else { 1079 $dst = [ $grp, $$opts{WILD} ? '*' : $tag ]; # copy to same group 1080 } 1081 $grp or $grp = '*'; # use '*' for any group 1452 $dst = [ $grp || '', $$opts{WILD} ? '*' : $tag ]; # use same group name for dest 1453 } 1082 1454 # save in reverse order so we don't set tags before an exclude 1083 unshift @setList, [ $fam, $grp, $tag, $dst, $opts ];1455 unshift @setList, [ \@fg, $tag, $dst, $opts ]; 1084 1456 } 1085 1457 # 2) initialize lists of matching tags for each setTag 1086 1458 foreach $set (@setList) { 1087 $$set[ 3] and $setMatches{$set} = [ ];1459 $$set[2] and $setMatches{$set} = [ ]; 1088 1460 } 1089 1461 # 3) loop through all tags in source image and save tags matching each setTag … … 1098 1470 my $lcTag = lc(GetTagName($tag)); 1099 1471 my (@grp, %grp); 1100 1472 SET: foreach $set (@setList) { 1101 1473 # check first for matching tag 1102 unless ($$set[ 2] eq $lcTag or $$set[2] eq '*') {1474 unless ($$set[1] eq $lcTag or $$set[1] eq '*') { 1103 1475 # handle wildcards 1104 next unless $$set[ 4]{WILD} and $lcTag =~ /^$$set[2]$/;1476 next unless $$set[3]{WILD} and $lcTag =~ /^$$set[1]$/; 1105 1477 } 1106 1478 # then check for matching group 1107 unless ($$set[1] eq '*') {1479 if (@{$$set[0]}) { 1108 1480 # get lower case group names if not done already 1109 1481 unless (@grp) { … … 1111 1483 $grp{$_} = 1 foreach @grp; 1112 1484 } 1113 # handle leading family number 1114 if (defined $$set[0]) { 1115 next unless $grp[$$set[0]] and $$set[1] eq $grp[$$set[0]]; 1116 } else { 1117 next unless $grp{$$set[1]}; 1118 } 1119 } 1120 last unless $$set[3]; # all done if we hit an exclude 1485 foreach (@{$$set[0]}) { 1486 my ($f, $g) = @$_; 1487 if (not defined $f) { 1488 next SET unless $grp{$g}; 1489 } elsif ($f == 7) { 1490 next SET unless IsSameID($srcExifTool->GetTagID($tag), $g); 1491 } else { 1492 next SET unless defined $grp[$f] and $g eq $grp[$f]; 1493 } 1494 } 1495 } 1496 last unless $$set[2]; # all done if we hit an exclude 1121 1497 # add to the list of tags matching this setTag 1122 1498 push @{$setMatches{$set}}, $tag; … … 1126 1502 foreach $set (reverse @setList) { 1127 1503 # get options for SetNewValue 1128 my $opts = $$set[ 4];1504 my $opts = $$set[3]; 1129 1505 # handle expressions 1130 1506 if ($$opts{EXPR}) { 1131 my $val = $srcExifTool->InsertTagValues(\@tags, $$set[ 2], 'Error');1132 unless (defined $val) {1133 # return warning if one of the tags didn't exist1134 $tag = Next TagKey(\%rtnInfo, 'Warning');1135 $rtnInfo{$tag} = $ srcExifTool->GetValue('Error');1136 delete $ srcExifTool->{VALUE}{Error};1137 next ;1138 } 1139 my ($dstGrp, $dstTag) = @{$$set[ 3]};1140 $$opts{Protected} = 1 ;1507 my $val = $srcExifTool->InsertTagValues(\@tags, $$set[1], 'Error'); 1508 if ($$srcExifTool{VALUE}{Error}) { 1509 # pass on any error as a warning 1510 $tag = NextFreeTagKey(\%rtnInfo, 'Warning'); 1511 $rtnInfo{$tag} = $$srcExifTool{VALUE}{Error}; 1512 delete $$srcExifTool{VALUE}{Error}; 1513 next unless defined $val; 1514 } 1515 my ($dstGrp, $dstTag) = @{$$set[2]}; 1516 $$opts{Protected} = 1 unless $dstTag =~ /[?*]/ and $dstTag ne '*'; 1141 1517 $$opts{Group} = $dstGrp if $dstGrp; 1142 1518 my @rtnVals = $self->SetNewValue($dstTag, $val, %$opts); … … 1151 1527 $val = $$info{$tag}; 1152 1528 } 1153 my ($dstGrp, $dstTag) = @{$$set[ 3]};1529 my ($dstGrp, $dstTag) = @{$$set[2]}; 1154 1530 if ($dstGrp) { 1155 if ($dstGrp eq '*') { 1156 $dstGrp = $srcExifTool->GetGroup($tag, 1); 1531 my @dstGrp = split /:/, $dstGrp; 1532 # destination group of '*' writes to same group as source tag 1533 # (family 1 unless otherwise specified) 1534 foreach (@dstGrp) { 1535 next unless /^(\d*)(all|\*)$/i; 1536 $_ = $1 . $srcExifTool->GetGroup($tag, length $1 ? $1 : 1); 1157 1537 $noWarn = 1; # don't warn on wildcard destinations 1158 1538 } 1159 $$opts{Group} = $dstGrp;1539 $$opts{Group} = join ':', @dstGrp; 1160 1540 } else { 1161 1541 delete $$opts{Group}; 1162 1542 } 1163 1543 # transfer maker note information if setting this tag 1164 if ($ srcExifTool->{TAG_INFO}{$tag}{MakerNotes}) {1165 $ self->{MAKER_NOTE_FIXUP} = $srcExifTool->{MAKER_NOTE_FIXUP};1166 $ self->{MAKER_NOTE_BYTE_ORDER} = $srcExifTool->{MAKER_NOTE_BYTE_ORDER};1544 if ($$srcExifTool{TAG_INFO}{$tag}{MakerNotes}) { 1545 $$self{MAKER_NOTE_FIXUP} = $$srcExifTool{MAKER_NOTE_FIXUP}; 1546 $$self{MAKER_NOTE_BYTE_ORDER} = $$srcExifTool{MAKER_NOTE_BYTE_ORDER}; 1167 1547 } 1168 1548 if ($dstTag eq '*') { … … 1170 1550 $noWarn = 1; 1171 1551 } 1172 # allow protected tags to be copied if specified explicitly 1173 $$opts{Protected} = ($$set[2] eq '*' ? undef : 1); 1552 if ($$set[1] eq '*' or $$set[3]{WILD}) { 1553 # don't copy from protected binary tags when using wildcards 1554 next if $$srcExifTool{TAG_INFO}{$tag}{Protected} and 1555 $$srcExifTool{TAG_INFO}{$tag}{Binary}; 1556 # don't copy to protected tags when using wildcards 1557 delete $$opts{Protected}; 1558 # don't copy flattened tags if copying structures too when copying all 1559 $$opts{NoFlat} = $structOpt eq '2' ? 1 : 0; 1560 } else { 1561 # allow protected tags to be copied if specified explicitly 1562 $$opts{Protected} = 1 unless $dstTag =~ /[?*]/; 1563 delete $$opts{NoFlat}; 1564 } 1174 1565 # set value(s) for this tag 1175 1566 my ($rtn, $wrn) = $self->SetNewValue($dstTag, $val, %$opts); 1567 # this was added in version 9.14, and allowed actions like "-subject<all" to 1568 # write values of multiple tags into a list, but it had the side effect of 1569 # duplicating items if there were multiple list tags with the same name 1570 # (eg. -use mwg "-creator<creator"), so disable this as of ExifTool 9.36: 1571 # $$opts{Replace} = 0; # accumulate values from tags matching a single argument 1176 1572 if ($wrn and not $noWarn) { 1177 1573 # return this warning 1178 $rtnInfo{Next TagKey(\%rtnInfo, 'Warning')} = $wrn;1574 $rtnInfo{NextFreeTagKey(\%rtnInfo, 'Warning')} = $wrn; 1179 1575 $noWarn = 1; 1180 1576 } … … 1187 1583 #------------------------------------------------------------------------------ 1188 1584 # Get new value(s) for tag 1189 # Inputs: 0) ExifTool object reference, 1) tag name or tagInfo hash ref1585 # Inputs: 0) ExifTool object reference, 1) tag name (or tagInfo or nvHash ref, not public) 1190 1586 # 2) optional pointer to return new value hash reference (not part of public API) 1191 # or 0) new value hash reference (not part of public API)1192 1587 # Returns: List of new Raw values (list may be empty if tag is being deleted) 1193 1588 # Notes: 1) Preferentially returns new value from Extra table if writable Extra tag exists 1194 1589 # 2) Must call AFTER IsOverwriting() returns 1 to get proper value for shifted times 1195 1590 # 3) Tag name is case sensitive and may be prefixed by family 0 or 1 group name 1196 sub GetNewValues($;$$) 1591 # 4) Value may have been modified by CHECK_PROC routine after ValueConv 1592 sub GetNewValue($$;$) 1197 1593 { 1198 1594 local $_; 1595 my $self = shift; 1596 my $tag = shift; 1199 1597 my $nvHash; 1200 if ( ref $_[0] eq 'HASH') {1201 $nvHash = shift;1598 if ((ref $tag eq 'HASH' and $$tag{IsNVH}) or not defined $tag) { 1599 $nvHash = $tag; 1202 1600 } else { 1203 my ($self, $tag, $newValueHashPt) = @_;1204 if ($ self->{NEW_VALUE}) {1601 my $newValueHashPt = shift; 1602 if ($$self{NEW_VALUE}) { 1205 1603 my ($group, $tagInfo); 1206 1604 if (ref $tag) { … … 1212 1610 } else { 1213 1611 # separate group from tag name 1214 $group = $1 if $tag =~ s/(.*)://; 1612 my @groups; 1613 @groups = split ':', $1 if $tag =~ s/(.*)://; 1215 1614 my @tagInfoList = FindTagInfo($tag); 1216 1615 # decide which tag we want 1217 1616 GNV_TagInfo: foreach $tagInfo (@tagInfoList) { 1218 1617 my $nvh = $self->GetNewValueHash($tagInfo) or next; 1219 # select tag in specified group if necessary 1220 while ($group and $group ne $$nvh{WriteGroup}) { 1618 # select tag in specified group(s) if necessary 1619 foreach (@groups) { 1620 next if $_ eq $$nvh{WriteGroup}; 1221 1621 my @grps = $self->GetGroup($tagInfo); 1222 1622 if ($grps[0] eq $$nvh{WriteGroup}) { 1223 1623 # check family 1 group only if WriteGroup is not specific 1224 last if $groupeq $grps[1];1624 next if $_ eq $grps[1]; 1225 1625 } else { 1226 1626 # otherwise check family 0 group 1227 last if $groupeq $grps[0];1627 next if $_ eq $grps[0]; 1228 1628 } 1629 # also check family 7 1630 next if /^ID-(.*)/i and IsSameID($$tagInfo{TagID}, $1); 1229 1631 # step to next entry in list 1230 1632 $nvh = $$nvh{Next} or next GNV_TagInfo; … … 1232 1634 $nvHash = $nvh; 1233 1635 # give priority to the one we are creating 1234 last if defined $ nvHash->{IsCreating};1636 last if defined $$nvHash{IsCreating}; 1235 1637 } 1236 1638 } … … 1239 1641 $newValueHashPt and $$newValueHashPt = $nvHash; 1240 1642 } 1241 if ($nvHash and $nvHash->{Value}) { 1242 my $vals = $nvHash->{Value}; 1243 # do inverse raw conversion if necessary 1244 if ($nvHash->{TagInfo}{RawConvInv}) { 1245 my @copyVals = @$vals; # modify a copy of the values 1246 $vals = \@copyVals; 1247 my $tagInfo = $$nvHash{TagInfo}; 1248 my $conv = $$tagInfo{RawConvInv}; 1249 my $self = $nvHash->{Self}; 1250 my ($val, $checkProc); 1251 my $table = $tagInfo->{Table}; 1252 $checkProc = $$table{CHECK_PROC} if $table; 1253 local $SIG{'__WARN__'} = \&SetWarning; 1254 undef $evalWarning; 1255 foreach $val (@$vals) { 1256 if (ref($conv) eq 'CODE') { 1257 $val = &$conv($val, $self); 1258 } else { 1259 #### eval RawConvInv ($self, $val, $taginfo) 1260 $val = eval $conv; 1261 $@ and $evalWarning = $@; 1262 } 1263 if ($evalWarning) { 1264 # an empty warning ("\n") ignores tag with no error 1265 if ($evalWarning ne "\n") { 1266 my $err = CleanWarning() . " in $$tagInfo{Name} (RawConvInv)"; 1267 $self->Warn($err); 1268 } 1269 @$vals = (); 1270 last; 1271 } 1272 # must check value now 1273 next unless $checkProc; 1643 unless ($nvHash and $$nvHash{Value}) { 1644 return () if wantarray; # return empty list 1645 return undef; 1646 } 1647 my $vals = $$nvHash{Value}; 1648 # do inverse raw conversion if necessary 1649 # - must also check after doing a Shift 1650 if ($$nvHash{TagInfo}{RawConvInv} or $$nvHash{Shift}) { 1651 my @copyVals = @$vals; # modify a copy of the values 1652 $vals = \@copyVals; 1653 my $tagInfo = $$nvHash{TagInfo}; 1654 my $conv = $$tagInfo{RawConvInv}; 1655 my $table = $$tagInfo{Table}; 1656 my ($val, $checkProc); 1657 $checkProc = $$table{CHECK_PROC} if $$nvHash{Shift} and $table; 1658 local $SIG{'__WARN__'} = \&SetWarning; 1659 undef $evalWarning; 1660 foreach $val (@$vals) { 1661 # must check value now if it was shifted 1662 if ($checkProc) { 1274 1663 my $err = &$checkProc($self, $tagInfo, \$val); 1275 1664 if ($err or not defined $val) { 1276 1665 $err or $err = 'Error generating raw value'; 1277 $self->Warn ("$err for $$tagInfo{Name}");1666 $self->WarnOnce("$err for $$tagInfo{Name}"); 1278 1667 @$vals = (); 1279 1668 last; 1280 1669 } 1281 } 1282 } 1283 # return our value(s) 1284 return @$vals if wantarray; 1285 return $$vals[0]; 1286 } 1287 return () if wantarray; # return empty list 1288 return undef; 1670 next unless $conv; 1671 } else { 1672 last unless $conv; 1673 } 1674 # do inverse raw conversion 1675 if (ref($conv) eq 'CODE') { 1676 $val = &$conv($val, $self); 1677 } else { 1678 #### eval RawConvInv ($self, $val, $tagInfo) 1679 $val = eval $conv; 1680 $@ and $evalWarning = $@; 1681 } 1682 if ($evalWarning) { 1683 # an empty warning ("\n") ignores tag with no error 1684 if ($evalWarning ne "\n") { 1685 my $err = CleanWarning() . " in $$tagInfo{Name} (RawConvInv)"; 1686 $self->WarnOnce($err); 1687 } 1688 @$vals = (); 1689 last; 1690 } 1691 } 1692 } 1693 # return our value(s) 1694 return @$vals if wantarray; 1695 return $$vals[0]; 1289 1696 } 1290 1697 … … 1292 1699 # Return the total number of new values set 1293 1700 # Inputs: 0) ExifTool object reference 1294 # Returns: Scalar context) Number of new values that have been set 1295 # List context) Number of new values , number of "pseudo" values1701 # Returns: Scalar context) Number of new values that have been set (incl pseudo) 1702 # List context) Number of new values (incl pseudo), number of "pseudo" values 1296 1703 # ("pseudo" values are those which don't require rewriting the file to change) 1297 1704 sub CountNewValues($) 1298 1705 { 1299 1706 my $self = shift; 1300 my $newVal = $self->{NEW_VALUE}; 1301 my $num = 0; 1302 my $tag; 1707 my $newVal = $$self{NEW_VALUE}; 1708 my ($num, $pseudo) = (0, 0); 1303 1709 if ($newVal) { 1304 $num += scalar keys %$newVal; 1305 # don't count "fake" tags (only in Extra table) 1306 foreach $tag (qw{Geotag Geosync}) { 1307 --$num if defined $$newVal{$Image::ExifTool::Extra{$tag}}; 1308 } 1309 } 1310 $num += scalar keys %{$self->{DEL_GROUP}}; 1710 $num = scalar keys %$newVal; 1711 my $nv; 1712 foreach $nv (values %$newVal) { 1713 my $tagInfo = $$nv{TagInfo}; 1714 # don't count tags that don't write anything 1715 $$tagInfo{WriteNothing} and --$num, next; 1716 # count the number of pseudo tags included 1717 $$tagInfo{WritePseudo} and ++$pseudo; 1718 } 1719 } 1720 $num += scalar keys %{$$self{DEL_GROUP}}; 1311 1721 return $num unless wantarray; 1312 my $pseudo = 0;1313 if ($newVal) {1314 # (Note: all writable "pseudo" tags must be found in Extra table)1315 foreach $tag (qw{FileName Directory FileModifyDate}) {1316 ++$pseudo if defined $$newVal{$Image::ExifTool::Extra{$tag}};1317 }1318 }1319 1722 return ($num, $pseudo); 1320 1723 } … … 1323 1726 # Save new values for subsequent restore 1324 1727 # Inputs: 0) ExifTool object reference 1728 # Returns: Number of times new values have been saved 1729 # Notes: increments SAVE_COUNT flag each time routine is called 1325 1730 sub SaveNewValues($) 1326 1731 { 1327 1732 my $self = shift; 1328 my $newValues = $self->{NEW_VALUE}; 1733 my $newValues = $$self{NEW_VALUE}; 1734 my $saveCount = ++$$self{SAVE_COUNT}; 1329 1735 my $key; 1330 1736 foreach $key (keys %$newValues) { 1331 1737 my $nvHash = $$newValues{$key}; 1332 1738 while ($nvHash) { 1333 $nvHash->{Save} = 1; # set Save flag 1334 $nvHash = $nvHash->{Next}; 1739 # set Save count if not done already 1740 $$nvHash{Save} or $$nvHash{Save} = $saveCount; 1741 $nvHash = $$nvHash{Next}; 1335 1742 } 1336 1743 } 1337 1744 # initialize hash for saving overwritten new values 1338 $ self->{SAVE_NEW_VALUE} = { };1745 $$self{SAVE_NEW_VALUE} = { }; 1339 1746 # make a copy of the delete group hash 1340 if ($self->{DEL_GROUP}) { 1341 my %delGrp = %{$self->{DEL_GROUP}}; 1342 $self->{SAVE_DEL_GROUP} = \%delGrp; 1343 } else { 1344 delete $self->{SAVE_DEL_GROUP}; 1345 } 1747 my %delGrp = %{$$self{DEL_GROUP}}; 1748 $$self{SAVE_DEL_GROUP} = \%delGrp; 1749 return $saveCount; 1346 1750 } 1347 1751 … … 1351 1755 # Notes: Restores saved new values, but currently doesn't restore them in the 1352 1756 # original order, so there may be some minor side-effects when restoring tags 1353 # with overlapping groups. ie) XMP:Identifier, XMP-dc:Identifier 1757 # with overlapping groups. eg) XMP:Identifier, XMP-dc:Identifier 1758 # Also, this doesn't do the right thing for list-type tags which accumulate 1759 # values across a save point 1354 1760 sub RestoreNewValues($) 1355 1761 { 1356 1762 my $self = shift; 1357 my $newValues = $ self->{NEW_VALUE};1358 my $savedValues = $ self->{SAVE_NEW_VALUE};1763 my $newValues = $$self{NEW_VALUE}; 1764 my $savedValues = $$self{SAVE_NEW_VALUE}; 1359 1765 my $key; 1360 1766 # 1) remove any new values which don't have the Save flag set … … 1365 1771 my $nvHash = $$newValues{$key}; 1366 1772 while ($nvHash) { 1367 if ($ nvHash->{Save}) {1773 if ($$nvHash{Save}) { 1368 1774 $lastHash = $nvHash; 1369 1775 } else { 1370 1776 # remove this entry from the list 1371 1777 if ($lastHash) { 1372 $ lastHash->{Next} = $nvHash->{Next};1373 } elsif ($ nvHash->{Next}) {1374 $$newValues{$key} = $ nvHash->{Next};1778 $$lastHash{Next} = $$nvHash{Next}; 1779 } elsif ($$nvHash{Next}) { 1780 $$newValues{$key} = $$nvHash{Next}; 1375 1781 } else { 1376 1782 delete $$newValues{$key}; 1377 1783 } 1378 1784 } 1379 $nvHash = $ nvHash->{Next};1785 $nvHash = $$nvHash{Next}; 1380 1786 } 1381 1787 } … … 1383 1789 # 2) restore saved new values 1384 1790 if ($savedValues) { 1385 $newValues or $newValues = $ self->{NEW_VALUE} = { };1791 $newValues or $newValues = $$self{NEW_VALUE} = { }; 1386 1792 foreach $key (keys %$savedValues) { 1387 1793 if ($$newValues{$key}) { 1388 1794 # add saved values to end of list 1389 1795 my $nvHash = LastInList($$newValues{$key}); 1390 $ nvHash->{Next} = $$savedValues{$key};1796 $$nvHash{Next} = $$savedValues{$key}; 1391 1797 } else { 1392 1798 $$newValues{$key} = $$savedValues{$key}; 1393 1799 } 1394 1800 } 1395 $ self->{SAVE_NEW_VALUE} = { }; # reset saved new values1801 $$self{SAVE_NEW_VALUE} = { }; # reset saved new values 1396 1802 } 1397 1803 # 3) restore delete groups 1398 if ($self->{SAVE_DEL_GROUP}) { 1399 my %delGrp = %{$self->{SAVE_DEL_GROUP}}; 1400 $self->{DEL_GROUP} = \%delGrp; 1401 } else { 1402 delete $self->{DEL_GROUP}; 1403 } 1404 } 1405 1406 #------------------------------------------------------------------------------ 1407 # Set file modification time from FileModifyDate tag 1804 my %delGrp = %{$$self{SAVE_DEL_GROUP}}; 1805 $$self{DEL_GROUP} = \%delGrp; 1806 } 1807 1808 #------------------------------------------------------------------------------ 1809 # Set filesystem time from from FileModifyDate or FileCreateDate tag 1408 1810 # Inputs: 0) ExifTool object reference, 1) file name or file ref 1409 # 2) modify time (-M) of original file (needed for time shift) 1811 # 2) time (-M or -C) of original file (used for shift; obtained from file if not given) 1812 # 3) tag name to write (undef for 'FileModifyDate') 1813 # 4) flag set if argument 2 has already been converted to Unix seconds 1410 1814 # Returns: 1=time changed OK, 0=nothing done, -1=error setting time 1411 # ( and increments CHANGED flag if time was changed)1412 sub SetFileModifyDate($$;$ )1413 { 1414 my ($self, $file, $originalTime ) = @_;1815 # (increments CHANGED flag and sets corresponding WRITTEN tag) 1816 sub SetFileModifyDate($$;$$$) 1817 { 1818 my ($self, $file, $originalTime, $tag, $isUnixTime) = @_; 1415 1819 my $nvHash; 1416 my $val = $self->GetNewValues('FileModifyDate', \$nvHash); 1820 $tag = 'FileModifyDate' unless defined $tag; 1821 my $val = $self->GetNewValue($tag, \$nvHash); 1417 1822 return 0 unless defined $val; 1418 my $isOverwriting = IsOverwriting($nvHash);1823 my $isOverwriting = $self->IsOverwriting($nvHash); 1419 1824 return 0 unless $isOverwriting; 1825 # can currently only set creation date on Windows systems 1826 # (and Mac now too, but that is handled with the MacOS tags) 1827 return 0 if $tag eq 'FileCreateDate' and $^O ne 'MSWin32'; 1420 1828 if ($isOverwriting < 0) { # are we shifting time? 1421 1829 # use original time of this file if not specified 1422 $originalTime = -M $file unless defined $originalTime; 1423 return 0 unless defined $originalTime; 1424 return 0 unless IsOverwriting($nvHash, $^T - $originalTime*(24*3600)); 1425 $val = $nvHash->{Value}[0]; # get shifted value 1426 } 1427 unless (utime($val, $val, $file)) { 1428 $self->Warn('Error setting FileModifyDate'); 1429 return -1; 1430 } 1431 ++$self->{CHANGED}; 1432 $self->VerboseValue('+ FileModifyDate', $val); 1830 unless (defined $originalTime) { 1831 my ($aTime, $mTime, $cTime) = $self->GetFileTime($file); 1832 $originalTime = ($tag eq 'FileCreateDate') ? $cTime : $mTime; 1833 return 0 unless defined $originalTime; 1834 $isUnixTime = 1; 1835 } 1836 $originalTime = int($^T - $originalTime*(24*3600) + 0.5) unless $isUnixTime; 1837 return 0 unless $self->IsOverwriting($nvHash, $originalTime); 1838 $val = $$nvHash{Value}[0]; # get shifted value 1839 } 1840 my ($aTime, $mTime, $cTime); 1841 if ($tag eq 'FileCreateDate') { 1842 eval { require Win32::API } or $self->WarnOnce("Install Win32::API to set $tag"), return -1; 1843 eval { require Win32API::File } or $self->WarnOnce("Install Win32API::File to set $tag"), return -1; 1844 $cTime = $val; 1845 } else { 1846 $aTime = $mTime = $val; 1847 } 1848 $self->SetFileTime($file, $aTime, $mTime, $cTime, 1) or $self->Warn("Error setting $tag"), return -1; 1849 ++$$self{CHANGED}; 1850 $$self{WRITTEN}{$tag} = $val; # remember that we wrote this tag 1851 $self->VerboseValue("+ $tag", $val); 1433 1852 return 1; 1434 1853 } … … 1437 1856 # Change file name and/or directory from FileName and Directory tags 1438 1857 # Inputs: 0) ExifTool object reference, 1) current file name (including path) 1439 # 2) New name (or undef to build from FileName and Directory tags) 1858 # 2) new name (or undef to build from FileName and Directory tags) 1859 # 3) option: 'HardLink'/'SymLink' to create hard/symbolic link instead of renaming 1860 # 'Test' to only print new file name 1861 # 4) 0 to indicate that a file will no longer exist (used for 'Test' only) 1440 1862 # Returns: 1=name changed OK, 0=nothing changed, -1=error changing name 1441 1863 # (and increments CHANGED flag if filename changed) 1442 1864 # Notes: Will not overwrite existing file. Creates directories as necessary. 1443 sub SetFileName($$;$ )1444 { 1445 my ($self, $file, $newName ) = @_;1865 sub SetFileName($$;$$$) 1866 { 1867 my ($self, $file, $newName, $opt, $usedFlag) = @_; 1446 1868 my ($nvHash, $doName, $doDir); 1869 1870 $opt or $opt = ''; 1447 1871 # determine the new file name 1448 1872 unless (defined $newName) { 1449 my $filename = $self->GetNewValues('FileName', \$nvHash); 1450 $doName = 1 if defined $filename and IsOverwriting($nvHash, $file); 1451 my $dir = $self->GetNewValues('Directory', \$nvHash); 1452 $doDir = 1 if defined $dir and IsOverwriting($nvHash, $file); 1453 return 0 unless $doName or $doDir; # nothing to do 1454 if ($doName) { 1455 $newName = GetNewFileName($file, $filename); 1456 $newName = GetNewFileName($newName, $dir) if $doDir; 1873 if ($opt) { 1874 if ($opt eq 'HardLink' or $opt eq 'Link') { 1875 $newName = $self->GetNewValue('HardLink'); 1876 } elsif ($opt eq 'SymLink') { 1877 $newName = $self->GetNewValue('SymLink'); 1878 } elsif ($opt eq 'Test') { 1879 $newName = $self->GetNewValue('TestName'); 1880 } 1881 return 0 unless defined $newName; 1457 1882 } else { 1458 $newName = GetNewFileName($file, $dir); 1459 } 1460 } 1461 if (-e $newName) { 1462 # don't replace existing file 1463 $self->Warn("File '$newName' already exists"); 1464 return -1; 1883 my $filename = $self->GetNewValue('FileName', \$nvHash); 1884 $doName = 1 if defined $filename and $self->IsOverwriting($nvHash, $file); 1885 my $dir = $self->GetNewValue('Directory', \$nvHash); 1886 $doDir = 1 if defined $dir and $self->IsOverwriting($nvHash, $file); 1887 return 0 unless $doName or $doDir; # nothing to do 1888 if ($doName) { 1889 $newName = GetNewFileName($file, $filename); 1890 $newName = GetNewFileName($newName, $dir) if $doDir; 1891 } else { 1892 $newName = GetNewFileName($file, $dir); 1893 } 1894 } 1895 } 1896 # validate new file name in Windows 1897 if ($^O eq 'MSWin32') { 1898 if ($newName =~ /[\0-\x1f<>"|*]/) { 1899 $self->Warn('New file name not allowed in Windows (contains reserved characters)'); 1900 return -1; 1901 } 1902 if ($newName =~ /:/ and $newName !~ /^[A-Z]:[^:]*$/i) { 1903 $self->Warn("New file name not allowed in Windows (contains ':')"); 1904 return -1; 1905 } 1906 if ($newName =~ /\?/ and $newName !~ m{^[\\/]{2}\?[\\/][^?]*$}) { 1907 $self->Warn("New file name not allowed in Windows (contains '?')"); 1908 return -1; 1909 } 1910 if ($newName =~ m{(^|[\\/])(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.[^.]*)?$}i) { 1911 $self->Warn('New file name not allowed in Windows (reserved device name)'); 1912 return -1; 1913 } 1914 if ($newName =~ /([. ])$/) { 1915 $self->Warn("New file name not recommended for Windows (ends with '${1}')", 2) and return -1; 1916 } 1917 if (length $newName > 259 and $newName !~ /\?/) { 1918 $self->Warn('New file name not recommended for Windows (exceeds 260 chars)', 2) and return -1; 1919 } 1920 } else { 1921 $newName =~ tr/\0//d; # make sure name doesn't contain nulls 1922 } 1923 # protect against empty file name 1924 length $newName or $self->Warn('New file name is empty'), return -1; 1925 # don't replace existing file 1926 if ($self->Exists($newName) and (not defined $usedFlag or $usedFlag)) { 1927 if ($file ne $newName or $opt =~ /Link$/) { 1928 # allow for case-insensitive filesystem 1929 if ($opt =~ /Link$/ or not $self->IsSameFile($file, $newName)) { 1930 $self->Warn("File '${newName}' already exists"); 1931 return -1; 1932 } 1933 } else { 1934 $self->Warn('File name is unchanged'); 1935 return 0; 1936 } 1937 } 1938 if ($opt eq 'Test') { 1939 my $out = $$self{OPTIONS}{TextOut}; 1940 print $out "'${file}' --> '${newName}'\n"; 1941 return 1; 1465 1942 } 1466 1943 # create directory for new file if necessary 1467 1944 my $result; 1468 if (($result = CreateDirectory($newName)) != 0) {1945 if (($result = $self->CreateDirectory($newName)) != 0) { 1469 1946 if ($result < 0) { 1470 $self->Warn("Error creating directory for '$ newName'");1947 $self->Warn("Error creating directory for '${newName}'"); 1471 1948 return -1; 1472 1949 } 1473 $self->VPrint(0, "Created directory for '$newName'"); 1950 $self->VPrint(0, "Created directory for '${newName}'\n"); 1951 } 1952 if ($opt eq 'HardLink' or $opt eq 'Link') { 1953 unless (link $file, $newName) { 1954 $self->Warn("Error creating hard link '${newName}'"); 1955 return -1; 1956 } 1957 ++$$self{CHANGED}; 1958 $self->VerboseValue('+ HardLink', $newName); 1959 return 1; 1960 } elsif ($opt eq 'SymLink') { 1961 $^O eq 'MSWin32' and $self->Warn('SymLink not supported in Windows'), return -1; 1962 $newName =~ s(^\./)(); # remove leading "./" from link name if it exists 1963 # path to linked file must be relative to the $newName directory, but $file 1964 # is relative to the current directory, so convert it to an absolute path 1965 # if using a relative directory and $newName isn't in the current directory 1966 if ($file !~ m(^/) and $newName =~ m(/)) { 1967 unless (eval { require Cwd }) { 1968 $self->Warn('Install Cwd to make symlinks to other directories'); 1969 return -1; 1970 } 1971 $file = eval { Cwd::abs_path($file) }; 1972 unless (defined $file) { 1973 $self->Warn('Error in Cwd::abs_path when creating symlink'); 1974 return -1; 1975 } 1976 } 1977 unless (eval { symlink $file, $newName } ) { 1978 $self->Warn("Error creating symbolic link '${newName}'"); 1979 return -1; 1980 } 1981 ++$$self{CHANGED}; 1982 $self->VerboseValue('+ SymLink', $newName); 1983 return 1; 1474 1984 } 1475 1985 # attempt to rename the file 1476 unless ( rename $file, $newName) {1986 unless ($self->Rename($file, $newName)) { 1477 1987 local (*EXIFTOOL_SFN_IN, *EXIFTOOL_SFN_OUT); 1478 1988 # renaming didn't work, so copy the file instead 1479 unless ( open EXIFTOOL_SFN_IN, $file) {1480 $self-> Warn("Error opening '$file'");1989 unless ($self->Open(\*EXIFTOOL_SFN_IN, $file)) { 1990 $self->Error("Error opening '${file}'"); 1481 1991 return -1; 1482 1992 } 1483 unless ( open EXIFTOOL_SFN_OUT, ">$newName") {1993 unless ($self->Open(\*EXIFTOOL_SFN_OUT, $newName, '>')) { 1484 1994 close EXIFTOOL_SFN_IN; 1485 $self-> Warn("Error creating '$newName'");1995 $self->Error("Error creating '${newName}'"); 1486 1996 return -1; 1487 1997 } … … 1495 2005 close EXIFTOOL_SFN_IN; 1496 2006 if ($err) { 1497 unlink $newName; # erase bad output file1498 $self-> Warn("Error writing '$newName'");2007 $self->Unlink($newName); # erase bad output file 2008 $self->Error("Error writing '${newName}'"); 1499 2009 return -1; 1500 2010 } 1501 2011 # preserve modification time 1502 my $modTime = $^T - (-M $file) * (24 * 3600); 1503 my $accTime = $^T - (-A $file) * (24 * 3600); 1504 utime($accTime, $modTime, $newName); 2012 my ($aTime, $mTime, $cTime) = $self->GetFileTime($file); 2013 $self->SetFileTime($newName, $aTime, $mTime, $cTime); 1505 2014 # remove the original file 1506 unlink $file or $self->Warn('Error removing old file'); 1507 } 1508 ++$self->{CHANGED}; 2015 $self->Unlink($file) or $self->Warn('Error removing old file'); 2016 } 2017 $$self{NewName} = $newName; # remember new file name 2018 ++$$self{CHANGED}; 1509 2019 $self->VerboseValue('+ FileName', $newName); 1510 2020 return 1; … … 1512 2022 1513 2023 #------------------------------------------------------------------------------ 2024 # Set file permissions, group/user id and various MDItem tags from new tag values 2025 # Inputs: 0) ExifTool ref, 1) file name or glob (must be a name for MDItem tags) 2026 # Returns: 1=something was set OK, 0=didn't try, -1=error (and warning set) 2027 # Notes: There may be errors even if 1 is returned 2028 sub SetSystemTags($$) 2029 { 2030 my ($self, $file) = @_; 2031 my $result = 0; 2032 2033 my $perm = $self->GetNewValue('FilePermissions'); 2034 if (defined $perm) { 2035 if (eval { chmod($perm & 07777, $file) }) { 2036 $self->VerboseValue('+ FilePermissions', $perm); 2037 $result = 1; 2038 } else { 2039 $self->WarnOnce('Error setting FilePermissions'); 2040 $result = -1; 2041 } 2042 } 2043 my $uid = $self->GetNewValue('FileUserID'); 2044 my $gid = $self->GetNewValue('FileGroupID'); 2045 if (defined $uid or defined $gid) { 2046 defined $uid or $uid = -1; 2047 defined $gid or $gid = -1; 2048 if (eval { chown($uid, $gid, $file) }) { 2049 $self->VerboseValue('+ FileUserID', $uid) if $uid >= 0; 2050 $self->VerboseValue('+ FileGroupID', $gid) if $gid >= 0; 2051 $result = 1; 2052 } else { 2053 $self->WarnOnce('Error setting FileGroup/UserID'); 2054 $result = -1 unless $result; 2055 } 2056 } 2057 my $tag; 2058 foreach $tag (@writableMacOSTags) { 2059 my $nvHash; 2060 my $val = $self->GetNewValue($tag, \$nvHash); 2061 next unless $nvHash; 2062 if ($^O eq 'darwin') { 2063 ref $file and $self->Warn('Setting MDItem tags requires a file name'), last; 2064 require Image::ExifTool::MacOS; 2065 my $res = Image::ExifTool::MacOS::SetMacOSTags($self, $file, \@writableMacOSTags); 2066 $result = $res if $res == 1 or not $result; 2067 last; 2068 } elsif ($tag ne 'FileCreateDate') { 2069 $self->WarnOnce('Can only set MDItem tags on OS X'); 2070 last; 2071 } 2072 } 2073 return $result; 2074 } 2075 2076 #------------------------------------------------------------------------------ 1514 2077 # Write information back to file 1515 2078 # Inputs: 0) ExifTool object reference, 1516 # 1) input filename, file ref, or scalar ref (or '' or undef to create from scratch)2079 # 1) input filename, file ref, RAF ref, or scalar ref (or '' or undef to create from scratch) 1517 2080 # 2) output filename, file ref, or scalar ref (or undef to overwrite) 1518 2081 # 3) optional output file type (required only if input file is not specified … … 1524 2087 my ($self, $infile, $outfile, $outType) = @_; 1525 2088 my (@fileTypeList, $fileType, $tiffType, $hdr, $seekErr, $type, $tmpfile); 1526 my ($inRef, $outRef, $closeIn, $closeOut, $outPos, $outBuff, $eraseIn); 1527 my $oldRaf = $self->{RAF}; 2089 my ($inRef, $outRef, $closeIn, $closeOut, $outPos, $outBuff, $eraseIn, $raf, $fileExt); 2090 my ($hardLink, $symLink, $testName); 2091 my $oldRaf = $$self{RAF}; 1528 2092 my $rtnVal = 0; 1529 2093 1530 2094 # initialize member variables 1531 2095 $self->Init(); 2096 $$self{IsWriting} = 1; 1532 2097 1533 2098 # first, save original file modify date if necessary 1534 2099 # (do this now in case we are modifying file in place and shifting date) 1535 my ($nvHash, $originalTime); 1536 my $fileModifyDate = $self->GetNewValues('FileModifyDate', \$nvHash); 1537 if (defined $fileModifyDate and IsOverwriting($nvHash) < 0 and 2100 my ($nvHash, $nvHash2, $originalTime, $createTime); 2101 my $setModDate = defined $self->GetNewValue('FileModifyDate', \$nvHash); 2102 my $setCreateDate = defined $self->GetNewValue('FileCreateDate', \$nvHash2); 2103 my ($aTime, $mTime, $cTime); 2104 if ($setModDate and $self->IsOverwriting($nvHash) < 0 and 1538 2105 defined $infile and ref $infile ne 'SCALAR') 1539 2106 { 1540 $originalTime = -M $infile; 2107 ($aTime, $mTime, $cTime) = $self->GetFileTime($infile); 2108 $originalTime = $mTime; 2109 } 2110 if ($setCreateDate and $self->IsOverwriting($nvHash2) < 0 and 2111 defined $infile and ref $infile ne 'SCALAR') 2112 { 2113 ($aTime, $mTime, $cTime) = $self->GetFileTime($infile) unless defined $cTime; 2114 $createTime = $cTime; 1541 2115 } 1542 2116 # … … 1545 2119 my ($numNew, $numPseudo) = $self->CountNewValues(); 1546 2120 if (not defined $outfile and defined $infile) { 1547 my $newFileName = $self->GetNewValues('FileName', \$nvHash); 2121 $hardLink = $self->GetNewValue('HardLink'); 2122 $symLink = $self->GetNewValue('SymLink'); 2123 $testName = $self->GetNewValue('TestName'); 2124 undef $hardLink if defined $hardLink and not length $hardLink; 2125 undef $symLink if defined $symLink and not length $symLink; 2126 undef $testName if defined $testName and not length $testName; 2127 my $newFileName = $self->GetNewValue('FileName', \$nvHash); 2128 my $newDir = $self->GetNewValue('Directory'); 2129 if (defined $newDir and length $newDir) { 2130 $newDir .= '/' unless $newDir =~ m{/$}; 2131 } else { 2132 undef $newDir; 2133 } 1548 2134 if ($numNew == $numPseudo) { 1549 2135 $rtnVal = 2; 1550 if (defined $fileModifyDate and (not ref $infile or UNIVERSAL::isa($infile,'GLOB'))) { 1551 $self->SetFileModifyDate($infile) > 0 and $rtnVal = 1; 1552 } 1553 if (defined $newFileName and not ref $infile) { 1554 $self->SetFileName($infile) > 0 and $rtnVal = 1; 2136 if ((defined $newFileName or defined $newDir) and not ref $infile) { 2137 my $result = $self->SetFileName($infile); 2138 if ($result > 0) { 2139 $infile = $$self{NewName}; # file name changed 2140 $rtnVal = 1; 2141 } elsif ($result < 0) { 2142 return 0; # don't try to do anything else 2143 } 2144 } 2145 if (not ref $infile or UNIVERSAL::isa($infile,'GLOB')) { 2146 $self->SetFileModifyDate($infile) > 0 and $rtnVal = 1 if $setModDate; 2147 $self->SetFileModifyDate($infile, undef, 'FileCreateDate') > 0 and $rtnVal = 1 if $setCreateDate; 2148 $self->SetSystemTags($infile) > 0 and $rtnVal = 1; 2149 } 2150 if (defined $hardLink or defined $symLink or defined $testName) { 2151 $hardLink and $self->SetFileName($infile, $hardLink, 'HardLink') and $rtnVal = 1; 2152 $symLink and $self->SetFileName($infile, $symLink, 'SymLink') and $rtnVal = 1; 2153 $testName and $self->SetFileName($infile, $testName, 'Test') and $rtnVal = 1; 1555 2154 } 1556 2155 return $rtnVal; … … 1561 2160 $outfile = $newFileName; 1562 2161 # can't delete original 1563 } elsif ( IsOverwriting($nvHash, $infile)) {2162 } elsif ($self->IsOverwriting($nvHash, $infile)) { 1564 2163 $outfile = GetNewFileName($infile, $newFileName); 1565 2164 $eraseIn = 1; # delete original 2165 } 2166 } 2167 # set new directory if specified 2168 if (defined $newDir) { 2169 $outfile = $infile unless defined $outfile or ref $infile; 2170 if (defined $outfile) { 2171 $outfile = GetNewFileName($outfile, $newDir); 2172 $eraseIn = 1 unless ref $infile; 1566 2173 } 1567 2174 } … … 1574 2181 if (UNIVERSAL::isa($inRef,'GLOB')) { 1575 2182 seek($inRef, 0, 0); # make sure we are at the start of the file 1576 } elsif ($] >= 5.006 and (eval 'require Encode; Encode::is_utf8($$inRef)' or $@)) { 2183 } elsif (UNIVERSAL::isa($inRef,'File::RandomAccess')) { 2184 $inRef->Seek(0); 2185 $raf = $inRef; 2186 } elsif ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($$inRef) } or $@)) { 1577 2187 # convert image data from UTF-8 to character stream if necessary 1578 my $buff = $@ ? pack('C*',unpack( 'U0C*',$$inRef)) : Encode::encode('utf8',$$inRef);2188 my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$inRef)) : Encode::encode('utf8',$$inRef); 1579 2189 if (defined $outfile) { 1580 2190 $inRef = \$buff; … … 1586 2196 # write to a temporary file if no output file given 1587 2197 $outfile = $tmpfile = "${infile}_exiftool_tmp" unless defined $outfile; 1588 if (open(EXIFTOOL_FILE2, $infile)) { 2198 if ($self->Open(\*EXIFTOOL_FILE2, $infile)) { 2199 $fileExt = GetFileExtension($infile); 1589 2200 $fileType = GetFileType($infile); 1590 2201 @fileTypeList = GetFileType($infile); … … 1604 2215 $outType = GetFileExtension($outfile) unless $outType or ref $outfile; 1605 2216 if (CanCreate($outType)) { 1606 $fileType = $tiffType = $outType; # use output file type if no input file 1607 $infile = "$fileType file"; # make bogus file name 1608 $self->VPrint(0, "Creating $infile...\n"); 1609 $inRef = \ ''; # set $inRef to reference to empty data 2217 if ($$self{OPTIONS}{WriteMode} =~ /g/i) { 2218 $fileType = $tiffType = $outType; # use output file type if no input file 2219 $infile = "$fileType file"; # make bogus file name 2220 $self->VPrint(0, "Creating $infile...\n"); 2221 $inRef = \ ''; # set $inRef to reference to empty data 2222 } else { 2223 $self->Error("Not creating new $outType file (disallowed by WriteMode)"); 2224 return 0; 2225 } 1610 2226 } elsif ($outType) { 1611 2227 $self->Error("Can't create $outType files"); … … 1640 2256 # editing in place, so write to memory first 1641 2257 # (only when infile is a file ref or scalar ref) 2258 if ($raf) { 2259 $self->Error("Can't edit File::RandomAccess object in place"); 2260 return 0; 2261 } 1642 2262 $outBuff = ''; 1643 2263 $outRef = \$outBuff; 1644 2264 $outPos = 0; 1645 } elsif ( -e $outfile) {2265 } elsif ($self->Exists($outfile)) { 1646 2266 $self->Error("File already exists: $outfile"); 1647 } elsif ( open(EXIFTOOL_OUTFILE, ">$outfile")) {2267 } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) { 1648 2268 $outRef = \*EXIFTOOL_OUTFILE; 1649 2269 $closeOut = 1; # we must close $outRef … … 1657 2277 # write the file 1658 2278 # 1659 until ($ self->{VALUE}{Error}) {2279 until ($$self{VALUE}{Error}) { 1660 2280 # create random access file object (disable seek test in case of straight copy) 1661 my$raf = new File::RandomAccess($inRef, 1);2281 $raf or $raf = new File::RandomAccess($inRef, 1); 1662 2282 $raf->BinMode(); 1663 2283 if ($numNew == $numPseudo) { … … 1671 2291 } elsif (not ref $infile and ($infile eq '-' or $infile =~ /\|$/)) { 1672 2292 # patch for Windows command shell pipe 1673 $ raf->{TESTED} = -1;# force buffering2293 $$raf{TESTED} = -1; # force buffering 1674 2294 } else { 1675 2295 $raf->SeekTest(); … … 1677 2297 # $raf->Debug() and warn " RAF debugging enabled!\n"; 1678 2298 my $inPos = $raf->Tell(); 1679 $ self->{RAF} = $raf;2299 $$self{RAF} = $raf; 1680 2300 my %dirInfo = ( 1681 2301 RAF => $raf, … … 1694 2314 } 1695 2315 # save file type in member variable 1696 $dirInfo{Parent} = $ self->{FILE_TYPE} = $self->{PATH}[0] = $type;2316 $dirInfo{Parent} = $$self{FILE_TYPE} = $$self{PATH}[0] = $type; 1697 2317 # determine which directories we must write for this file type 1698 2318 $self->InitWriteDirs($type); 1699 if ($type eq 'JPEG' ) {2319 if ($type eq 'JPEG' or $type eq 'EXV') { 1700 2320 $rtnVal = $self->WriteJPEG(\%dirInfo); 1701 2321 } elsif ($type eq 'TIFF') { … … 1705 2325 undef $rtnVal; 1706 2326 } else { 2327 if ($tiffType eq 'FFF') { 2328 # (see https://exiftool.org/forum/index.php?topic=10848.0) 2329 $self->Error('Phocus may not properly update previews of edited FFF images', 1); 2330 } 1707 2331 $dirInfo{Parent} = $tiffType; 1708 2332 $rtnVal = $self->ProcessTIFF(\%dirInfo); 1709 2333 } 1710 } elsif ($type eq 'GIF') { 1711 require Image::ExifTool::GIF; 1712 $rtnVal = Image::ExifTool::GIF::ProcessGIF($self,\%dirInfo); 1713 } elsif ($type eq 'CRW') { 1714 require Image::ExifTool::CanonRaw; 1715 $rtnVal = Image::ExifTool::CanonRaw::WriteCRW($self, \%dirInfo); 1716 } elsif ($type eq 'MRW') { 1717 require Image::ExifTool::MinoltaRaw; 1718 $rtnVal = Image::ExifTool::MinoltaRaw::ProcessMRW($self, \%dirInfo); 1719 } elsif ($type eq 'RAF') { 1720 require Image::ExifTool::FujiFilm; 1721 $rtnVal = Image::ExifTool::FujiFilm::WriteRAF($self, \%dirInfo); 2334 } elsif (exists $writableType{$type}) { 2335 my ($module, $func); 2336 if (ref $writableType{$type} eq 'ARRAY') { 2337 $module = $writableType{$type}[0] || $type; 2338 $func = $writableType{$type}[1]; 2339 } else { 2340 $module = $writableType{$type} || $type; 2341 } 2342 require "Image/ExifTool/$module.pm"; 2343 $func = "Image::ExifTool::${module}::" . ($func || "Process$type"); 2344 no strict 'refs'; 2345 $rtnVal = &$func($self, \%dirInfo); 2346 use strict 'refs'; 1722 2347 } elsif ($type eq 'ORF' or $type eq 'RAW') { 1723 2348 $rtnVal = $self->ProcessTIFF(\%dirInfo); 1724 } elsif ($type eq 'X3F') {1725 require Image::ExifTool::SigmaRaw;1726 $rtnVal = Image::ExifTool::SigmaRaw::ProcessX3F($self, \%dirInfo);1727 } elsif ($type eq 'PNG') {1728 require Image::ExifTool::PNG;1729 $rtnVal = Image::ExifTool::PNG::ProcessPNG($self, \%dirInfo);1730 } elsif ($type eq 'MIE') {1731 require Image::ExifTool::MIE;1732 $rtnVal = Image::ExifTool::MIE::ProcessMIE($self, \%dirInfo);1733 } elsif ($type eq 'XMP') {1734 require Image::ExifTool::XMP;1735 $rtnVal = Image::ExifTool::XMP::WriteXMP($self, \%dirInfo);1736 } elsif ($type eq 'PPM') {1737 require Image::ExifTool::PPM;1738 $rtnVal = Image::ExifTool::PPM::ProcessPPM($self, \%dirInfo);1739 } elsif ($type eq 'PSD') {1740 require Image::ExifTool::Photoshop;1741 $rtnVal = Image::ExifTool::Photoshop::ProcessPSD($self, \%dirInfo);1742 } elsif ($type eq 'EPS' or $type eq 'PS') {1743 require Image::ExifTool::PostScript;1744 $rtnVal = Image::ExifTool::PostScript::WritePS($self, \%dirInfo);1745 } elsif ($type eq 'PDF') {1746 require Image::ExifTool::PDF;1747 $rtnVal = Image::ExifTool::PDF::WritePDF($self, \%dirInfo);1748 } elsif ($type eq 'ICC') {1749 require Image::ExifTool::ICC_Profile;1750 $rtnVal = Image::ExifTool::ICC_Profile::WriteICC($self, \%dirInfo);1751 } elsif ($type eq 'VRD') {1752 require Image::ExifTool::CanonVRD;1753 $rtnVal = Image::ExifTool::CanonVRD::ProcessVRD($self, \%dirInfo);1754 } elsif ($type eq 'JP2') {1755 require Image::ExifTool::Jpeg2000;1756 $rtnVal = Image::ExifTool::Jpeg2000::ProcessJP2($self, \%dirInfo);1757 } elsif ($type eq 'IND') {1758 require Image::ExifTool::InDesign;1759 $rtnVal = Image::ExifTool::InDesign::ProcessIND($self, \%dirInfo);1760 2349 } elsif ($type eq 'EXIF') { 1761 2350 # go through WriteDirectory so block writes, etc are handled … … 1787 2376 $err = 'Error seeking in file'; 1788 2377 } elsif ($fileType and defined $rtnVal) { 1789 if ($ self->{VALUE}{Error}) {2378 if ($$self{VALUE}{Error}) { 1790 2379 # existing error message will do 1791 2380 } elsif ($fileType eq 'RAW') { … … 1793 2382 } else { 1794 2383 if ($wrongType) { 1795 $err = "Not a valid $fileType"; 2384 my $type = $fileExt || ($fileType eq 'TIFF' ? $tiffType : $fileType); 2385 $err = "Not a valid $type"; 1796 2386 # do a quick check to see what this file looks like 1797 2387 foreach $type (@fileTypes) { … … 1820 2410 # don't return success code if any error occurred 1821 2411 if ($rtnVal > 0) { 1822 unless (Tell($outRef) or $self->{VALUE}{Error}) { 2412 if ($outType and $type and $outType ne $type) { 2413 my @types = GetFileType($outType); 2414 unless (grep /^$type$/, @types) { 2415 $self->Error("Can't create $outType file from $type"); 2416 $rtnVal = 0; 2417 } 2418 } 2419 if ($rtnVal > 0 and not Tell($outRef) and not $$self{VALUE}{Error}) { 1823 2420 # don't write a file with zero length 1824 2421 if (defined $hdr and length $hdr) { 2422 $type = '<unk>' unless defined $type; 1825 2423 $self->Error("Can't delete all meta information from $type file"); 1826 2424 } else { … … 1828 2426 } 1829 2427 } 1830 $rtnVal = 0 if $ self->{VALUE}{Error};2428 $rtnVal = 0 if $$self{VALUE}{Error}; 1831 2429 } 1832 2430 1833 2431 # rewrite original file in place if required 1834 2432 if (defined $outBuff) { 1835 if ($rtnVal <= 0 or not $ self->{CHANGED}) {2433 if ($rtnVal <= 0 or not $$self{CHANGED}) { 1836 2434 # nothing changed, so no need to write $outBuff 1837 2435 } elsif (UNIVERSAL::isa($inRef,'GLOB')) { … … 1844 2442 print $inRef $outBuff and # write the new data 1845 2443 ($len >= $size or # if necessary: 1846 eval 'truncate($inRef, $len)'); # shorten output file2444 eval { truncate($inRef, $len) }); # shorten output file 1847 2445 } else { 1848 2446 $$inRef = $outBuff; # replace original data … … 1856 2454 if ($rtnVal > 0) { 1857 2455 # copy Mac OS resource fork if it exists 1858 if ($^O eq 'darwin' and -s "$infile/ rsrc") {1859 if ($$self{DEL_GROUP} and $$self{DEL_GROUP}{RSRC}) {2456 if ($^O eq 'darwin' and -s "$infile/..namedfork/rsrc") { 2457 if ($$self{DEL_GROUP}{RSRC}) { 1860 2458 $self->VPrint(0,"Deleting Mac OS resource fork\n"); 1861 2459 ++$$self{CHANGED}; … … 1864 2462 my ($buf, $err); 1865 2463 local (*SRC, *DST); 1866 if ( open SRC, "$infile/rsrc") {1867 if ( open DST, ">$outfile/rsrc") {2464 if ($self->Open(\*SRC, "$infile/..namedfork/rsrc")) { 2465 if ($self->Open(\*DST, "$outfile/..namedfork/rsrc", '>')) { 1868 2466 binmode SRC; # (not necessary for Darwin, but let's be thorough) 1869 2467 binmode DST; … … 1880 2478 $err = 'opening'; 1881 2479 } 1882 $rtnVal = 0 if $err and $self->Error("Error $err Mac OS resource fork", 1);2480 $rtnVal = 0 if $err and $self->Error("Error $err Mac OS resource fork", 2); 1883 2481 } 1884 2482 } 1885 2483 # erase input file if renaming while editing information in place 1886 unlink $infileor $self->Warn('Error erasing original file') if $eraseIn;2484 $self->Unlink($infile) or $self->Warn('Error erasing original file') if $eraseIn; 1887 2485 } 1888 2486 } … … 1893 2491 # erase the output file if we weren't successful 1894 2492 if ($rtnVal <= 0) { 1895 unlink $outfile;2493 $self->Unlink($outfile); 1896 2494 # else rename temporary file if necessary 1897 2495 } elsif ($tmpfile) { 1898 CopyFileAttrs($infile, $tmpfile);# copy attributes to new file1899 unless ( rename($tmpfile, $infile)) {2496 $self->CopyFileAttrs($infile, $tmpfile); # copy attributes to new file 2497 unless ($self->Rename($tmpfile, $infile)) { 1900 2498 # some filesystems won't overwrite with 'rename', so try erasing original 1901 if (not unlink($infile)) {1902 unlink $tmpfile;2499 if (not $self->Unlink($infile)) { 2500 $self->Unlink($tmpfile); 1903 2501 $self->Error('Error renaming temporary file'); 1904 2502 $rtnVal = 0; 1905 } elsif (not rename($tmpfile, $infile)) {2503 } elsif (not $self->Rename($tmpfile, $infile)) { 1906 2504 $self->Error('Error renaming temporary file after deleting original'); 1907 2505 $rtnVal = 0; 1908 2506 } 1909 2507 } 1910 } 1911 } 1912 # set FileModifyDate if requested (and if possible!) 1913 if (defined $fileModifyDate and $rtnVal > 0 and 1914 ($closeOut or ($closeIn and defined $outBuff)) and 1915 $self->SetFileModifyDate($closeOut ? $outfile : $infile, $originalTime) > 0) 1916 { 1917 ++$self->{CHANGED}; # we changed something 2508 # the output file should now have the name of the original infile 2509 $outfile = $infile if $rtnVal > 0; 2510 } 2511 } 2512 # set filesystem attributes if requested (and if possible!) 2513 if ($rtnVal > 0 and ($closeOut or (defined $outBuff and ($closeIn or UNIVERSAL::isa($infile,'GLOB'))))) { 2514 my $target = $closeOut ? $outfile : $infile; 2515 # set file permissions if requested 2516 ++$$self{CHANGED} if $self->SetSystemTags($target) > 0; 2517 if ($closeIn) { # (no use setting file times unless the input file is closed) 2518 ++$$self{CHANGED} if $setModDate and $self->SetFileModifyDate($target, $originalTime, undef, 1) > 0; 2519 # set FileCreateDate if requested (and if possible!) 2520 ++$$self{CHANGED} if $setCreateDate and $self->SetFileModifyDate($target, $createTime, 'FileCreateDate', 1) > 0; 2521 # create hard link if requested and no output filename specified (and if possible!) 2522 ++$$self{CHANGED} if defined $hardLink and $self->SetFileName($target, $hardLink, 'HardLink'); 2523 ++$$self{CHANGED} if defined $symLink and $self->SetFileName($target, $symLink, 'SymLink'); 2524 defined $testName and $self->SetFileName($target, $testName, 'Test'); 2525 } 1918 2526 } 1919 2527 # check for write error and set appropriate error message and return value … … 1922 2530 $rtnVal = 0; # return 0 on failure 1923 2531 } elsif ($rtnVal > 0) { 1924 ++$rtnVal unless $ self->{CHANGED};2532 ++$rtnVal unless $$self{CHANGED}; 1925 2533 } 1926 2534 # set things back to the way they were 1927 $ self->{RAF} = $oldRaf;2535 $$self{RAF} = $oldRaf; 1928 2536 1929 2537 return $rtnVal; … … 1942 2550 @groups = split ':', $group if $group; 1943 2551 1944 my $e xifTool= new Image::ExifTool;2552 my $et = new Image::ExifTool; 1945 2553 LoadAllTables(); # first load all our tables 1946 2554 my @tableNames = keys %allTables; … … 1949 2557 while (@tableNames) { 1950 2558 my $table = GetTagTable(pop @tableNames); 2559 # generate flattened tag names for structure fields if this is an XMP table 2560 if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { 2561 Image::ExifTool::XMP::AddFlattenedTags($table); 2562 } 1951 2563 my $tagID; 1952 2564 foreach $tagID (TagTableKeys($table)) { … … 1960 2572 next if $$tagInfo{Hidden}; # ignore hidden tags 1961 2573 if (@groups) { 1962 my @tg = $e xifTool->GetGroup($tagInfo);2574 my @tg = $et->GetGroup($tagInfo); 1963 2575 foreach $group (@groups) { 1964 2576 next GATInfo unless grep /^$group$/i, @tg; … … 1975 2587 # Get list of all writable tags 1976 2588 # Inputs: 0) optional group name (or names separated by colons) 1977 # Returns: tag list (sorted alph betically)2589 # Returns: tag list (sorted alphabetically) 1978 2590 sub GetWritableTags(;$) 1979 2591 { … … 1983 2595 @groups = split ':', $group if $group; 1984 2596 1985 my $e xifTool= new Image::ExifTool;2597 my $et = new Image::ExifTool; 1986 2598 LoadAllTables(); 1987 2599 my @tableNames = keys %allTables; … … 1990 2602 my $tableName = pop @tableNames; 1991 2603 my $table = GetTagTable($tableName); 2604 # generate flattened tag names for structure fields if this is an XMP table 2605 if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') { 2606 Image::ExifTool::XMP::AddFlattenedTags($table); 2607 } 1992 2608 # attempt to load Write tables if autoloaded 1993 my @pa th= split(/::/,$tableName);1994 if (@pa th> 3) {1995 my $i = $#pa th- 1;1996 $pa th[$i] = "Write$path[$i]"; # add 'Write' before class name1997 my $module = join('::',@pa th[0..($#path-1)]);1998 eval "require $module"; # (fails silently if nothing loaded)2609 my @parts = split(/::/,$tableName); 2610 if (@parts > 3) { 2611 my $i = $#parts - 1; 2612 $parts[$i] = "Write$parts[$i]"; # add 'Write' before class name 2613 my $module = join('::',@parts[0..$i]); 2614 eval { require $module }; # (fails silently if nothing loaded) 1999 2615 } 2000 2616 my $tagID; … … 2006 2622 $tag or warn("no name for tag!\n"), next; 2007 2623 my $writable = $$tagInfo{Writable}; 2008 next unless $writable or ($ table->{WRITABLE} and2624 next unless $writable or ($$table{WRITABLE} and 2009 2625 not defined $writable and not $$tagInfo{SubDirectory}); 2010 2626 next if $$tagInfo{Hidden}; # ignore hidden tags 2011 2627 if (@groups) { 2012 my @tg = $e xifTool->GetGroup($tagInfo);2628 my @tg = $et->GetGroup($tagInfo); 2013 2629 foreach $group (@groups) { 2014 2630 next GWTInfo unless grep /^$group$/i, @tg; … … 2024 2640 #------------------------------------------------------------------------------ 2025 2641 # Get list of all group names 2026 # Inputs: 1) Group family number2642 # Inputs: 0) [optional] ExifTool ref, 1) Group family number 2027 2643 # Returns: List of group names (sorted alphabetically) 2028 sub GetAllGroups($ )2644 sub GetAllGroups($;$) 2029 2645 { 2030 2646 local $_; 2031 2647 my $family = shift || 0; 2648 my $self; 2649 ref $family and $self = $family, $family = shift || 0; 2032 2650 2033 2651 $family == 3 and return('Doc#', 'Main'); 2034 2652 $family == 4 and return('Copy#'); 2653 $family == 5 and return('[too many possibilities to list]'); 2654 $family == 6 and return(@Image::ExifTool::Exif::formatName[1..$#Image::ExifTool::Exif::formatName]); 2035 2655 2036 2656 LoadAllTables(); # first load all our tables … … 2046 2666 foreach $tag (TagTableKeys($table)) { 2047 2667 my @infoArray = GetTagInfoList($table, $tag); 2048 foreach $tagInfo (@infoArray) { 2049 next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family}); 2050 $allGroups{$grp} = 1; 2051 } 2052 } 2053 } 2668 if ($family == 7) { 2669 foreach $tagInfo (@infoArray) { 2670 my $id = $$tagInfo{TagID}; 2671 if (not defined $id) { 2672 $id = ''; # (just to be safe) 2673 } elsif ($id =~ /^\d+$/) { 2674 $id = sprintf('0x%x', $id) if $self and $$self{OPTIONS}{HexTagIDs}; 2675 } else { 2676 $id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge; 2677 } 2678 $allGroups{'ID-' . $id} = 1; 2679 } 2680 } else { 2681 foreach $tagInfo (@infoArray) { 2682 next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family}); 2683 $allGroups{$grp} = 1; 2684 } 2685 } 2686 } 2687 } 2688 delete $allGroups{'*'}; # (not a real group) 2054 2689 return sort keys %allGroups; 2055 2690 } … … 2062 2697 { 2063 2698 my $self = shift; 2064 return @{$ self->{WRITE_GROUPS}};2699 return @{$$self{WRITE_GROUPS}}; 2065 2700 } 2066 2701 … … 2070 2705 sub GetDeleteGroups() 2071 2706 { 2072 return sort @delGroups; 2707 return sort @delGroups, @delGroup2; 2708 } 2709 2710 #------------------------------------------------------------------------------ 2711 # Add user-defined tags at run time 2712 # Inputs: 0) destination table name, 1) tagID/tagInfo pairs for tags to add 2713 # Returns: number of tags added 2714 # Notes: will replace existing tags 2715 sub AddUserDefinedTags($%) 2716 { 2717 local $_; 2718 my ($tableName, %addTags) = @_; 2719 my $table = GetTagTable($tableName) or return 0; 2720 # add tags to writer lookup 2721 Image::ExifTool::TagLookup::AddTags(\%addTags, $tableName); 2722 my $tagID; 2723 my $num = 0; 2724 foreach $tagID (keys %addTags) { 2725 next if $specialTags{$tagID}; 2726 delete $$table{$tagID}; # delete old entry if it existed 2727 AddTagToTable($table, $tagID, $addTags{$tagID}, 1); 2728 ++$num; 2729 } 2730 return $num; 2073 2731 } 2074 2732 2075 2733 #============================================================================== 2076 2734 # Functions below this are not part of the public API 2735 2736 #------------------------------------------------------------------------------ 2737 # Maintain backward compatibility for old GetNewValues function name 2738 sub GetNewValues($$;$) 2739 { 2740 my ($self, $tag, $nvHashPt) = @_; 2741 return $self->GetNewValue($tag, $nvHashPt); 2742 } 2077 2743 2078 2744 #------------------------------------------------------------------------------ … … 2087 2753 # make sure the Perl UTF-8 flag is OFF for the value if perl 5.6 or greater 2088 2754 # (otherwise our byte manipulations get corrupted!!) 2089 if ($] >= 5.006 and (eval 'require Encode; Encode::is_utf8($$valPt)' or $@)) { 2755 if ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($$valPt) } or $@)) { 2756 local $SIG{'__WARN__'} = \&SetWarning; 2090 2757 # repack by hand if Encode isn't available 2091 $$valPt = $@ ? pack('C*',unpack( 'U0C*',$$valPt)) : Encode::encode('utf8',$$valPt);2758 $$valPt = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$valPt)) : Encode::encode('utf8',$$valPt); 2092 2759 } 2093 2760 # un-escape value if necessary … … 2097 2764 $$valPt = Image::ExifTool::XMP::UnescapeXML($$valPt); 2098 2765 } elsif ($$self{OPTIONS}{Escape} eq 'HTML') { 2099 $$valPt = Image::ExifTool::HTML::UnescapeHTML($$valPt );2766 $$valPt = Image::ExifTool::HTML::UnescapeHTML($$valPt, $$self{OPTIONS}{Charset}); 2100 2767 } 2101 2768 } … … 2106 2773 # Inputs: 0) ExifTool ref, 1) value, 2) tagInfo (or Struct item) ref, 2107 2774 # 3) tag name, 4) group 1 name, 5) conversion type (or undef), 2108 # 6) [optional] want group 2775 # 6) [optional] want group ("" for structure field) 2109 2776 # Returns: 0) converted value, 1) error string (or undef on success) 2110 # Notes: Uses ExifTool "ConvType" member to specify conversion type 2777 # Notes: 2778 # - uses ExifTool "ConvType" member when conversion type is undef 2779 # - conversion types other than 'ValueConv' and 'PrintConv' are treated as 'Raw' 2111 2780 sub ConvInv($$$$$;$$) 2112 2781 { 2113 2782 my ($self, $val, $tagInfo, $tag, $wgrp1, $convType, $wantGroup) = @_; 2114 2783 my ($err, $type); 2784 2785 $convType or $convType = $$self{ConvType} || 'PrintConv'; 2115 2786 2116 2787 Conv: for (;;) { … … 2118 2789 # split value into list if necessary 2119 2790 if ($$tagInfo{List}) { 2120 my $listSplit = $$tagInfo{AutoSplit} || $self->{OPTIONS}{ListSplit}; 2121 if (defined $listSplit) { 2791 my $listSplit = $$tagInfo{AutoSplit} || $$self{OPTIONS}{ListSplit}; 2792 if (defined $listSplit and not $$tagInfo{Struct} and 2793 ($wantGroup or not defined $wantGroup)) 2794 { 2122 2795 $listSplit = ',?\s+' if $listSplit eq '1' and $$tagInfo{AutoSplit}; 2123 my @splitVal = split /$listSplit/, $val ;2124 $val = \@splitVal if @splitVal > 1;2125 } 2126 } 2127 $type = $convType || $$self{ConvType} || 'PrintConv';2128 } elsif ($type ne 'ValueConv') {2796 my @splitVal = split /$listSplit/, $val, -1; 2797 $val = @splitVal > 1 ? \@splitVal : @splitVal ? $splitVal[0] : ''; 2798 } 2799 } 2800 $type = $convType; 2801 } elsif ($type eq 'PrintConv') { 2129 2802 $type = 'ValueConv'; 2130 } else { 2803 } else { 2804 # split raw value if necessary 2805 if ($$tagInfo{RawJoin} and $$tagInfo{List} and not ref $val) { 2806 my @splitVal = split ' ', $val; 2807 $val = \@splitVal if @splitVal > 1; 2808 } 2131 2809 # finally, do our value check 2132 2810 my ($err2, $v); 2133 if ($ tagInfo->{WriteCheck}) {2811 if ($$tagInfo{WriteCheck}) { 2134 2812 #### eval WriteCheck ($self, $tagInfo, $val) 2135 $err2 = eval $ tagInfo->{WriteCheck};2813 $err2 = eval $$tagInfo{WriteCheck}; 2136 2814 $@ and warn($@), $err2 = 'Error evaluating WriteCheck'; 2137 2815 } 2138 2816 unless ($err2) { 2139 my $table = $ tagInfo->{Table};2140 if ($table and $ table->{CHECK_PROC} and not $$tagInfo{RawConvInv}) {2141 my $checkProc = $ table->{CHECK_PROC};2817 my $table = $$tagInfo{Table}; 2818 if ($table and $$table{CHECK_PROC} and not $$tagInfo{RawConvInv}) { 2819 my $checkProc = $$table{CHECK_PROC}; 2142 2820 if (ref $val eq 'ARRAY') { 2143 2821 # loop through array values 2144 2822 foreach $v (@$val) { 2145 $err2 = &$checkProc($self, $tagInfo, \$v );2823 $err2 = &$checkProc($self, $tagInfo, \$v, $convType); 2146 2824 last if $err2; 2147 2825 } 2148 2826 } else { 2149 $err2 = &$checkProc($self, $tagInfo, \$val );2827 $err2 = &$checkProc($self, $tagInfo, \$val, $convType); 2150 2828 } 2151 2829 } 2152 2830 } 2153 2831 if (defined $err2) { 2154 # skip writing this tag if error string is empty 2155 $err2 or goto WriteAlso; 2156 $err = "$err2 for $wgrp1:$tag"; 2157 $self->VPrint(2, "$err\n"); 2158 undef $val; # value was invalid 2832 if ($err2) { 2833 $err = "$err2 for $wgrp1:$tag"; 2834 $self->VPrint(2, "$err\n"); 2835 undef $val; # value was invalid 2836 } else { 2837 $err = $err2; # empty error (quietly don't write tag) 2838 } 2159 2839 } 2160 2840 last; 2161 2841 } 2162 my $conv = $ tagInfo->{$type};2163 my $convInv = $ tagInfo->{"${type}Inv"};2842 my $conv = $$tagInfo{$type}; 2843 my $convInv = $$tagInfo{"${type}Inv"}; 2164 2844 # nothing to do at this level if no conversion defined 2165 2845 next unless defined $conv or defined $convInv; … … 2212 2892 } 2213 2893 } elsif ($conv) { 2214 if (ref $conv eq 'HASH' ) {2894 if (ref $conv eq 'HASH' and (not exists $$tagInfo{"${type}Inv"} or $convInvList)) { 2215 2895 my ($multi, $lc); 2216 2896 # insert alternate language print conversions if required 2217 2897 if ($$self{CUR_LANG} and $type eq 'PrintConv' and 2218 ref($lc = $ self->{CUR_LANG}{$tag}) eq 'HASH' and2898 ref($lc = $$self{CUR_LANG}{$tag}) eq 'HASH' and 2219 2899 ($lc = $$lc{PrintConv})) 2220 2900 { … … 2234 2914 $conv = \%newConv; 2235 2915 } 2916 undef $evalWarning; 2236 2917 if ($$conv{BITMASK}) { 2237 2918 my $lookupBits = $$conv{BITMASK}; 2238 my ($val2, $err2) = EncodeBits($val, $lookupBits); 2919 my ($wbits, $tbits) = @$tagInfo{'BitsPerWord','BitsTotal'}; 2920 my ($val2, $err2) = EncodeBits($val, $lookupBits, $wbits, $tbits); 2239 2921 if ($err2) { 2240 2922 # ok, try matching a straight value … … 2255 2937 ($val, $multi) = ReverseLookup($val, $conv); 2256 2938 } 2257 unless (defined $val) {2258 $err = "Can't convert $wgrp1:$tag (" .2259 ($multi ? 'matches more than one' : 'not in') . " $type)";2939 if (not defined $val) { 2940 my $prob = $evalWarning ? lcfirst CleanWarning() : ($multi ? 'matches more than one ' : 'not in ') . $type; 2941 $err = "Can't convert $wgrp1:$tag ($prob)"; 2260 2942 $self->VPrint(2, "$err\n"); 2261 2943 last Conv; 2944 } elsif ($evalWarning) { 2945 $self->VPrint(2, CleanWarning() . " for $wgrp1:$tag\n"); 2262 2946 } 2263 2947 } elsif (not $$tagInfo{WriteAlso}) { … … 2285 2969 2286 2970 #------------------------------------------------------------------------------ 2287 # convert tag names to values in a string (ie. "${EXIF:ISO}x $$" --> "100x $") 2971 # Convert tag names to values or variables in a string 2972 # (eg. '${EXIF:ISO}x $$' --> '100x $' without hash ref, or "$info{'EXIF:ISO'}x $" with) 2288 2973 # Inputs: 0) ExifTool object ref, 1) reference to list of found tags 2289 2974 # 2) string with embedded tag names, 3) Options: … … 2291 2976 # 'Error' - issue minor error on missing tag (and return undef) 2292 2977 # 'Warn' - issue minor warning on missing tag (and return undef) 2293 # Hash ref - hash for return of tag/value pairs 2978 # 'Silent' - just return undef on missing tag (no errors/warnings) 2979 # Hash ref - defined to interpolate as variables in string instead of values 2980 # --> receives tag/value pairs for interpolation of the variables 2981 # 4) document group name if extracting from a specific document 2982 # 5) hash ref to cache tag keys for subsequent calls in document loop 2294 2983 # Returns: string with embedded tag values (or '$info{TAGNAME}' entries with Hash ref option) 2295 2984 # Notes: 2296 2985 # - tag names are not case sensitive and may end with '#' for ValueConv value 2297 2986 # - uses MissingTagValue option if set 2298 sub InsertTagValues($$$;$) 2299 { 2300 my ($self, $foundTags, $line, $opt) = @_; 2987 # - '$GROUP:all' evaluates to 1 if any tag from GROUP exists, or 0 otherwise 2988 # - advanced feature allows Perl expressions inside braces (eg. '${model;tr/ //d}') 2989 # - an error/warning in an advanced expression ("${TAG;EXPR}") generates an error 2990 # if option set to 'Error', or a warning otherwise 2991 sub InsertTagValues($$$;$$$) 2992 { 2993 local $_; 2994 my ($self, $foundTags, $line, $opt, $docGrp, $cache) = @_; 2301 2995 my $rtnStr = ''; 2302 while ($line =~ /(.*?)\$(\{?)([-\w]+|\$|\/)(.*)/s) { 2303 my (@tags, $pre, $var, $bra, $val, $tg, @vals, $type); 2304 ($pre, $bra, $var, $line) = ($1, $2, $3, $4); 2996 my ($docNum, $tag); 2997 if ($docGrp) { 2998 $docNum = $docGrp =~ /(\d+)$/ ? $1 : 0; 2999 } else { 3000 undef $cache; # no cache if no document groups 3001 } 3002 while ($line =~ s/(.*?)\$(\{\s*)?([-\w]*\w|\$|\/)//s) { 3003 my ($pre, $bra, $var) = ($1, $2, $3); 3004 my (@tags, $val, $tg, @val, $type, $expr, $didExpr, $level, $asList); 2305 3005 # "$$" represents a "$" symbol, and "$/" is a newline 2306 3006 if ($var eq '$' or $var eq '/') { 2307 $var = "\n" if $var eq '/'; 3007 $line =~ s/^\s*\}// if $bra; 3008 if ($var eq '/') { 3009 $var = "\n"; 3010 } elsif ($line =~ /^self\b/ and not $rtnStr =~ /\$$/) { 3011 $var = '$$'; # ("$$self{var}" in string) 3012 } 2308 3013 $rtnStr .= "$pre$var"; 2309 $line =~ s/^\}// if $bra;2310 3014 next; 2311 3015 } 2312 3016 # allow multiple group names 2313 while ($line =~ /^:([-\w] +)(.*)/s) {3017 while ($line =~ /^:([-\w]*\w)(.*)/s) { 2314 3018 my $group = $var; 2315 3019 ($var, $line) = ($1, $2); … … 2318 3022 # allow trailing '#' to indicate ValueConv value 2319 3023 $type = 'ValueConv' if $line =~ s/^#//; 3024 # special advanced formatting '@' feature to evaluate list values separately 3025 if ($bra and $line =~ s/^\@(#)?//) { 3026 $asList = 1; 3027 $type = 'ValueConv' if $1; 3028 } 2320 3029 # remove trailing bracket if there was a leading one 2321 $line =~ s/^\}// if $bra; 3030 # and extract Perl expression from inside brackets if it exists 3031 if ($bra and $line !~ s/^\s*\}// and $line =~ s/^\s*;\s*(.*?)\s*\}//s) { 3032 my $part = $1; 3033 $expr = ''; 3034 for ($level=0; ; --$level) { 3035 # increase nesting level for each opening brace 3036 ++$level while $part =~ /\{/g; 3037 $expr .= $part; 3038 last unless $level and $line =~ s/^(.*?)\s*\}//s; # get next part 3039 $part = $1; 3040 $expr .= '}'; # this brace was part of the expression 3041 } 3042 # use default Windows filename filter if expression is empty 3043 $expr = 'tr(/\\\\?*:|"<>\\0)()d' unless length $expr; 3044 } 2322 3045 push @tags, $var; 2323 3046 ExpandShortcuts(\@tags); 2324 3047 @tags or $rtnStr .= $pre, next; 3048 # save advanced formatting expression to allow access by user-defined ValueConv 3049 $$self{FMT_EXPR} = $expr; 2325 3050 2326 3051 for (;;) { 2327 my $tag = shift @tags; 2328 if ($tag =~ /(.*):(.+)/) { 3052 # temporarily reset ListJoin option if evaluating list values separately 3053 my $oldListJoin; 3054 $oldListJoin = $self->Options(ListJoin => undef) if $asList; 3055 $tag = shift @tags; 3056 my $lcTag = lc $tag; 3057 if ($cache and $lcTag !~ /(^|:)all$/) { 3058 # remove group from tag name (but not lower-case version) 2329 3059 my $group; 2330 ($group, $tag) = ($1, $2); 2331 # find the specified tag 2332 my @matches = grep /^$tag(\s|$)/i, @$foundTags; 2333 @matches = $self->GroupMatches($group, \@matches); 2334 foreach $tg (@matches) { 2335 if (defined $val and $tg =~ / \((\d+)\)$/) { 2336 # take the most recently extracted tag 2337 my $tagNum = $1; 2338 next if $tag !~ / \((\d+)\)$/ or $1 > $tagNum; 2339 } 2340 $val = $self->GetValue($tg, $type); 2341 $tag = $tg; 2342 last unless $tag =~ / /; # all done if we got our best match 3060 $tag =~ s/^(.*):// and $group = $1; 3061 # cache tag keys to speed processing for a large number of sub-documents 3062 # (similar to code in BuildCompositeTags(), but this is case-insensitive) 3063 my $cacheTag = $$cache{$lcTag}; 3064 unless ($cacheTag) { 3065 $cacheTag = $$cache{$lcTag} = [ ]; 3066 # find all matching keys, organize into groups, and store in cache 3067 my $ex = $$self{TAG_EXTRA}; 3068 my @matches = grep /^$tag(\s|$)/i, @$foundTags; 3069 @matches = $self->GroupMatches($group, \@matches) if defined $group; 3070 foreach (@matches) { 3071 my $doc = $$ex{$_} ? $$ex{$_}{G3} || 0 : 0; 3072 if (defined $$cacheTag[$doc]) { 3073 next unless $$cacheTag[$doc] =~ / \((\d+)\)$/; 3074 my $cur = $1; 3075 # keep the most recently extracted tag 3076 next if / \((\d+)\)$/ and $1 < $cur; 3077 } 3078 $$cacheTag[$doc] = $_; 3079 } 3080 } 3081 my $doc = $lcTag =~ /\b(main|doc(\d+)):/ ? ($2 || 0) : $docNum; 3082 if ($$cacheTag[$doc]) { 3083 $tag = $$cacheTag[$doc]; 3084 $val = $self->GetValue($tag, $type); 2343 3085 } 2344 3086 } else { 2345 # get the tag value 2346 $val = $self->GetValue($tag, $type); 2347 unless (defined $val) { 2348 # check for tag name with different case 2349 ($tg) = grep /^$tag$/i, @$foundTags; 2350 if (defined $tg) { 2351 $val = $self->GetValue($tg, $type); 2352 $tag = $tg; 2353 } 2354 } 2355 } 3087 # add document number to tag if specified and it doesn't already exist 3088 if ($docGrp and $lcTag !~ /\b(main|doc\d+):/) { 3089 $tag = $docGrp . ':' . $tag; 3090 $lcTag = lc $tag; 3091 } 3092 if ($lcTag eq 'all') { 3093 $val = 1; # always some tag available 3094 } elsif (defined $$self{OPTIONS}{UserParam}{$lcTag}) { 3095 $val = $$self{OPTIONS}{UserParam}{$lcTag}; 3096 } elsif ($tag =~ /(.*):(.+)/) { 3097 my $group; 3098 ($group, $tag) = ($1, $2); 3099 if (lc $tag eq 'all') { 3100 # see if any tag from the specified group exists 3101 my $match = $self->GroupMatches($group, $foundTags); 3102 $val = $match ? 1 : 0; 3103 } else { 3104 # find the specified tag 3105 my @matches = grep /^$tag(\s|$)/i, @$foundTags; 3106 @matches = $self->GroupMatches($group, \@matches); 3107 foreach $tg (@matches) { 3108 if (defined $val and $tg =~ / \((\d+)\)$/) { 3109 # take the most recently extracted tag 3110 my $tagNum = $1; 3111 next if $tag !~ / \((\d+)\)$/ or $1 > $tagNum; 3112 } 3113 $val = $self->GetValue($tg, $type); 3114 $tag = $tg; 3115 last unless $tag =~ / /; # all done if we got our best match 3116 } 3117 } 3118 } elsif ($tag eq 'self') { 3119 $val = $self; # ("$self{var}" or "$self->{var}" in string) 3120 } else { 3121 # get the tag value 3122 $val = $self->GetValue($tag, $type); 3123 unless (defined $val) { 3124 # check for tag name with different case 3125 ($tg) = grep /^$tag$/i, @$foundTags; 3126 if (defined $tg) { 3127 $val = $self->GetValue($tg, $type); 3128 $tag = $tg; 3129 } 3130 } 3131 } 3132 } 3133 $self->Options(ListJoin => $oldListJoin) if $asList; 2356 3134 if (ref $val eq 'ARRAY') { 2357 $val = join($self->{OPTIONS}{ListSep}, @$val); 3135 push @val, @$val; 3136 undef $val; 3137 last unless @tags; 2358 3138 } elsif (ref $val eq 'SCALAR') { 2359 if ($ self->{OPTIONS}{Binary} or $$val =~ /^Binary data/) {3139 if ($$self{OPTIONS}{Binary} or $$val =~ /^Binary data/) { 2360 3140 $val = $$val; 2361 3141 } else { 2362 3142 $val = 'Binary data ' . length($$val) . ' bytes'; 2363 3143 } 3144 } elsif (ref $val eq 'HASH') { 3145 require 'Image/ExifTool/XMPStruct.pl'; 3146 $val = Image::ExifTool::XMP::SerializeStruct($val); 2364 3147 } elsif (not defined $val) { 2365 last unless @tags; 2366 next; 3148 $val = $$self{OPTIONS}{MissingTagValue} if $asList; 2367 3149 } 2368 3150 last unless @tags; 2369 push @val s,$val;3151 push @val, $val if defined $val; 2370 3152 undef $val; 2371 3153 } 2372 if (@vals) { 2373 push @vals, $val if defined $val; 2374 $val = join '', @vals; 3154 if (@val) { 3155 push @val, $val if defined $val; 3156 $val = join $$self{OPTIONS}{ListSep}, @val; 3157 } else { 3158 push @val, $val if defined $val; # (so the eval has access to @val if required) 3159 } 3160 # evaluate advanced formatting expression if given (eg. "${TAG;EXPR}") 3161 if (defined $expr and defined $val) { 3162 local $SIG{'__WARN__'} = \&SetWarning; 3163 undef $evalWarning; 3164 $advFmtSelf = $self; 3165 if ($asList) { 3166 foreach (@val) { 3167 #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) 3168 eval $expr; 3169 $@ and $evalWarning = $@; 3170 } 3171 # join back together if any values are still defined 3172 @val = grep defined, @val; 3173 $val = @val ? join $$self{OPTIONS}{ListSep}, @val : undef; 3174 } else { 3175 $_ = $val; 3176 #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) 3177 eval $expr; 3178 $@ and $evalWarning = $@; 3179 $val = ref $_ eq 'ARRAY' ? join($$self{OPTIONS}{ListSep}, @$_): $_; 3180 } 3181 if ($evalWarning) { 3182 my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; 3183 my $str = CleanWarning() . " for '$g3${var}'"; 3184 if ($opt) { 3185 if ($opt eq 'Error') { 3186 $self->Error($str); 3187 } elsif ($opt ne 'Silent') { 3188 $self->Warn($str); 3189 } 3190 } 3191 } 3192 undef $advFmtSelf; 3193 $didExpr = 1; # set flag indicating an expression was evaluated 2375 3194 } 2376 3195 unless (defined $val or ref $opt) { 2377 $val = $ self->{OPTIONS}{MissingTagValue};3196 $val = $$self{OPTIONS}{MissingTagValue}; 2378 3197 unless (defined $val) { 3198 my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; 3199 my $msg = $didExpr ? "Advanced formatting expression returned undef for '$g3${var}'" : 3200 "Tag '$g3${var}' not defined"; 2379 3201 no strict 'refs'; 2380 return undef if $opt and &$opt($self, "Tag '$var' not defined", 1);3202 $opt and ($opt eq 'Silent' or &$opt($self, $msg, 2)) and return $$self{FMT_EXPR} = undef; 2381 3203 $val = ''; 2382 3204 } … … 2384 3206 if (ref $opt eq 'HASH') { 2385 3207 $var .= '#' if $type; 2386 $rtnStr .= "$pre\$info{'$var'}"; 3208 if (defined $expr) { 3209 # generate unique variable name for this modified tag value 3210 my $i = 1; 3211 ++$i while exists $$opt{"$var.expr$i"}; 3212 $var .= '.expr' . $i; 3213 } 3214 $rtnStr .= "$pre\$info{'${var}'}"; 2387 3215 $$opt{$var} = $val; 2388 3216 } else { … … 2390 3218 } 2391 3219 } 3220 $$self{FMT_EXPR} = undef; 2392 3221 return $rtnStr . $line; 3222 } 3223 3224 #------------------------------------------------------------------------------ 3225 # Reformat date/time value in $_ based on specified format string 3226 # Inputs: 0) date/time format string 3227 sub DateFmt($) 3228 { 3229 my $et = bless { OPTIONS => { DateFormat => shift, StrictDate => 1 } }; 3230 my $shift; 3231 if ($advFmtSelf and defined($shift = $$advFmtSelf{OPTIONS}{GlobalTimeShift})) { 3232 $$et{OPTIONS}{GlobalTimeShift} = $shift; 3233 $$et{GLOBAL_TIME_OFFSET} = $$advFmtSelf{GLOBAL_TIME_OFFSET}; 3234 } 3235 $_ = $et->ConvertDateTime($_); 3236 defined $_ or warn "Error converting date/time\n"; 3237 $$advFmtSelf{GLOBAL_TIME_OFFSET} = $$et{GLOBAL_TIME_OFFSET} if $shift; 3238 } 3239 3240 #------------------------------------------------------------------------------ 3241 # Utility routine to remove duplicate items from default input string 3242 # Inputs: 0) true to set $_ to undef if not changed 3243 # Notes: - for use only in advanced formatting expressions 3244 sub NoDups 3245 { 3246 my %seen; 3247 my $sep = $advFmtSelf ? $$advFmtSelf{OPTIONS}{ListSep} : ', '; 3248 my $new = join $sep, grep { !$seen{$_}++ } split /\Q$sep\E/, $_; 3249 $_ = ($_[0] and $new eq $_) ? undef : $new; 2393 3250 } 2394 3251 … … 2408 3265 my $tagInfo; 2409 3266 foreach $tagInfo (@tagInfo) { 2410 return 1 if $$tagInfo{Writable} or $tagInfo->{Table}{WRITABLE}; 3267 return $$tagInfo{Writable} ? 1 : 0 if defined $$tagInfo{Writable}; 3268 return 1 if $$tagInfo{Table}{WRITABLE}; 2411 3269 # must call WRITE_PROC to autoload writer because this may set the writable tag 2412 my $writeProc = $tagInfo->{Table}{WRITE_PROC}; 2413 next unless $writeProc; 2414 &$writeProc(); # dummy call to autoload writer 2415 return 1 if $$tagInfo{Writable}; 3270 my $writeProc = $$tagInfo{Table}{WRITE_PROC}; 3271 if ($writeProc) { 3272 no strict 'refs'; 3273 &$writeProc(); # dummy call to autoload writer 3274 return 1 if $$tagInfo{Writable}; 3275 } 2416 3276 } 2417 3277 return 0; … … 2419 3279 2420 3280 #------------------------------------------------------------------------------ 3281 # Check to see if these are the same file 3282 # Inputs: 0) ExifTool ref, 1) first file name, 2) second file name 3283 # Returns: true if file names reference the same file 3284 sub IsSameFile($$$) 3285 { 3286 my ($self, $file, $file2) = @_; 3287 return 0 unless lc $file eq lc $file2; # (only looking for differences in case) 3288 my ($isSame, $interrupted); 3289 my $tmp1 = "${file}_ExifTool_tmp_$$"; 3290 my $tmp2 = "${file2}_ExifTool_tmp_$$"; 3291 { 3292 local *TMP1; 3293 local $SIG{INT} = sub { $interrupted = 1 }; 3294 if ($self->Open(\*TMP1, $tmp1, '>')) { 3295 close TMP1; 3296 $isSame = 1 if $self->Exists($tmp2); 3297 $self->Unlink($tmp1); 3298 } 3299 } 3300 if ($interrupted and $SIG{INT}) { 3301 no strict 'refs'; 3302 &{$SIG{INT}}(); 3303 } 3304 return $isSame; 3305 } 3306 3307 #------------------------------------------------------------------------------ 3308 # Is this a raw file type? 3309 # Inputs: 0) ExifTool ref 3310 # Returns: true if FileType is a type of RAW image 3311 sub IsRawType($) 3312 { 3313 my $self = shift; 3314 return $rawType{$$self{FileType}}; 3315 } 3316 3317 #------------------------------------------------------------------------------ 2421 3318 # Create directory for specified file 2422 # Inputs: 0) complete file name including path3319 # Inputs: 0) ExifTool ref, 1) complete file name including path 2423 3320 # Returns: 1 = directory created, 0 = nothing done, -1 = error 2424 sub CreateDirectory($) 3321 my $k32CreateDir; 3322 sub CreateDirectory($$) 2425 3323 { 2426 3324 local $_; 2427 my $file = shift;3325 my ($self, $file) = @_; 2428 3326 my $rtnVal = 0; 3327 my $enc = $$self{OPTIONS}{CharsetFileName}; 2429 3328 my $dir; 2430 3329 ($dir = $file) =~ s/[^\/]*$//; # remove filename from path specification 2431 if ($dir and not -d $dir) { 3330 # recode as UTF-8 if necessary 3331 if ($dir and not $self->IsDirectory($dir)) { 2432 3332 my @parts = split /\//, $dir; 2433 3333 $dir = ''; 2434 3334 foreach (@parts) { 2435 3335 $dir .= $_; 2436 if (length $dir and not -d $dir) {3336 if (length $dir and not $self->IsDirectory($dir)) { 2437 3337 # create directory since it doesn't exist 2438 mkdir($dir, 0777) or return -1; 3338 my $d2 = $dir; # (must make a copy in case EncodeFileName recodes it) 3339 if ($self->EncodeFileName($d2)) { 3340 # handle Windows Unicode directory names 3341 unless (eval { require Win32::API }) { 3342 $self->Warn('Install Win32::API to create directories with Unicode names'); 3343 return -1; 3344 } 3345 unless ($k32CreateDir) { 3346 return -1 if defined $k32CreateDir; 3347 $k32CreateDir = new Win32::API('KERNEL32', 'CreateDirectoryW', 'PP', 'I'); 3348 unless ($k32CreateDir) { 3349 $self->Warn('Error calling Win32::API::CreateDirectoryW'); 3350 $k32CreateDir = 0; 3351 return -1; 3352 } 3353 } 3354 $k32CreateDir->Call($d2, 0) or return -1; 3355 } else { 3356 mkdir($d2, 0777) or return -1; 3357 } 2439 3358 $rtnVal = 1; 2440 3359 } … … 2447 3366 #------------------------------------------------------------------------------ 2448 3367 # Copy file attributes from one file to another 2449 # Inputs: 0) source file name, 1) destination file name3368 # Inputs: 0) ExifTool ref, 1) source file name, 2) destination file name 2450 3369 # Notes: eventually add support for extended attributes? 2451 sub CopyFileAttrs($$ )2452 { 2453 my ($s rc, $dst) = @_;3370 sub CopyFileAttrs($$$) 3371 { 3372 my ($self, $src, $dst) = @_; 2454 3373 my ($mode, $uid, $gid) = (stat($src))[2, 4, 5]; 2455 eval { chmod($mode & 07777, $dst) } if defined $mode; 2456 eval { chown($uid, $gid, $dst) } if defined $uid and defined $gid; 2457 } 2458 2459 #------------------------------------------------------------------------------ 2460 # Get new file name 2461 # Inputs: 0) existing name, 1) new name 3374 # copy file attributes unless we already set them 3375 if (defined $mode and not defined $self->GetNewValue('FilePermissions')) { 3376 eval { chmod($mode & 07777, $dst) }; 3377 } 3378 my $newUid = $self->GetNewValue('FileUserID'); 3379 my $newGid = $self->GetNewValue('FileGroupID'); 3380 if (defined $uid and defined $gid and (not defined $newUid or not defined $newGid)) { 3381 defined $newGid and $gid = $newGid; 3382 defined $newUid and $uid = $newUid; 3383 eval { chown($uid, $gid, $dst) }; 3384 } 3385 } 3386 3387 #------------------------------------------------------------------------------ 3388 # Get new file path name 3389 # Inputs: 0) existing name (may contain directory), 3390 # 1) new file name, new directory, or new path (dir+name) 2462 3391 # Returns: new file path name 2463 3392 sub GetNewFileName($$) … … 2478 3407 # Inputs: 0) hash reference (keys are tag keys), 1) tag name 2479 3408 # Returns: next available tag key 2480 sub Next TagKey($$)3409 sub NextFreeTagKey($$) 2481 3410 { 2482 3411 my ($info, $tag) = @_; … … 2501 3430 $val = $1; # was unknown 2502 3431 if ($val =~ /^0x([\da-fA-F]+)$/) { 3432 # disable "Hexadecimal number > 0xffffffff non-portable" warning 3433 local $SIG{'__WARN__'} = sub { }; 2503 3434 $val = hex($val); # convert hex value 2504 3435 } 2505 3436 } else { 2506 my $qval = quotemeta $val; 3437 my $qval = $val; 3438 $qval =~ s/\s+$//; # remove trailing whitespace 3439 $qval = quotemeta $qval; 2507 3440 my @patterns = ( 2508 3441 "^$qval\$", # exact match … … 2533 3466 unless ($found) { 2534 3467 # call OTHER conversion routine if available 2535 $val = $$conv{OTHER} ? &{$$conv{OTHER}}($val,1,$conv) : undef; 3468 if ($$conv{OTHER}) { 3469 local $SIG{'__WARN__'} = \&SetWarning; 3470 undef $evalWarning; 3471 $val = &{$$conv{OTHER}}($val,1,$conv); 3472 } else { 3473 $val = undef; 3474 } 2536 3475 $multi = 1 if $matches > 1; 2537 3476 } … … 2543 3482 #------------------------------------------------------------------------------ 2544 3483 # Return true if we are deleting or overwriting the specified tag 2545 # Inputs: 0) new value hash reference2546 # 1) optional tag value (before RawConv) if deleting specific values3484 # Inputs: 0) ExifTool object ref, 1) new value hash reference 3485 # 2) optional tag value (before RawConv) if deleting specific values 2547 3486 # Returns: >0 - tag should be overwritten 2548 3487 # =0 - the tag should be preserved 2549 3488 # <0 - not sure, we need the value to know 2550 sub IsOverwriting($;$) 2551 { 2552 my ($nvHash, $val) = @_; 3489 # Notes: $$nvHash{Value} is updated with the new value when shifting a value 3490 sub IsOverwriting($$;$) 3491 { 3492 my ($self, $nvHash, $val) = @_; 2553 3493 return 0 unless $nvHash; 2554 3494 # overwrite regardless if no DelValues specified … … 2566 3506 undef $evalWarning; 2567 3507 if (ref $conv eq 'CODE') { 2568 $val = &$conv($val, $ $nvHash{Self});3508 $val = &$conv($val, $self); 2569 3509 } else { 2570 my $self = $$nvHash{Self};3510 my ($priority, @grps); 2571 3511 my $tag = $$tagInfo{Name}; 2572 #### eval RawConv ($self, $val, $tag, $tagInfo )3512 #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) 2573 3513 $val = eval $conv; 2574 3514 $@ and $evalWarning = $@; … … 2576 3516 return -1 unless defined $val; 2577 3517 } 2578 # apply time shift if necessary 3518 # do not overwrite if only creating 3519 return 0 if $$nvHash{CreateOnly}; 3520 # apply time/number shift if necessary 2579 3521 if (defined $shift) { 3522 my $shiftType = $$tagInfo{Shift}; 3523 unless ($shiftType and $shiftType eq 'Time') { 3524 unless (IsFloat($val)) { 3525 # do the ValueConv to try to get a number 3526 my $conv = $$tagInfo{ValueConv}; 3527 if (defined $conv) { 3528 local $SIG{'__WARN__'} = \&SetWarning; 3529 undef $evalWarning; 3530 if (ref $conv eq 'CODE') { 3531 $val = &$conv($val, $self); 3532 } elsif (not ref $conv) { 3533 #### eval ValueConv ($val, $self) 3534 $val = eval $conv; 3535 $@ and $evalWarning = $@; 3536 } 3537 if ($evalWarning) { 3538 $self->Warn("ValueConv $$tagInfo{Name}: " . CleanWarning()); 3539 return 0; 3540 } 3541 } 3542 unless (defined $val and IsFloat($val)) { 3543 $self->Warn("Can't shift $$tagInfo{Name} (not a number)"); 3544 return 0; 3545 } 3546 } 3547 $shiftType = 'Number'; # allow any number to be shifted 3548 } 2580 3549 require 'Image/ExifTool/Shift.pl'; 2581 my $err = ApplyShift($$tagInfo{Shift}, $shift, $val, $nvHash);3550 my $err = $self->ApplyShift($shiftType, $shift, $val, $nvHash); 2582 3551 if ($err) { 2583 $ nvHash->{Self}->Warn("$err when shifting $$tagInfo{Name}");3552 $self->Warn("$err when shifting $$tagInfo{Name}"); 2584 3553 return 0; 2585 3554 } 3555 # ensure that the shifted value is valid and reformat if necessary 3556 my $checkVal = $self->GetNewValue($nvHash); 3557 return 0 unless defined $checkVal; 2586 3558 # don't bother overwriting if value is the same 2587 3559 return 0 if $val eq $$nvHash{Value}[0]; … … 2597 3569 2598 3570 #------------------------------------------------------------------------------ 2599 # Return true if we are creating the specified tag even if it didn't exist before2600 # Inputs: 0) new value hash reference2601 # Returns: true if we should add the tag2602 sub IsCreating($)2603 {2604 return $_[0]{IsCreating};2605 }2606 2607 #------------------------------------------------------------------------------2608 3571 # Get write group for specified tag 2609 3572 # Inputs: 0) new value hash reference … … 2621 3584 { 2622 3585 my ($self, $tagInfo, $writeGroup) = @_; 2623 return $writeGroup unless $writeGroup =~ /^(MakerNotes|XMP|Composite )$/;3586 return $writeGroup unless $writeGroup =~ /^(MakerNotes|XMP|Composite|QuickTime)$/; 2624 3587 return $self->GetGroup($tagInfo, 1); 2625 3588 } … … 2628 3591 # Get new value hash for specified tagInfo/writeGroup 2629 3592 # Inputs: 0) ExifTool object reference, 1) reference to tag info hash 2630 # 2) Write group name, 3) Options: 'delete' or 'create' 3593 # 2) Write group name, 3) Options: 'delete' or 'create' new value hash 3594 # 4) optional ProtectSaved value, 5) true if we are deleting a value 2631 3595 # Returns: new value hash reference for specified write group 2632 3596 # (or first new value hash in linked list if write group not specified) 2633 sub GetNewValueHash($$;$$) 3597 # Notes: May return undef when 'create' is used with ProtectSaved 3598 sub GetNewValueHash($$;$$$$) 2634 3599 { 2635 3600 my ($self, $tagInfo, $writeGroup, $opts) = @_; 2636 my $nvHash = $self->{NEW_VALUE}{$tagInfo}; 3601 return undef unless $tagInfo; 3602 my $nvHash = $$self{NEW_VALUE}{$tagInfo}; 2637 3603 2638 3604 my %opts; # quick lookup for options … … 2642 3608 if ($writeGroup) { 2643 3609 # find the new value in the list with the specified write group 2644 while ($nvHash and $nvHash->{WriteGroup} ne $writeGroup) { 2645 $nvHash = $nvHash->{Next}; 3610 while ($nvHash and $$nvHash{WriteGroup} ne $writeGroup) { 3611 # QuickTime and All are special cases because all group1 tags may be updated at once 3612 last if $$nvHash{WriteGroup} =~ /^(QuickTime|All)$/; 3613 # replace existing entry if WriteGroup is 'All' (avoids confusion of forum10349) 3614 last if $$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All'; 3615 $nvHash = $$nvHash{Next}; 2646 3616 } 2647 3617 } 2648 3618 # remove this entry if deleting, or if creating a new entry and 2649 3619 # this entry is marked with "Save" flag 2650 if (defined $nvHash and ($opts{'delete'} or 2651 ($opts{'create'} and $nvHash->{Save}))) 2652 { 2653 if ($opts{'delete'}) { 3620 if (defined $nvHash and ($opts{'delete'} or ($opts{'create'} and $$nvHash{Save}))) { 3621 my $protect = (defined $_[4] and defined $$nvHash{Save} and $$nvHash{Save} > $_[4]); 3622 # this is a bit tricky: we want to add to a protected nvHash only if we 3623 # are adding a conditional delete ($_[5] true or DelValue with no Shift) 3624 # or accumulating List items (NoReplace true) 3625 if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or 3626 ($$nvHash{DelValue} and not defined $$nvHash{Shift})))) 3627 { 3628 return undef; # honour ProtectSaved value by not writing this tag 3629 } elsif ($opts{'delete'}) { 2654 3630 $self->RemoveNewValueHash($nvHash, $tagInfo); 2655 3631 undef $nvHash; … … 2657 3633 # save a copy of this new value hash 2658 3634 my %copy = %$nvHash; 3635 # make copy of Value and DelValue lists 2659 3636 my $key; 2660 # make copy of Value and DelValue lists2661 3637 foreach $key (keys %copy) { 2662 3638 next unless ref $copy{$key} eq 'ARRAY'; 2663 3639 $copy{$key} = [ @{$copy{$key}} ]; 2664 3640 } 2665 my $saveHash = $ self->{SAVE_NEW_VALUE};3641 my $saveHash = $$self{SAVE_NEW_VALUE}; 2666 3642 # add to linked list of saved new value hashes 2667 $copy{Next} = $saveHash->{$tagInfo}; 2668 $saveHash->{$tagInfo} = \%copy; 2669 delete $nvHash->{Save}; # don't save it again 3643 $copy{Next} = $$saveHash{$tagInfo}; 3644 $$saveHash{$tagInfo} = \%copy; 3645 delete $$nvHash{Save}; # don't save it again 3646 $$nvHash{AddBefore} = scalar @{$$nvHash{Value}} if $protect and $$nvHash{Value}; 2670 3647 } 2671 3648 } … … 2675 3652 TagInfo => $tagInfo, 2676 3653 WriteGroup => $writeGroup, 2677 Self => $self,3654 IsNVH => 1, # set flag so we can recognize a new value hash 2678 3655 }; 2679 3656 # add entry to our NEW_VALUE hash 2680 if ($ self->{NEW_VALUE}{$tagInfo}) {3657 if ($$self{NEW_VALUE}{$tagInfo}) { 2681 3658 # add to end of linked list 2682 my $lastHash = LastInList($ self->{NEW_VALUE}{$tagInfo});2683 $ lastHash->{Next} = $nvHash;3659 my $lastHash = LastInList($$self{NEW_VALUE}{$tagInfo}); 3660 $$lastHash{Next} = $nvHash; 2684 3661 } else { 2685 $ self->{NEW_VALUE}{$tagInfo} = $nvHash;3662 $$self{NEW_VALUE}{$tagInfo} = $nvHash; 2686 3663 } 2687 3664 } … … 2711 3688 $table = GetTagTable(shift @tableNames); 2712 3689 # call write proc if it exists in case it adds tags to the table 2713 my $writeProc = $table->{WRITE_PROC}; 2714 $writeProc and &$writeProc(); 3690 my $writeProc = $$table{WRITE_PROC}; 3691 if ($writeProc) { 3692 no strict 'refs'; 3693 &$writeProc(); 3694 } 2715 3695 # recursively scan through tables in subdirectories 2716 3696 foreach (TagTableKeys($table)) { … … 2736 3716 { 2737 3717 my ($self, $nvHash, $tagInfo) = @_; 2738 my $firstHash = $ self->{NEW_VALUE}{$tagInfo};3718 my $firstHash = $$self{NEW_VALUE}{$tagInfo}; 2739 3719 if ($nvHash eq $firstHash) { 2740 3720 # remove first entry from linked list 2741 if ($ nvHash->{Next}) {2742 $ self->{NEW_VALUE}{$tagInfo} = $nvHash->{Next};3721 if ($$nvHash{Next}) { 3722 $$self{NEW_VALUE}{$tagInfo} = $$nvHash{Next}; 2743 3723 } else { 2744 delete $ self->{NEW_VALUE}{$tagInfo};3724 delete $$self{NEW_VALUE}{$tagInfo}; 2745 3725 } 2746 3726 } else { 2747 3727 # find the list element pointing to this hash 2748 $firstHash = $ firstHash->{Next} while $firstHash->{Next} ne $nvHash;3728 $firstHash = $$firstHash{Next} while $$firstHash{Next} ne $nvHash; 2749 3729 # remove from linked list 2750 $ firstHash->{Next} = $nvHash->{Next};3730 $$firstHash{Next} = $$nvHash{Next}; 2751 3731 } 2752 3732 # save the existing entry if necessary 2753 if ($ nvHash->{Save}) {2754 my $saveHash = $ self->{SAVE_NEW_VALUE};3733 if ($$nvHash{Save}) { 3734 my $saveHash = $$self{SAVE_NEW_VALUE}; 2755 3735 # add to linked list of saved new value hashes 2756 $ nvHash->{Next} = $saveHash->{$tagInfo};2757 $ saveHash->{$tagInfo} = $nvHash;3736 $$nvHash{Next} = $$saveHash{$tagInfo}; 3737 $$saveHash{$tagInfo} = $nvHash; 2758 3738 } 2759 3739 } … … 2766 3746 my ($self, $group) = @_; 2767 3747 2768 return unless $ self->{NEW_VALUE};3748 return unless $$self{NEW_VALUE}; 2769 3749 2770 3750 # make list of all groups we must remove … … 2773 3753 2774 3754 my ($out, @keys, $hashKey); 2775 $out = $ self->{OPTIONS}{TextOut} if $self->{OPTIONS}{Verbose} > 1;3755 $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose} > 1; 2776 3756 2777 3757 # loop though all new values, and remove any in this group 2778 @keys = keys %{$ self->{NEW_VALUE}};3758 @keys = keys %{$$self{NEW_VALUE}}; 2779 3759 foreach $hashKey (@keys) { 2780 my $nvHash = $ self->{NEW_VALUE}{$hashKey};3760 my $nvHash = $$self{NEW_VALUE}{$hashKey}; 2781 3761 # loop through each entry in linked list 2782 3762 for (;;) { 2783 my $nextHash = $ nvHash->{Next};2784 my $tagInfo = $ nvHash->{TagInfo};3763 my $nextHash = $$nvHash{Next}; 3764 my $tagInfo = $$nvHash{TagInfo}; 2785 3765 my ($grp0,$grp1) = $self->GetGroup($tagInfo); 2786 my $wgrp = $ nvHash->{WriteGroup};3766 my $wgrp = $$nvHash{WriteGroup}; 2787 3767 # use group1 if write group is not specific 2788 3768 $wgrp = $grp1 if $wgrp eq $grp0; … … 2805 3785 my ($self, $tagTablePtr) = @_; 2806 3786 my @tagInfoList; 2807 my $nv = $ self->{NEW_VALUE};3787 my $nv = $$self{NEW_VALUE}; 2808 3788 if ($nv) { 2809 3789 my $hashKey; 2810 3790 foreach $hashKey (keys %$nv) { 2811 my $tagInfo = $ nv->{$hashKey}{TagInfo};2812 next if $tagTablePtr and $tagTablePtr ne $ tagInfo->{Table};3791 my $tagInfo = $$nv{$hashKey}{TagInfo}; 3792 next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; 2813 3793 push @tagInfoList, $tagInfo; 2814 3794 } … … 2821 3801 # Inputs: 0) ExifTool object reference, 1-N) tag table pointers 2822 3802 # Returns: hash reference 3803 # Notes: returns only one tagInfo ref for each conditional list 2823 3804 sub GetNewTagInfoHash($@) 2824 3805 { 2825 3806 my $self = shift; 2826 3807 my (%tagInfoHash, $hashKey); 2827 my $nv = $ self->{NEW_VALUE};3808 my $nv = $$self{NEW_VALUE}; 2828 3809 while ($nv) { 2829 3810 my $tagTablePtr = shift || last; 2830 3811 foreach $hashKey (keys %$nv) { 2831 my $tagInfo = $ nv->{$hashKey}{TagInfo};2832 next if $tagTablePtr and $tagTablePtr ne $ tagInfo->{Table};3812 my $tagInfo = $$nv{$hashKey}{TagInfo}; 3813 next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; 2833 3814 $tagInfoHash{$$tagInfo{TagID}} = $tagInfo; 2834 3815 } … … 2846 3827 { 2847 3828 my ($self, $tagTablePtr, $parent) = @_; 2848 $parent or $parent = $ tagTablePtr->{GROUPS}{0};3829 $parent or $parent = $$tagTablePtr{GROUPS}{0}; 2849 3830 my $tagID; 2850 3831 my %addDirHash; 2851 3832 my %editDirHash; 2852 my $addDirs = $ self->{ADD_DIRS};2853 my $editDirs = $ self->{EDIT_DIRS};3833 my $addDirs = $$self{ADD_DIRS}; 3834 my $editDirs = $$self{EDIT_DIRS}; 2854 3835 foreach $tagID (TagTableKeys($tagTablePtr)) { 2855 3836 my @infoArray = GetTagInfoList($tagTablePtr,$tagID); … … 2860 3841 # (take directory name from SubDirectory DirName if it exists, 2861 3842 # otherwise Group0 name of SubDirectory TagTable or tag Group1 name) 2862 my $dirName = $ tagInfo->{SubDirectory}{DirName};3843 my $dirName = $$tagInfo{SubDirectory}{DirName}; 2863 3844 unless ($dirName) { 2864 3845 # use tag name for directory name and save for next time 2865 3846 $dirName = $$tagInfo{Name}; 2866 $ tagInfo->{SubDirectory}{DirName} = $dirName;3847 $$tagInfo{SubDirectory}{DirName} = $dirName; 2867 3848 } 2868 3849 # save this directory information if we are writing it … … 2879 3860 #------------------------------------------------------------------------------ 2880 3861 # Get localized version of tagInfo hash (used by MIE, XMP, PNG and QuickTime) 2881 # Inputs: 0) tagInfo hash ref, 1) locale code ( ie. "en_CA" for MIE)3862 # Inputs: 0) tagInfo hash ref, 1) locale code (eg. "en_CA" for MIE) 2882 3863 # Returns: new tagInfo hash ref, or undef if invalid 2883 3864 # - sets LangCode member in new tagInfo … … 2897 3878 " ($langCode)", 2898 3879 LangCode => $langCode, 3880 SrcTagInfo => $tagInfo, # save reference to original tagInfo 2899 3881 }; 2900 3882 AddTagToTable($table, $tagID, $langInfo); … … 2905 3887 #------------------------------------------------------------------------------ 2906 3888 # initialize ADD_DIRS and EDIT_DIRS hashes for all directories that need 2907 # needto be created or will have tags changed in them3889 # to be created or will have tags changed in them 2908 3890 # Inputs: 0) ExifTool object reference, 1) file type string (or map hash ref) 2909 # 2) preferred family 0 group name for creating tags 2910 # Notes: The ADD_DIRS and EDIT_DIRS keys are the directory names, and the values 2911 # are the names of the parent directories (undefined for a top-level directory) 2912 sub InitWriteDirs($$;$) 2913 { 2914 my ($self, $fileType, $preferredGroup) = @_; 2915 my $editDirs = $self->{EDIT_DIRS} = { }; 2916 my $addDirs = $self->{ADD_DIRS} = { }; 3891 # 2) preferred family 0 group for creating tags, 3) alternate preferred group 3892 # Notes: 3893 # - the ADD_DIRS and EDIT_DIRS keys are the directory names, and the values 3894 # are the names of the parent directories (undefined for a top-level directory) 3895 # - also initializes FORCE_WRITE lookup 3896 sub InitWriteDirs($$;$$) 3897 { 3898 my ($self, $fileType, $preferredGroup, $altGroup) = @_; 3899 my $editDirs = $$self{EDIT_DIRS} = { }; 3900 my $addDirs = $$self{ADD_DIRS} = { }; 2917 3901 my $fileDirs = $dirMap{$fileType}; 2918 3902 unless ($fileDirs) { … … 2930 3914 for ($nvHash=$self->GetNewValueHash($tagInfo); $nvHash; $nvHash=$$nvHash{Next}) { 2931 3915 # are we creating this tag? (otherwise just deleting or editing it) 2932 my $isCreating = $nvHash->{IsCreating}; 2933 if ($isCreating) { 2934 # if another group is taking priority, only create 2935 # directory if specifically adding tags to this group 2936 # or if this tag isn't being added to the priority group 2937 $isCreating = 0 if $preferredGroup and 2938 $preferredGroup ne $self->GetGroup($tagInfo, 0) and 2939 $nvHash->{CreateGroups}{$preferredGroup}; 2940 } else { 2941 # creating this directory if any tag is preferred and has a value 2942 $isCreating = 1 if $preferredGroup and $$nvHash{Value} and 2943 $preferredGroup eq $self->GetGroup($tagInfo, 0); 3916 my $isCreating = $$nvHash{IsCreating}; 3917 if ($preferredGroup) { 3918 my $g0 = $self->GetGroup($tagInfo, 0); 3919 if ($isCreating) { 3920 # if another group is taking priority, only create 3921 # directory if specifically adding tags to this group 3922 # or if this tag isn't being added to the priority group 3923 $isCreating = 0 if $preferredGroup ne $g0 and 3924 $$nvHash{CreateGroups}{$preferredGroup} and 3925 (not $altGroup or $altGroup ne $g0); 3926 } else { 3927 # create this directory if any tag is preferred and has a value 3928 # (unless group creation is disabled via the WriteMode option) 3929 $isCreating = 1 if $$nvHash{Value} and $preferredGroup eq $g0 and 3930 not $$nvHash{EditOnly} and $$self{OPTIONS}{WriteMode} =~ /g/; 3931 } 2944 3932 } 2945 3933 # tag belongs to directory specified by WriteGroup, or by 2946 3934 # the Group0 name if WriteGroup not defined 2947 my $dirName = $ nvHash->{WriteGroup};3935 my $dirName = $$nvHash{WriteGroup}; 2948 3936 # remove MIE copy number(s) if they exist 2949 3937 if ($dirName =~ /^MIE\d*(-[a-z]+)?\d*$/i) { … … 2951 3939 } 2952 3940 my @dirNames; 3941 # allow a group name of '*' to force writing EXIF/IPTC/XMP/PNG (ForceWrite tag) 3942 if ($dirName eq '*' and $$nvHash{Value}) { 3943 my $val = $$nvHash{Value}[0]; 3944 if ($val) { 3945 foreach (qw(EXIF IPTC XMP PNG FixBase)) { 3946 next unless $val =~ /\b($_|All)\b/i; 3947 push @dirNames, $_; 3948 push @dirNames, 'EXIF' if $_ eq 'FixBase'; 3949 $$self{FORCE_WRITE}{$_} = 1; 3950 } 3951 } 3952 $dirName = shift @dirNames; 3953 } elsif ($dirName eq 'QuickTime') { 3954 # write to specific QuickTime group 3955 $dirName = $self->GetGroup($tagInfo, 1); 3956 } 2953 3957 while ($dirName) { 2954 3958 my $parent = $$fileDirs{$dirName}; … … 2963 3967 } 2964 3968 } 2965 if (%{$ self->{DEL_GROUP}}) {3969 if (%{$$self{DEL_GROUP}}) { 2966 3970 # add delete groups to list of edited groups 2967 foreach (keys %{$ self->{DEL_GROUP}}) {3971 foreach (keys %{$$self{DEL_GROUP}}) { 2968 3972 next if /^-/; # ignore excluded groups 2969 3973 my $dirName = $_; … … 2990 3994 } 2991 3995 2992 if ($ self->{OPTIONS}{Verbose}) {2993 my $out = $ self->{OPTIONS}{TextOut};3996 if ($$self{OPTIONS}{Verbose}) { 3997 my $out = $$self{OPTIONS}{TextOut}; 2994 3998 print $out " Editing tags in: "; 2995 3999 foreach (sort keys %$editDirs) { print $out "$_ "; } 2996 4000 print $out "\n"; 2997 return unless $ self->{OPTIONS}{Verbose} > 1;4001 return unless $$self{OPTIONS}{Verbose} > 1; 2998 4002 print $out " Creating tags in: "; 2999 4003 foreach (sort keys %$addDirs) { print $out "$_ "; } … … 3006 4010 # Inputs: 0) ExifTool object reference, 1) source directory information reference 3007 4011 # 2) tag table reference, 3) optional reference to writing procedure 3008 # Returns: New directory data or undefined on error 4012 # Returns: New directory data or undefined on error (or empty string to delete directory) 3009 4013 sub WriteDirectory($$$;$) 3010 4014 { 3011 4015 my ($self, $dirInfo, $tagTablePtr, $writeProc) = @_; 3012 my ($out, $nvHash );4016 my ($out, $nvHash, $delFlag); 3013 4017 3014 4018 $tagTablePtr or return undef; 3015 $out = $ self->{OPTIONS}{TextOut} if $self->{OPTIONS}{Verbose};4019 $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose}; 3016 4020 # set directory name from default group0 name if not done already 3017 4021 my $dirName = $$dirInfo{DirName}; 3018 4022 my $dataPt = $$dirInfo{DataPt}; 3019 my $grp0 = $ tagTablePtr->{GROUPS}{0};4023 my $grp0 = $$tagTablePtr{GROUPS}{0}; 3020 4024 $dirName or $dirName = $$dirInfo{DirName} = $grp0; 3021 if (%{$ self->{DEL_GROUP}}) {3022 my $delGroup = $ self->{DEL_GROUP};4025 if (%{$$self{DEL_GROUP}}) { 4026 my $delGroup = $$self{DEL_GROUP}; 3023 4027 # delete entire directory if specified 3024 4028 my $grp1 = $dirName; 3025 my $delFlag = ($$delGroup{$grp0} or $$delGroup{$grp1}); 4029 $delFlag = ($$delGroup{$grp0} or $$delGroup{$grp1}) unless $permanentDir{$grp0}; 4030 # (never delete an entire QuickTime group) 3026 4031 if ($delFlag) { 3027 unless ($blockExifTypes{$$self{FILE_TYPE}}) { 4032 if (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and 4033 $self->IsRawType() and 4034 # allow non-permanent MakerNote directories to be deleted (ie. NikonCapture) 4035 (not $$dirInfo{TagInfo} or not defined $$dirInfo{TagInfo}{Permanent} or 4036 $$dirInfo{TagInfo}{Permanent})) 4037 { 4038 $self->WarnOnce("Can't delete $1 from $$self{FileType}",1); 4039 undef $grp1; 4040 } elsif (not $blockExifTypes{$$self{FILE_TYPE}}) { 3028 4041 # restrict delete logic to prevent entire tiff image from being killed 3029 4042 # (don't allow IFD0 to be deleted, and delete only ExifIFD if EXIF specified) … … 3034 4047 # allow anything to be deleted from PostScript files 3035 4048 } elsif ($grp1 eq 'IFD0') { 3036 my $type = $ self->{TIFF_TYPE} || $self->{FILE_TYPE};4049 my $type = $$self{TIFF_TYPE} || $$self{FILE_TYPE}; 3037 4050 $$delGroup{IFD0} and $self->Warn("Can't delete IFD0 from $type",1); 3038 4051 undef $grp1; … … 3043 4056 if ($grp1) { 3044 4057 if ($dataPt or $$dirInfo{RAF}) { 3045 ++$ self->{CHANGED};4058 ++$$self{CHANGED}; 3046 4059 $out and print $out " Deleting $grp1\n"; 4060 $self->Warn('ICC_Profile deleted. Image colors may be affected') if $grp1 eq 'ICC_Profile'; 3047 4061 # can no longer validate TIFF_END if deleting an entire IFD 3048 delete $ self->{TIFF_END} if $dirName =~ /IFD/;4062 delete $$self{TIFF_END} if $dirName =~ /IFD/; 3049 4063 } 3050 4064 # don't add back into the wrong location … … 3056 4070 # also check grandparent because some routines create 2 levels in 1 3057 4071 my $right2 = $$self{ADD_DIRS}{$right} || ''; 3058 if (not $$dirInfo{Parent} or $$dirInfo{Parent} eq $right or 3059 $$dirInfo{Parent} eq $right2) 3060 { 3061 # create new empty directory 4072 my $parent = $$dirInfo{Parent}; 4073 if (not $parent or $parent eq $right or $parent eq $right2) { 4074 # prevent duplicate directories from being recreated at the same path 4075 my $path = join '-', @{$$self{PATH}}, $dirName; 4076 $$self{Recreated} or $$self{Recreated} = { }; 4077 if ($$self{Recreated}{$path}) { 4078 my $p = $parent ? " in $parent" : ''; 4079 $self->Warn("Not recreating duplicate $grp1$p",1); 4080 return ''; 4081 } 4082 $$self{Recreated}{$path} = 1; 4083 # empty the directory 3062 4084 my $data = ''; 3063 my %dirInfo = ( 3064 DirName => $$dirInfo{DirName}, 3065 Parent => $$dirInfo{Parent}, 3066 DirStart => 0, 3067 DirLen => 0, 3068 DataPt => \$data, 3069 NewDataPos => $$dirInfo{NewDataPos}, 3070 Fixup => $$dirInfo{Fixup}, 3071 ); 3072 $dirInfo = \%dirInfo; 4085 $$dirInfo{DataPt} = \$data; 4086 $$dirInfo{DataLen} = 0; 4087 $$dirInfo{DirStart} = 0; 4088 $$dirInfo{DirLen} = 0; 4089 delete $$dirInfo{RAF}; 4090 delete $$dirInfo{Base}; 4091 delete $$dirInfo{DataPos}; 3073 4092 } else { 3074 $self->Warn("Not recreating $grp1 in $ $dirInfo{Parent}(should be in $right)",1);4093 $self->Warn("Not recreating $grp1 in $parent (should be in $right)",1); 3075 4094 return ''; 3076 4095 } … … 3083 4102 # use default proc from tag table if no proc specified 3084 4103 $writeProc or $writeProc = $$tagTablePtr{WRITE_PROC} or return undef; 4104 4105 # are we rewriting a pre-existing directory? 4106 my $isRewriting = ($$dirInfo{DirLen} or (defined $dataPt and length $$dataPt) or $$dirInfo{RAF}); 3085 4107 3086 4108 # copy or delete new directory as a block if specified … … 3088 4110 $blockName = 'EXIF' if $blockName eq 'IFD0'; 3089 4111 my $tagInfo = $Image::ExifTool::Extra{$blockName} || $$dirInfo{TagInfo}; 3090 while ($tagInfo and ($nvHash = $self->{NEW_VALUE}{$tagInfo}) and IsOverwriting($nvHash)) { 4112 while ($tagInfo and ($nvHash = $$self{NEW_VALUE}{$tagInfo}) and 4113 $self->IsOverwriting($nvHash) and not ($$nvHash{CreateOnly} and $isRewriting)) 4114 { 3091 4115 # protect against writing EXIF to wrong file types, etc 3092 4116 if ($blockName eq 'EXIF') { … … 3095 4119 last; 3096 4120 } 3097 unless ($writeProc eq \&Image::ExifTool::WriteTIFF) { 3098 # this could happen if we called WriteDirectory for an EXIF directory 3099 # without going through WriteTIFF as the WriteProc, which would be bad 3100 # because the EXIF block could end up with two TIFF headers 3101 $self->Warn('Internal error writing EXIF -- please report'); 3102 last; 3103 } 3104 } 4121 # this can happen if we call WriteDirectory for an EXIF directory without going 4122 # through WriteTIFF as the WriteProc (which happens if conditionally replacing 4123 # the EXIF block and the condition fails), but we never want to do a block write 4124 # in this case because the EXIF block would end up with two TIFF headers 4125 last unless $writeProc eq \&Image::ExifTool::WriteTIFF; 4126 } 4127 last unless $self->IsOverwriting($nvHash, $dataPt ? $$dataPt : ''); 3105 4128 my $verb = 'Writing'; 3106 my $newVal = GetNewValues($nvHash);4129 my $newVal = $self->GetNewValue($nvHash); 3107 4130 unless (defined $newVal and length $newVal) { 4131 return '' unless $dataPt or $$dirInfo{RAF}; # nothing to do if block never existed 4132 # don't allow MakerNotes to be removed from RAW files 4133 if ($blockName eq 'MakerNotes' and $rawType{$$self{FileType}}) { 4134 $self->Warn("Can't delete MakerNotes from $$self{VALUE}{FileType}",1); 4135 return undef; 4136 } 3108 4137 $verb = 'Deleting'; 3109 4138 $newVal = ''; … … 3111 4140 $$dirInfo{BlockWrite} = 1; # set flag indicating we did a block write 3112 4141 $out and print $out " $verb $blockName as a block\n"; 3113 ++$ self->{CHANGED};4142 ++$$self{CHANGED}; 3114 4143 return $newVal; 3115 4144 } 3116 4145 # guard against writing the same directory twice 3117 if (defined $dataPt and defined $$dirInfo{DirStart} and defined $$dirInfo{DataPos}) { 4146 if (defined $dataPt and defined $$dirInfo{DirStart} and defined $$dirInfo{DataPos} and 4147 not $$dirInfo{NoRefTest}) 4148 { 3118 4149 my $addr = $$dirInfo{DirStart} + $$dirInfo{DataPos} + ($$dirInfo{Base}||0) + $$self{BASE}; 3119 4150 # (Phase One P25 IIQ files have ICC_Profile duplicated in IFD0 and IFD1) 3120 if ($self->{PROCESSED}{$addr} and ($dirName ne 'ICC_Profile' or $$self{TIFF_TYPE} ne 'IIQ')) { 3121 if ($self->Error("$dirName pointer references previous $self->{PROCESSED}{$addr} directory", 1)) { 4151 if ($$self{PROCESSED}{$addr} and ($dirName ne 'ICC_Profile' or $$self{TIFF_TYPE} ne 'IIQ')) { 4152 if (defined $$dirInfo{DirLen} and not $$dirInfo{DirLen} and $dirName ne $$self{PROCESSED}{$addr}) { 4153 # it is hypothetically possible to have 2 different directories 4154 # with the same address if one has a length of zero 4155 } elsif ($self->Error("$dirName pointer references previous $$self{PROCESSED}{$addr} directory", 2)) { 3122 4156 return undef; 3123 4157 } else { 3124 4158 $self->Warn("Deleting duplicate $dirName directory"); 3125 4159 $out and print $out " Deleting $dirName\n"; 3126 return ''; # delete the duplicate directory 3127 } 3128 } 3129 $self->{PROCESSED}{$addr} = $dirName; 3130 } 3131 my $oldDir = $self->{DIR_NAME}; 3132 my $isRewriting = ($$dirInfo{DirLen} or (defined $dataPt and length $$dataPt) or $$dirInfo{RAF}); 4160 # delete the duplicate directory (don't recreate it when writing new 4161 # tags to prevent propagating a duplicate IFD in cases like when the 4162 # same ExifIFD exists in both IFD0 and IFD1) 4163 return ''; 4164 } 4165 } else { 4166 $$self{PROCESSED}{$addr} = $dirName; 4167 } 4168 } 4169 my $oldDir = $$self{DIR_NAME}; 4170 my @save = @$self{'Compression','SubfileType'}; 3133 4171 my $name; 3134 4172 if ($out) { 3135 4173 $name = ($dirName eq 'MakerNotes' and $$dirInfo{TagInfo}) ? 3136 $ dirInfo->{TagInfo}{Name} : $dirName;4174 $$dirInfo{TagInfo}{Name} : $dirName; 3137 4175 if (not defined $oldDir or $oldDir ne $name) { 3138 4176 my $verb = $isRewriting ? 'Rewriting' : 'Creating'; … … 3141 4179 } 3142 4180 my $saveOrder = GetByteOrder(); 3143 my $oldChanged = $ self->{CHANGED};3144 $ self->{DIR_NAME} = $dirName;3145 push @{$ self->{PATH}}, $$dirInfo{DirName};4181 my $oldChanged = $$self{CHANGED}; 4182 $$self{DIR_NAME} = $dirName; 4183 push @{$$self{PATH}}, $dirName; 3146 4184 $$dirInfo{IsWriting} = 1; 3147 my $newData = &$writeProc($self, $dirInfo, $tagTablePtr); 3148 pop @{$self->{PATH}}; 4185 my $newData; 4186 { 4187 no strict 'refs'; 4188 $newData = &$writeProc($self, $dirInfo, $tagTablePtr); 4189 } 4190 pop @{$$self{PATH}}; 3149 4191 # nothing changed if error occurred or nothing was created 3150 $self->{CHANGED} = $oldChanged unless defined $newData and (length($newData) or $isRewriting); 3151 $self->{DIR_NAME} = $oldDir; 4192 $$self{CHANGED} = $oldChanged unless defined $newData and (length($newData) or $isRewriting); 4193 $$self{DIR_NAME} = $oldDir; 4194 @$self{'Compression','SubfileType'} = @save; 3152 4195 SetByteOrder($saveOrder); 3153 4196 print $out " Deleting $name\n" if $out and defined $newData and not length $newData; … … 3173 4216 my $lo = Get32u($dataPt, $pos + 4 - $pt); 3174 4217 return $hi * 4294967296 + $lo; 4218 } 4219 sub GetFixed64s($$) 4220 { 4221 my ($dataPt, $pos) = @_; 4222 my $val = Get64s($dataPt, $pos) / 4294967296; 4223 # remove insignificant digits 4224 return int($val * 1e10 + ($val>0 ? 0.5 : -0.5)) / 1e10; 3175 4225 } 3176 4226 # Decode extended 80-bit float used by Apple SANE and Intel 8087 … … 3191 4241 # Inputs: 0) data reference, 1) length or undef, 2-N) Options: 3192 4242 # Options: Start => offset to start of data (default=0) 3193 # Addr => address to print for data start (default=DataPos+Start) 3194 # DataPos => address of start of data 4243 # Addr => address to print for data start (default=DataPos+Base+Start) 4244 # DataPos => position of data within block (relative to Base) 4245 # Base => base offset for pointers from start of file 3195 4246 # Width => width of printout (bytes, default=16) 3196 4247 # Prefix => prefix to print at start of line (default='') … … 3213 4264 $len = $opts{Len} if defined $opts{Len}; 3214 4265 3215 $addr = $start + ($opts{DataPos} || 0) unless defined $addr;4266 $addr = $start + ($opts{DataPos} || 0) + ($opts{Base} || 0) unless defined $addr; 3216 4267 $len = $datLen unless defined $len; 3217 4268 if ($maxLen and $len > $maxLen) { … … 3238 4289 print $out "[$dat]\n"; 3239 4290 } 3240 $more and print f$out "$prefix [snip $more bytes]\n";4291 $more and print $out "$prefix [snip $more bytes]\n"; 3241 4292 } 3242 4293 … … 3250 4301 # DataPt => reference to value data block 3251 4302 # DataPos => location of data block in file 4303 # Base => base added to all offsets 3252 4304 # Size => length of value data within block 3253 4305 # Format => value format string … … 3259 4311 { 3260 4312 my ($self, $tagID, $tagInfo, %parms) = @_; 3261 my $verbose = $ self->{OPTIONS}{Verbose};3262 my $out = $ self->{OPTIONS}{TextOut};4313 my $verbose = $$self{OPTIONS}{Verbose}; 4314 my $out = $$self{OPTIONS}{TextOut}; 3263 4315 my ($tag, $line, $hexID); 3264 4316 … … 3285 4337 my $size = $parms{Size}; 3286 4338 $size = length $$dataPt unless defined $size or not $dataPt; 3287 my $indent = $ self->{INDENT};4339 my $indent = $$self{INDENT}; 3288 4340 3289 4341 # Level 1: print tag/value information … … 3300 4352 } else { 3301 4353 my $maxLen = 90 - length($line); 3302 if (defined $parms{Value}) { 3303 $line .= ' = ' . $self->Printable($parms{Value}, $maxLen); 4354 my $val = $parms{Value}; 4355 if (defined $val) { 4356 $val = '[' . join(',',@$val) . ']' if ref $val eq 'ARRAY'; 4357 $line .= ' = ' . $self->Printable($val, $maxLen); 3304 4358 } elsif ($dataPt) { 3305 4359 my $start = $parms{Start} || 0; … … 3318 4372 } else { 3319 4373 $tagID =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/ge; 3320 $line .= "'$ tagID'";4374 $line .= "'${tagID}'"; 3321 4375 } 3322 4376 $line .= $parms{Extra} if defined $parms{Extra}; … … 3339 4393 3340 4394 # Level 3: do hex dump of value 3341 if ($verbose > 2 and $parms{DataPt} ) {4395 if ($verbose > 2 and $parms{DataPt} and (not $tagInfo or not $$tagInfo{ReadFromRAF})) { 3342 4396 $parms{Out} = $out; 3343 4397 $parms{Prefix} = $indent; … … 3359 4413 my $trailer = $$dirInfo{DirName} || 'Unknown'; 3360 4414 my $pos = $$dirInfo{DataPos}; 3361 my $verbose = $ self->{OPTIONS}{Verbose};3362 my $htmlDump = $ self->{HTML_DUMP};4415 my $verbose = $$self{OPTIONS}{Verbose}; 4416 my $htmlDump = $$self{HTML_DUMP}; 3363 4417 my ($buff, $buf2); 3364 4418 my $size = $$dirInfo{DirLen}; … … 3380 4434 last; 3381 4435 } 3382 my $out = $ self->{OPTIONS}{TextOut};4436 my $out = $$self{OPTIONS}{TextOut}; 3383 4437 printf $out "$trailer trailer (%d bytes at offset 0x%.4x):\n", $size, $pos; 3384 4438 last unless $verbose > 2; … … 3420 4474 my $endPos = $pos + $$dirInfo{DirLen}; 3421 4475 # account for preview/MPF image trailer 3422 my $prePos = $ self->{VALUE}{PreviewImageStart} || $$self{PreviewImageStart};3423 my $preLen = $ self->{VALUE}{PreviewImageLength} || $$self{PreviewImageLength};4476 my $prePos = $$self{VALUE}{PreviewImageStart} || $$self{PreviewImageStart}; 4477 my $preLen = $$self{VALUE}{PreviewImageLength} || $$self{PreviewImageLength}; 3424 4478 my $tag = 'PreviewImage'; 3425 4479 my $mpImageNum = 0; … … 3431 4485 # look for MPF images (in the the proper order) 3432 4486 ++$mpImageNum; 3433 $prePos = $ self->{VALUE}{"MPImageStart ($mpImageNum)"};4487 $prePos = $$self{VALUE}{"MPImageStart ($mpImageNum)"}; 3434 4488 if (defined $prePos) { 3435 $preLen = $ self->{VALUE}{"MPImageLength ($mpImageNum)"};4489 $preLen = $$self{VALUE}{"MPImageLength ($mpImageNum)"}; 3436 4490 } else { 3437 $prePos = $ self->{VALUE}{'MPImageStart'};3438 $preLen = $ self->{VALUE}{'MPImageLength'};4491 $prePos = $$self{VALUE}{'MPImageStart'}; 4492 $preLen = $$self{VALUE}{'MPImageLength'}; 3439 4493 $lastOne = 1; 3440 4494 } … … 3454 4508 last unless $preLen; 3455 4509 # dump image if verbose (it is htmlDump'd by ExtractImage) 3456 if ($ self->{OPTIONS}{Verbose}) {4510 if ($$self{OPTIONS}{Verbose}) { 3457 4511 $$dirInfo{DirName} = $tag; 3458 4512 $$dirInfo{DataPos} = $prePos; … … 3471 4525 { 3472 4526 my $element = shift; 3473 while ($ element->{Next}) {3474 $element = $ element->{Next};4527 while ($$element{Next}) { 4528 $element = $$element{Next}; 3475 4529 } 3476 4530 return $element; … … 3478 4532 3479 4533 #------------------------------------------------------------------------------ 3480 # Print verbose directory information3481 # Inputs: 0) ExifTool object reference, 1) directory name or dirInfo ref3482 # 2) number of entries in directory (or 0 if unknown)3483 # 3) optional size of directory in bytes3484 sub VerboseDir($$;$$)3485 {3486 my ($self, $name, $entries, $size) = @_;3487 return unless $self->{OPTIONS}{Verbose};3488 if (ref $name eq 'HASH') {3489 $size = $$name{DirLen} unless $size;3490 $name = $$name{Name} || $$name{DirName};3491 }3492 my $indent = substr($self->{INDENT}, 0, -2);3493 my $out = $self->{OPTIONS}{TextOut};3494 my $str = $entries ? " with $entries entries" : '';3495 $str .= ", $size bytes" if $size;3496 print $out "$indent+ [$name directory$str]\n";3497 }3498 3499 #------------------------------------------------------------------------------3500 4534 # Print verbose value while writing 3501 # Inputs: 0) ExifTool object ref, 1) heading " ie. '+ IPTC:Keywords',4535 # Inputs: 0) ExifTool object ref, 1) heading "eg. '+ IPTC:Keywords', 3502 4536 # 2) value, 3) [optional] extra text after value 3503 4537 sub VerboseValue($$$;$) … … 3505 4539 return unless $_[0]{OPTIONS}{Verbose} > 1; 3506 4540 my ($self, $str, $val, $xtra) = @_; 3507 my $out = $ self->{OPTIONS}{TextOut};4541 my $out = $$self{OPTIONS}{TextOut}; 3508 4542 $xtra or $xtra = ''; 3509 4543 my $maxLen = 81 - length($str) - length($xtra); 3510 4544 $val = $self->Printable($val, $maxLen); 3511 print $out " $str = '$ val'$xtra\n";4545 print $out " $str = '${val}'$xtra\n"; 3512 4546 } 3513 4547 … … 3591 4625 3592 4626 #------------------------------------------------------------------------------ 4627 # Generate a new, random GUID 4628 # Inputs: <none> 4629 # Returns: GUID string 4630 my $guidCount; 4631 sub NewGUID() 4632 { 4633 my @tm = localtime time; 4634 $guidCount = 0 unless defined $guidCount and ++$guidCount < 0x100; 4635 return sprintf('%.4d%.2d%.2d%.2d%.2d%.2d%.2X%.4X%.4X%.4X%.4X', 4636 $tm[5]+1900, $tm[4]+1, $tm[3], $tm[2], $tm[1], $tm[0], $guidCount, 4637 $$ & 0xffff, rand(0x10000), rand(0x10000), rand(0x10000)); 4638 } 4639 4640 #------------------------------------------------------------------------------ 4641 # Make TIFF header for raw data 4642 # Inputs: 0) width, 1) height, 2) num colour components, 3) bits, 4) resolution 4643 # 5) color-map data for palette-color image (8 or 16 bit) 4644 # Returns: TIFF header 4645 # Notes: Multi-byte data must be little-endian 4646 sub MakeTiffHeader($$$$;$$) 4647 { 4648 my ($w, $h, $cols, $bits, $res, $cmap) = @_; 4649 $res or $res = 72; 4650 my $saveOrder = GetByteOrder(); 4651 SetByteOrder('II'); 4652 if (not $cmap) { 4653 $cmap = ''; 4654 } elsif (length $cmap == 3 * 2**$bits) { 4655 # convert to short 4656 $cmap = pack 'v*', map { $_ | ($_<<8) } unpack 'C*', $cmap; 4657 } elsif (length $cmap != 6 * 2**$bits) { 4658 $cmap = ''; 4659 } 4660 my $cmo = $cmap ? 12 : 0; # offset due to ColorMap IFD entry 4661 my $hdr = 4662 "\x49\x49\x2a\0\x08\0\0\0\x0e\0" . # 0x00 14 menu entries: 4663 "\xfe\x00\x04\0\x01\0\0\0\x00\0\0\0" . # 0x0a SubfileType = 0 4664 "\x00\x01\x04\0\x01\0\0\0" . Set32u($w) . # 0x16 ImageWidth 4665 "\x01\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x22 ImageHeight 4666 "\x02\x01\x03\0" . Set32u($cols) . # 0x2e BitsPerSample 4667 Set32u($cols == 1 ? $bits : 0xb6 + $cmo) . 4668 "\x03\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x3a Compression = 1 4669 "\x06\x01\x03\0\x01\0\0\0" . # 0x46 PhotometricInterpretation 4670 Set32u($cmap ? 3 : $cols == 1 ? 1 : 2) . 4671 "\x11\x01\x04\0\x01\0\0\0" . # 0x52 StripOffsets 4672 Set32u(0xcc + $cmo + length($cmap)) . 4673 "\x15\x01\x03\0\x01\0\0\0" . Set32u($cols) . # 0x5e SamplesPerPixel 4674 "\x16\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x6a RowsPerStrip 4675 "\x17\x01\x04\0\x01\0\0\0" . # 0x76 StripByteCounts 4676 Set32u($w * $h * $cols * int(($bits+7)/8)) . 4677 "\x1a\x01\x05\0\x01\0\0\0" . Set32u(0xbc + $cmo) . # 0x82 XResolution 4678 "\x1b\x01\x05\0\x01\0\0\0" . Set32u(0xc4 + $cmo) . # 0x8e YResolution 4679 "\x1c\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x9a PlanarConfiguration = 1 4680 "\x28\x01\x03\0\x01\0\0\0\x02\0\0\0" . # 0xa6 ResolutionUnit = 2 4681 ($cmap ? # 0xb2 ColorMap [optional] 4682 "\x40\x01\x03\0" . Set32u(3 * 2**$bits) . "\xd8\0\0\0" : '') . 4683 "\0\0\0\0" . # 0xb2+$cmo (no IFD1) 4684 (Set16u($bits) x 3) . # 0xb6+$cmo BitsPerSample value 4685 Set32u($res) . "\x01\0\0\0" . # 0xbc+$cmo XResolution = 72 4686 Set32u($res) . "\x01\0\0\0" . # 0xc4+$cmo YResolution = 72 4687 $cmap; # 0xcc or 0xd8 (cmap and data go here) 4688 SetByteOrder($saveOrder); 4689 return $hdr; 4690 } 4691 4692 #------------------------------------------------------------------------------ 4693 # Return current time in EXIF format 4694 # Inputs: 0) [optional] ExifTool ref, 1) flag to include timezone (0 to disable, 4695 # undef or 1 to include) 4696 # Returns: time string 4697 # - a consistent value is returned for each processed file 4698 sub TimeNow(;$$) 4699 { 4700 my ($self, $tzFlag) = @_; 4701 my $timeNow; 4702 ref $self or $tzFlag = $self, $self = { }; 4703 if ($$self{Now}) { 4704 $timeNow = $$self{Now}[0]; 4705 } else { 4706 my $time = time(); 4707 my @tm = localtime $time; 4708 my $tz = TimeZoneString(\@tm, $time); 4709 $timeNow = sprintf("%4d:%.2d:%.2d %.2d:%.2d:%.2d", 4710 $tm[5]+1900, $tm[4]+1, $tm[3], 4711 $tm[2], $tm[1], $tm[0]); 4712 $$self{Now} = [ $timeNow, $tz ]; 4713 } 4714 $timeNow .= $$self{Now}[1] if $tzFlag or not defined $tzFlag; 4715 return $timeNow; 4716 } 4717 4718 #------------------------------------------------------------------------------ 3593 4719 # Inverse date/time print conversion (reformat to YYYY:mm:dd HH:MM:SS[.ss][+-HH:MM|Z]) 3594 4720 # Inputs: 0) ExifTool object ref, 1) Date/Time string, 2) timezone flag: … … 3599 4725 # Returns: formatted date/time string (or undef and issues warning on error) 3600 4726 # Notes: currently accepts different separators, but doesn't use DateFormat yet 4727 my $strptimeLib; # strptime library name if available 3601 4728 sub InverseDateTime($$;$$) 3602 4729 { 3603 4730 my ($self, $val, $tzFlag, $dateOnly) = @_; 3604 4731 my ($rtnVal, $tz); 4732 my $fmt = $$self{OPTIONS}{DateFormat}; 3605 4733 # strip off timezone first if it exists 3606 if ( $val =~ s/([+-])(\d{1,2}):?(\d{2})$//i) {4734 if (not $fmt and $val =~ s/([+-])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { 3607 4735 $tz = sprintf("$1%.2d:$3", $2); 3608 } elsif ( $val =~ s/Z$//i) {4736 } elsif (not $fmt and $val =~ s/Z$//i) { 3609 4737 $tz = 'Z'; 3610 4738 } else { 3611 4739 $tz = ''; 3612 } 3613 # strip of sub seconds 3614 my $fs = $val =~ /(\.\d+)$/ ? $1 : ''; 4740 # allow special value of 'now' 4741 return $self->TimeNow($tzFlag) if lc($val) eq 'now'; 4742 } 4743 # only convert date if a format was specified and the date is recognizable 4744 if ($fmt) { 4745 unless (defined $strptimeLib) { 4746 if (eval { require POSIX::strptime }) { 4747 $strptimeLib = 'POSIX::strptime'; 4748 } elsif (eval { require Time::Piece }) { 4749 $strptimeLib = 'Time::Piece'; 4750 # (call use_locale() to convert localized date/time, 4751 # only available in Time::Piece 1.32 and later) 4752 eval { Time::Piece->use_locale() }; 4753 } else { 4754 $strptimeLib = ''; 4755 } 4756 } 4757 my ($lib, $wrn, @a); 4758 TryLib: for ($lib=$strptimeLib; ; $lib='') { 4759 if (not $lib) { 4760 last unless $$self{OPTIONS}{StrictDate}; 4761 warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n"; 4762 return undef; 4763 } elsif ($lib eq 'POSIX::strptime') { 4764 @a = eval { POSIX::strptime($val, $fmt) }; 4765 } else { 4766 # protect against a negative epoch time, it can cause a hard crash in Windows 4767 if ($^O eq 'MSWin32' and $fmt =~ /%s/ and $val =~ /-\d/) { 4768 warn "Can't convert negative epoch time\n"; 4769 return undef; 4770 } 4771 @a = eval { 4772 my $t = Time::Piece->strptime($val, $fmt); 4773 return ($t->sec, $t->min, $t->hour, $t->mday, $t->_mon, $t->_year); 4774 }; 4775 } 4776 if (defined $a[5] and length $a[5]) { 4777 $a[5] += 1900; # add 1900 to year 4778 } else { 4779 $wrn = "Invalid date/time (no year) using $lib\n"; 4780 next; 4781 } 4782 ++$a[4] if defined $a[4] and length $a[4]; # add 1 to month 4783 my $i; 4784 foreach $i (0..4) { 4785 if (not defined $a[$i] or not length $a[$i]) { 4786 if ($i < 2 or $dateOnly) { # (allow missing minutes/seconds) 4787 $a[$i] = ' '; 4788 } else { 4789 $wrn = "Incomplete date/time specification using $lib\n"; 4790 next TryLib; 4791 } 4792 } elsif (length($a[$i]) < 2) { 4793 $$a[$i] = "0$a[$i]";# pad to 2 digits if necessary 4794 } 4795 } 4796 $val = join(':', @a[5,4,3]) . ' ' . join(':', @a[2,1,0]); 4797 last; 4798 } 4799 } 3615 4800 if ($val =~ /(\d{4})/g) { # get YYYY 3616 4801 my $yr = $1; 3617 my @a = ($val =~ /\d{2}/g); # get mm, dd, HH, and maybe MM, SS 4802 my @a = ($val =~ /\d{1,2}/g); # get mm, dd, HH, and maybe MM, SS 4803 length($_) < 2 and $_ = "0$_" foreach @a; # pad to 2 digits if necessary 3618 4804 if (@a >= 3) { 3619 4805 my $ss = $a[4]; # get SS 3620 4806 push @a, '00' while @a < 5; # add MM, SS if not given 4807 # get sub-seconds if they exist (must be after SS, and have leading ".") 4808 my $fs = (@a > 5 and $val =~ /(\.\d+)\s*$/) ? $1 : ''; 3621 4809 # add/remove timezone if necessary 3622 4810 if ($tzFlag) { 3623 4811 if (not $tz) { 3624 if (eval 'require Time::Local') {4812 if (eval { require Time::Local }) { 3625 4813 # determine timezone offset for this time 3626 my @args = ($a[4],$a[3],$a[2],$a[1],$a[0]-1,$yr -1900);4814 my @args = ($a[4],$a[3],$a[2],$a[1],$a[0]-1,$yr); 3627 4815 my $diff = Time::Local::timegm(@args) - TimeLocal(@args); 3628 4816 $tz = TimeZoneString($diff / 60); … … 3634 4822 $tz = $fs = ''; # remove timezone and sub-seconds 3635 4823 } 3636 if (defined $ss ) {4824 if (defined $ss and $ss < 60) { 3637 4825 $ss = ":$ss"; 3638 4826 } elsif ($dateOnly) { … … 3642 4830 } 3643 4831 # construct properly formatted date/time string 4832 if ($a[0] < 1 or $a[0] > 12) { 4833 warn "Month '$a[0]' out of range 1..12\n"; 4834 return undef; 4835 } 4836 if ($a[1] < 1 or $a[1] > 31) { 4837 warn "Day '$a[1]' out of range 1..31\n"; 4838 return undef; 4839 } 4840 $a[2] > 24 and warn("Hour '$a[2]' out of range 0..24\n"), return undef; 4841 $a[3] > 59 and warn("Minutes '$a[3]' out of range 0..59\n"), return undef; 3644 4842 $rtnVal = "$yr:$a[0]:$a[1] $a[2]:$a[3]$ss$fs$tz"; 3645 4843 } elsif ($dateOnly) { … … 3653 4851 #------------------------------------------------------------------------------ 3654 4852 # Set byte order according to our current preferences 3655 # Inputs: 0) ExifTool object ref 4853 # Inputs: 0) ExifTool object ref, 1) default byte order 3656 4854 # Returns: new byte order ('II' or 'MM') and sets current byte order 3657 4855 # Notes: takes the first of the following that is valid: 3658 4856 # 1) ByteOrder option 3659 4857 # 2) new value for ExifByteOrder 3660 # 3) makenote byte order from last file read 3661 # 4) big endian 3662 sub SetPreferredByteOrder($) 3663 { 3664 my $self = shift; 4858 # 3) default byte order passed to this routine 4859 # 4) makenote byte order from last file read 4860 # 5) big endian 4861 sub SetPreferredByteOrder($;$) 4862 { 4863 my ($self, $default) = @_; 3665 4864 my $byteOrder = $self->Options('ByteOrder') || 3666 $self->GetNewValue s('ExifByteOrder') ||3667 $ self->{MAKER_NOTE_BYTE_ORDER} || 'MM';4865 $self->GetNewValue('ExifByteOrder') || 4866 $default || $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; 3668 4867 unless (SetByteOrder($byteOrder)) { 3669 warn "Invalid byte order '$ byteOrder'\n" if $self->Options('Verbose');3670 $byteOrder = $ self->{MAKER_NOTE_BYTE_ORDER} || 'MM';4868 warn "Invalid byte order '${byteOrder}'\n" if $self->Options('Verbose'); 4869 $byteOrder = $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; 3671 4870 SetByteOrder($byteOrder); 3672 4871 } … … 3692 4891 # Notes: 3693 4892 # - the returned rational will be accurate to at least 8 significant figures if possible 3694 # - ie. an input of 3.14159265358979 returns a rational of 104348/33215,4893 # - eg. an input of 3.14159265358979 returns a rational of 104348/33215, 3695 4894 # which equals 3.14159265392142 and is accurate to 10 significant figures 4895 # - the returned rational will be reduced to the lowest common denominator except when 4896 # the input is a fraction in which case the input is returned unchanged 3696 4897 # - these routines were a bit tricky, but fun to write! 3697 4898 sub Rationalize($;$) … … 3741 4942 return Set32u($val, @_); 3742 4943 } 4944 sub Set64u(@) 4945 { 4946 my $val = $_[0]; 4947 my $hi = int($val / 4294967296); 4948 my $lo = Set32u($val - $hi * 4294967296); 4949 $hi = Set32u($hi); 4950 $val = GetByteOrder() eq 'MM' ? $hi . $lo : $lo . $hi; 4951 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4952 return $val; 4953 } 3743 4954 sub SetRational64u(@) { 3744 4955 my ($numer,$denom) = Rationalize($_[0],0xffffffff); … … 3802 5013 int32s => \&Set32s, 3803 5014 int32u => \&Set32u, 5015 int64u => \&Set64u, 3804 5016 rational32s => \&SetRational32s, 3805 5017 rational32u => \&SetRational32u, … … 3835 5047 # write binary data value (with current byte ordering) 3836 5048 # Inputs: 0) value, 1) format string 3837 # 2) optional number of values (1 or string length if not specified) 3838 # 3) optional data reference, 4) value offset 5049 # 2) number of values: 5050 # undef = 1 for numerical types, or data length for string/undef types 5051 # -1 = number of space-delimited values in the input string 5052 # 3) optional data reference, 4) value offset (may be negative for bytes from end) 3839 5053 # Returns: packed value (and sets value in data) or undef on error 5054 # Notes: May modify input value to round for integer formats 3840 5055 sub WriteValue($$;$$$$) 3841 5056 { … … 3857 5072 # validate numerical formats 3858 5073 if ($format =~ /^int/) { 3859 return undef unless IsInt($val) or IsHex($val); 5074 unless (IsInt($val) or IsHex($val)) { 5075 return undef unless IsFloat($val); 5076 # round to nearest integer 5077 $val = int($val + ($val < 0 ? -0.5 : 0.5)); 5078 $_[0] = $val; 5079 } 3860 5080 } elsif (not IsFloat($val)) { 3861 5081 return undef unless $format =~ /^rational/ and ($val eq 'inf' or … … 3920 5140 # don't return error string unless more than one value 3921 5141 return undef unless @vals > 1 and wantarray; 3922 return (undef, "no match for '$ val'");5142 return (undef, "no match for '${val}'"); 3923 5143 } 3924 5144 } … … 3938 5158 3939 5159 #------------------------------------------------------------------------------ 3940 # get current position in output file 5160 # get current position in output file (or end of file if a scalar reference) 3941 5161 # Inputs: 0) file or scalar reference 3942 5162 # Returns: Current position or -1 on error … … 3974 5194 { 3975 5195 my ($self, $trailInfo, $outfile) = @_; 3976 if ($ self->{DEL_GROUP}{Trailer}) {5196 if ($$self{DEL_GROUP}{Trailer}) { 3977 5197 $self->VPrint(0, " Deleting trailer ($$trailInfo{Offset} bytes)\n"); 3978 ++$ self->{CHANGED};5198 ++$$self{CHANGED}; 3979 5199 return 1; 3980 5200 } … … 3985 5205 if ($pos > 0) { 3986 5206 # shift offsets to final AFCP location and write it out 3987 $ trailInfo->{Fixup}{Shift} += $pos;3988 $ trailInfo->{Fixup}->ApplyFixup($trailPt);5207 $$trailInfo{Fixup}{Shift} += $pos; 5208 $$trailInfo{Fixup}->ApplyFixup($trailPt); 3989 5209 } else { 3990 5210 $self->Error("Can't get file position for trailer offset fixup",1); … … 4007 5227 $types[0] or shift @types; # (in case undef data ref is passed) 4008 5228 # add all possible trailers if none specified (currently only CanonVRD) 4009 @types or @types = qw(CanonVRD );4010 # add trailers as a block 5229 @types or @types = qw(CanonVRD CanonDR4); 5230 # add trailers as a block (if not done already) 4011 5231 my $type; 4012 5232 foreach $type (@types) { 4013 next unless $self->{NEW_VALUE}{$Image::ExifTool::Extra{$type}}; 4014 my $val = $self->GetNewValues($type) or next; 5233 next unless $$self{NEW_VALUE}{$Image::ExifTool::Extra{$type}}; 5234 next if $$self{"Did$type"}; 5235 my $val = $self->GetNewValue($type) or next; 5236 # DR4 record must be wrapped in VRD trailer package 5237 if ($type eq 'CanonDR4') { 5238 next if $$self{DidCanonVRD}; # (only allow one VRD trailer) 5239 require Image::ExifTool::CanonVRD; 5240 $val = Image::ExifTool::CanonVRD::WrapDR4($val); 5241 $$self{DidCanonVRD} = 1; 5242 } 4015 5243 my $verb = $trailPt ? 'Writing' : 'Adding'; 4016 5244 $self->VPrint(0, " $verb $type as a block\n"); … … 4020 5248 $trailPt = \$val; 4021 5249 } 5250 $$self{"Did$type"} = 1; 4022 5251 ++$$self{CHANGED}; 4023 5252 } … … 4030 5259 # 2) segment header, 3) segment data ref, 4) segment type 4031 5260 # Returns: number of segments written, or 0 on error 5261 # Notes: Writes a single empty segment if data is empty 4032 5262 sub WriteMultiSegment($$$$;$) 4033 5263 { … … 4040 5270 $maxLen -= 2 if $type eq 'ICC'; # leave room for segment counters 4041 5271 my $num = int(($len + $maxLen - 1) / $maxLen); # number of segments to write 4042 my $n ;5272 my $n = 0; 4043 5273 # write data, splitting into multiple segments if necessary 4044 5274 # (each segment gets its own header) 4045 for ( $n=0; $n<$len; $n+=$maxLen) {5275 for (;;) { 4046 5276 ++$count; 4047 5277 my $size = $len - $n; 4048 $size > $maxLen and $size = $maxLen; 5278 if ($size > $maxLen) { 5279 $size = $maxLen; 5280 # avoid starting an Extended EXIF segment with a valid TIFF header 5281 # (because we would interpret that as a separate EXIF segment) 5282 --$size if $type eq 'EXIF' and $n+$maxLen <= $len-4 and 5283 substr($$dataPt, $n+$maxLen, 4) =~ /^(MM\0\x2a|II\x2a\0)/; 5284 } 4049 5285 my $buff = substr($$dataPt,$n,$size); 5286 $n += $size; 4050 5287 $size += length($header); 4051 5288 if ($type eq 'ICC') { … … 4056 5293 my $segHdr = $hdr . pack('n', $size + 2); 4057 5294 Write($outfile, $segHdr, $header, $buff) or return 0; 5295 last if $n >= $len; 4058 5296 } 4059 5297 return $count; … … 4081 5319 if (defined $guid) { 4082 5320 $size = length($$extPt); 4083 my $maxLen = $maxXMPLen - 75; # maximum size without 75 5321 my $maxLen = $maxXMPLen - 75; # maximum size without 75-byte header 4084 5322 my $off; 4085 5323 for ($off=0; $off<$size; $off+=$maxLen) { … … 4106 5344 my $outfile = $$dirInfo{OutFile}; 4107 5345 my $raf = $$dirInfo{RAF}; 4108 my ($ch, $s,$length);4109 my $verbose = $ self->{OPTIONS}{Verbose};4110 my $out = $ self->{OPTIONS}{TextOut};5346 my ($ch, $s, $length,$err, %doneDir, $isEXV, $creatingEXV); 5347 my $verbose = $$self{OPTIONS}{Verbose}; 5348 my $out = $$self{OPTIONS}{TextOut}; 4111 5349 my $rtnVal = 0; 4112 my ($err, %doneDir);4113 5350 my %dumpParms = ( Out => $out ); 4114 5351 my ($writeBuffer, $oldOutfile); # used to buffer writing until PreviewImage position is known 4115 5352 4116 # check to be sure this is a valid JPG file 4117 return 0 unless $raf->Read($s,2) == 2 and $s eq "\xff\xd8"; 5353 # check to be sure this is a valid JPG or EXV file 5354 unless ($raf->Read($s,2) == 2 and $s eq "\xff\xd8") { 5355 if (defined $s and length $s) { 5356 return 0 unless $s eq "\xff\x01" and $raf->Read($s,5) == 5 and $s eq 'Exiv2'; 5357 } else { 5358 return 0 unless $$self{FILE_TYPE} eq 'EXV'; 5359 $s = 'Exiv2'; 5360 $creatingEXV = 1; 5361 } 5362 Write($outfile,"\xff\x01") or $err = 1; 5363 $isEXV = 1; 5364 } 4118 5365 $dumpParms{MaxLen} = 128 unless $verbose > 3; 4119 5366 4120 delete $ self->{PREVIEW_INFO}; # reset preview information4121 delete $ self->{DEL_PREVIEW}; # reset flag to delete preview5367 delete $$self{PREVIEW_INFO}; # reset preview information 5368 delete $$self{DEL_PREVIEW}; # reset flag to delete preview 4122 5369 4123 5370 Write($outfile, $s) or $err = 1; 4124 5371 # figure out what segments we need to write for the tags we have set 4125 my $addDirs = $ self->{ADD_DIRS};4126 my $editDirs = $ self->{EDIT_DIRS};4127 my $delGroup = $ self->{DEL_GROUP};5372 my $addDirs = $$self{ADD_DIRS}; 5373 my $editDirs = $$self{EDIT_DIRS}; 5374 my $delGroup = $$self{DEL_GROUP}; 4128 5375 my $path = $$self{PATH}; 4129 5376 my $pn = scalar @$path; … … 4145 5392 last unless $marker == 0xff; 4146 5393 } 4147 # SOS signifies end of meta information 4148 if ($marker == 0xda) { 4149 push(@dirOrder, 'SOS'); 4150 $dirCount{SOS} = 1; 5394 my $dirName; 5395 # stop pre-scan at SOS (end of meta information) or EOI (end of image) 5396 if ($marker == 0xda or $marker == 0xd9) { 5397 $dirName = $jpegMarker{$marker}; 5398 push(@dirOrder, $dirName); 5399 $dirCount{$dirName} = 1; 4151 5400 last; 4152 5401 } 4153 my $dirName;4154 5402 # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) 4155 5403 if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { … … 4167 5415 $raf->Read($s, $n) == $n or last; 4168 5416 $len -= $n; 4169 # (Note: only necessary to recognize APP segments that we can create) 5417 # Note: only necessary to recognize APP segments that we can create, 5418 # or delete as a group (and the names below should match @delGroups) 4170 5419 if ($marker == 0xe0) { 4171 5420 $s =~ /^JFIF\0/ and $dirName = 'JFIF'; 4172 5421 $s =~ /^JFXX\0\x10/ and $dirName = 'JFXX'; 5422 $s =~ /^(II|MM).{4}HEAPJPGM/s and $dirName = 'CIFF'; 4173 5423 } elsif ($marker == 0xe1) { 4174 $s =~ /^$exifAPP1hdr/ and $dirName = 'IFD0'; 5424 if ($s =~ /^(.{0,4})$exifAPP1hdr(.{1,4})/is) { 5425 $dirName = 'IFD0'; 5426 my ($junk, $bytes) = ($1, $2); 5427 # support multi-segment EXIF 5428 if (@dirOrder and $dirOrder[-1] =~ /^(IFD0|ExtendedEXIF)$/ and 5429 not length $junk and $bytes !~ /^(MM\0\x2a|II\x2a\0)/) 5430 { 5431 $dirName = 'ExtendedEXIF'; 5432 } 5433 } 4175 5434 $s =~ /^$xmpAPP1hdr/ and $dirName = 'XMP'; 4176 5435 $s =~ /^$xmpExtAPP1hdr/ and $dirName = 'XMP'; 4177 5436 } elsif ($marker == 0xe2) { 4178 5437 $s =~ /^ICC_PROFILE\0/ and $dirName = 'ICC_Profile'; 5438 $s =~ /^FPXR\0/ and $dirName = 'FlashPix'; 5439 $s =~ /^MPF\0/ and $dirName = 'MPF'; 5440 } elsif ($marker == 0xe3) { 5441 $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta'; 5442 } elsif ($marker == 0xe5) { 5443 $s =~ /^RMETA\0/ and $dirName = 'RMETA'; 4179 5444 } elsif ($marker == 0xec) { 4180 5445 $s =~ /^Ducky/ and $dirName = 'Ducky'; 4181 5446 } elsif ($marker == 0xed) { 4182 5447 $s =~ /^$psAPP13hdr/ and $dirName = 'Photoshop'; 5448 } elsif ($marker == 0xee) { 5449 $s =~ /^Adobe/ and $dirName = 'Adobe'; 4183 5450 } 4184 5451 # initialize doneDir as a flag that the directory exists … … 4193 5460 } 4194 5461 unless ($marker and $marker == 0xda) { 4195 $ self->Error('Corrupted JPEG image');4196 return 1;5462 $isEXV or $self->Error('Corrupted JPEG image'), return 1; 5463 $marker and $marker != 0xd9 and $self->Error('Corrupted EXV file'), return 1; 4197 5464 } 4198 5465 $raf->Seek($pos, 0) or $self->Error('Seek error'), return 1; … … 4200 5467 # re-write the image 4201 5468 # 4202 my ($combinedSegData, $segPos, %extendedXMP); 5469 my ($combinedSegData, $segPos, $firstSegPos, %extendedXMP); 5470 my (@iccChunk, $iccChunkCount, $iccChunksTotal); 4203 5471 # read through each segment in the JPEG file 4204 5472 Marker: for (;;) { … … 4208 5476 $raf->ReadLine($segJunk) or $segJunk = ''; 4209 5477 # remove the 0xff but write the rest of the junk up to this point 5478 # (this will handle the data after the first 7 bytes of SOF segments) 4210 5479 chomp($segJunk); 4211 5480 Write($outfile, $segJunk) if length $segJunk; 4212 5481 # JPEG markers can be padded with unlimited 0xff's 4213 5482 for (;;) { 4214 $raf->Read($ch, 1) or $self->Error('Format error'), return 1; 4215 $marker = ord($ch); 4216 last unless $marker == 0xff; 5483 if ($raf->Read($ch, 1)) { 5484 $marker = ord($ch); 5485 last unless $marker == 0xff; 5486 } elsif ($creatingEXV) { 5487 # create EXV from scratch 5488 $marker = 0xd9; # EOI 5489 push @dirOrder, 'EOI'; 5490 $dirCount{EOI} = 1; 5491 last; 5492 } else { 5493 $self->Error('Format error'); 5494 return 1; 5495 } 4217 5496 } 4218 5497 # read the segment data … … 4222 5501 last unless $raf->Read($segData, 7) == 7; 4223 5502 # read data for all markers except stand-alone 4224 # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, RST0-RST7) 4225 } elsif ($marker!=0x00 and $marker!=0x01 and ($marker<0xd0 or $marker>0xd7)) { 5503 # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, EOI, RST0-RST7) 5504 } elsif ($marker!=0x00 and $marker!=0x01 and $marker!=0xd9 and 5505 ($marker<0xd0 or $marker>0xd7)) 5506 { 4226 5507 # read record length word 4227 5508 last unless $raf->Read($s, 2) == 2; … … 4236 5517 my $markerName = JpegMarkerName($marker); 4237 5518 my $dirName = shift @dirOrder; # get directory name 4238 $$path[$pn] = $markerName;4239 5519 # 4240 5520 # create all segments that must come before this one … … 4244 5524 if (exists $$addDirs{JFIF} and not defined $doneDir{JFIF}) { 4245 5525 $doneDir{JFIF} = 1; 4246 if ($verbose) { 4247 print $out "Creating APP0:\n"; 4248 print $out " Creating JFIF with default values\n"; 4249 } 4250 my $jfif = "\x01\x02\x01\0\x48\0\x48\0\0"; 4251 SetByteOrder('MM'); 4252 my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); 4253 my %dirInfo = ( 4254 DataPt => \$jfif, 4255 DirStart => 0, 4256 DirLen => length $jfif, 4257 ); 4258 # must temporarily remove JFIF from DEL_GROUP so we can 4259 # delete JFIF and add it back again in a single step 4260 my $delJFIF = $$delGroup{JFIF}; 4261 delete $$delGroup{JFIF}; 4262 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4263 $$delGroup{JFIF} = $delJFIF if defined $delJFIF; 4264 if (defined $newData and length $newData) { 4265 my $app0hdr = "\xff\xe0" . pack('n', length($newData) + 7); 4266 Write($outfile,$app0hdr,"JFIF\0",$newData) or $err = 1; 5526 if (defined $doneDir{Adobe}) { 5527 # JFIF overrides Adobe APP14 colour components, so don't allow this 5528 # (ref https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html) 5529 $self->Warn('Not creating JFIF in JPEG with Adobe APP14'); 5530 } else { 5531 if ($verbose) { 5532 print $out "Creating APP0:\n"; 5533 print $out " Creating JFIF with default values\n"; 5534 } 5535 my $jfif = "\x01\x02\x01\0\x48\0\x48\0\0"; 5536 SetByteOrder('MM'); 5537 my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); 5538 my %dirInfo = ( 5539 DataPt => \$jfif, 5540 DirStart => 0, 5541 DirLen => length $jfif, 5542 Parent => 'JFIF', 5543 ); 5544 # must temporarily remove JFIF from DEL_GROUP so we can 5545 # delete JFIF and add it back again in a single step 5546 my $delJFIF = $$delGroup{JFIF}; 5547 delete $$delGroup{JFIF}; 5548 $$path[$pn] = 'JFIF'; 5549 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5550 $$delGroup{JFIF} = $delJFIF if defined $delJFIF; 5551 if (defined $newData and length $newData) { 5552 my $app0hdr = "\xff\xe0" . pack('n', length($newData) + 7); 5553 Write($outfile,$app0hdr,"JFIF\0",$newData) or $err = 1; 5554 } 4267 5555 } 4268 5556 } 4269 5557 # don't create anything before APP0 or APP1 EXIF (containing IFD0) 4270 last if $markerName eq 'APP0' or $dirCount{IFD0} ;5558 last if $markerName eq 'APP0' or $dirCount{IFD0} or $dirCount{ExtendedEXIF}; 4271 5559 # EXIF information must come immediately after APP0 4272 5560 if (exists $$addDirs{IFD0} and not defined $doneDir{IFD0}) { … … 4274 5562 $verbose and print $out "Creating APP1:\n"; 4275 5563 # write new EXIF data 4276 $ self->{TIFF_TYPE} = 'APP1';5564 $$self{TIFF_TYPE} = 'APP1'; 4277 5565 my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); 4278 5566 my %dirInfo = ( … … 4280 5568 Parent => 'APP1', 4281 5569 ); 5570 $$path[$pn] = 'APP1'; 4282 5571 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 4283 5572 if (defined $buff and length $buff) { 4284 my $size = length($buff) + length($exifAPP1hdr); 4285 if ($size <= $maxSegmentLen) { 4286 # switch to buffered output if required 4287 if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { 4288 $writeBuffer = ''; 4289 $oldOutfile = $outfile; 4290 $outfile = \$writeBuffer; 4291 # account for segment, EXIF and TIFF headers 4292 $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; 4293 $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; 5573 if (length($buff) + length($exifAPP1hdr) > $maxSegmentLen) { 5574 if ($self->Options('NoMultiExif')) { 5575 $self->Error('EXIF is too large for JPEG segment'); 5576 } else { 5577 $self->Warn('Creating multi-segment EXIF',1); 4294 5578 } 4295 # write the new segment with appropriate header 4296 my $app1hdr = "\xff\xe1" . pack('n', $size + 2); 4297 Write($outfile,$app1hdr,$exifAPP1hdr,$buff) or $err = 1; 4298 } else { 4299 delete $self->{PREVIEW_INFO}; 4300 $self->Warn("EXIF APP1 segment too large! ($size bytes)"); 4301 } 5579 } 5580 # switch to buffered output if required 5581 if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { 5582 $writeBuffer = ''; 5583 $oldOutfile = $outfile; 5584 $outfile = \$writeBuffer; 5585 # account for segment, EXIF and TIFF headers 5586 $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; 5587 $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; 5588 } 5589 # write as multi-segment 5590 my $n = WriteMultiSegment($outfile, 0xe1, $exifAPP1hdr, \$buff, 'EXIF'); 5591 if (not $n) { 5592 $err = 1; 5593 } elsif ($n > 1 and $oldOutfile) { 5594 # (punt on this because updating the pointers would be a real pain) 5595 $self->Error("Can't write multi-segment EXIF with external pointers"); 5596 } 5597 ++$$self{CHANGED}; 4302 5598 } 4303 5599 } … … 4312 5608 Parent => 'APP13', 4313 5609 ); 5610 $$path[$pn] = 'APP13'; 4314 5611 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4315 5612 if (defined $buff and length $buff) { 4316 5613 WriteMultiSegment($outfile, 0xed, $psAPP13hdr, \$buff) or $err = 1; 4317 ++$ self->{CHANGED};5614 ++$$self{CHANGED}; 4318 5615 } 4319 5616 } … … 4330 5627 MaxDataLen => $maxXMPLen - length($xmpAPP1hdr), 4331 5628 ); 5629 $$path[$pn] = 'APP1'; 4332 5630 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4333 5631 if (defined $buff and length $buff) { … … 4347 5645 Parent => 'APP2', 4348 5646 ); 5647 $$path[$pn] = 'APP2'; 4349 5648 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4350 5649 if (defined $buff and length $buff) { 4351 5650 WriteMultiSegment($outfile, 0xe2, "ICC_PROFILE\0", \$buff, 'ICC') or $err = 1; 4352 ++$ self->{CHANGED};5651 ++$$self{CHANGED}; 4353 5652 } 4354 5653 } … … 4363 5662 Parent => 'APP12', 4364 5663 ); 5664 $$path[$pn] = 'APP12'; 4365 5665 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4366 5666 if (defined $buff and length $buff) { … … 4371 5671 Write($outfile, $app12hdr, 'Ducky', $buff) or $err = 1; 4372 5672 } else { 4373 $self->Warn("Ducky APP12 segment too large! ($size bytes)"); 5673 $self->Warn("APP12 Ducky segment too large! ($size bytes)"); 5674 } 5675 } 5676 } 5677 # then APP14 Adobe segment 5678 last if $dirCount{Adobe}; 5679 if (exists $$addDirs{Adobe} and not defined $doneDir{Adobe}) { 5680 $doneDir{Adobe} = 1; 5681 my $buff = $self->GetNewValue('Adobe'); 5682 if ($buff) { 5683 $verbose and print $out "Creating APP14:\n Creating Adobe segment\n"; 5684 my $size = length($buff); 5685 if ($size <= $maxSegmentLen) { 5686 # write the new segment with appropriate header 5687 my $app14hdr = "\xff\xee" . pack('n', $size + 2); 5688 Write($outfile, $app14hdr, $buff) or $err = 1; 5689 ++$$self{CHANGED}; 5690 } else { 5691 $self->Warn("APP14 Adobe segment too large! ($size bytes)"); 4374 5692 } 4375 5693 } … … 4380 5698 $doneDir{COM} = 1; 4381 5699 next if $$delGroup{File} and $$delGroup{File} != 2; 4382 my $newComment = $self->GetNewValue s('Comment');4383 if (defined $newComment and length($newComment)) {5700 my $newComment = $self->GetNewValue('Comment'); 5701 if (defined $newComment) { 4384 5702 if ($verbose) { 4385 5703 print $out "Creating COM:\n"; … … 4387 5705 } 4388 5706 WriteMultiSegment($outfile, 0xfe, '', \$newComment) or $err = 1; 4389 ++$ self->{CHANGED};5707 ++$$self{CHANGED}; 4390 5708 } 4391 5709 } 4392 5710 last; # didn't want to loop anyway 4393 5711 } 5712 $$path[$pn] = $markerName; 4394 5713 # decrement counter for this directory since we are about to process it 4395 5714 --$dirCount{$dirName}; … … 4409 5728 Write($outfile, $hdr, $s, $segData) or $err = 1; 4410 5729 my ($buff, $endPos, $trailInfo); 4411 my $delPreview = $ self->{DEL_PREVIEW};5730 my $delPreview = $$self{DEL_PREVIEW}; 4412 5731 $trailInfo = IdentifyTrailer($raf) unless $$delGroup{Trailer}; 4413 unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer}) { 5732 my $nvTrail = $self->GetNewValueHash($Image::ExifTool::Extra{Trailer}); 5733 unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer} or $nvTrail) { 4414 5734 # blindly copy the rest of the file 4415 5735 while ($raf->Read($buff, 65536)) { … … 4442 5762 # remember position of last data copied 4443 5763 $endPos = $raf->Tell() - length($buff); 4444 # rewrite trailers if they exist 5764 # write new trailer if specified 5765 if ($nvTrail) { 5766 # access new value directly to avoid copying a potentially very large data block 5767 if ($$nvTrail{Value} and $$nvTrail{Value}[0]) { # (note: "0" will also delete the trailer) 5768 $self->VPrint(0, ' Writing new trailer'); 5769 Write($outfile, $$nvTrail{Value}[0]) or $err = 1; 5770 ++$$self{CHANGED}; 5771 } elsif ($raf->Seek(0, 2) and $raf->Tell() != $endPos) { 5772 $self->VPrint(0, ' Deleting trailer (', $raf->Tell() - $endPos, ' bytes)'); 5773 ++$$self{CHANGED}; # changed if there was previously a trailer 5774 } 5775 last; # all done 5776 } 5777 # rewrite existing trailers 4445 5778 if ($trailInfo) { 4446 5779 my $tbuf = ''; … … 4473 5806 # use this fixup to set the size too (sneaky) 4474 5807 my $trailSize = defined($dat) ? length($dat) - $junk : $$self{LeicaTrailer}{Size}; 4475 $ fixup->{Start} -= 4; $fixup->{Shift} += 4;4476 $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', $trailSize) ;4477 $ fixup->{Start} += 4; $fixup->{Shift} -= 4;5808 $$fixup{Start} -= 4; $$fixup{Shift} += 4; 5809 $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', $trailSize) if defined $trailSize; 5810 $$fixup{Start} += 4; $$fixup{Shift} -= 4; 4478 5811 # clean up and write the buffered data 4479 5812 $outfile = $oldOutfile; … … 4487 5820 } else { 4488 5821 # locate preview image and fix up preview offsets 4489 my $scanLen = $$self{Make} =~ / Sony/i ? 65536 : 1024;5822 my $scanLen = $$self{Make} =~ /^SONY/i ? 65536 : 1024; 4490 5823 if (length($buff) < $scanLen) { # make sure we have enough trailer to scan 4491 5824 my $buf2; … … 4495 5828 my $newPos = length($$outfile) - 10; # (subtract 10 for segment and EXIF headers) 4496 5829 my $junkLen; 4497 # adjust position if image isn't at the start (ie. Olympus E-1/E-300) 4498 if ($buff =~ m/(\xff\xd8\xff.|.\xd8\xff\xdb)/sg) { 4499 $junkLen = pos($buff) - 4; 5830 # adjust position if image isn't at the start (eg. Olympus E-1/E-300) 5831 if ($buff =~ /(\xff\xd8\xff.|.\xd8\xff\xdb)(..)/sg) { 5832 my ($jpegHdr, $segLen) = ($1, $2); 5833 $junkLen = pos($buff) - 6; 4500 5834 # Sony previewimage trailer has a 32 byte header 4501 $junkLen -= 32 if $$self{Make} =~/SONY/i and $junkLen > 32; 5835 if ($$self{Make} =~ /^SONY/i and $junkLen > 32) { 5836 # with some newer Sony models, the makernotes preview pointer 5837 # points to JPEG at end of EXIF inside MPImage preview (what a pain!) 5838 if ($jpegHdr eq "\xff\xd8\xff\xe1") { # is the first segment EXIF? 5839 $segLen = unpack('n', $segLen); # the EXIF segment length 5840 # Sony PreviewImage starts with last 2 bytes of EXIF segment 5841 # (and first byte is usually "\0", not "\xff", so don't check this) 5842 if (length($buff) > $junkLen + $segLen + 6 and 5843 substr($buff, $junkLen + $segLen + 3, 3) eq "\xd8\xff\xdb") 5844 { 5845 $junkLen += $segLen + 2; 5846 # (note: this will not copy the trailer after PreviewImage, 5847 # which is a 14kB block full of zeros for the A77) 5848 } 5849 } 5850 $junkLen -= 32; 5851 } 4502 5852 $newPos += $junkLen; 4503 5853 } 4504 5854 # fix up the preview offsets to point to the start of the new image 4505 my $previewInfo = $ self->{PREVIEW_INFO};4506 delete $ self->{PREVIEW_INFO};5855 my $previewInfo = $$self{PREVIEW_INFO}; 5856 delete $$self{PREVIEW_INFO}; 4507 5857 my $fixup = $$previewInfo{Fixup}; 4508 5858 $newPos += ($$previewInfo{BaseShift} || 0); … … 4511 5861 if ($$previewInfo{Relative}) { 4512 5862 # adjust for our base by looking at how far the pointer got shifted 4513 $newPos -= $fixup->GetMarkerPointers($outfile, 'PreviewImage');5863 $newPos -= ($fixup->GetMarkerPointers($outfile, 'PreviewImage') || 0); 4514 5864 } elsif ($$previewInfo{ChangeBase}) { 4515 5865 # Leica S2 uses relative offsets for the preview only (leica sucks) … … 4548 5898 if ($$delGroup{Trailer}) { 4549 5899 $verbose and print $out " Deleting unknown trailer ($extra bytes)\n"; 4550 ++$ self->{CHANGED};5900 ++$$self{CHANGED}; 4551 5901 } else { 4552 5902 # copy over unknown trailer … … 4564 5914 last; # all done parsing file 4565 5915 5916 } elsif ($marker==0xd9 and $isEXV) { 5917 # write EXV EOI (any trailer will be lost) 5918 Write($outfile, "\xff\xd9") or $err = 1; 5919 $rtnVal = 1; 5920 last; 5921 4566 5922 } elsif ($marker==0x00 or $marker==0x01 or ($marker>=0xd0 and $marker<=0xd7)) { 4567 5923 $verbose and $marker and print $out "JPEG $markerName:\n"; … … 4573 5929 # NOTE: A 'next' statement after this point will cause $$segDataPt 4574 5930 # not to be written if there is an output file, so in this case 4575 # the $ self->{CHANGED} flags must be updated5931 # the $$self{CHANGED} flags must be updated 4576 5932 # 4577 5933 my $segDataPt = \$segData; … … 4583 5939 } 4584 5940 } 5941 # group delete of APP segments 5942 if ($$delGroup{$dirName}) { 5943 $verbose and print $out " Deleting $dirName segment\n"; 5944 $self->Warn('ICC_Profile deleted. Image colors may be affected') if $dirName eq 'ICC_Profile'; 5945 ++$$self{CHANGED}; 5946 next Marker; 5947 } 4585 5948 my ($segType, $del); 4586 5949 # rewrite this segment only if we are changing a tag which is contained in its 4587 5950 # directory (or deleting '*', in which case we need to identify the segment type) 4588 5951 while (exists $$editDirs{$markerName} or $$delGroup{'*'}) { 4589 my $oldChanged = $self->{CHANGED};4590 5952 if ($marker == 0xe0) { # APP0 (JFIF, CIFF) 4591 5953 if ($$segDataPt =~ /^JFIF\0/) { … … 4631 5993 } elsif ($marker == 0xe1) { # APP1 (EXIF, XMP) 4632 5994 # check for EXIF data 4633 if ($$segDataPt =~ /^$exifAPP1hdr/) { 5995 if ($$segDataPt =~ /^(.{0,4})$exifAPP1hdr/is) { 5996 my $hdrLen = length $exifAPP1hdr; 5997 if (length $1) { 5998 $hdrLen += length $1; 5999 $self->Error('Unknown garbage at start of EXIF segment',1); 6000 } elsif ($$segDataPt !~ /^Exif\0/) { 6001 $self->Error('Incorrect EXIF segment identifier',1); 6002 } 4634 6003 $segType = 'EXIF'; 4635 $doneDir{IFD0} and $self->Warn('Multiple APP1 EXIF segments'); 6004 last unless $$editDirs{IFD0}; 6005 # add this data to the combined data if it exists 6006 if (defined $combinedSegData) { 6007 $combinedSegData .= substr($$segDataPt,$hdrLen); 6008 $segDataPt = \$combinedSegData; 6009 $segPos = $firstSegPos; 6010 $length = length $combinedSegData; # update length 6011 } 6012 # peek ahead to see if the next segment is extended EXIF 6013 if ($dirOrder[0] eq 'ExtendedEXIF') { 6014 # initialize combined data if necessary 6015 unless (defined $combinedSegData) { 6016 $combinedSegData = $$segDataPt; 6017 $firstSegPos = $segPos; 6018 $self->Warn('File contains multi-segment EXIF',1); 6019 } 6020 next Marker; # get the next segment to combine 6021 } 6022 $doneDir{IFD0} and $self->Warn('Multiple APP1 EXIF records'); 4636 6023 $doneDir{IFD0} = 1; 4637 last unless $$editDirs{IFD0};4638 6024 # check del groups now so we can change byte order in one step 4639 6025 if ($$delGroup{IFD0} or $$delGroup{EXIF}) { … … 4645 6031 my %dirInfo = ( 4646 6032 DataPt => $segDataPt, 4647 DataPos => $segPos,4648 DirStart => 6,4649 Base => $segPos + 6,6033 DataPos => -$hdrLen, # (remember: relative to Base!) 6034 DirStart => $hdrLen, 6035 Base => $segPos + $hdrLen, 4650 6036 Parent => $markerName, 4651 6037 DirName => 'IFD0', … … 4655 6041 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 4656 6042 if (defined $buff) { 4657 # update segment with new data4658 $ $segDataPt = $exifAPP1hdr .$buff;6043 undef $$segDataPt; # free the old buffer 6044 $segDataPt = \$buff; 4659 6045 } else { 4660 6046 last Marker unless $self->Options('IgnoreMinorErrors'); 4661 $self->{CHANGED} = $oldChanged; # nothing changed 6047 } 6048 # delete segment if IFD contains no entries 6049 length $$segDataPt or $del = 1, last; 6050 if (length($$segDataPt) + length($exifAPP1hdr) > $maxSegmentLen) { 6051 if ($self->Options('NoMultiExif')) { 6052 $self->Error('EXIF is too large for JPEG segment'); 6053 } else { 6054 $self->Warn('Writing multi-segment EXIF',1); 6055 } 4662 6056 } 4663 6057 # switch to buffered output if required … … 4670 6064 $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; 4671 6065 } 4672 # delete segment if IFD contains no entries 4673 $del = 1 unless length($$segDataPt) > length($exifAPP1hdr); 6066 # write as multi-segment 6067 my $n = WriteMultiSegment($outfile, $marker, $exifAPP1hdr, $segDataPt, 'EXIF'); 6068 if (not $n) { 6069 $err = 1; 6070 } elsif ($n > 1 and $oldOutfile) { 6071 # (punt on this because updating the pointers would be a real pain) 6072 $self->Error("Can't write multi-segment EXIF with external pointers"); 6073 } 6074 undef $combinedSegData; 6075 undef $$segDataPt; 6076 next Marker; 4674 6077 # check for XMP data 4675 6078 } elsif ($$segDataPt =~ /^($xmpAPP1hdr|$xmpExtAPP1hdr)/) { … … 4688 6091 my ($size, $off) = unpack('x67N2', $$segDataPt); 4689 6092 $guid = substr($$segDataPt, 35, 32); 4690 # remember extended data for each GUID 4691 $extXMP = $extendedXMP{$guid}; 4692 if ($extXMP) { 4693 $size == $$extXMP{Size} or $extendedXMP{Error} = 'Invalid size'; 6093 if ($guid =~ /[^A-Za-z0-9]/) { # (technically, should be uppercase) 6094 $extendedXMP{Error} = 'Invalid GUID'; 4694 6095 } else { 4695 $extXMP = $extendedXMP{$guid} = { }; 6096 # remember extended data for each GUID 6097 $extXMP = $extendedXMP{$guid}; 6098 if ($extXMP) { 6099 $size == $$extXMP{Size} or $extendedXMP{Error} = 'Inconsistent size'; 6100 } else { 6101 $extXMP = $extendedXMP{$guid} = { }; 6102 } 6103 $$extXMP{Size} = $size; 6104 $$extXMP{$off} = substr($$segDataPt, 75); 4696 6105 } 4697 $$extXMP{Size} = $size;4698 $$extXMP{$off} = substr($$segDataPt, 75);4699 6106 } 4700 6107 } else { … … 4707 6114 # reconstruct an XMP super-segment 4708 6115 $$segDataPt = $xmpAPP1hdr; 4709 $$segDataPt .= $_ foreach @{$extendedXMP{Main}}; 6116 my $goodGuid = ''; 6117 foreach (@{$extendedXMP{Main}}) { 6118 # get the HasExtendedXMP GUID if it exists 6119 if (/:HasExtendedXMP\s*(=\s*['"]|>)(\w{32})/) { 6120 # warn of subsequent XMP blocks specifying a different 6121 # HasExtendedXMP (have never seen this) 6122 if ($goodGuid and $goodGuid ne $2) { 6123 $self->WarnOnce('Multiple XMP segments specifying different extended XMP GUID'); 6124 } 6125 $goodGuid = $2; # GUID for the standard extended XMP 6126 } 6127 $$segDataPt .= $_; 6128 } 6129 # GUID of the extended XMP that we want to read 6130 my $readGuid = $$self{OPTIONS}{ExtendedXMP} || 0; 6131 $readGuid = $goodGuid if $readGuid eq '1'; 4710 6132 foreach $guid (sort keys %extendedXMP) { 4711 next unless length $guid == 32; # ignore other keys 6133 next unless length $guid == 32; # ignore other (internal) keys 6134 if ($guid ne $readGuid and $readGuid ne '2') { 6135 my $non = $guid eq $goodGuid ? '' : 'non-'; 6136 $self->Warn("Ignored ${non}standard extended XMP (GUID $guid)"); 6137 next; 6138 } 6139 if ($guid ne $goodGuid) { 6140 $self->Warn("Reading non-standard extended XMP (GUID $guid)"); 6141 } 4712 6142 $extXMP = $extendedXMP{$guid}; 4713 6143 next unless ref $extXMP eq 'HASH'; # (just to be safe) … … 4723 6153 $$segDataPt .= $$extXMP{$_} foreach @offsets; 4724 6154 } else { 4725 $ extendedXMP{Error} = 'Missing XMP data';6155 $self->Error("Incomplete extended XMP (GUID $guid)", 1); 4726 6156 } 4727 6157 } … … 4750 6180 } 4751 6181 } else { 4752 $self->{CHANGED} = $oldChanged;4753 6182 $verbose and print $out " [XMP rewritten with no changes]\n"; 4754 6183 if ($doneDir{XMP} > 1) { … … 4781 6210 $self->Warn('Ignored APP1 XMP segment with non-standard header', 1); 4782 6211 } 4783 } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR )4784 if ($$segDataPt =~ /^ICC_PROFILE\0/ ) {6212 } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF) 6213 if ($$segDataPt =~ /^ICC_PROFILE\0/ and $length >= 14) { 4785 6214 $segType = 'ICC_Profile'; 4786 6215 $$delGroup{ICC_Profile} and $del = 1, last; 4787 6216 # must concatenate blocks of profile 4788 my $block_num = ord(substr($$segDataPt, 12, 1)); 4789 my $blocks_tot = ord(substr($$segDataPt, 13, 1)); 4790 $combinedSegData = '' if $block_num == 1; 4791 unless (defined $combinedSegData) { 4792 $self->Warn('APP2 ICC_Profile segments out of sequence'); 6217 my $chunkNum = Get8u($segDataPt, 12); 6218 my $chunksTot = Get8u($segDataPt, 13); 6219 if (defined $iccChunksTotal) { 6220 # abort parsing ICC_Profile if the total chunk count is inconsistent 6221 if ($chunksTot != $iccChunksTotal and defined $iccChunkCount) { 6222 # an error because the accumulated profile data will be lost 6223 $self->Error('Inconsistent ICC_Profile chunk count', 1); 6224 undef $iccChunkCount; # abort ICC_Profile parsing 6225 undef $chunkNum; # avoid 2nd warning below 6226 ++$$self{CHANGED}; # we are deleting the bad chunks before this one 6227 } 6228 } else { 6229 $iccChunkCount = 0; 6230 $iccChunksTotal = $chunksTot; 6231 $self->Warn('ICC_Profile chunk count is zero') if !$chunksTot; 6232 } 6233 if (defined $iccChunkCount) { 6234 # save this chunk 6235 if (defined $iccChunk[$chunkNum]) { 6236 $self->Warn("Duplicate ICC_Profile chunk number $chunkNum"); 6237 $iccChunk[$chunkNum] .= substr($$segDataPt, 14); 6238 } else { 6239 $iccChunk[$chunkNum] = substr($$segDataPt, 14); 6240 } 6241 # continue accumulating chunks unless we have all of them 6242 next Marker unless ++$iccChunkCount >= $iccChunksTotal; 6243 undef $iccChunkCount; # prevent reprocessing 6244 $doneDir{ICC_Profile} = 1; 6245 # combine the ICC_Profile chunks 6246 my $icc_profile = ''; 6247 defined $_ and $icc_profile .= $_ foreach @iccChunk; 6248 undef @iccChunk; # free memory 6249 $segDataPt = \$icc_profile; 6250 $length = length $icc_profile; 6251 my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); 6252 my %dirInfo = ( 6253 DataPt => $segDataPt, 6254 DataPos => $segPos + 14, 6255 DataLen => $length, 6256 DirStart => 0, 6257 DirLen => $length, 6258 Parent => $markerName, 6259 ); 6260 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 6261 if (defined $newData) { 6262 undef $$segDataPt; # free the old buffer 6263 $segDataPt = \$newData; 6264 } 6265 length $$segDataPt or $del = 1, last; 6266 # write as ICC multi-segment 6267 WriteMultiSegment($outfile, $marker, "ICC_PROFILE\0", $segDataPt, 'ICC') or $err = 1; 6268 undef $$segDataPt; 4793 6269 next Marker; 4794 } 4795 $combinedSegData .= substr($$segDataPt, 14); 4796 # continue accumulating segments unless this is the last 4797 next Marker unless $block_num == $blocks_tot; 4798 $doneDir{ICC_Profile} and $self->Warn('Multiple ICC_Profile records'); 4799 $doneDir{ICC_Profile} = 1; 4800 $segDataPt = \$combinedSegData; 4801 $length = length $combinedSegData; 4802 my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); 4803 my %dirInfo = ( 4804 DataPt => $segDataPt, 4805 DataPos => $segPos + 14, 4806 DataLen => $length, 4807 DirStart => 0, 4808 DirLen => $length, 4809 Parent => $markerName, 4810 ); 4811 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 4812 if (defined $newData) { 4813 undef $$segDataPt; # free the old buffer 4814 $segDataPt = \$newData; 4815 } 4816 length $$segDataPt or $del = 1, last; 4817 # write as ICC multi-segment 4818 WriteMultiSegment($outfile, $marker, "ICC_PROFILE\0", $segDataPt, 'ICC') or $err = 1; 4819 undef $combinedSegData; 4820 undef $$segDataPt; 4821 next Marker; 6270 } elsif (defined $chunkNum) { 6271 $self->WarnOnce('Invalid or extraneous ICC_Profile chunk(s)'); 6272 # fall through to preserve this extra profile... 6273 } 4822 6274 } elsif ($$segDataPt =~ /^FPXR\0/) { 4823 6275 $segType = 'FPXR'; 4824 6276 $$delGroup{FlashPix} and $del = 1; 6277 } elsif ($$segDataPt =~ /^MPF\0/) { 6278 $segType = 'MPF'; 6279 $$delGroup{MPF} and $del = 1; 4825 6280 } 4826 6281 } elsif ($marker == 0xe3) { # APP3 (Kodak Meta) … … 4834 6289 my %dirInfo = ( 4835 6290 DataPt => $segDataPt, 4836 DataPos => $segPos,6291 DataPos => -6, # (remember: relative to Base!) 4837 6292 DirStart => 6, 4838 6293 Base => $segPos + 6, … … 4848 6303 } else { 4849 6304 last Marker unless $self->Options('IgnoreMinorErrors'); 4850 $self->{CHANGED} = $oldChanged; # nothing changed4851 6305 } 4852 6306 # delete segment if IFD contains no entries … … 4880 6334 $newData = 'Ducky' . $newData if length $newData; 4881 6335 $segDataPt = \$newData; 4882 } else {4883 $self->{CHANGED} = $oldChanged;4884 6336 } 4885 6337 $del = 1 unless length $$segDataPt; … … 4921 6373 undef $$segDataPt; # free the old buffer 4922 6374 $segDataPt = \$newData; 4923 } else {4924 $self->{CHANGED} = $oldChanged;4925 6375 } 4926 6376 length $$segDataPt or $del = 1, last; … … 4930 6380 undef $$segDataPt; 4931 6381 next Marker; 6382 } 6383 } elsif ($marker == 0xee) { # APP14 (Adobe) 6384 if ($$segDataPt =~ /^Adobe/) { 6385 $segType = 'Adobe'; 6386 # delete it and replace it later if editing 6387 if ($$delGroup{Adobe} or $$editDirs{Adobe}) { 6388 $del = 1; 6389 undef $doneDir{Adobe}; # so we can add it back again above 6390 } 4932 6391 } 4933 6392 } elsif ($marker == 0xfe) { # COM (JPEG comment) … … 4938 6397 my $tagInfo = $Image::ExifTool::Extra{Comment}; 4939 6398 my $nvHash = $self->GetNewValueHash($tagInfo); 4940 if (IsOverwriting($nvHash, $segData) or $$delGroup{File}) { 4941 $newComment = GetNewValues($nvHash); 6399 my $val = $segData; 6400 $val =~ s/\0+$//; # allow for stupid software that adds NULL terminator 6401 if ($self->IsOverwriting($nvHash, $val) or $$delGroup{File}) { 6402 $newComment = $self->GetNewValue($nvHash); 4942 6403 } else { 4943 6404 delete $$editDirs{COM}; # we aren't editing COM after all … … 4947 6408 } 4948 6409 $self->VerboseValue('- Comment', $$segDataPt); 4949 if (defined $newComment and length $newComment) {6410 if (defined $newComment) { 4950 6411 # write out the comments 4951 6412 $self->VerboseValue('+ Comment', $newComment); … … 4954 6415 $verbose and print $out " Deleting COM segment\n"; 4955 6416 } 4956 ++$ self->{CHANGED};# increment the changed flag6417 ++$$self{CHANGED}; # increment the changed flag 4957 6418 undef $segDataPt; # don't write existing comment 4958 6419 } 4959 6420 last; # didn't want to loop anyway 4960 6421 } 6422 4961 6423 # delete necessary segments (including unknown segments if deleting all) 4962 6424 if ($del or ($$delGroup{'*'} and not $segType and $marker>=0xe0 and $marker<=0xef)) { 4963 6425 $segType = 'unknown' unless $segType; 4964 6426 $verbose and print $out " Deleting $markerName $segType segment\n"; 4965 ++$ self->{CHANGED};6427 ++$$self{CHANGED}; 4966 6428 next Marker; 4967 6429 } 4968 6430 # write out this segment if $segDataPt is still defined 4969 if (defined $segDataPt ) {6431 if (defined $segDataPt and defined $$segDataPt) { 4970 6432 # write the data for this record (the data could have been 4971 6433 # modified, so recalculate the length word) … … 4980 6442 } 4981 6443 undef $$segDataPt; # free the buffer 4982 } 4983 } 6444 undef $segDataPt; 6445 } 6446 } 6447 # make sure the ICC_Profile was complete 6448 $self->Error('Incomplete ICC_Profile record', 1) if defined $iccChunkCount; 4984 6449 pop @$path if @$path > $pn; 4985 6450 # if oldOutfile is still set, there was an error copying the JPEG … … 4992 6457 # set return value to -1 if we only had a write error 4993 6458 $rtnVal = -1 if $rtnVal and $err; 6459 if ($creatingEXV and $rtnVal > 0 and not $$self{CHANGED}) { 6460 $self->Error('Nothing written'); 6461 $rtnVal = -1; 6462 } 4994 6463 return $rtnVal; 4995 6464 } … … 5005 6474 $self->Options('IgnoreMinorErrors')) 5006 6475 { 5007 return '[ minor] Not a valid image';6476 return '[Minor] Not a valid image'; 5008 6477 } 5009 6478 return undef; … … 5073 6542 } 5074 6543 } 5075 return 'Not a floating point number' 6544 return 'Not a floating point number'; 5076 6545 } 5077 6546 if ($format =~ /^rational\d+u$/ and $val < 0) { … … 5111 6580 5112 6581 #------------------------------------------------------------------------------ 6582 # Rename a file (with patch for Windows Unicode file names, and other problem) 6583 # Inputs: 0) ExifTool ref, 1) old name, 2) new name 6584 # Returns: true on success 6585 sub Rename($$$) 6586 { 6587 my ($self, $old, $new) = @_; 6588 my ($result, $try, $winUni); 6589 6590 if ($self->EncodeFileName($old)) { 6591 $self->EncodeFileName($new, 1); 6592 $winUni = 1; 6593 } elsif ($self->EncodeFileName($new)) { 6594 $old = $_[1]; 6595 $self->EncodeFileName($old, 1); 6596 $winUni = 1; 6597 } 6598 for (;;) { 6599 if ($winUni) { 6600 $result = eval { Win32API::File::MoveFileExW($old, $new, 6601 Win32API::File::MOVEFILE_REPLACE_EXISTING() | 6602 Win32API::File::MOVEFILE_COPY_ALLOWED()) }; 6603 } else { 6604 $result = rename($old, $new); 6605 } 6606 last if $result or $^O ne 'MSWin32'; 6607 # keep trying for up to 0.5 seconds 6608 # (patch for Windows denial-of-service susceptibility) 6609 $try = ($try || 1) + 1; 6610 last if $try > 50; 6611 select(undef,undef,undef,0.01); # sleep for 0.01 sec 6612 } 6613 return $result; 6614 } 6615 6616 #------------------------------------------------------------------------------ 6617 # Delete a file (with patch for Windows Unicode file names) 6618 # Inputs: 0) ExifTool ref, 1-N) names of files to delete 6619 # Returns: number of files deleted 6620 sub Unlink($@) 6621 { 6622 my $self = shift; 6623 my $result = 0; 6624 while (@_) { 6625 my $file = shift; 6626 if ($self->EncodeFileName($file)) { 6627 ++$result if eval { Win32API::File::DeleteFileW($file) }; 6628 } else { 6629 ++$result if unlink $file; 6630 } 6631 } 6632 return $result; 6633 } 6634 6635 #------------------------------------------------------------------------------ 6636 # Set file times (Unix seconds since the epoch) 6637 # Inputs: 0) ExifTool ref, 1) file name or ref, 2) access time, 3) modification time, 6638 # 4) inode change or creation time (or undef for any time to avoid setting) 6639 # 5) flag to suppress warning 6640 # Returns: 1 on success, 0 on error 6641 my $k32SetFileTime; 6642 sub SetFileTime($$;$$$$) 6643 { 6644 my ($self, $file, $atime, $mtime, $ctime, $noWarn) = @_; 6645 my $saveFile; 6646 local *FH; 6647 6648 # open file by name if necessary 6649 unless (ref $file) { 6650 # (file will be automatically closed when *FH goes out of scope) 6651 unless ($self->Open(\*FH, $file, '+<')) { 6652 my $success; 6653 if (defined $atime or defined $mtime) { 6654 my ($a, $m, $c) = $self->GetFileTime($file); 6655 $atime = $a unless defined $atime; 6656 $mtime = $m unless defined $mtime; 6657 $success = eval { utime($atime, $mtime, $file) } if defined $atime and defined $mtime; 6658 } 6659 $self->Warn('Error opening file for update') unless $success; 6660 return $success; 6661 } 6662 $saveFile = $file; 6663 $file = \*FH; 6664 } 6665 # on Windows, try to work around incorrect file times when daylight saving time is in effect 6666 if ($^O eq 'MSWin32') { 6667 if (not eval { require Win32::API }) { 6668 $self->WarnOnce('Install Win32::API for proper handling of Windows file times'); 6669 } elsif (not eval { require Win32API::File }) { 6670 $self->WarnOnce('Install Win32API::File for proper handling of Windows file times'); 6671 } else { 6672 # get Win32 handle, needed for SetFileTime 6673 my $win32Handle = eval { Win32API::File::GetOsFHandle($file) }; 6674 unless ($win32Handle) { 6675 $self->Warn('Win32API::File::GetOsFHandle returned invalid handle'); 6676 return 0; 6677 } 6678 # convert Unix seconds to FILETIME structs 6679 my $time; 6680 foreach $time ($atime, $mtime, $ctime) { 6681 # set to NULL if not defined (i.e. do not change) 6682 defined $time or $time = 0, next; 6683 # convert to 100 ns intervals since 0:00 UTC Jan 1, 1601 6684 # (89 leap years between 1601 and 1970) 6685 my $wt = ($time + (((1970-1601)*365+89)*24*3600)) * 1e7; 6686 my $hi = int($wt / 4294967296); 6687 $time = pack 'LL', int($wt - $hi * 4294967296), $hi; # pack FILETIME struct 6688 } 6689 unless ($k32SetFileTime) { 6690 return 0 if defined $k32SetFileTime; 6691 $k32SetFileTime = new Win32::API('KERNEL32', 'SetFileTime', 'NPPP', 'I'); 6692 unless ($k32SetFileTime) { 6693 $self->Warn('Error calling Win32::API::SetFileTime'); 6694 $k32SetFileTime = 0; 6695 return 0; 6696 } 6697 } 6698 unless ($k32SetFileTime->Call($win32Handle, $ctime, $atime, $mtime)) { 6699 $self->Warn('Win32::API::SetFileTime returned ' . Win32::GetLastError()); 6700 return 0; 6701 } 6702 return 1; 6703 } 6704 } 6705 # other OS (or Windows fallback) 6706 if (defined $atime and defined $mtime) { 6707 my $success; 6708 local $SIG{'__WARN__'} = \&SetWarning; # (this may not be necessary) 6709 for (;;) { 6710 undef $evalWarning; 6711 # (this may fail on the first try if futimes is not implemented) 6712 $success = eval { utime($atime, $mtime, $file) }; 6713 last if $success or not defined $saveFile; 6714 close $file; 6715 $file = $saveFile; 6716 undef $saveFile; 6717 } 6718 unless ($noWarn) { 6719 if ($@ or $evalWarning) { 6720 $self->Warn(CleanWarning($@ || $evalWarning)); 6721 } elsif (not $success) { 6722 $self->Warn('Error setting file time'); 6723 } 6724 } 6725 return $success; 6726 } 6727 return 1; # (nothing to do) 6728 } 6729 6730 #------------------------------------------------------------------------------ 5113 6731 # Copy data block from RAF to output file in max 64kB chunks 5114 6732 # Inputs: 0) RAF ref, 1) outfile ref, 2) block size … … 5129 6747 5130 6748 #------------------------------------------------------------------------------ 5131 # copy image data from one file to another6749 # Copy image data from one file to another 5132 6750 # Inputs: 0) ExifTool object reference 5133 6751 # 1) reference to list of image data [ position, size, pad bytes ] … … 5137 6755 { 5138 6756 my ($self, $imageDataBlocks, $outfile) = @_; 5139 my $raf = $ self->{RAF};6757 my $raf = $$self{RAF}; 5140 6758 my ($dataBlock, $err); 5141 6759 my $num = @$imageDataBlocks; … … 5158 6776 5159 6777 #------------------------------------------------------------------------------ 5160 # write to binary data block6778 # Write to binary data block 5161 6779 # Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref 5162 6780 # Returns: Binary data block or undefined on error … … 5191 6809 my $tagInfo; 5192 6810 $dataPt = \$newData; 5193 foreach $tagInfo ( $self->GetNewTagInfoList($tagTablePtr)) {5194 my $tagID = $ tagInfo->{TagID};6811 foreach $tagInfo (sort { $$a{TagID} <=> $$b{TagID} } $self->GetNewTagInfoList($tagTablePtr)) { 6812 my $tagID = $$tagInfo{TagID}; 5195 6813 # evaluate conditional tags now if necessary 5196 6814 if (ref $$tagTablePtr{$tagID} eq 'ARRAY' or $$tagInfo{Condition}) { … … 5199 6817 } 5200 6818 # add offsets for variable-sized tags if necessary 5201 while (@varInfo and $varInfo[0] < $tagID) {5202 shift @varInfo; # discard index5203 $varSize = shift @varInfo; # get accumulated variable size6819 while (@varInfo and $varInfo[0][0] < $tagID) { 6820 $varSize = $varInfo[0][1]; # get accumulated variable size 6821 shift @varInfo; 5204 6822 } 5205 6823 my $count = 1; … … 5222 6840 $format = $defaultFormat; 5223 6841 } 6842 # read/write using variable format if changed in Hook 6843 $format = $varInfo[0][2] if @varInfo and $varInfo[0][0] == $tagID; 5224 6844 my $val = ReadValue($dataPt, $entry, $format, $count, $dirLen-$entry); 5225 6845 next unless defined $val; 5226 my $nvHash = $self->GetNewValueHash($tagInfo );5227 next unless IsOverwriting($nvHash, $val);5228 my $newVal = GetNewValues($nvHash);6846 my $nvHash = $self->GetNewValueHash($tagInfo, $$self{CUR_WRITE_GROUP}); 6847 next unless $self->IsOverwriting($nvHash, $val) > 0; 6848 my $newVal = $self->GetNewValue($nvHash); 5229 6849 next unless defined $newVal; # can't delete from a binary table 6850 # update DataMember with new value if necessary 6851 $$self{$$tagInfo{DataMember}} = $newVal if $$tagInfo{DataMember}; 5230 6852 # only write masked bits if specified 5231 6853 my $mask = $$tagInfo{Mask}; 5232 $newVal = ( $newVal & $mask) | ($val & ~$mask) if defined$mask;6854 $newVal = (($newVal << $$tagInfo{BitShift}) & $mask) | ($val & ~$mask) if $mask; 5233 6855 # set the size 5234 6856 if ($$tagInfo{DataTag} and not $$tagInfo{IsOffset}) { 5235 6857 warn 'Internal error' unless $newVal == 0xfeedfeed; 5236 my $data = $self->GetNewValue s($$tagInfo{DataTag});6858 my $data = $self->GetNewValue($$tagInfo{DataTag}); 5237 6859 $newVal = length($data) if defined $data; 5238 6860 my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; … … 5245 6867 $self->VerboseValue("- $dirName:$$tagInfo{Name}", $val); 5246 6868 $self->VerboseValue("+ $dirName:$$tagInfo{Name}", $newVal); 5247 ++$ self->{CHANGED};6869 ++$$self{CHANGED}; 5248 6870 } 5249 6871 } … … 5254 6876 my $fixup = $$dirInfo{Fixup}; 5255 6877 my $tagID; 5256 foreach $tagID (@{$ tagTablePtr->{IS_OFFSET}}) {6878 foreach $tagID (@{$$tagTablePtr{IS_OFFSET}}) { 5257 6879 $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID) or next; 5258 while (@varInfo and $varInfo[0] < $tagID) { 6880 while (@varInfo and $varInfo[0][0] < $tagID) { 6881 $varSize = $varInfo[0][1]; 5259 6882 shift @varInfo; 5260 $varSize = shift @varInfo;5261 6883 } 5262 6884 my $entry = $tagID * $increment + $varSize; # (no offset to dirStart for new dir data) … … 5265 6887 my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; 5266 6888 my $offset = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); 5267 # ignore if offset is zero ( ie. Ricoh DNG uses this to indicate no preview)6889 # ignore if offset is zero (eg. Ricoh DNG uses this to indicate no preview) 5268 6890 next unless $offset; 5269 6891 $fixup->AddFixup($entry, $$tagInfo{DataTag}, $format); 5270 6892 # handle the preview image now if this is a JPEG file 5271 next unless $ self->{FILE_TYPE} eq 'JPEG' and $$tagInfo{DataTag} and6893 next unless $$self{FILE_TYPE} eq 'JPEG' and $$tagInfo{DataTag} and 5272 6894 $$tagInfo{DataTag} eq 'PreviewImage' and defined $$tagInfo{OffsetPair}; 5273 6895 # NOTE: here we assume there are no var-sized tags between the … … 5276 6898 $entry = $$tagInfo{OffsetPair} * $increment + $varSize; 5277 6899 my $size = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); 5278 my $previewInfo = $self->{PREVIEW_INFO}; 5279 $previewInfo or $previewInfo = $self->{PREVIEW_INFO} = { }; 6900 my $previewInfo = $$self{PREVIEW_INFO}; 6901 $previewInfo or $previewInfo = $$self{PREVIEW_INFO} = { 6902 Fixup => new Image::ExifTool::Fixup, 6903 }; 5280 6904 # set flag indicating we are using short pointers 5281 6905 $$previewInfo{IsShort} = 1 unless $format eq 'int32u'; 5282 6906 $$previewInfo{Absolute} = 1 if $$tagInfo{IsOffset} and $$tagInfo{IsOffset} eq '3'; 5283 6907 # get the value of the Composite::PreviewImage tag 5284 $$previewInfo{Data} = $self->GetNewValue s($Image::ExifTool::Composite{PreviewImage});6908 $$previewInfo{Data} = $self->GetNewValue(GetCompositeTagInfo('PreviewImage')); 5285 6909 unless (defined $$previewInfo{Data}) { 5286 6910 if ($offset >= 0 and $offset + $size <= $$dirInfo{DataLen}) { … … 5300 6924 my $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID); 5301 6925 next unless defined $tagInfo; 5302 while (@varInfo and $varInfo[0] < $tagID) { 6926 while (@varInfo and $varInfo[0][0] < $tagID) { 6927 $varSize = $varInfo[0][1]; 5303 6928 shift @varInfo; 5304 $varSize = shift @varInfo;5305 6929 } 5306 6930 my $entry = int($tagID) * $increment + $varSize; … … 5316 6940 next unless $$tagInfo{SubDirectory}; # (just to be safe) 5317 6941 my %subdirInfo = ( DataPt => \$newData, DirStart => $entry ); 5318 my $subTablePtr = GetTagTable($ tagInfo->{SubDirectory}{TagTable});6942 my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); 5319 6943 my $dat = $self->WriteDirectory(\%subdirInfo, $subTablePtr); 5320 6944 substr($newData, $entry) = $dat if defined $dat and length $dat; … … 5331 6955 { 5332 6956 my ($self, $dirInfo, $tagTablePtr) = @_; 6957 $self or return 1; # allow dummy access 5333 6958 my $buff = ''; 5334 6959 $$dirInfo{OutFile} = \$buff; … … 5356 6981 =head1 AUTHOR 5357 6982 5358 Copyright 2003-20 11, Phil Harvey (phil at owl.phy.queensu.ca)6983 Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com) 5359 6984 5360 6985 This library is free software; you can redistribute it and/or modify it
Note:
See TracChangeset
for help on using the changeset viewer.