1#------------------------------------------------------------------------------ 2# File: Writer.pl 3# 4# Description: ExifTool write routines 5# 6# Notes: Also contains some less used ExifTool functions 7# 8# URL: https://exiftool.org/ 9# 10# Revisions: 12/16/2004 - P. Harvey Created 11#------------------------------------------------------------------------------ 12 13package Image::ExifTool; 14 15use strict; 16 17use Image::ExifTool::TagLookup qw(FindTagInfo TagExists); 18use Image::ExifTool::Fixup; 19 20sub AssembleRational($$@); 21sub LastInList($); 22sub CreateDirectory($$); 23sub NextFreeTagKey($$); 24sub RemoveNewValueHash($$$); 25sub RemoveNewValuesForGroup($$); 26sub GetWriteGroup1($$); 27sub Sanitize($$); 28sub ConvInv($$$$$;$$); 29 30my $loadedAllTables; # flag indicating we loaded all tables 31my $advFmtSelf; # ExifTool object during evaluation of advanced formatting expr 32 33# the following is a road map of where we write each directory 34# in the different types of files. 35my %tiffMap = ( 36 IFD0 => 'TIFF', 37 IFD1 => 'IFD0', 38 XMP => 'IFD0', 39 ICC_Profile => 'IFD0', 40 ExifIFD => 'IFD0', 41 GPS => 'IFD0', 42 SubIFD => 'IFD0', 43 GlobParamIFD => 'IFD0', 44 PrintIM => 'IFD0', 45 IPTC => 'IFD0', 46 Photoshop => 'IFD0', 47 InteropIFD => 'ExifIFD', 48 MakerNotes => 'ExifIFD', 49 CanonVRD => 'MakerNotes', # (so VRDOffset will get updated) 50 NikonCapture => 'MakerNotes', # (to allow delete by group) 51 PhaseOne => 'MakerNotes', # (for editing PhaseOne SensorCalibration tags) 52); 53my %exifMap = ( 54 IFD1 => 'IFD0', 55 EXIF => 'IFD0', # to write EXIF as a block 56 ExifIFD => 'IFD0', 57 GPS => 'IFD0', 58 SubIFD => 'IFD0', 59 GlobParamIFD => 'IFD0', 60 PrintIM => 'IFD0', 61 InteropIFD => 'ExifIFD', 62 MakerNotes => 'ExifIFD', 63 NikonCapture => 'MakerNotes', # (to allow delete by group) 64 # (no CanonVRD trailer allowed) 65); 66my %jpegMap = ( 67 %exifMap, # covers all JPEG EXIF mappings 68 JFIF => 'APP0', 69 CIFF => 'APP0', 70 IFD0 => 'APP1', 71 XMP => 'APP1', 72 ICC_Profile => 'APP2', 73 FlashPix => 'APP2', 74 MPF => 'APP2', 75 Meta => 'APP3', 76 MetaIFD => 'Meta', 77 RMETA => 'APP5', 78 Ducky => 'APP12', 79 Photoshop => 'APP13', 80 Adobe => 'APP14', 81 IPTC => 'Photoshop', 82 MakerNotes => ['ExifIFD', 'CIFF'], # (first parent is the default) 83 CanonVRD => 'MakerNotes', # (so VRDOffset will get updated) 84 NikonCapture => 'MakerNotes', # (to allow delete by group) 85 Comment => 'COM', 86); 87my %dirMap = ( 88 JPEG => \%jpegMap, 89 EXV => \%jpegMap, 90 TIFF => \%tiffMap, 91 ORF => \%tiffMap, 92 RAW => \%tiffMap, 93 EXIF => \%exifMap, 94); 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 99my %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 123my %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 131# groups we are allowed to delete 132# Notes: 133# 1) these names must either exist in %dirMap, or be translated in InitWriteDirs()) 134# 2) any dependencies must be added to %excludeGroups 135my @delGroups = qw( 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-* 142); 143# family 2 group names that we can delete 144my @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 149my %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 156my %permanentDir = ( QuickTime => 1 ); 157 158# lookup for all valid family 2 groups (lower case) 159my %family2groups = map { lc $_ => 1 } @delGroup2, 'Unknown'; 160 161# groups we don't delete when deleting all information 162my $protectedGroups = '(IFD1|SubIFD|InteropIFD|GlobParamIFD|PDF-update|Adobe)'; 163 164# other group names of new tag values to remove when deleting an entire group 165my %removeGroups = ( 166 IFD0 => [ 'EXIF', 'MakerNotes' ], 167 EXIF => [ 'MakerNotes' ], 168 ExifIFD => [ 'MakerNotes', 'InteropIFD' ], 169 Trailer => [ 'CanonVRD' ], #(because we can add back CanonVRD as a block) 170); 171# related family 0/1 groups in @delGroups (and not already in %jpegMap) 172# that must be removed from delete list when excluding a group 173my %excludeGroups = ( 174 EXIF => [ qw(IFD0 IFD1 ExifIFD GPS MakerNotes GlobParamIFD InteropIFD PrintIM SubIFD) ], 175 IFD0 => [ 'EXIF' ], 176 IFD1 => [ 'EXIF' ], 177 ExifIFD => [ 'EXIF' ], 178 GPS => [ 'EXIF' ], 179 MakerNotes => [ 'EXIF' ], 180 InteropIFD => [ 'EXIF' ], 181 GlobParamIFD => [ 'EXIF' ], 182 PrintIM => [ 'EXIF' ], 183 CIFF => [ 'MakerNotes' ], 184 # technically correct, but very uncommon and not a good reason to avoid deleting trailer 185 # IPTC => [ qw(AFCP FotoStation Trailer) ], 186 AFCP => [ 'Trailer' ], 187 FotoStation => [ 'Trailer' ], 188 CanonVRD => [ 'Trailer' ], 189 PhotoMechanic=> [ 'Trailer' ], 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 194my %translateWantGroup = ( 195 ciff => 'canonraw', 196); 197# group names to translate for writing 198my %translateWriteGroup = ( 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', 206); 207# names of valid EXIF and Meta directories (lower case keys): 208my %exifDirs = ( 209 gps => 'GPS', 210 exififd => 'ExifIFD', 211 subifd => 'SubIFD', 212 globparamifd => 'GlobParamIFD', 213 interopifd => 'InteropIFD', 214 previewifd => 'PreviewIFD', # (in MakerNotes) 215 metaifd => 'MetaIFD', # Kodak APP3 Meta 216 makernotes => 'MakerNotes', 217); 218# valid family 0 groups when WriteGroup is set to "All" 219my %allFam0 = ( 220 exif => 1, 221 makernotes => 1, 222); 223 224my @writableMacOSTags = qw( 225 FileCreateDate MDItemFinderComment MDItemFSCreationDate MDItemFSLabel MDItemUserTags 226 XAttrQuarantine 227); 228 229# min/max values for integer formats 230my %intRange = ( 231 'int8u' => [0, 0xff], 232 'int8s' => [-0x80, 0x7f], 233 'int16u' => [0, 0xffff], 234 'int16uRev' => [0, 0xffff], 235 'int16s' => [-0x8000, 0x7fff], 236 'int32u' => [0, 0xffffffff], 237 'int32s' => [-0x80000000, 0x7fffffff], 238 'int64u' => [0, 18446744073709551615], 239 'int64s' => [-9223372036854775808, 9223372036854775807], 240); 241# lookup for file types with block-writable EXIF 242my %blockExifTypes = map { $_ => 1 } qw(JPEG PNG JP2 MIE EXIF FLIF MOV MP4); 243 244my $maxSegmentLen = 0xfffd; # maximum length of data in a JPEG segment 245my $maxXMPLen = $maxSegmentLen; # maximum length of XMP data in JPEG 246 247# value separators when conversion list is used (in SetNewValue) 248my %listSep = ( PrintConv => '; ?', ValueConv => ' ' ); 249 250# printConv hash keys to ignore when doing reverse lookup 251my %ignorePrintConv = map { $_ => 1 } qw(OTHER BITMASK Notes); 252 253#------------------------------------------------------------------------------ 254# Set tag value 255# Inputs: 0) ExifTool object reference 256# 1) tag key, tag name, or '*' (optionally prefixed by group name), 257# or undef to reset all previous SetNewValue() calls 258# 2) new value (scalar, scalar ref or list ref), or undef to delete tag 259# 3-N) Options: 260# Type => PrintConv, ValueConv or Raw - specifies value type 261# AddValue => true to add to list of existing values instead of overwriting 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 264# Group => family 0 or 1 group name (case insensitive) 265# Replace => 0, 1 or 2 - overwrite previous new values (2=reset) 266# Protected => bitmask to write tags with specified protections 267# EditOnly => true to only edit existing tags (don't create new tag) 268# EditGroup => true to only edit existing groups (don't create new group) 269# Shift => undef, 0, +1 or -1 - shift value if possible 270# NoFlat => treat flattened tags as 'unsafe' 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 274# CreateGroups => [internal use] createGroups hash ref from related tags 275# ListOnly => [internal use] set only list or non-list tags 276# SetTags => [internal use] hash ref to return tagInfo refs of set tags 277# Sanitized => [internal use] set to avoid double-sanitizing the value 278# Returns: number of tags set (plus error string in list context) 279# Notes: For tag lists (like Keywords), call repeatedly with the same tag name for 280# each value in the list. Internally, the new information is stored in 281# the following members of the $$self{NEW_VALUE}{$tagInfo} hash: 282# TagInfo - tag info ref 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) 292# CreateGroups - hash of all family 0 group names where tag may be created 293# WriteGroup - group name where information is being written (correct case) 294# WantGroup - group name as specified in call to function (case insensitive) 295# Next - pointer to next new value hash (if more than one) 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 299# Shift - shift value 300# Save - counter used by SaveNewValues()/RestoreNewValues() 301# MAKER_NOTE_FIXUP - pointer to fixup if necessary for a maker note value 302sub SetNewValue($;$$%) 303{ 304 local $_; 305 my ($self, $tag, $value, %options) = @_; 306 my ($err, $tagInfo, $family); 307 my $verbose = $$self{OPTIONS}{Verbose}; 308 my $out = $$self{OPTIONS}{TextOut}; 309 my $protected = $options{Protected} || 0; 310 my $listOnly = $options{ListOnly}; 311 my $setTags = $options{SetTags}; 312 my $noFlat = $options{NoFlat}; 313 my $numSet = 0; 314 315 unless (defined $tag) { 316 delete $$self{NEW_VALUE}; 317 $$self{SAVE_COUNT} = 0; 318 $$self{DEL_GROUP} = { }; 319 return 1; 320 } 321 # allow value to be scalar or list reference 322 if (ref $value) { 323 if (ref $value eq 'ARRAY') { 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 } 346 } elsif (ref $value eq 'SCALAR') { 347 $value = $$value; 348 } 349 } 350 # un-escape as necessary and make sure the Perl UTF-8 flag is OFF for the value 351 # if perl is 5.6 or greater (otherwise our byte manipulations get corrupted!!) 352 $self->Sanitize(\$value) if defined $value and not ref $value and not $options{Sanitized}; 353 354 # set group name in options if specified 355 ($options{Group}, $tag) = ($1, $2) if $tag =~ /(.*):(.+)/; 356 357 # allow trailing '#' for ValueConv value 358 $options{Type} = 'ValueConv' if $tag =~ s/#$//; 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 $_; 527 } 528# 529# get list of tags we want to set 530# 531 my $origTag = $tag; 532 my @matchingTags = FindTagInfo($tag); 533 until (@matchingTags) { 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 =~ /-/; 576 } else { 577 $err = "Invalid tag name '${tag}'"; 578 $err .= " (remove the leading '\$')" if $tag =~ /^\$/; 579 } 580 } elsif ($langCode) { 581 $err = "Tag '${tag}' does not support alternate languages"; 582 } elsif ($wantGroup) { 583 $err = "Sorry, $wantGroup:$origTag doesn't exist or isn't writable"; 584 } else { 585 $err = "Sorry, $origTag is not writable"; 586 } 587 $verbose > 2 and print $out "$err\n"; 588 } 589 # all done 590 return ($numSet, $err) if wantarray; 591 $err and warn "$err\n"; 592 return $numSet; 593 } 594 # get group name that we're looking for 595 my $foundMatch = 0; 596# 597# determine the groups for all tags found, and the tag with 598# the highest priority group 599# 600 my (@tagInfoList, @writeAlsoList, %writeGroup, %preferred, %tagPriority); 601 my (%avoid, $wasProtected, $noCreate, %highestPriority, %highestQT); 602 603TAG: 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}; 610 if ($wantGroup) { 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); 661 $writeGroup = $mieGroup; # write to specific MIE group 662 # set specific write group with document number if specified 663 if ($writeGroup =~ /^MIE\d+$/ and $$tagInfo{Table}{WRITE_GROUP}) { 664 $writeGroup = $$tagInfo{Table}{WRITE_GROUP}; 665 $writeGroup =~ s/^MIE/$mieGroup/; 666 } 667 } elsif (not $$tagInfo{AllowGroup} or $lcWant !~ /^$$tagInfo{AllowGroup}$/i) { 668 # allow group1 name to be specified 669 next TAG unless $lcWant eq lc $grp[1]; 670 } 671 } 672 $writeGroup or $writeGroup = ($$tagInfo{WriteGroup} || $grp[0]); 673 $priority = $hiPri; # highest priority since group was specified 674 } 675 ++$foundMatch; 676 # must do a dummy call to the write proc to autoload write package 677 # before checking Writable flag 678 my $table = $$tagInfo{Table}; 679 my $writeProc = $$table{WRITE_PROC}; 680 # load source table if this was a user-defined table 681 if ($$table{SRC_TABLE}) { 682 my $src = GetTagTable($$table{SRC_TABLE}); 683 $writeProc = $$src{WRITE_PROC} unless $writeProc; 684 } 685 { 686 no strict 'refs'; 687 next unless $writeProc and &$writeProc(); 688 } 689 # must still check writable flags in case of UserDefined tags 690 my $writable = $$tagInfo{Writable}; 691 next unless $writable or ($$table{WRITABLE} and 692 not defined $writable and not $$tagInfo{SubDirectory}); 693 # set specific write group (if we didn't already) 694 if (not $writeGroup or ($translateWriteGroup{$writeGroup} and 695 (not $$tagInfo{WriteGroup} or $$tagInfo{WriteGroup} ne 'All'))) 696 { 697 # use default write group 698 $writeGroup = $$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP}; 699 # use group 0 name if no WriteGroup specified 700 my $group0 = $self->GetGroup($tagInfo, 0); 701 $writeGroup or $writeGroup = $group0; 702 # get priority for this group 703 unless ($priority) { 704 $priority = $$self{WRITE_PRIORITY}{lc($writeGroup)}; 705 unless ($priority) { 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; 711 } 712 # don't write tag if protected 713 my $prot = $$tagInfo{Protected}; 714 $prot = 1 if $noFlat and defined $$tagInfo{Flat}; 715 if ($prot) { 716 $prot &= ~$protected; 717 if ($prot) { 718 my %lkup = ( 1=>'unsafe', 2=>'protected', 3=>'unsafe and protected'); 719 $wasProtected = $lkup{$prot}; 720 if ($verbose > 1) { 721 my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); 722 print $out "Sorry, $wgrp1:$tag is $wasProtected for writing\n"; 723 } 724 next; 725 } 726 } 727 # set priority for this tag 728 $tagPriority{$tagInfo} = $priority; 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}) { 737 # create all tags with highest priority 738 $preferred{$lcTag}{$tagInfo} = 1; 739 ++$avoid{$lcTag} if $$tagInfo{Avoid}; 740 } 741 if ($$tagInfo{WriteAlso}) { 742 # store WriteAlso tags separately so we can set them first 743 push @writeAlsoList, $tagInfo; 744 } else { 745 push @tagInfoList, $tagInfo; 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 } 752 $writeGroup{$tagInfo} = $writeGroup; 753 } 754 # sort tag info list in reverse order of priority (highest number last) 755 # so we get the highest priority error message in the end 756 @tagInfoList = sort { $tagPriority{$a} <=> $tagPriority{$b} } @tagInfoList; 757 # must write any tags which also write other tags first 758 unshift @tagInfoList, @writeAlsoList if @writeAlsoList; 759 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 } 802 } 803 } 804 } 805 } 806# 807# generate new value hash for each tag 808# 809 my ($prioritySet, $createGroups, %alsoWrote); 810 811 delete $$self{CHECK_WARN}; # reset CHECK_PROC warnings 812 813 # loop through all valid tags to find the one(s) to write 814 foreach $tagInfo (@tagInfoList) { 815 next if $alsoWrote{$tagInfo}; # don't rewrite tags we already wrote 816 # only process List or non-List tags if specified 817 next if defined $listOnly and ($listOnly xor $$tagInfo{List}); 818 my $noConv; 819 my $writeGroup = $writeGroup{$tagInfo}; 820 my $permanent = $$tagInfo{Permanent}; 821 $permanent = $$tagInfo{Table}{PERMANENT} unless defined $permanent; 822 $writeGroup eq 'MakerNotes' and $permanent = 1 unless defined $permanent; 823 my $wgrp1 = $self->GetWriteGroup1($tagInfo, $writeGroup); 824 $tag = $$tagInfo{Name}; # get tag name for warnings 825 my $lcTag = lc $tag; 826 my $pref = $preferred{$lcTag} || { }; 827 my $shift = $options{Shift}; 828 my $addValue = $options{AddValue}; 829 if (defined $shift) { 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}; 836 unless ($shift) { 837 # set shift according to AddValue/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'; 842 } 843 if ($shift and (not defined $value or not length $value)) { 844 # (now allow -= to be used for shiftable tag - v8.05) 845 #$err = "No value for time shift of $wgrp1:$tag"; 846 #$verbose > 2 and print $out "$err\n"; 847 #next; 848 undef $shift; 849 } 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 { 856 $err = "$wgrp1:$tag is not shiftable"; 857 $verbose > 2 and print $out "$err\n"; 858 next; 859 } 860 } 861 my $val = $value; 862 if (defined $val) { 863 # check to make sure this is a List or Shift tag if adding 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 } 872 } 873 if ($shift) { 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"; 889 $verbose > 2 and print $out "$err\n"; 890 next; 891 } 892 $noConv = 1; # no conversions if shifting tag 893 } elsif (not length $val and $options{DelValue}) { 894 $noConv = 1; # no conversions for deleting empty value 895 } elsif (ref $val eq 'HASH' and not $$tagInfo{Struct}) { 896 $err = "Can't write a structure to $wgrp1:$tag"; 897 $verbose > 2 and print $out "$err\n"; 898 next; 899 } 900 } elsif ($permanent) { 901 return 0 if $options{IgnorePermanent}; 902 # can't delete permanent tags, so set them to DelValue or empty string instead 903 if (defined $$tagInfo{DelValue}) { 904 $val = $$tagInfo{DelValue}; 905 $noConv = 1; # DelValue is the raw value, so no conversion necessary 906 } else { 907 $val = ''; 908 } 909 } elsif ($addValue or $options{DelValue}) { 910 $err = "No value to add or delete in $wgrp1:$tag"; 911 $verbose > 2 and print $out "$err\n"; 912 next; 913 } else { 914 if ($$tagInfo{DelCheck}) { 915 #### eval DelCheck ($self, $tagInfo, $wantGroup) 916 my $err2 = eval $$tagInfo{DelCheck}; 917 $@ and warn($@), $err2 = 'Error evaluating DelCheck'; 918 if (defined $err2) { 919 # (allow other tags to be set using DelCheck as a hook) 920 $err2 or goto WriteAlso; # GOTO! 921 $err2 .= ' for' unless $err2 =~ /delete$/; 922 $err = "$err2 $wgrp1:$tag"; 923 $verbose > 2 and print $out "$err\n"; 924 next; 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; 940 } 941 $noConv = 1; # value is not defined, so don't do conversion 942 } 943 # apply inverse PrintConv and ValueConv conversions 944 # save ValueConv setting for use in ConvInv() 945 unless ($noConv) { 946 # set default conversion type used by ConvInv() and CHECK_PROC routines 947 $$self{ConvType} = $convType; 948 my $e; 949 ($val,$e) = $self->ConvInv($val,$tagInfo,$tag,$wgrp1,$$self{ConvType},$wantGroup); 950 if (defined $e) { 951 # empty error string causes error to be ignored without setting the value 952 $e or goto WriteAlso; # GOTO! 953 $err = $e; 954 } 955 } 956 if (not defined $val and defined $value) { 957 # if value conversion failed, we must still add a NEW_VALUE 958 # entry for this tag it it was a DelValue 959 next unless $options{DelValue}; 960 $val = 'xxx never delete xxx'; 961 } 962 $$self{NEW_VALUE} or $$self{NEW_VALUE} = { }; 963 if ($options{Replace}) { 964 # delete the previous new value 965 $self->GetNewValueHash($tagInfo, $writeGroup, 'delete', $options{ProtectSaved}); 966 # also delete related tag previous new values 967 if ($$tagInfo{WriteAlso}) { 968 my ($wgrp, $wtag); 969 if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { 970 $wgrp = $writeGroup . ':'; 971 } else { 972 $wgrp = ''; 973 } 974 foreach $wtag (keys %{$$tagInfo{WriteAlso}}) { 975 my ($n,$e) = $self->SetNewValue($wgrp . $wtag, undef, Replace=>2); 976 $numSet += $n; 977 } 978 } 979 $options{Replace} == 2 and ++$numSet, next; 980 } 981 982 if (defined $val) { 983 # we are editing this tag, so create a NEW_VALUE hash entry 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; 991 # save maker note information if writing maker notes 992 if ($$tagInfo{MakerNotes}) { 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) { 1000 # flag any AddValue or DelValue by creating the DelValue list 1001 $$nvHash{DelValue} or $$nvHash{DelValue} = [ ]; 1002 if ($shift) { 1003 # add shift value to list 1004 $$nvHash{Shift} = $val; 1005 } elsif ($options{DelValue}) { 1006 # don't create if we are replacing a specific value 1007 $$nvHash{IsCreating} = 0 unless $val eq '' or $$tagInfo{List}; 1008 # add delete value to list 1009 push @{$$nvHash{DelValue}}, ref $val eq 'ARRAY' ? @$val : $val; 1010 if ($verbose > 1) { 1011 my $verb = $permanent ? 'Replacing' : 'Deleting'; 1012 my $fromList = $$tagInfo{List} ? ' from list' : ''; 1013 my @vals = (ref $val eq 'ARRAY' ? @$val : $val); 1014 foreach (@vals) { 1015 if (ref $_ eq 'HASH') { 1016 require 'Image/ExifTool/XMPStruct.pl'; 1017 $_ = Image::ExifTool::XMP::SerializeStruct($_); 1018 } 1019 print $out "$verb $wgrp1:$tag$fromList if value is '${_}'\n"; 1020 } 1021 } 1022 } 1023 } 1024 # set priority flag to add only the high priority info 1025 # (will only create the priority tag if it doesn't exist, 1026 # others get changed only if they already exist) 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) { 1033 if ($permanent or $shift) { 1034 # don't create permanent or Shift-ed tag but define IsCreating 1035 # so we know that it is the preferred tag 1036 $$nvHash{IsCreating} = 0; 1037 } elsif (($$tagInfo{List} and not $options{DelValue}) or 1038 not ($$nvHash{DelValue} and @{$$nvHash{DelValue}}) or 1039 # also create tag if any DelValue value is empty ('') 1040 grep(/^$/,@{$$nvHash{DelValue}})) 1041 { 1042 $$nvHash{IsCreating} = $editOnly ? 0 : ($editGroup ? 2 : 1); 1043 # add to hash of groups where this tag is being created 1044 $createGroups or $createGroups = $options{CreateGroups} || { }; 1045 $$createGroups{$self->GetGroup($tagInfo, 0)} = 1; 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) 1068 } 1069 if ($shift or not $options{DelValue}) { 1070 $$nvHash{Value} or $$nvHash{Value} = [ ]; 1071 if (not $$tagInfo{List}) { 1072 # not a List tag -- overwrite existing value 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; 1078 } else { 1079 # add at end of existing list 1080 push @{$$nvHash{Value}}, ref $val eq 'ARRAY' ? @$val : $val; 1081 } 1082 if ($verbose > 1) { 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')); 1091 print $out "$verb $wgrp1:$tag$ifExists\n"; 1092 } 1093 } 1094 } elsif ($permanent) { 1095 $err = "Can't delete Permanent tag $wgrp1:$tag"; 1096 $verbose > 1 and print $out "$err\n"; 1097 next; 1098 } elsif ($addValue or $options{DelValue}) { 1099 $verbose > 1 and print $out "Adding/Deleting nothing does nothing\n"; 1100 next; 1101 } else { 1102 # create empty new value hash entry to delete this tag 1103 $self->GetNewValueHash($tagInfo, $writeGroup, 'delete'); 1104 my $nvHash = $self->GetNewValueHash($tagInfo, $writeGroup, 'create'); 1105 $$nvHash{WantGroup} = $wantGroup; 1106 $verbose > 1 and print $out "Deleting $wgrp1:$tag\n"; 1107 } 1108 $$setTags{$tagInfo} = 1 if $setTags; 1109 $prioritySet = 1 if $$pref{$tagInfo}; 1110WriteAlso: 1111 ++$numSet; 1112 # also write related tags 1113 my $writeAlso = $$tagInfo{WriteAlso}; 1114 if ($writeAlso) { 1115 my ($wgrp, $wtag, $n); 1116 if ($$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All' and $writeGroup) { 1117 $wgrp = $writeGroup . ':'; 1118 } else { 1119 $wgrp = ''; 1120 } 1121 local $SIG{'__WARN__'} = \&SetWarning; 1122 foreach $wtag (keys %$writeAlso) { 1123 my %opts = ( 1124 Type => 'ValueConv', 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 1132 ); 1133 undef $evalWarning; 1134 #### eval WriteAlso ($val) 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; 1139 $@ and $evalWarning = $@; 1140 unless ($evalWarning) { 1141 ($n,$evalWarning) = $self->SetNewValue($wgrp . $wtag, $v, %opts); 1142 $numSet += $n; 1143 # count this as being set if any related tag is set 1144 $prioritySet = 1 if $n and $$pref{$tagInfo}; 1145 } 1146 if ($evalWarning and (not $err or $verbose > 2)) { 1147 my $str = CleanWarning(); 1148 if ($str) { 1149 $str .= " for $wtag" unless $str =~ / for [-\w:]+$/; 1150 $str .= " in $wgrp1:$tag (WriteAlso)"; 1151 $err or $err = $str; 1152 print $out "$str\n" if $verbose > 2; 1153 } 1154 } 1155 } 1156 } 1157 } 1158 # print warning if we couldn't set our priority tag 1159 if (defined $err and not $prioritySet) { 1160 warn "$err\n" if $err and not wantarray; 1161 } elsif (not $numSet) { 1162 my $pre = $wantGroup ? $wantGroup . ':' : ''; 1163 if ($wasProtected) { 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 } 1193 } elsif ($$self{CHECK_WARN}) { 1194 $err = $$self{CHECK_WARN}; 1195 $verbose > 2 and print $out "$err\n"; 1196 } elsif ($err and not $verbose) { 1197 undef $err; 1198 } 1199 return ($numSet, $err) if wantarray; 1200 return $numSet; 1201} 1202 1203#------------------------------------------------------------------------------ 1204# set new values from information in specified file 1205# Inputs: 0) ExifTool object reference, 1) source file name or reference, etc 1206# 2-N) List of tags to set (or all if none specified), or reference(s) to 1207# hash for options to pass to SetNewValue. The Replace option defaults 1208# to 1 for SetNewValuesFromFile -- set this to 0 to allow multiple tags 1209# to be copied to a list 1210# Returns: Hash of information set successfully (includes Warning or Error messages) 1211# Notes: Tag names may contain a group prefix, a leading '-' to exclude from copy, 1212# and/or a trailing '#' to copy the ValueConv value. The tag name '*' may 1213# be used to represent all tags in a group. An optional destination tag 1214# may be specified with '>DSTTAG' ('DSTTAG<TAG' also works, but in this 1215# case the source tag may also be an expression involving tag names). 1216sub SetNewValuesFromFile($$;@) 1217{ 1218 local $_; 1219 my ($self, $srcFile, @setTags) = @_; 1220 my ($key, $tag, @exclude, @reqTags); 1221 1222 # get initial SetNewValuesFromFile options 1223 my %opts = ( Replace => 1 ); # replace existing list items by default 1224 while (ref $setTags[0] eq 'HASH') { 1225 $_ = shift @setTags; 1226 foreach $key (keys %$_) { 1227 $opts{$key} = $$_{$key}; 1228 } 1229 } 1230 # expand shortcuts 1231 @setTags and ExpandShortcuts(\@setTags); 1232 my $srcExifTool = new Image::ExifTool; 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}++; 1237 # set options for our extraction tool 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; 1243 # +------------------------------------------+ 1244 # ! DON'T FORGET!! Must consider each new ! 1245 # ! option to decide how it is handled here. ! 1246 # +------------------------------------------+ 1247 $srcExifTool->Options( 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}, 1262 ExtractEmbedded => $$options{ExtractEmbedded}, 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}, 1276 MissingTagValue => $$options{MissingTagValue}, 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}, 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; 1321 my $printConv = $$options{PrintConv}; 1322 if ($opts{Type}) { 1323 # save source type separately because it may be different than dst Type 1324 $opts{SrcType} = $opts{Type}; 1325 # override PrintConv option with initial Type if given 1326 $printConv = ($opts{Type} eq 'PrintConv' ? 1 : 0); 1327 $srcExifTool->Options(PrintConv => $printConv); 1328 } 1329 my $srcType = $printConv ? 'PrintConv' : 'ValueConv'; 1330 1331 # get all tags from source file (including MakerNotes block) 1332 my $info = $srcExifTool->ImageInfo($srcFile); 1333 return $info if $$info{Error} and $$info{Error} eq 'Error opening file'; 1334 delete $$srcExifTool{VALUE}{Error}; # delete so we can check this later 1335 1336 # sort tags in reverse order so we get priority tag last 1337 my @tags = reverse sort keys %$info; 1338# 1339# simply transfer all tags from source image if no tags specified 1340# 1341 unless (@setTags) { 1342 # transfer maker note information to this object 1343 $$self{MAKER_NOTE_FIXUP} = $$srcExifTool{MAKER_NOTE_FIXUP}; 1344 $$self{MAKER_NOTE_BYTE_ORDER} = $$srcExifTool{MAKER_NOTE_BYTE_ORDER}; 1345 foreach $tag (@tags) { 1346 # don't try to set errors or warnings 1347 next if $tag =~ /^(Error|Warning)\b/; 1348 # get appropriate value type if necessary 1349 if ($opts{SrcType} and $opts{SrcType} ne $srcType) { 1350 $$info{$tag} = $srcExifTool->GetValue($tag, $opts{SrcType}); 1351 } 1352 # set value for this tag 1353 my ($n, $e) = $self->SetNewValue($tag, $$info{$tag}, %opts); 1354 # delete this tag if we couldn't set it 1355 $n or delete $$info{$tag}; 1356 } 1357 return $info; 1358 } 1359# 1360# transfer specified tags in the proper order 1361# 1362 # 1) loop through input list of tags to set, and build @setList 1363 my (@setList, $set, %setMatches, $t); 1364 foreach $t (@setTags) { 1365 if (ref $t eq 'HASH') { 1366 # update current options 1367 foreach $key (keys %$t) { 1368 $opts{$key} = $$t{$key}; 1369 } 1370 next; 1371 } 1372 # make a copy of the current options for this setTag 1373 # (also use this hash to store expression and wildcard flags, EXPR and WILD) 1374 my $opts = { %opts }; 1375 $tag = lc $t; # change tag/group names to all lower case 1376 my (@fg, $grp, $dst, $dstGrp, $dstTag, $isExclude); 1377 # handle redirection to another tag 1378 if ($tag =~ /(.+?)\s*(>|<)\s*(.+)/) { 1379 $dstGrp = ''; 1380 my $opt; 1381 if ($2 eq '>') { 1382 ($tag, $dstTag) = ($1, $3); 1383 # flag add and delete (eg. '+<' and '-<') redirections 1384 $opt = $1 if $tag =~ s/\s*([-+])$// or $dstTag =~ s/^([-+])\s*//; 1385 } else { 1386 ($tag, $dstTag) = ($3, $1); 1387 $opt = $1 if $dstTag =~ s/\s*([-+])$//; 1388 # handle expressions 1389 if ($tag =~ /\$/) { 1390 $tag = $t; # restore original case 1391 # recover leading whitespace (except for initial single space) 1392 $tag =~ s/(.+?)\s*(>|<) ?//; 1393 $$opts{EXPR} = 1; # flag this expression 1394 } else { 1395 $opt = $1 if $tag =~ s/^([-+])\s*//; 1396 } 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; 1401 # translate '+' and '-' to appropriate SetNewValue option 1402 if ($opt) { 1403 $$opts{{ '+' => 'AddValue', '-' => 'DelValue' }->{$opt}} = 1; 1404 $$opts{Shift} = 0; # shift if shiftable 1405 } 1406 ($dstGrp, $dstTag) = ($1, $2) if $dstTag =~ /(.*):(.+)/; 1407 # ValueConv may be specified separately on the destination with '#' 1408 $$opts{Type} = 'ValueConv' if $dstTag =~ s/#$//; 1409 # replace tag name of 'all' with '*' 1410 $dstTag = '*' if $dstTag eq 'all'; 1411 } 1412 unless ($$opts{EXPR}) { 1413 $isExclude = ($tag =~ s/^-//); 1414 if ($tag =~ /(.*):(.+)/) { 1415 ($grp, $tag) = ($1, $2); 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 } 1423 } 1424 # allow ValueConv to be specified by a '#' on the tag name 1425 if ($tag =~ s/#$//) { 1426 $$opts{SrcType} = 'ValueConv'; 1427 $$opts{Type} = 'ValueConv' unless $dstTag; 1428 } 1429 # replace 'all' with '*' in tag and group names 1430 $tag = '*' if $tag eq 'all'; 1431 # allow wildcards in tag names (handle differently from all tags: '*') 1432 if ($tag =~ /[?*]/ and $tag ne '*') { 1433 $$opts{WILD} = 1; # set flag indicating wildcards were used in source tag 1434 $tag =~ s/\*/[-\\w]*/g; 1435 $tag =~ s/\?/[-\\w]/g; 1436 } 1437 } 1438 # redirect, exclude or set this tag (Note: @fg is empty if we don't care about the group) 1439 if ($dstTag) { 1440 # redirect this tag 1441 $isExclude and return { Error => "Can't redirect excluded tag" }; 1442 # set destination group the same as source if necessary 1443 # (removed in 7.72 so '-*:*<xmp:*' will preserve XMP family 1 groups) 1444 # $dstGrp = $grp if $dstGrp eq '*' and $grp; 1445 # write to specified destination group/tag 1446 $dst = [ $dstGrp, $dstTag ]; 1447 } elsif ($isExclude) { 1448 # implicitly assume '*' if first entry is an exclusion 1449 unshift @setList, [ [ ], '*', [ '', '*' ], $opts ] unless @setList; 1450 # exclude this tag by leaving $dst undefined 1451 } else { 1452 $dst = [ $grp || '', $$opts{WILD} ? '*' : $tag ]; # use same group name for dest 1453 } 1454 # save in reverse order so we don't set tags before an exclude 1455 unshift @setList, [ \@fg, $tag, $dst, $opts ]; 1456 } 1457 # 2) initialize lists of matching tags for each setTag 1458 foreach $set (@setList) { 1459 $$set[2] and $setMatches{$set} = [ ]; 1460 } 1461 # 3) loop through all tags in source image and save tags matching each setTag 1462 my %rtnInfo; 1463 foreach $tag (@tags) { 1464 # don't try to set errors or warnings 1465 if ($tag =~ /^(Error|Warning)( |$)/) { 1466 $rtnInfo{$tag} = $$info{$tag}; 1467 next; 1468 } 1469 # only set specified tags 1470 my $lcTag = lc(GetTagName($tag)); 1471 my (@grp, %grp); 1472SET: foreach $set (@setList) { 1473 # check first for matching tag 1474 unless ($$set[1] eq $lcTag or $$set[1] eq '*') { 1475 # handle wildcards 1476 next unless $$set[3]{WILD} and $lcTag =~ /^$$set[1]$/; 1477 } 1478 # then check for matching group 1479 if (@{$$set[0]}) { 1480 # get lower case group names if not done already 1481 unless (@grp) { 1482 @grp = map(lc, $srcExifTool->GetGroup($tag)); 1483 $grp{$_} = 1 foreach @grp; 1484 } 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 1497 # add to the list of tags matching this setTag 1498 push @{$setMatches{$set}}, $tag; 1499 } 1500 } 1501 # 4) loop through each setTag in original order, setting new tag values 1502 foreach $set (reverse @setList) { 1503 # get options for SetNewValue 1504 my $opts = $$set[3]; 1505 # handle expressions 1506 if ($$opts{EXPR}) { 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 '*'; 1517 $$opts{Group} = $dstGrp if $dstGrp; 1518 my @rtnVals = $self->SetNewValue($dstTag, $val, %$opts); 1519 $rtnInfo{$dstTag} = $val if $rtnVals[0]; # tag was set successfully 1520 next; 1521 } 1522 foreach $tag (@{$setMatches{$set}}) { 1523 my ($val, $noWarn); 1524 if ($$opts{SrcType} and $$opts{SrcType} ne $srcType) { 1525 $val = $srcExifTool->GetValue($tag, $$opts{SrcType}); 1526 } else { 1527 $val = $$info{$tag}; 1528 } 1529 my ($dstGrp, $dstTag) = @{$$set[2]}; 1530 if ($dstGrp) { 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); 1537 $noWarn = 1; # don't warn on wildcard destinations 1538 } 1539 $$opts{Group} = join ':', @dstGrp; 1540 } else { 1541 delete $$opts{Group}; 1542 } 1543 # transfer maker note information if setting this tag 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}; 1547 } 1548 if ($dstTag eq '*') { 1549 $dstTag = $tag; 1550 $noWarn = 1; 1551 } 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 } 1565 # set value(s) for this tag 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 1572 if ($wrn and not $noWarn) { 1573 # return this warning 1574 $rtnInfo{NextFreeTagKey(\%rtnInfo, 'Warning')} = $wrn; 1575 $noWarn = 1; 1576 } 1577 $rtnInfo{$tag} = $val if $rtn; # tag was set successfully 1578 } 1579 } 1580 return \%rtnInfo; # return information that we set 1581} 1582 1583#------------------------------------------------------------------------------ 1584# Get new value(s) for tag 1585# Inputs: 0) ExifTool object reference, 1) tag name (or tagInfo or nvHash ref, not public) 1586# 2) optional pointer to return new value hash reference (not part of public API) 1587# Returns: List of new Raw values (list may be empty if tag is being deleted) 1588# Notes: 1) Preferentially returns new value from Extra table if writable Extra tag exists 1589# 2) Must call AFTER IsOverwriting() returns 1 to get proper value for shifted times 1590# 3) Tag name is case sensitive and may be prefixed by family 0 or 1 group name 1591# 4) Value may have been modified by CHECK_PROC routine after ValueConv 1592sub GetNewValue($$;$) 1593{ 1594 local $_; 1595 my $self = shift; 1596 my $tag = shift; 1597 my $nvHash; 1598 if ((ref $tag eq 'HASH' and $$tag{IsNVH}) or not defined $tag) { 1599 $nvHash = $tag; 1600 } else { 1601 my $newValueHashPt = shift; 1602 if ($$self{NEW_VALUE}) { 1603 my ($group, $tagInfo); 1604 if (ref $tag) { 1605 $nvHash = $self->GetNewValueHash($tag); 1606 } elsif (defined($tagInfo = $Image::ExifTool::Extra{$tag}) and 1607 $$tagInfo{Writable}) 1608 { 1609 $nvHash = $self->GetNewValueHash($tagInfo); 1610 } else { 1611 # separate group from tag name 1612 my @groups; 1613 @groups = split ':', $1 if $tag =~ s/(.*)://; 1614 my @tagInfoList = FindTagInfo($tag); 1615 # decide which tag we want 1616GNV_TagInfo: foreach $tagInfo (@tagInfoList) { 1617 my $nvh = $self->GetNewValueHash($tagInfo) or next; 1618 # select tag in specified group(s) if necessary 1619 foreach (@groups) { 1620 next if $_ eq $$nvh{WriteGroup}; 1621 my @grps = $self->GetGroup($tagInfo); 1622 if ($grps[0] eq $$nvh{WriteGroup}) { 1623 # check family 1 group only if WriteGroup is not specific 1624 next if $_ eq $grps[1]; 1625 } else { 1626 # otherwise check family 0 group 1627 next if $_ eq $grps[0]; 1628 } 1629 # also check family 7 1630 next if /^ID-(.*)/i and IsSameID($$tagInfo{TagID}, $1); 1631 # step to next entry in list 1632 $nvh = $$nvh{Next} or next GNV_TagInfo; 1633 } 1634 $nvHash = $nvh; 1635 # give priority to the one we are creating 1636 last if defined $$nvHash{IsCreating}; 1637 } 1638 } 1639 } 1640 # return new value hash if requested 1641 $newValueHashPt and $$newValueHashPt = $nvHash; 1642 } 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) { 1663 my $err = &$checkProc($self, $tagInfo, \$val); 1664 if ($err or not defined $val) { 1665 $err or $err = 'Error generating raw value'; 1666 $self->WarnOnce("$err for $$tagInfo{Name}"); 1667 @$vals = (); 1668 last; 1669 } 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]; 1696} 1697 1698#------------------------------------------------------------------------------ 1699# Return the total number of new values set 1700# Inputs: 0) ExifTool object reference 1701# 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 1703# ("pseudo" values are those which don't require rewriting the file to change) 1704sub CountNewValues($) 1705{ 1706 my $self = shift; 1707 my $newVal = $$self{NEW_VALUE}; 1708 my ($num, $pseudo) = (0, 0); 1709 if ($newVal) { 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}}; 1721 return $num unless wantarray; 1722 return ($num, $pseudo); 1723} 1724 1725#------------------------------------------------------------------------------ 1726# Save new values for subsequent restore 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 1730sub SaveNewValues($) 1731{ 1732 my $self = shift; 1733 my $newValues = $$self{NEW_VALUE}; 1734 my $saveCount = ++$$self{SAVE_COUNT}; 1735 my $key; 1736 foreach $key (keys %$newValues) { 1737 my $nvHash = $$newValues{$key}; 1738 while ($nvHash) { 1739 # set Save count if not done already 1740 $$nvHash{Save} or $$nvHash{Save} = $saveCount; 1741 $nvHash = $$nvHash{Next}; 1742 } 1743 } 1744 # initialize hash for saving overwritten new values 1745 $$self{SAVE_NEW_VALUE} = { }; 1746 # make a copy of the delete group hash 1747 my %delGrp = %{$$self{DEL_GROUP}}; 1748 $$self{SAVE_DEL_GROUP} = \%delGrp; 1749 return $saveCount; 1750} 1751 1752#------------------------------------------------------------------------------ 1753# Restore new values to last saved state 1754# Inputs: 0) ExifTool object reference 1755# Notes: Restores saved new values, but currently doesn't restore them in the 1756# original order, so there may be some minor side-effects when restoring tags 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 1760sub RestoreNewValues($) 1761{ 1762 my $self = shift; 1763 my $newValues = $$self{NEW_VALUE}; 1764 my $savedValues = $$self{SAVE_NEW_VALUE}; 1765 my $key; 1766 # 1) remove any new values which don't have the Save flag set 1767 if ($newValues) { 1768 my @keys = keys %$newValues; 1769 foreach $key (@keys) { 1770 my $lastHash; 1771 my $nvHash = $$newValues{$key}; 1772 while ($nvHash) { 1773 if ($$nvHash{Save}) { 1774 $lastHash = $nvHash; 1775 } else { 1776 # remove this entry from the list 1777 if ($lastHash) { 1778 $$lastHash{Next} = $$nvHash{Next}; 1779 } elsif ($$nvHash{Next}) { 1780 $$newValues{$key} = $$nvHash{Next}; 1781 } else { 1782 delete $$newValues{$key}; 1783 } 1784 } 1785 $nvHash = $$nvHash{Next}; 1786 } 1787 } 1788 } 1789 # 2) restore saved new values 1790 if ($savedValues) { 1791 $newValues or $newValues = $$self{NEW_VALUE} = { }; 1792 foreach $key (keys %$savedValues) { 1793 if ($$newValues{$key}) { 1794 # add saved values to end of list 1795 my $nvHash = LastInList($$newValues{$key}); 1796 $$nvHash{Next} = $$savedValues{$key}; 1797 } else { 1798 $$newValues{$key} = $$savedValues{$key}; 1799 } 1800 } 1801 $$self{SAVE_NEW_VALUE} = { }; # reset saved new values 1802 } 1803 # 3) restore delete groups 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 1810# Inputs: 0) ExifTool object reference, 1) file name or file ref 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 1814# Returns: 1=time changed OK, 0=nothing done, -1=error setting time 1815# (increments CHANGED flag and sets corresponding WRITTEN tag) 1816sub SetFileModifyDate($$;$$$) 1817{ 1818 my ($self, $file, $originalTime, $tag, $isUnixTime) = @_; 1819 my $nvHash; 1820 $tag = 'FileModifyDate' unless defined $tag; 1821 my $val = $self->GetNewValue($tag, \$nvHash); 1822 return 0 unless defined $val; 1823 my $isOverwriting = $self->IsOverwriting($nvHash); 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'; 1828 if ($isOverwriting < 0) { # are we shifting time? 1829 # use original time of this file if not specified 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); 1852 return 1; 1853} 1854 1855#------------------------------------------------------------------------------ 1856# Change file name and/or directory from FileName and Directory tags 1857# Inputs: 0) ExifTool object reference, 1) current file name (including path) 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) 1862# Returns: 1=name changed OK, 0=nothing changed, -1=error changing name 1863# (and increments CHANGED flag if filename changed) 1864# Notes: Will not overwrite existing file. Creates directories as necessary. 1865sub SetFileName($$;$$$) 1866{ 1867 my ($self, $file, $newName, $opt, $usedFlag) = @_; 1868 my ($nvHash, $doName, $doDir); 1869 1870 $opt or $opt = ''; 1871 # determine the new file name 1872 unless (defined $newName) { 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; 1882 } else { 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; 1942 } 1943 # create directory for new file if necessary 1944 my $result; 1945 if (($result = $self->CreateDirectory($newName)) != 0) { 1946 if ($result < 0) { 1947 $self->Warn("Error creating directory for '${newName}'"); 1948 return -1; 1949 } 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; 1984 } 1985 # attempt to rename the file 1986 unless ($self->Rename($file, $newName)) { 1987 local (*EXIFTOOL_SFN_IN, *EXIFTOOL_SFN_OUT); 1988 # renaming didn't work, so copy the file instead 1989 unless ($self->Open(\*EXIFTOOL_SFN_IN, $file)) { 1990 $self->Error("Error opening '${file}'"); 1991 return -1; 1992 } 1993 unless ($self->Open(\*EXIFTOOL_SFN_OUT, $newName, '>')) { 1994 close EXIFTOOL_SFN_IN; 1995 $self->Error("Error creating '${newName}'"); 1996 return -1; 1997 } 1998 binmode EXIFTOOL_SFN_IN; 1999 binmode EXIFTOOL_SFN_OUT; 2000 my ($buff, $err); 2001 while (read EXIFTOOL_SFN_IN, $buff, 65536) { 2002 print EXIFTOOL_SFN_OUT $buff or $err = 1; 2003 } 2004 close EXIFTOOL_SFN_OUT or $err = 1; 2005 close EXIFTOOL_SFN_IN; 2006 if ($err) { 2007 $self->Unlink($newName); # erase bad output file 2008 $self->Error("Error writing '${newName}'"); 2009 return -1; 2010 } 2011 # preserve modification time 2012 my ($aTime, $mTime, $cTime) = $self->GetFileTime($file); 2013 $self->SetFileTime($newName, $aTime, $mTime, $cTime); 2014 # remove the original file 2015 $self->Unlink($file) or $self->Warn('Error removing old file'); 2016 } 2017 $$self{NewName} = $newName; # remember new file name 2018 ++$$self{CHANGED}; 2019 $self->VerboseValue('+ FileName', $newName); 2020 return 1; 2021} 2022 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 2028sub 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#------------------------------------------------------------------------------ 2077# Write information back to file 2078# Inputs: 0) ExifTool object reference, 2079# 1) input filename, file ref, RAF ref, or scalar ref (or '' or undef to create from scratch) 2080# 2) output filename, file ref, or scalar ref (or undef to overwrite) 2081# 3) optional output file type (required only if input file is not specified 2082# and output file is a reference) 2083# Returns: 1=file written OK, 2=file written but no changes made, 0=file write error 2084sub WriteInfo($$;$$) 2085{ 2086 local ($_, *EXIFTOOL_FILE2, *EXIFTOOL_OUTFILE); 2087 my ($self, $infile, $outfile, $outType) = @_; 2088 my (@fileTypeList, $fileType, $tiffType, $hdr, $seekErr, $type, $tmpfile); 2089 my ($inRef, $outRef, $closeIn, $closeOut, $outPos, $outBuff, $eraseIn, $raf, $fileExt); 2090 my ($hardLink, $symLink, $testName); 2091 my $oldRaf = $$self{RAF}; 2092 my $rtnVal = 0; 2093 2094 # initialize member variables 2095 $self->Init(); 2096 $$self{IsWriting} = 1; 2097 2098 # first, save original file modify date if necessary 2099 # (do this now in case we are modifying file in place and shifting date) 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 2105 defined $infile and ref $infile ne 'SCALAR') 2106 { 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; 2115 } 2116# 2117# do quick in-place change of file dir/name or date if that is all we are doing 2118# 2119 my ($numNew, $numPseudo) = $self->CountNewValues(); 2120 if (not defined $outfile and defined $infile) { 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 } 2134 if ($numNew == $numPseudo) { 2135 $rtnVal = 2; 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; 2154 } 2155 return $rtnVal; 2156 } elsif (defined $newFileName and length $newFileName) { 2157 # can't simply rename file, so just set the output name if new FileName 2158 # --> in this case, must erase original copy 2159 if (ref $infile) { 2160 $outfile = $newFileName; 2161 # can't delete original 2162 } elsif ($self->IsOverwriting($nvHash, $infile)) { 2163 $outfile = GetNewFileName($infile, $newFileName); 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; 2173 } 2174 } 2175 } 2176# 2177# set up input file 2178# 2179 if (ref $infile) { 2180 $inRef = $infile; 2181 if (UNIVERSAL::isa($inRef,'GLOB')) { 2182 seek($inRef, 0, 0); # make sure we are at the start of the file 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 $@)) { 2187 # convert image data from UTF-8 to character stream if necessary 2188 my $buff = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$inRef)) : Encode::encode('utf8',$$inRef); 2189 if (defined $outfile) { 2190 $inRef = \$buff; 2191 } else { 2192 $$inRef = $buff; 2193 } 2194 } 2195 } elsif (defined $infile and $infile ne '') { 2196 # write to a temporary file if no output file given 2197 $outfile = $tmpfile = "${infile}_exiftool_tmp" unless defined $outfile; 2198 if ($self->Open(\*EXIFTOOL_FILE2, $infile)) { 2199 $fileExt = GetFileExtension($infile); 2200 $fileType = GetFileType($infile); 2201 @fileTypeList = GetFileType($infile); 2202 $tiffType = $$self{FILE_EXT} = GetFileExtension($infile); 2203 $self->VPrint(0, "Rewriting $infile...\n"); 2204 $inRef = \*EXIFTOOL_FILE2; 2205 $closeIn = 1; # we must close the file since we opened it 2206 } else { 2207 $self->Error('Error opening file'); 2208 return 0; 2209 } 2210 } elsif (not defined $outfile) { 2211 $self->Error("WriteInfo(): Must specify infile or outfile\n"); 2212 return 0; 2213 } else { 2214 # create file from scratch 2215 $outType = GetFileExtension($outfile) unless $outType or ref $outfile; 2216 if (CanCreate($outType)) { 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 } 2226 } elsif ($outType) { 2227 $self->Error("Can't create $outType files"); 2228 return 0; 2229 } else { 2230 $self->Error("Can't create file (unknown type)"); 2231 return 0; 2232 } 2233 } 2234 unless (@fileTypeList) { 2235 if ($fileType) { 2236 @fileTypeList = ( $fileType ); 2237 } else { 2238 @fileTypeList = @fileTypes; 2239 $tiffType = 'TIFF'; 2240 } 2241 } 2242# 2243# set up output file 2244# 2245 if (ref $outfile) { 2246 $outRef = $outfile; 2247 if (UNIVERSAL::isa($outRef,'GLOB')) { 2248 binmode($outRef); 2249 $outPos = tell($outRef); 2250 } else { 2251 # initialize our output buffer if necessary 2252 defined $$outRef or $$outRef = ''; 2253 $outPos = length($$outRef); 2254 } 2255 } elsif (not defined $outfile) { 2256 # editing in place, so write to memory first 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 } 2262 $outBuff = ''; 2263 $outRef = \$outBuff; 2264 $outPos = 0; 2265 } elsif ($self->Exists($outfile)) { 2266 $self->Error("File already exists: $outfile"); 2267 } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) { 2268 $outRef = \*EXIFTOOL_OUTFILE; 2269 $closeOut = 1; # we must close $outRef 2270 binmode($outRef); 2271 $outPos = 0; 2272 } else { 2273 my $tmp = $tmpfile ? ' temporary' : ''; 2274 $self->Error("Error creating$tmp file: $outfile"); 2275 } 2276# 2277# write the file 2278# 2279 until ($$self{VALUE}{Error}) { 2280 # create random access file object (disable seek test in case of straight copy) 2281 $raf or $raf = new File::RandomAccess($inRef, 1); 2282 $raf->BinMode(); 2283 if ($numNew == $numPseudo) { 2284 $rtnVal = 1; 2285 # just do a straight copy of the file (no "real" tags are being changed) 2286 my $buff; 2287 while ($raf->Read($buff, 65536)) { 2288 Write($outRef, $buff) or $rtnVal = -1, last; 2289 } 2290 last; 2291 } elsif (not ref $infile and ($infile eq '-' or $infile =~ /\|$/)) { 2292 # patch for Windows command shell pipe 2293 $$raf{TESTED} = -1; # force buffering 2294 } else { 2295 $raf->SeekTest(); 2296 } 2297 # $raf->Debug() and warn " RAF debugging enabled!\n"; 2298 my $inPos = $raf->Tell(); 2299 $$self{RAF} = $raf; 2300 my %dirInfo = ( 2301 RAF => $raf, 2302 OutFile => $outRef, 2303 ); 2304 $raf->Read($hdr, 1024) or $hdr = ''; 2305 $raf->Seek($inPos, 0) or $seekErr = 1; 2306 my $wrongType; 2307 until ($seekErr) { 2308 $type = shift @fileTypeList; 2309 # do quick test to see if this is the right file type 2310 if ($magicNumber{$type} and length($hdr) and $hdr !~ /^$magicNumber{$type}/s) { 2311 next if @fileTypeList; 2312 $wrongType = 1; 2313 last; 2314 } 2315 # save file type in member variable 2316 $dirInfo{Parent} = $$self{FILE_TYPE} = $$self{PATH}[0] = $type; 2317 # determine which directories we must write for this file type 2318 $self->InitWriteDirs($type); 2319 if ($type eq 'JPEG' or $type eq 'EXV') { 2320 $rtnVal = $self->WriteJPEG(\%dirInfo); 2321 } elsif ($type eq 'TIFF') { 2322 # disallow writing of some TIFF-based RAW images: 2323 if (grep /^$tiffType$/, @{$noWriteFile{TIFF}}) { 2324 $fileType = $tiffType; 2325 undef $rtnVal; 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 } 2331 $dirInfo{Parent} = $tiffType; 2332 $rtnVal = $self->ProcessTIFF(\%dirInfo); 2333 } 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'; 2347 } elsif ($type eq 'ORF' or $type eq 'RAW') { 2348 $rtnVal = $self->ProcessTIFF(\%dirInfo); 2349 } elsif ($type eq 'EXIF') { 2350 # go through WriteDirectory so block writes, etc are handled 2351 my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); 2352 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 2353 if (defined $buff) { 2354 $rtnVal = Write($outRef, $buff) ? 1 : -1; 2355 } else { 2356 $rtnVal = 0; 2357 } 2358 } else { 2359 undef $rtnVal; # flag that we don't write this type of file 2360 } 2361 # all done unless we got the wrong type 2362 last if $rtnVal; 2363 last unless @fileTypeList; 2364 # seek back to original position in files for next try 2365 $raf->Seek($inPos, 0) or $seekErr = 1, last; 2366 if (UNIVERSAL::isa($outRef,'GLOB')) { 2367 seek($outRef, 0, $outPos); 2368 } else { 2369 $$outRef = substr($$outRef, 0, $outPos); 2370 } 2371 } 2372 # print file format errors 2373 unless ($rtnVal) { 2374 my $err; 2375 if ($seekErr) { 2376 $err = 'Error seeking in file'; 2377 } elsif ($fileType and defined $rtnVal) { 2378 if ($$self{VALUE}{Error}) { 2379 # existing error message will do 2380 } elsif ($fileType eq 'RAW') { 2381 $err = 'Writing this type of RAW file is not supported'; 2382 } else { 2383 if ($wrongType) { 2384 my $type = $fileExt || ($fileType eq 'TIFF' ? $tiffType : $fileType); 2385 $err = "Not a valid $type"; 2386 # do a quick check to see what this file looks like 2387 foreach $type (@fileTypes) { 2388 next unless $magicNumber{$type}; 2389 next unless $hdr =~ /^$magicNumber{$type}/s; 2390 $err .= " (looks more like a $type)"; 2391 last; 2392 } 2393 } else { 2394 $err = 'Format error in file'; 2395 } 2396 } 2397 } elsif ($fileType) { 2398 # get specific type of file from extension 2399 $fileType = GetFileExtension($infile) if $infile and GetFileType($infile); 2400 $err = "Writing of $fileType files is not yet supported"; 2401 } else { 2402 $err = 'Writing of this type of file is not supported'; 2403 } 2404 $self->Error($err) if $err; 2405 $rtnVal = 0; # (in case it was undef) 2406 } 2407 # $raf->Close(); # only used to force debug output 2408 last; # (didn't really want to loop) 2409 } 2410 # don't return success code if any error occurred 2411 if ($rtnVal > 0) { 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}) { 2420 # don't write a file with zero length 2421 if (defined $hdr and length $hdr) { 2422 $type = '<unk>' unless defined $type; 2423 $self->Error("Can't delete all meta information from $type file"); 2424 } else { 2425 $self->Error('Nothing to write'); 2426 } 2427 } 2428 $rtnVal = 0 if $$self{VALUE}{Error}; 2429 } 2430 2431 # rewrite original file in place if required 2432 if (defined $outBuff) { 2433 if ($rtnVal <= 0 or not $$self{CHANGED}) { 2434 # nothing changed, so no need to write $outBuff 2435 } elsif (UNIVERSAL::isa($inRef,'GLOB')) { 2436 my $len = length($outBuff); 2437 my $size; 2438 $rtnVal = -1 unless 2439 seek($inRef, 0, 2) and # seek to the end of file 2440 ($size = tell $inRef) >= 0 and # get the file size 2441 seek($inRef, 0, 0) and # seek back to the start 2442 print $inRef $outBuff and # write the new data 2443 ($len >= $size or # if necessary: 2444 eval { truncate($inRef, $len) }); # shorten output file 2445 } else { 2446 $$inRef = $outBuff; # replace original data 2447 } 2448 $outBuff = ''; # free memory but leave $outBuff defined 2449 } 2450 # close input file if we opened it 2451 if ($closeIn) { 2452 # errors on input file are significant if we edited the file in place 2453 $rtnVal and $rtnVal = -1 unless close($inRef) or not defined $outBuff; 2454 if ($rtnVal > 0) { 2455 # copy Mac OS resource fork if it exists 2456 if ($^O eq 'darwin' and -s "$infile/..namedfork/rsrc") { 2457 if ($$self{DEL_GROUP}{RSRC}) { 2458 $self->VPrint(0,"Deleting Mac OS resource fork\n"); 2459 ++$$self{CHANGED}; 2460 } else { 2461 $self->VPrint(0,"Copying Mac OS resource fork\n"); 2462 my ($buf, $err); 2463 local (*SRC, *DST); 2464 if ($self->Open(\*SRC, "$infile/..namedfork/rsrc")) { 2465 if ($self->Open(\*DST, "$outfile/..namedfork/rsrc", '>')) { 2466 binmode SRC; # (not necessary for Darwin, but let's be thorough) 2467 binmode DST; 2468 while (read SRC, $buf, 65536) { 2469 print DST $buf or $err = 'copying', last; 2470 } 2471 close DST or $err or $err = 'closing'; 2472 } else { 2473 # (this is normal if the destination filesystem isn't Mac OS) 2474 $self->Warn('Error creating Mac OS resource fork'); 2475 } 2476 close SRC; 2477 } else { 2478 $err = 'opening'; 2479 } 2480 $rtnVal = 0 if $err and $self->Error("Error $err Mac OS resource fork", 2); 2481 } 2482 } 2483 # erase input file if renaming while editing information in place 2484 $self->Unlink($infile) or $self->Warn('Error erasing original file') if $eraseIn; 2485 } 2486 } 2487 # close output file if we created it 2488 if ($closeOut) { 2489 # close file and set $rtnVal to -1 if there was an error 2490 $rtnVal and $rtnVal = -1 unless close($outRef); 2491 # erase the output file if we weren't successful 2492 if ($rtnVal <= 0) { 2493 $self->Unlink($outfile); 2494 # else rename temporary file if necessary 2495 } elsif ($tmpfile) { 2496 $self->CopyFileAttrs($infile, $tmpfile); # copy attributes to new file 2497 unless ($self->Rename($tmpfile, $infile)) { 2498 # some filesystems won't overwrite with 'rename', so try erasing original 2499 if (not $self->Unlink($infile)) { 2500 $self->Unlink($tmpfile); 2501 $self->Error('Error renaming temporary file'); 2502 $rtnVal = 0; 2503 } elsif (not $self->Rename($tmpfile, $infile)) { 2504 $self->Error('Error renaming temporary file after deleting original'); 2505 $rtnVal = 0; 2506 } 2507 } 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 } 2526 } 2527 # check for write error and set appropriate error message and return value 2528 if ($rtnVal < 0) { 2529 $self->Error('Error writing output file') unless $$self{VALUE}{Error}; 2530 $rtnVal = 0; # return 0 on failure 2531 } elsif ($rtnVal > 0) { 2532 ++$rtnVal unless $$self{CHANGED}; 2533 } 2534 # set things back to the way they were 2535 $$self{RAF} = $oldRaf; 2536 2537 return $rtnVal; 2538} 2539 2540#------------------------------------------------------------------------------ 2541# Get list of all available tags for specified group 2542# Inputs: 0) optional group name (or string of names separated by colons) 2543# Returns: tag list (sorted alphabetically) 2544# Notes: Can't get tags for specific IFD 2545sub GetAllTags(;$) 2546{ 2547 local $_; 2548 my $group = shift; 2549 my (%allTags, @groups); 2550 @groups = split ':', $group if $group; 2551 2552 my $et = new Image::ExifTool; 2553 LoadAllTables(); # first load all our tables 2554 my @tableNames = keys %allTables; 2555 2556 # loop through all tables and save tag names to %allTags hash 2557 while (@tableNames) { 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 } 2563 my $tagID; 2564 foreach $tagID (TagTableKeys($table)) { 2565 my @infoArray = GetTagInfoList($table,$tagID); 2566 my $tagInfo; 2567GATInfo: foreach $tagInfo (@infoArray) { 2568 my $tag = $$tagInfo{Name}; 2569 $tag or warn("no name for tag!\n"), next; 2570 # don't list subdirectories unless they are writable 2571 next if $$tagInfo{SubDirectory} and not $$tagInfo{Writable}; 2572 next if $$tagInfo{Hidden}; # ignore hidden tags 2573 if (@groups) { 2574 my @tg = $et->GetGroup($tagInfo); 2575 foreach $group (@groups) { 2576 next GATInfo unless grep /^$group$/i, @tg; 2577 } 2578 } 2579 $allTags{$tag} = 1; 2580 } 2581 } 2582 } 2583 return sort keys %allTags; 2584} 2585 2586#------------------------------------------------------------------------------ 2587# Get list of all writable tags 2588# Inputs: 0) optional group name (or names separated by colons) 2589# Returns: tag list (sorted alphabetically) 2590sub GetWritableTags(;$) 2591{ 2592 local $_; 2593 my $group = shift; 2594 my (%writableTags, @groups); 2595 @groups = split ':', $group if $group; 2596 2597 my $et = new Image::ExifTool; 2598 LoadAllTables(); 2599 my @tableNames = keys %allTables; 2600 2601 while (@tableNames) { 2602 my $tableName = pop @tableNames; 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 } 2608 # attempt to load Write tables if autoloaded 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) 2615 } 2616 my $tagID; 2617 foreach $tagID (TagTableKeys($table)) { 2618 my @infoArray = GetTagInfoList($table,$tagID); 2619 my $tagInfo; 2620GWTInfo: foreach $tagInfo (@infoArray) { 2621 my $tag = $$tagInfo{Name}; 2622 $tag or warn("no name for tag!\n"), next; 2623 my $writable = $$tagInfo{Writable}; 2624 next unless $writable or ($$table{WRITABLE} and 2625 not defined $writable and not $$tagInfo{SubDirectory}); 2626 next if $$tagInfo{Hidden}; # ignore hidden tags 2627 if (@groups) { 2628 my @tg = $et->GetGroup($tagInfo); 2629 foreach $group (@groups) { 2630 next GWTInfo unless grep /^$group$/i, @tg; 2631 } 2632 } 2633 $writableTags{$tag} = 1; 2634 } 2635 } 2636 } 2637 return sort keys %writableTags; 2638} 2639 2640#------------------------------------------------------------------------------ 2641# Get list of all group names 2642# Inputs: 0) Group family number 2643# Returns: List of group names (sorted alphabetically) 2644sub GetAllGroups($) 2645{ 2646 local $_; 2647 my $family = shift || 0; 2648 2649 $family == 3 and return('Doc#', 'Main'); 2650 $family == 4 and return('Copy#'); 2651 $family == 5 and return('[too many possibilities to list]'); 2652 $family == 6 and return(@Image::ExifTool::Exif::formatName[1..$#Image::ExifTool::Exif::formatName]); 2653 2654 LoadAllTables(); # first load all our tables 2655 2656 my @tableNames = keys %allTables; 2657 2658 # loop through all tag tables and get all group names 2659 my %allGroups; 2660 while (@tableNames) { 2661 my $table = GetTagTable(pop @tableNames); 2662 my ($grps, $grp, $tag, $tagInfo); 2663 $allGroups{$grp} = 1 if ($grps = $$table{GROUPS}) and ($grp = $$grps{$family}); 2664 foreach $tag (TagTableKeys($table)) { 2665 my @infoArray = GetTagInfoList($table, $tag); 2666 foreach $tagInfo (@infoArray) { 2667 next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family}); 2668 $allGroups{$grp} = 1; 2669 } 2670 } 2671 } 2672 delete $allGroups{'*'}; # (not a real group) 2673 return sort keys %allGroups; 2674} 2675 2676#------------------------------------------------------------------------------ 2677# get priority group list for new values 2678# Inputs: 0) ExifTool object reference 2679# Returns: List of group names 2680sub GetNewGroups($) 2681{ 2682 my $self = shift; 2683 return @{$$self{WRITE_GROUPS}}; 2684} 2685 2686#------------------------------------------------------------------------------ 2687# Get list of all deletable group names 2688# Returns: List of group names (sorted alphabetically) 2689sub GetDeleteGroups() 2690{ 2691 return sort @delGroups, @delGroup2; 2692} 2693 2694#------------------------------------------------------------------------------ 2695# Add user-defined tags at run time 2696# Inputs: 0) destination table name, 1) tagID/tagInfo pairs for tags to add 2697# Returns: number of tags added 2698# Notes: will replace existing tags 2699sub AddUserDefinedTags($%) 2700{ 2701 local $_; 2702 my ($tableName, %addTags) = @_; 2703 my $table = GetTagTable($tableName) or return 0; 2704 # add tags to writer lookup 2705 Image::ExifTool::TagLookup::AddTags(\%addTags, $tableName); 2706 my $tagID; 2707 my $num = 0; 2708 foreach $tagID (keys %addTags) { 2709 next if $specialTags{$tagID}; 2710 delete $$table{$tagID}; # delete old entry if it existed 2711 AddTagToTable($table, $tagID, $addTags{$tagID}, 1); 2712 ++$num; 2713 } 2714 return $num; 2715} 2716 2717#============================================================================== 2718# Functions below this are not part of the public API 2719 2720#------------------------------------------------------------------------------ 2721# Maintain backward compatibility for old GetNewValues function name 2722sub GetNewValues($$;$) 2723{ 2724 my ($self, $tag, $nvHashPt) = @_; 2725 return $self->GetNewValue($tag, $nvHashPt); 2726} 2727 2728#------------------------------------------------------------------------------ 2729# Un-escape string according to options settings and clear UTF-8 flag 2730# Inputs: 0) ExifTool ref, 1) string ref or string ref ref 2731# Notes: also de-references SCALAR values 2732sub Sanitize($$) 2733{ 2734 my ($self, $valPt) = @_; 2735 # de-reference SCALAR references 2736 $$valPt = $$$valPt if ref $$valPt eq 'SCALAR'; 2737 # make sure the Perl UTF-8 flag is OFF for the value if perl 5.6 or greater 2738 # (otherwise our byte manipulations get corrupted!!) 2739 if ($] >= 5.006 and (eval { require Encode; Encode::is_utf8($$valPt) } or $@)) { 2740 local $SIG{'__WARN__'} = \&SetWarning; 2741 # repack by hand if Encode isn't available 2742 $$valPt = $@ ? pack('C*',unpack($] < 5.010000 ? 'U0C*' : 'C0C*',$$valPt)) : Encode::encode('utf8',$$valPt); 2743 } 2744 # un-escape value if necessary 2745 if ($$self{OPTIONS}{Escape}) { 2746 # (XMP.pm and HTML.pm were require'd as necessary when option was set) 2747 if ($$self{OPTIONS}{Escape} eq 'XML') { 2748 $$valPt = Image::ExifTool::XMP::UnescapeXML($$valPt); 2749 } elsif ($$self{OPTIONS}{Escape} eq 'HTML') { 2750 $$valPt = Image::ExifTool::HTML::UnescapeHTML($$valPt, $$self{OPTIONS}{Charset}); 2751 } 2752 } 2753} 2754 2755#------------------------------------------------------------------------------ 2756# Apply inverse conversions 2757# Inputs: 0) ExifTool ref, 1) value, 2) tagInfo (or Struct item) ref, 2758# 3) tag name, 4) group 1 name, 5) conversion type (or undef), 2759# 6) [optional] want group ("" for structure field) 2760# Returns: 0) converted value, 1) error string (or undef on success) 2761# Notes: 2762# - uses ExifTool "ConvType" member when conversion type is undef 2763# - conversion types other than 'ValueConv' and 'PrintConv' are treated as 'Raw' 2764sub ConvInv($$$$$;$$) 2765{ 2766 my ($self, $val, $tagInfo, $tag, $wgrp1, $convType, $wantGroup) = @_; 2767 my ($err, $type); 2768 2769Conv: for (;;) { 2770 if (not defined $type) { 2771 # split value into list if necessary 2772 if ($$tagInfo{List}) { 2773 my $listSplit = $$tagInfo{AutoSplit} || $$self{OPTIONS}{ListSplit}; 2774 if (defined $listSplit and not $$tagInfo{Struct} and 2775 ($wantGroup or not defined $wantGroup)) 2776 { 2777 $listSplit = ',?\s+' if $listSplit eq '1' and $$tagInfo{AutoSplit}; 2778 my @splitVal = split /$listSplit/, $val, -1; 2779 $val = @splitVal > 1 ? \@splitVal : @splitVal ? $splitVal[0] : ''; 2780 } 2781 } 2782 $type = $convType || $$self{ConvType} || 'PrintConv'; 2783 } elsif ($type eq 'PrintConv') { 2784 $type = 'ValueConv'; 2785 } else { 2786 # split raw value if necessary 2787 if ($$tagInfo{RawJoin} and $$tagInfo{List} and not ref $val) { 2788 my @splitVal = split ' ', $val; 2789 $val = \@splitVal if @splitVal > 1; 2790 } 2791 # finally, do our value check 2792 my ($err2, $v); 2793 if ($$tagInfo{WriteCheck}) { 2794 #### eval WriteCheck ($self, $tagInfo, $val) 2795 $err2 = eval $$tagInfo{WriteCheck}; 2796 $@ and warn($@), $err2 = 'Error evaluating WriteCheck'; 2797 } 2798 unless ($err2) { 2799 my $table = $$tagInfo{Table}; 2800 if ($table and $$table{CHECK_PROC} and not $$tagInfo{RawConvInv}) { 2801 my $checkProc = $$table{CHECK_PROC}; 2802 if (ref $val eq 'ARRAY') { 2803 # loop through array values 2804 foreach $v (@$val) { 2805 $err2 = &$checkProc($self, $tagInfo, \$v); 2806 last if $err2; 2807 } 2808 } else { 2809 $err2 = &$checkProc($self, $tagInfo, \$val); 2810 } 2811 } 2812 } 2813 if (defined $err2) { 2814 if ($err2) { 2815 $err = "$err2 for $wgrp1:$tag"; 2816 $self->VPrint(2, "$err\n"); 2817 undef $val; # value was invalid 2818 } else { 2819 $err = $err2; # empty error (quietly don't write tag) 2820 } 2821 } 2822 last; 2823 } 2824 my $conv = $$tagInfo{$type}; 2825 my $convInv = $$tagInfo{"${type}Inv"}; 2826 # nothing to do at this level if no conversion defined 2827 next unless defined $conv or defined $convInv; 2828 2829 my (@valList, $index, $convList, $convInvList); 2830 if (ref $val eq 'ARRAY') { 2831 # handle ValueConv of ListSplit and AutoSplit values 2832 @valList = @$val; 2833 $val = $valList[$index = 0]; 2834 } elsif (ref $conv eq 'ARRAY' or ref $convInv eq 'ARRAY') { 2835 # handle conversion lists 2836 @valList = split /$listSep{$type}/, $val; 2837 $val = $valList[$index = 0]; 2838 if (ref $conv eq 'ARRAY') { 2839 $convList = $conv; 2840 $conv = $$conv[0]; 2841 } 2842 if (ref $convInv eq 'ARRAY') { 2843 $convInvList = $convInv; 2844 $convInv = $$convInv[0]; 2845 } 2846 } 2847 # loop through multiple values if necessary 2848 for (;;) { 2849 if ($convInv) { 2850 # capture eval warnings too 2851 local $SIG{'__WARN__'} = \&SetWarning; 2852 undef $evalWarning; 2853 if (ref($convInv) eq 'CODE') { 2854 $val = &$convInv($val, $self); 2855 } else { 2856 #### eval PrintConvInv/ValueConvInv ($val, $self, $wantGroup) 2857 $val = eval $convInv; 2858 $@ and $evalWarning = $@; 2859 } 2860 if ($evalWarning) { 2861 # an empty warning ("\n") ignores tag with no error 2862 if ($evalWarning eq "\n") { 2863 $err = '' unless defined $err; 2864 } else { 2865 $err = CleanWarning() . " in $wgrp1:$tag (${type}Inv)"; 2866 $self->VPrint(2, "$err\n"); 2867 } 2868 undef $val; 2869 last Conv; 2870 } elsif (not defined $val) { 2871 $err = "Error converting value for $wgrp1:$tag (${type}Inv)"; 2872 $self->VPrint(2, "$err\n"); 2873 last Conv; 2874 } 2875 } elsif ($conv) { 2876 if (ref $conv eq 'HASH' and (not exists $$tagInfo{"${type}Inv"} or $convInvList)) { 2877 my ($multi, $lc); 2878 # insert alternate language print conversions if required 2879 if ($$self{CUR_LANG} and $type eq 'PrintConv' and 2880 ref($lc = $$self{CUR_LANG}{$tag}) eq 'HASH' and 2881 ($lc = $$lc{PrintConv})) 2882 { 2883 my %newConv; 2884 foreach (keys %$conv) { 2885 my $val = $$conv{$_}; 2886 defined $$lc{$val} or $newConv{$_} = $val, next; 2887 $newConv{$_} = $self->Decode($$lc{$val}, 'UTF8'); 2888 } 2889 if ($$conv{BITMASK}) { 2890 foreach (keys %{$$conv{BITMASK}}) { 2891 my $val = $$conv{BITMASK}{$_}; 2892 defined $$lc{$val} or $newConv{BITMASK}{$_} = $val, next; 2893 $newConv{BITMASK}{$_} = $self->Decode($$lc{$val}, 'UTF8'); 2894 } 2895 } 2896 $conv = \%newConv; 2897 } 2898 undef $evalWarning; 2899 if ($$conv{BITMASK}) { 2900 my $lookupBits = $$conv{BITMASK}; 2901 my ($wbits, $tbits) = @$tagInfo{'BitsPerWord','BitsTotal'}; 2902 my ($val2, $err2) = EncodeBits($val, $lookupBits, $wbits, $tbits); 2903 if ($err2) { 2904 # ok, try matching a straight value 2905 ($val, $multi) = ReverseLookup($val, $conv); 2906 unless (defined $val) { 2907 $err = "Can't encode $wgrp1:$tag ($err2)"; 2908 $self->VPrint(2, "$err\n"); 2909 last Conv; 2910 } 2911 } elsif (defined $val2) { 2912 $val = $val2; 2913 } else { 2914 delete $$conv{BITMASK}; 2915 ($val, $multi) = ReverseLookup($val, $conv); 2916 $$conv{BITMASK} = $lookupBits; 2917 } 2918 } else { 2919 ($val, $multi) = ReverseLookup($val, $conv); 2920 } 2921 if (not defined $val) { 2922 my $prob = $evalWarning ? lcfirst CleanWarning() : ($multi ? 'matches more than one ' : 'not in ') . $type; 2923 $err = "Can't convert $wgrp1:$tag ($prob)"; 2924 $self->VPrint(2, "$err\n"); 2925 last Conv; 2926 } elsif ($evalWarning) { 2927 $self->VPrint(2, CleanWarning() . " for $wgrp1:$tag\n"); 2928 } 2929 } elsif (not $$tagInfo{WriteAlso}) { 2930 $err = "Can't convert value for $wgrp1:$tag (no ${type}Inv)"; 2931 $self->VPrint(2, "$err\n"); 2932 undef $val; 2933 last Conv; 2934 } 2935 } 2936 last unless @valList; 2937 $valList[$index] = $val; 2938 if (++$index >= @valList) { 2939 # leave AutoSplit lists in ARRAY form, or join conversion lists 2940 $val = $$tagInfo{List} ? \@valList : join ' ', @valList; 2941 last; 2942 } 2943 $conv = $$convList[$index] if $convList; 2944 $convInv = $$convInvList[$index] if $convInvList; 2945 $val = $valList[$index]; 2946 } 2947 } # end ValueConv/PrintConv loop 2948 2949 return($val, $err); 2950} 2951 2952#------------------------------------------------------------------------------ 2953# Convert tag names to values or variables in a string 2954# (eg. '${EXIF:ISO}x $$' --> '100x $' without hash ref, or "$info{'EXIF:ISO'}x $" with) 2955# Inputs: 0) ExifTool object ref, 1) reference to list of found tags 2956# 2) string with embedded tag names, 3) Options: 2957# undef - set missing tags to '' 2958# 'Error' - issue minor error on missing tag (and return undef) 2959# 'Warn' - issue minor warning on missing tag (and return undef) 2960# 'Silent' - just return undef on missing tag (no errors/warnings) 2961# Hash ref - defined to interpolate as variables in string instead of values 2962# --> receives tag/value pairs for interpolation of the variables 2963# 4) document group name if extracting from a specific document 2964# 5) hash ref to cache tag keys for subsequent calls in document loop 2965# Returns: string with embedded tag values (or '$info{TAGNAME}' entries with Hash ref option) 2966# Notes: 2967# - tag names are not case sensitive and may end with '#' for ValueConv value 2968# - uses MissingTagValue option if set 2969# - '$GROUP:all' evaluates to 1 if any tag from GROUP exists, or 0 otherwise 2970# - advanced feature allows Perl expressions inside braces (eg. '${model;tr/ //d}') 2971# - an error/warning in an advanced expression ("${TAG;EXPR}") generates an error 2972# if option set to 'Error', or a warning otherwise 2973sub InsertTagValues($$$;$$$) 2974{ 2975 local $_; 2976 my ($self, $foundTags, $line, $opt, $docGrp, $cache) = @_; 2977 my $rtnStr = ''; 2978 my ($docNum, $tag); 2979 if ($docGrp) { 2980 $docNum = $docGrp =~ /(\d+)$/ ? $1 : 0; 2981 } else { 2982 undef $cache; # no cache if no document groups 2983 } 2984 while ($line =~ s/(.*?)\$(\{\s*)?([-\w]*\w|\$|\/)//s) { 2985 my ($pre, $bra, $var) = ($1, $2, $3); 2986 my (@tags, $val, $tg, @val, $type, $expr, $didExpr, $level, $asList); 2987 # "$$" represents a "$" symbol, and "$/" is a newline 2988 if ($var eq '$' or $var eq '/') { 2989 $line =~ s/^\s*\}// if $bra; 2990 if ($var eq '/') { 2991 $var = "\n"; 2992 } elsif ($line =~ /^self\b/ and not $rtnStr =~ /\$$/) { 2993 $var = '$$'; # ("$$self{var}" in string) 2994 } 2995 $rtnStr .= "$pre$var"; 2996 next; 2997 } 2998 # allow multiple group names 2999 while ($line =~ /^:([-\w]*\w)(.*)/s) { 3000 my $group = $var; 3001 ($var, $line) = ($1, $2); 3002 $var = "$group:$var"; 3003 } 3004 # allow trailing '#' to indicate ValueConv value 3005 $type = 'ValueConv' if $line =~ s/^#//; 3006 # special advanced formatting '@' feature to evaluate list values separately 3007 if ($bra and $line =~ s/^\@(#)?//) { 3008 $asList = 1; 3009 $type = 'ValueConv' if $1; 3010 } 3011 # remove trailing bracket if there was a leading one 3012 # and extract Perl expression from inside brackets if it exists 3013 if ($bra and $line !~ s/^\s*\}// and $line =~ s/^\s*;\s*(.*?)\s*\}//s) { 3014 my $part = $1; 3015 $expr = ''; 3016 for ($level=0; ; --$level) { 3017 # increase nesting level for each opening brace 3018 ++$level while $part =~ /\{/g; 3019 $expr .= $part; 3020 last unless $level and $line =~ s/^(.*?)\s*\}//s; # get next part 3021 $part = $1; 3022 $expr .= '}'; # this brace was part of the expression 3023 } 3024 # use default Windows filename filter if expression is empty 3025 $expr = 'tr(/\\\\?*:|"<>\\0)()d' unless length $expr; 3026 } 3027 push @tags, $var; 3028 ExpandShortcuts(\@tags); 3029 @tags or $rtnStr .= $pre, next; 3030 # save advanced formatting expression to allow access by user-defined ValueConv 3031 $$self{FMT_EXPR} = $expr; 3032 3033 for (;;) { 3034 # temporarily reset ListJoin option if evaluating list values separately 3035 my $oldListJoin; 3036 $oldListJoin = $self->Options(ListJoin => undef) if $asList; 3037 $tag = shift @tags; 3038 my $lcTag = lc $tag; 3039 if ($cache and $lcTag !~ /(^|:)all$/) { 3040 # remove group from tag name (but not lower-case version) 3041 my $group; 3042 $tag =~ s/^(.*):// and $group = $1; 3043 # cache tag keys to speed processing for a large number of sub-documents 3044 # (similar to code in BuildCompositeTags(), but this is case-insensitive) 3045 my $cacheTag = $$cache{$lcTag}; 3046 unless ($cacheTag) { 3047 $cacheTag = $$cache{$lcTag} = [ ]; 3048 # find all matching keys, organize into groups, and store in cache 3049 my $ex = $$self{TAG_EXTRA}; 3050 my @matches = grep /^$tag(\s|$)/i, @$foundTags; 3051 @matches = $self->GroupMatches($group, \@matches) if defined $group; 3052 foreach (@matches) { 3053 my $doc = $$ex{$_} ? $$ex{$_}{G3} || 0 : 0; 3054 if (defined $$cacheTag[$doc]) { 3055 next unless $$cacheTag[$doc] =~ / \((\d+)\)$/; 3056 my $cur = $1; 3057 # keep the most recently extracted tag 3058 next if / \((\d+)\)$/ and $1 < $cur; 3059 } 3060 $$cacheTag[$doc] = $_; 3061 } 3062 } 3063 my $doc = $lcTag =~ /\b(main|doc(\d+)):/ ? ($2 || 0) : $docNum; 3064 if ($$cacheTag[$doc]) { 3065 $tag = $$cacheTag[$doc]; 3066 $val = $self->GetValue($tag, $type); 3067 } 3068 } else { 3069 # add document number to tag if specified and it doesn't already exist 3070 if ($docGrp and $lcTag !~ /\b(main|doc\d+):/) { 3071 $tag = $docGrp . ':' . $tag; 3072 $lcTag = lc $tag; 3073 } 3074 if ($lcTag eq 'all') { 3075 $val = 1; # always some tag available 3076 } elsif (defined $$self{OPTIONS}{UserParam}{$lcTag}) { 3077 $val = $$self{OPTIONS}{UserParam}{$lcTag}; 3078 } elsif ($tag =~ /(.*):(.+)/) { 3079 my $group; 3080 ($group, $tag) = ($1, $2); 3081 if (lc $tag eq 'all') { 3082 # see if any tag from the specified group exists 3083 my $match = $self->GroupMatches($group, $foundTags); 3084 $val = $match ? 1 : 0; 3085 } else { 3086 # find the specified tag 3087 my @matches = grep /^$tag(\s|$)/i, @$foundTags; 3088 @matches = $self->GroupMatches($group, \@matches); 3089 foreach $tg (@matches) { 3090 if (defined $val and $tg =~ / \((\d+)\)$/) { 3091 # take the most recently extracted tag 3092 my $tagNum = $1; 3093 next if $tag !~ / \((\d+)\)$/ or $1 > $tagNum; 3094 } 3095 $val = $self->GetValue($tg, $type); 3096 $tag = $tg; 3097 last unless $tag =~ / /; # all done if we got our best match 3098 } 3099 } 3100 } elsif ($tag eq 'self') { 3101 $val = $self; # ("$self{var}" or "$self->{var}" in string) 3102 } else { 3103 # get the tag value 3104 $val = $self->GetValue($tag, $type); 3105 unless (defined $val) { 3106 # check for tag name with different case 3107 ($tg) = grep /^$tag$/i, @$foundTags; 3108 if (defined $tg) { 3109 $val = $self->GetValue($tg, $type); 3110 $tag = $tg; 3111 } 3112 } 3113 } 3114 } 3115 $self->Options(ListJoin => $oldListJoin) if $asList; 3116 if (ref $val eq 'ARRAY') { 3117 push @val, @$val; 3118 undef $val; 3119 last unless @tags; 3120 } elsif (ref $val eq 'SCALAR') { 3121 if ($$self{OPTIONS}{Binary} or $$val =~ /^Binary data/) { 3122 $val = $$val; 3123 } else { 3124 $val = 'Binary data ' . length($$val) . ' bytes'; 3125 } 3126 } elsif (ref $val eq 'HASH') { 3127 require 'Image/ExifTool/XMPStruct.pl'; 3128 $val = Image::ExifTool::XMP::SerializeStruct($val); 3129 } elsif (not defined $val) { 3130 $val = $$self{OPTIONS}{MissingTagValue} if $asList; 3131 } 3132 last unless @tags; 3133 push @val, $val if defined $val; 3134 undef $val; 3135 } 3136 if (@val) { 3137 push @val, $val if defined $val; 3138 $val = join $$self{OPTIONS}{ListSep}, @val; 3139 } else { 3140 push @val, $val if defined $val; # (so the eval has access to @val if required) 3141 } 3142 # evaluate advanced formatting expression if given (eg. "${TAG;EXPR}") 3143 if (defined $expr and defined $val) { 3144 local $SIG{'__WARN__'} = \&SetWarning; 3145 undef $evalWarning; 3146 $advFmtSelf = $self; 3147 if ($asList) { 3148 foreach (@val) { 3149 #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) 3150 eval $expr; 3151 $@ and $evalWarning = $@; 3152 } 3153 # join back together if any values are still defined 3154 @val = grep defined, @val; 3155 $val = @val ? join $$self{OPTIONS}{ListSep}, @val : undef; 3156 } else { 3157 $_ = $val; 3158 #### eval advanced formatting expression ($_, $self, @val, $advFmtSelf) 3159 eval $expr; 3160 $@ and $evalWarning = $@; 3161 $val = ref $_ eq 'ARRAY' ? join($$self{OPTIONS}{ListSep}, @$_): $_; 3162 } 3163 if ($evalWarning) { 3164 my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; 3165 my $str = CleanWarning() . " for '$g3${var}'"; 3166 if ($opt) { 3167 if ($opt eq 'Error') { 3168 $self->Error($str); 3169 } elsif ($opt ne 'Silent') { 3170 $self->Warn($str); 3171 } 3172 } 3173 } 3174 undef $advFmtSelf; 3175 $didExpr = 1; # set flag indicating an expression was evaluated 3176 } 3177 unless (defined $val or ref $opt) { 3178 $val = $$self{OPTIONS}{MissingTagValue}; 3179 unless (defined $val) { 3180 my $g3 = ($docGrp and $var !~ /\b(main|doc\d+):/i) ? $docGrp . ':' : ''; 3181 my $msg = $didExpr ? "Advanced formatting expression returned undef for '$g3${var}'" : 3182 "Tag '$g3${var}' not defined"; 3183 no strict 'refs'; 3184 $opt and ($opt eq 'Silent' or &$opt($self, $msg, 2)) and return $$self{FMT_EXPR} = undef; 3185 $val = ''; 3186 } 3187 } 3188 if (ref $opt eq 'HASH') { 3189 $var .= '#' if $type; 3190 if (defined $expr) { 3191 # generate unique variable name for this modified tag value 3192 my $i = 1; 3193 ++$i while exists $$opt{"$var.expr$i"}; 3194 $var .= '.expr' . $i; 3195 } 3196 $rtnStr .= "$pre\$info{'${var}'}"; 3197 $$opt{$var} = $val; 3198 } else { 3199 $rtnStr .= "$pre$val"; 3200 } 3201 } 3202 $$self{FMT_EXPR} = undef; 3203 return $rtnStr . $line; 3204} 3205 3206#------------------------------------------------------------------------------ 3207# Reformat date/time value in $_ based on specified format string 3208# Inputs: 0) date/time format string 3209sub DateFmt($) 3210{ 3211 my $et = bless { OPTIONS => { DateFormat => shift, StrictDate => 1 } }; 3212 my $shift; 3213 if ($advFmtSelf and defined($shift = $$advFmtSelf{OPTIONS}{GlobalTimeShift})) { 3214 $$et{OPTIONS}{GlobalTimeShift} = $shift; 3215 $$et{GLOBAL_TIME_OFFSET} = $$advFmtSelf{GLOBAL_TIME_OFFSET}; 3216 } 3217 $_ = $et->ConvertDateTime($_); 3218 defined $_ or warn "Error converting date/time\n"; 3219 $$advFmtSelf{GLOBAL_TIME_OFFSET} = $$et{GLOBAL_TIME_OFFSET} if $shift; 3220} 3221 3222#------------------------------------------------------------------------------ 3223# Utility routine to remove duplicate items from default input string 3224# Inputs: 0) true to set $_ to undef if not changed 3225# Notes: - for use only in advanced formatting expressions 3226sub NoDups 3227{ 3228 my %seen; 3229 my $sep = $advFmtSelf ? $$advFmtSelf{OPTIONS}{ListSep} : ', '; 3230 my $new = join $sep, grep { !$seen{$_}++ } split /\Q$sep\E/, $_; 3231 $_ = ($_[0] and $new eq $_) ? undef : $new; 3232} 3233 3234#------------------------------------------------------------------------------ 3235# Is specified tag writable 3236# Inputs: 0) tag name, case insensitive (optional group name currently ignored) 3237# Returns: 0=exists but not writable, 1=writable, undef=doesn't exist 3238sub IsWritable($) 3239{ 3240 my $tag = shift; 3241 $tag =~ s/^(.*)://; # ignore group name 3242 my @tagInfo = FindTagInfo($tag); 3243 unless (@tagInfo) { 3244 return 0 if TagExists($tag); 3245 return undef; 3246 } 3247 my $tagInfo; 3248 foreach $tagInfo (@tagInfo) { 3249 return $$tagInfo{Writable} ? 1 : 0 if defined $$tagInfo{Writable}; 3250 return 1 if $$tagInfo{Table}{WRITABLE}; 3251 # must call WRITE_PROC to autoload writer because this may set the writable tag 3252 my $writeProc = $$tagInfo{Table}{WRITE_PROC}; 3253 if ($writeProc) { 3254 no strict 'refs'; 3255 &$writeProc(); # dummy call to autoload writer 3256 return 1 if $$tagInfo{Writable}; 3257 } 3258 } 3259 return 0; 3260} 3261 3262#------------------------------------------------------------------------------ 3263# Check to see if these are the same file 3264# Inputs: 0) ExifTool ref, 1) first file name, 2) second file name 3265# Returns: true if file names reference the same file 3266sub IsSameFile($$$) 3267{ 3268 my ($self, $file, $file2) = @_; 3269 return 0 unless lc $file eq lc $file2; # (only looking for differences in case) 3270 my ($isSame, $interrupted); 3271 my $tmp1 = "${file}_ExifTool_tmp_$$"; 3272 my $tmp2 = "${file2}_ExifTool_tmp_$$"; 3273 { 3274 local *TMP1; 3275 local $SIG{INT} = sub { $interrupted = 1 }; 3276 if ($self->Open(\*TMP1, $tmp1, '>')) { 3277 close TMP1; 3278 $isSame = 1 if $self->Exists($tmp2); 3279 $self->Unlink($tmp1); 3280 } 3281 } 3282 if ($interrupted and $SIG{INT}) { 3283 no strict 'refs'; 3284 &{$SIG{INT}}(); 3285 } 3286 return $isSame; 3287} 3288 3289#------------------------------------------------------------------------------ 3290# Is this a raw file type? 3291# Inputs: 0) ExifTool ref 3292# Returns: true if FileType is a type of RAW image 3293sub IsRawType($) 3294{ 3295 my $self = shift; 3296 return $rawType{$$self{FileType}}; 3297} 3298 3299#------------------------------------------------------------------------------ 3300# Create directory for specified file 3301# Inputs: 0) ExifTool ref, 1) complete file name including path 3302# Returns: 1 = directory created, 0 = nothing done, -1 = error 3303my $k32CreateDir; 3304sub CreateDirectory($$) 3305{ 3306 local $_; 3307 my ($self, $file) = @_; 3308 my $rtnVal = 0; 3309 my $enc = $$self{OPTIONS}{CharsetFileName}; 3310 my $dir; 3311 ($dir = $file) =~ s/[^\/]*$//; # remove filename from path specification 3312 # recode as UTF-8 if necessary 3313 if ($dir and not $self->IsDirectory($dir)) { 3314 my @parts = split /\//, $dir; 3315 $dir = ''; 3316 foreach (@parts) { 3317 $dir .= $_; 3318 if (length $dir and not $self->IsDirectory($dir)) { 3319 # create directory since it doesn't exist 3320 my $d2 = $dir; # (must make a copy in case EncodeFileName recodes it) 3321 if ($self->EncodeFileName($d2)) { 3322 # handle Windows Unicode directory names 3323 unless (eval { require Win32::API }) { 3324 $self->Warn('Install Win32::API to create directories with Unicode names'); 3325 return -1; 3326 } 3327 unless ($k32CreateDir) { 3328 return -1 if defined $k32CreateDir; 3329 $k32CreateDir = new Win32::API('KERNEL32', 'CreateDirectoryW', 'PP', 'I'); 3330 unless ($k32CreateDir) { 3331 $self->Warn('Error calling Win32::API::CreateDirectoryW'); 3332 $k32CreateDir = 0; 3333 return -1; 3334 } 3335 } 3336 $k32CreateDir->Call($d2, 0) or return -1; 3337 } else { 3338 mkdir($d2, 0777) or return -1; 3339 } 3340 $rtnVal = 1; 3341 } 3342 $dir .= '/'; 3343 } 3344 } 3345 return $rtnVal; 3346} 3347 3348#------------------------------------------------------------------------------ 3349# Copy file attributes from one file to another 3350# Inputs: 0) ExifTool ref, 1) source file name, 2) destination file name 3351# Notes: eventually add support for extended attributes? 3352sub CopyFileAttrs($$$) 3353{ 3354 my ($self, $src, $dst) = @_; 3355 my ($mode, $uid, $gid) = (stat($src))[2, 4, 5]; 3356 # copy file attributes unless we already set them 3357 if (defined $mode and not defined $self->GetNewValue('FilePermissions')) { 3358 eval { chmod($mode & 07777, $dst) }; 3359 } 3360 my $newUid = $self->GetNewValue('FileUserID'); 3361 my $newGid = $self->GetNewValue('FileGroupID'); 3362 if (defined $uid and defined $gid and (not defined $newUid or not defined $newGid)) { 3363 defined $newGid and $gid = $newGid; 3364 defined $newUid and $uid = $newUid; 3365 eval { chown($uid, $gid, $dst) }; 3366 } 3367} 3368 3369#------------------------------------------------------------------------------ 3370# Get new file path name 3371# Inputs: 0) existing name (may contain directory), 3372# 1) new file name, new directory, or new path (dir+name) 3373# Returns: new file path name 3374sub GetNewFileName($$) 3375{ 3376 my ($oldName, $newName) = @_; 3377 my ($dir, $name) = ($oldName =~ m{(.*/)(.*)}); 3378 ($dir, $name) = ('', $oldName) unless defined $dir; 3379 if ($newName =~ m{/$}) { 3380 $newName = "$newName$name"; # change dir only 3381 } elsif ($newName !~ m{/}) { 3382 $newName = "$dir$newName"; # change name only if newname doesn't specify dir 3383 } # else change dir and name 3384 return $newName; 3385} 3386 3387#------------------------------------------------------------------------------ 3388# Get next available tag key 3389# Inputs: 0) hash reference (keys are tag keys), 1) tag name 3390# Returns: next available tag key 3391sub NextFreeTagKey($$) 3392{ 3393 my ($info, $tag) = @_; 3394 return $tag unless exists $$info{$tag}; 3395 my $i; 3396 for ($i=1; ; ++$i) { 3397 my $key = "$tag ($i)"; 3398 return $key unless exists $$info{$key}; 3399 } 3400} 3401 3402#------------------------------------------------------------------------------ 3403# Reverse hash lookup 3404# Inputs: 0) value, 1) hash reference 3405# Returns: Hash key or undef if not found (plus flag for multiple matches in list context) 3406sub ReverseLookup($$) 3407{ 3408 my ($val, $conv) = @_; 3409 return undef unless defined $val; 3410 my $multi; 3411 if ($val =~ /^Unknown\s*\((.*)\)$/i) { 3412 $val = $1; # was unknown 3413 if ($val =~ /^0x([\da-fA-F]+)$/) { 3414 # disable "Hexadecimal number > 0xffffffff non-portable" warning 3415 local $SIG{'__WARN__'} = sub { }; 3416 $val = hex($val); # convert hex value 3417 } 3418 } else { 3419 my $qval = $val; 3420 $qval =~ s/\s+$//; # remove trailing whitespace 3421 $qval = quotemeta $qval; 3422 my @patterns = ( 3423 "^$qval\$", # exact match 3424 "^(?i)$qval\$", # case-insensitive 3425 "^(?i)$qval", # beginning of string 3426 "(?i)$qval", # substring 3427 ); 3428 # hash entries to ignore in reverse lookup 3429 my ($pattern, $found, $matches); 3430PAT: foreach $pattern (@patterns) { 3431 $matches = scalar grep /$pattern/, values(%$conv); 3432 next unless $matches; 3433 # multiple matches are bad unless they were exact 3434 if ($matches > 1 and $pattern !~ /\$$/) { 3435 # don't match entries that we should ignore 3436 foreach (keys %ignorePrintConv) { 3437 --$matches if defined $$conv{$_} and $$conv{$_} =~ /$pattern/; 3438 } 3439 last if $matches > 1; 3440 } 3441 foreach (sort keys %$conv) { 3442 next if $$conv{$_} !~ /$pattern/ or $ignorePrintConv{$_}; 3443 $val = $_; 3444 $found = 1; 3445 last PAT; 3446 } 3447 } 3448 unless ($found) { 3449 # call OTHER conversion routine if available 3450 if ($$conv{OTHER}) { 3451 local $SIG{'__WARN__'} = \&SetWarning; 3452 undef $evalWarning; 3453 $val = &{$$conv{OTHER}}($val,1,$conv); 3454 } else { 3455 $val = undef; 3456 } 3457 $multi = 1 if $matches > 1; 3458 } 3459 } 3460 return ($val, $multi) if wantarray; 3461 return $val; 3462} 3463 3464#------------------------------------------------------------------------------ 3465# Return true if we are deleting or overwriting the specified tag 3466# Inputs: 0) ExifTool object ref, 1) new value hash reference 3467# 2) optional tag value (before RawConv) if deleting specific values 3468# Returns: >0 - tag should be overwritten 3469# =0 - the tag should be preserved 3470# <0 - not sure, we need the value to know 3471# Notes: $$nvHash{Value} is updated with the new value when shifting a value 3472sub IsOverwriting($$;$) 3473{ 3474 my ($self, $nvHash, $val) = @_; 3475 return 0 unless $nvHash; 3476 # overwrite regardless if no DelValues specified 3477 return 1 unless $$nvHash{DelValue}; 3478 # never overwrite if DelValue list exists but is empty 3479 my $shift = $$nvHash{Shift}; 3480 return 0 unless @{$$nvHash{DelValue}} or defined $shift; 3481 # return "don't know" if we don't have a value to test 3482 return -1 unless defined $val; 3483 # apply raw conversion if necessary 3484 my $tagInfo = $$nvHash{TagInfo}; 3485 my $conv = $$tagInfo{RawConv}; 3486 if ($conv) { 3487 local $SIG{'__WARN__'} = \&SetWarning; 3488 undef $evalWarning; 3489 if (ref $conv eq 'CODE') { 3490 $val = &$conv($val, $self); 3491 } else { 3492 my ($priority, @grps); 3493 my $tag = $$tagInfo{Name}; 3494 #### eval RawConv ($self, $val, $tag, $tagInfo, $priority, @grps) 3495 $val = eval $conv; 3496 $@ and $evalWarning = $@; 3497 } 3498 return -1 unless defined $val; 3499 } 3500 # do not overwrite if only creating 3501 return 0 if $$nvHash{CreateOnly}; 3502 # apply time/number shift if necessary 3503 if (defined $shift) { 3504 my $shiftType = $$tagInfo{Shift}; 3505 unless ($shiftType and $shiftType eq 'Time') { 3506 unless (IsFloat($val)) { 3507 # do the ValueConv to try to get a number 3508 my $conv = $$tagInfo{ValueConv}; 3509 if (defined $conv) { 3510 local $SIG{'__WARN__'} = \&SetWarning; 3511 undef $evalWarning; 3512 if (ref $conv eq 'CODE') { 3513 $val = &$conv($val, $self); 3514 } elsif (not ref $conv) { 3515 #### eval ValueConv ($val, $self) 3516 $val = eval $conv; 3517 $@ and $evalWarning = $@; 3518 } 3519 if ($evalWarning) { 3520 $self->Warn("ValueConv $$tagInfo{Name}: " . CleanWarning()); 3521 return 0; 3522 } 3523 } 3524 unless (defined $val and IsFloat($val)) { 3525 $self->Warn("Can't shift $$tagInfo{Name} (not a number)"); 3526 return 0; 3527 } 3528 } 3529 $shiftType = 'Number'; # allow any number to be shifted 3530 } 3531 require 'Image/ExifTool/Shift.pl'; 3532 my $err = $self->ApplyShift($shiftType, $shift, $val, $nvHash); 3533 if ($err) { 3534 $self->Warn("$err when shifting $$tagInfo{Name}"); 3535 return 0; 3536 } 3537 # ensure that the shifted value is valid and reformat if necessary 3538 my $checkVal = $self->GetNewValue($nvHash); 3539 return 0 unless defined $checkVal; 3540 # don't bother overwriting if value is the same 3541 return 0 if $val eq $$nvHash{Value}[0]; 3542 return 1; 3543 } 3544 # return 1 if value matches a DelValue 3545 my $delVal; 3546 foreach $delVal (@{$$nvHash{DelValue}}) { 3547 return 1 if $val eq $delVal; 3548 } 3549 return 0; 3550} 3551 3552#------------------------------------------------------------------------------ 3553# Get write group for specified tag 3554# Inputs: 0) new value hash reference 3555# Returns: Write group name 3556sub GetWriteGroup($) 3557{ 3558 return $_[0]{WriteGroup}; 3559} 3560 3561#------------------------------------------------------------------------------ 3562# Get name of write group or family 1 group 3563# Inputs: 0) ExifTool ref, 1) tagInfo ref, 2) write group name 3564# Returns: Name of group for verbose message 3565sub GetWriteGroup1($$) 3566{ 3567 my ($self, $tagInfo, $writeGroup) = @_; 3568 return $writeGroup unless $writeGroup =~ /^(MakerNotes|XMP|Composite|QuickTime)$/; 3569 return $self->GetGroup($tagInfo, 1); 3570} 3571 3572#------------------------------------------------------------------------------ 3573# Get new value hash for specified tagInfo/writeGroup 3574# Inputs: 0) ExifTool object reference, 1) reference to tag info hash 3575# 2) Write group name, 3) Options: 'delete' or 'create' new value hash 3576# 4) optional ProtectSaved value, 5) true if we are deleting a value 3577# Returns: new value hash reference for specified write group 3578# (or first new value hash in linked list if write group not specified) 3579# Notes: May return undef when 'create' is used with ProtectSaved 3580sub GetNewValueHash($$;$$$$) 3581{ 3582 my ($self, $tagInfo, $writeGroup, $opts) = @_; 3583 return undef unless $tagInfo; 3584 my $nvHash = $$self{NEW_VALUE}{$tagInfo}; 3585 3586 my %opts; # quick lookup for options 3587 $opts and $opts{$opts} = 1; 3588 $writeGroup = '' unless defined $writeGroup; 3589 3590 if ($writeGroup) { 3591 # find the new value in the list with the specified write group 3592 while ($nvHash and $$nvHash{WriteGroup} ne $writeGroup) { 3593 # QuickTime and All are special cases because all group1 tags may be updated at once 3594 last if $$nvHash{WriteGroup} =~ /^(QuickTime|All)$/; 3595 # replace existing entry if WriteGroup is 'All' (avoids confusion of forum10349) 3596 last if $$tagInfo{WriteGroup} and $$tagInfo{WriteGroup} eq 'All'; 3597 $nvHash = $$nvHash{Next}; 3598 } 3599 } 3600 # remove this entry if deleting, or if creating a new entry and 3601 # this entry is marked with "Save" flag 3602 if (defined $nvHash and ($opts{'delete'} or ($opts{'create'} and $$nvHash{Save}))) { 3603 my $protect = (defined $_[4] and defined $$nvHash{Save} and $$nvHash{Save} > $_[4]); 3604 # this is a bit tricky: we want to add to a protected nvHash only if we 3605 # are adding a conditional delete ($_[5] true or DelValue with no Shift) 3606 # or accumulating List items (NoReplace true) 3607 if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or 3608 ($$nvHash{DelValue} and not defined $$nvHash{Shift})))) 3609 { 3610 return undef; # honour ProtectSaved value by not writing this tag 3611 } elsif ($opts{'delete'}) { 3612 $self->RemoveNewValueHash($nvHash, $tagInfo); 3613 undef $nvHash; 3614 } else { 3615 # save a copy of this new value hash 3616 my %copy = %$nvHash; 3617 # make copy of Value and DelValue lists 3618 my $key; 3619 foreach $key (keys %copy) { 3620 next unless ref $copy{$key} eq 'ARRAY'; 3621 $copy{$key} = [ @{$copy{$key}} ]; 3622 } 3623 my $saveHash = $$self{SAVE_NEW_VALUE}; 3624 # add to linked list of saved new value hashes 3625 $copy{Next} = $$saveHash{$tagInfo}; 3626 $$saveHash{$tagInfo} = \%copy; 3627 delete $$nvHash{Save}; # don't save it again 3628 $$nvHash{AddBefore} = scalar @{$$nvHash{Value}} if $protect and $$nvHash{Value}; 3629 } 3630 } 3631 if (not defined $nvHash and $opts{'create'}) { 3632 # create a new entry 3633 $nvHash = { 3634 TagInfo => $tagInfo, 3635 WriteGroup => $writeGroup, 3636 IsNVH => 1, # set flag so we can recognize a new value hash 3637 }; 3638 # add entry to our NEW_VALUE hash 3639 if ($$self{NEW_VALUE}{$tagInfo}) { 3640 # add to end of linked list 3641 my $lastHash = LastInList($$self{NEW_VALUE}{$tagInfo}); 3642 $$lastHash{Next} = $nvHash; 3643 } else { 3644 $$self{NEW_VALUE}{$tagInfo} = $nvHash; 3645 } 3646 } 3647 return $nvHash; 3648} 3649 3650#------------------------------------------------------------------------------ 3651# Load all tag tables 3652sub LoadAllTables() 3653{ 3654 return if $loadedAllTables; 3655 3656 # load all of our non-referenced tables (first our modules) 3657 my $table; 3658 foreach $table (@loadAllTables) { 3659 my $tableName = "Image::ExifTool::$table"; 3660 $tableName .= '::Main' unless $table =~ /:/; 3661 GetTagTable($tableName); 3662 } 3663 # (then our special tables) 3664 GetTagTable('Image::ExifTool::Extra'); 3665 GetTagTable('Image::ExifTool::Composite'); 3666 # recursively load all tables referenced by the current tables 3667 my @tableNames = keys %allTables; 3668 my %pushedTables; 3669 while (@tableNames) { 3670 $table = GetTagTable(shift @tableNames); 3671 # call write proc if it exists in case it adds tags to the table 3672 my $writeProc = $$table{WRITE_PROC}; 3673 if ($writeProc) { 3674 no strict 'refs'; 3675 &$writeProc(); 3676 } 3677 # recursively scan through tables in subdirectories 3678 foreach (TagTableKeys($table)) { 3679 my @infoArray = GetTagInfoList($table,$_); 3680 my $tagInfo; 3681 foreach $tagInfo (@infoArray) { 3682 my $subdir = $$tagInfo{SubDirectory} or next; 3683 my $tableName = $$subdir{TagTable} or next; 3684 # next if table already loaded or queued for loading 3685 next if $allTables{$tableName} or $pushedTables{$tableName}; 3686 push @tableNames, $tableName; # must scan this one too 3687 $pushedTables{$tableName} = 1; 3688 } 3689 } 3690 } 3691 $loadedAllTables = 1; 3692} 3693 3694#------------------------------------------------------------------------------ 3695# Remove new value hash from linked list (and save if necessary) 3696# Inputs: 0) ExifTool object reference, 1) new value hash ref, 2) tagInfo ref 3697sub RemoveNewValueHash($$$) 3698{ 3699 my ($self, $nvHash, $tagInfo) = @_; 3700 my $firstHash = $$self{NEW_VALUE}{$tagInfo}; 3701 if ($nvHash eq $firstHash) { 3702 # remove first entry from linked list 3703 if ($$nvHash{Next}) { 3704 $$self{NEW_VALUE}{$tagInfo} = $$nvHash{Next}; 3705 } else { 3706 delete $$self{NEW_VALUE}{$tagInfo}; 3707 } 3708 } else { 3709 # find the list element pointing to this hash 3710 $firstHash = $$firstHash{Next} while $$firstHash{Next} ne $nvHash; 3711 # remove from linked list 3712 $$firstHash{Next} = $$nvHash{Next}; 3713 } 3714 # save the existing entry if necessary 3715 if ($$nvHash{Save}) { 3716 my $saveHash = $$self{SAVE_NEW_VALUE}; 3717 # add to linked list of saved new value hashes 3718 $$nvHash{Next} = $$saveHash{$tagInfo}; 3719 $$saveHash{$tagInfo} = $nvHash; 3720 } 3721} 3722 3723#------------------------------------------------------------------------------ 3724# Remove all new value entries for specified group 3725# Inputs: 0) ExifTool object reference, 1) group name 3726sub RemoveNewValuesForGroup($$) 3727{ 3728 my ($self, $group) = @_; 3729 3730 return unless $$self{NEW_VALUE}; 3731 3732 # make list of all groups we must remove 3733 my @groups = ( $group ); 3734 push @groups, @{$removeGroups{$group}} if $removeGroups{$group}; 3735 3736 my ($out, @keys, $hashKey); 3737 $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose} > 1; 3738 3739 # loop though all new values, and remove any in this group 3740 @keys = keys %{$$self{NEW_VALUE}}; 3741 foreach $hashKey (@keys) { 3742 my $nvHash = $$self{NEW_VALUE}{$hashKey}; 3743 # loop through each entry in linked list 3744 for (;;) { 3745 my $nextHash = $$nvHash{Next}; 3746 my $tagInfo = $$nvHash{TagInfo}; 3747 my ($grp0,$grp1) = $self->GetGroup($tagInfo); 3748 my $wgrp = $$nvHash{WriteGroup}; 3749 # use group1 if write group is not specific 3750 $wgrp = $grp1 if $wgrp eq $grp0; 3751 if (grep /^($grp0|$wgrp)$/i, @groups) { 3752 $out and print $out "Removed new value for $wgrp:$$tagInfo{Name}\n"; 3753 # remove from linked list 3754 $self->RemoveNewValueHash($nvHash, $tagInfo); 3755 } 3756 $nvHash = $nextHash or last; 3757 } 3758 } 3759} 3760 3761#------------------------------------------------------------------------------ 3762# Get list of tagInfo hashes for all new data 3763# Inputs: 0) ExifTool object reference, 1) optional tag table pointer 3764# Returns: list of tagInfo hashes 3765sub GetNewTagInfoList($;$) 3766{ 3767 my ($self, $tagTablePtr) = @_; 3768 my @tagInfoList; 3769 my $nv = $$self{NEW_VALUE}; 3770 if ($nv) { 3771 my $hashKey; 3772 foreach $hashKey (keys %$nv) { 3773 my $tagInfo = $$nv{$hashKey}{TagInfo}; 3774 next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; 3775 push @tagInfoList, $tagInfo; 3776 } 3777 } 3778 return @tagInfoList; 3779} 3780 3781#------------------------------------------------------------------------------ 3782# Get hash of tagInfo references keyed on tagID for a specific table 3783# Inputs: 0) ExifTool object reference, 1-N) tag table pointers 3784# Returns: hash reference 3785# Notes: returns only one tagInfo ref for each conditional list 3786sub GetNewTagInfoHash($@) 3787{ 3788 my $self = shift; 3789 my (%tagInfoHash, $hashKey); 3790 my $nv = $$self{NEW_VALUE}; 3791 while ($nv) { 3792 my $tagTablePtr = shift || last; 3793 foreach $hashKey (keys %$nv) { 3794 my $tagInfo = $$nv{$hashKey}{TagInfo}; 3795 next if $tagTablePtr and $tagTablePtr ne $$tagInfo{Table}; 3796 $tagInfoHash{$$tagInfo{TagID}} = $tagInfo; 3797 } 3798 } 3799 return \%tagInfoHash; 3800} 3801 3802#------------------------------------------------------------------------------ 3803# Get a tagInfo/tagID hash for subdirectories we need to add 3804# Inputs: 0) ExifTool object reference, 1) parent tag table reference 3805# 2) parent directory name (taken from GROUP0 of tag table if not defined) 3806# Returns: Reference to Hash of subdirectory tagInfo references keyed by tagID 3807# (plus Reference to edit directory hash in list context) 3808sub GetAddDirHash($$;$) 3809{ 3810 my ($self, $tagTablePtr, $parent) = @_; 3811 $parent or $parent = $$tagTablePtr{GROUPS}{0}; 3812 my $tagID; 3813 my %addDirHash; 3814 my %editDirHash; 3815 my $addDirs = $$self{ADD_DIRS}; 3816 my $editDirs = $$self{EDIT_DIRS}; 3817 foreach $tagID (TagTableKeys($tagTablePtr)) { 3818 my @infoArray = GetTagInfoList($tagTablePtr,$tagID); 3819 my $tagInfo; 3820 foreach $tagInfo (@infoArray) { 3821 next unless $$tagInfo{SubDirectory}; 3822 # get name for this sub directory 3823 # (take directory name from SubDirectory DirName if it exists, 3824 # otherwise Group0 name of SubDirectory TagTable or tag Group1 name) 3825 my $dirName = $$tagInfo{SubDirectory}{DirName}; 3826 unless ($dirName) { 3827 # use tag name for directory name and save for next time 3828 $dirName = $$tagInfo{Name}; 3829 $$tagInfo{SubDirectory}{DirName} = $dirName; 3830 } 3831 # save this directory information if we are writing it 3832 if ($$editDirs{$dirName} and $$editDirs{$dirName} eq $parent) { 3833 $editDirHash{$tagID} = $tagInfo; 3834 $addDirHash{$tagID} = $tagInfo if $$addDirs{$dirName}; 3835 } 3836 } 3837 } 3838 return (\%addDirHash, \%editDirHash) if wantarray; 3839 return \%addDirHash; 3840} 3841 3842#------------------------------------------------------------------------------ 3843# Get localized version of tagInfo hash (used by MIE, XMP, PNG and QuickTime) 3844# Inputs: 0) tagInfo hash ref, 1) locale code (eg. "en_CA" for MIE) 3845# Returns: new tagInfo hash ref, or undef if invalid 3846# - sets LangCode member in new tagInfo 3847sub GetLangInfo($$) 3848{ 3849 my ($tagInfo, $langCode) = @_; 3850 # make a new tagInfo hash for this locale 3851 my $table = $$tagInfo{Table}; 3852 my $tagID = $$tagInfo{TagID} . '-' . $langCode; 3853 my $langInfo = $$table{$tagID}; 3854 unless ($langInfo) { 3855 # make a new tagInfo entry for this locale 3856 $langInfo = { 3857 %$tagInfo, 3858 Name => $$tagInfo{Name} . '-' . $langCode, 3859 Description => Image::ExifTool::MakeDescription($$tagInfo{Name}) . 3860 " ($langCode)", 3861 LangCode => $langCode, 3862 SrcTagInfo => $tagInfo, # save reference to original tagInfo 3863 }; 3864 AddTagToTable($table, $tagID, $langInfo); 3865 } 3866 return $langInfo; 3867} 3868 3869#------------------------------------------------------------------------------ 3870# initialize ADD_DIRS and EDIT_DIRS hashes for all directories that need 3871# to be created or will have tags changed in them 3872# Inputs: 0) ExifTool object reference, 1) file type string (or map hash ref) 3873# 2) preferred family 0 group for creating tags, 3) alternate preferred group 3874# Notes: 3875# - the ADD_DIRS and EDIT_DIRS keys are the directory names, and the values 3876# are the names of the parent directories (undefined for a top-level directory) 3877# - also initializes FORCE_WRITE lookup 3878sub InitWriteDirs($$;$$) 3879{ 3880 my ($self, $fileType, $preferredGroup, $altGroup) = @_; 3881 my $editDirs = $$self{EDIT_DIRS} = { }; 3882 my $addDirs = $$self{ADD_DIRS} = { }; 3883 my $fileDirs = $dirMap{$fileType}; 3884 unless ($fileDirs) { 3885 return unless ref $fileType eq 'HASH'; 3886 $fileDirs = $fileType; 3887 } 3888 my @tagInfoList = $self->GetNewTagInfoList(); 3889 my ($tagInfo, $nvHash); 3890 3891 # save the preferred group 3892 $$self{PreferredGroup} = $preferredGroup; 3893 3894 foreach $tagInfo (@tagInfoList) { 3895 # cycle through all hashes in linked list 3896 for ($nvHash=$self->GetNewValueHash($tagInfo); $nvHash; $nvHash=$$nvHash{Next}) { 3897 # are we creating this tag? (otherwise just deleting or editing it) 3898 my $isCreating = $$nvHash{IsCreating}; 3899 if ($preferredGroup) { 3900 my $g0 = $self->GetGroup($tagInfo, 0); 3901 if ($isCreating) { 3902 # if another group is taking priority, only create 3903 # directory if specifically adding tags to this group 3904 # or if this tag isn't being added to the priority group 3905 $isCreating = 0 if $preferredGroup ne $g0 and 3906 $$nvHash{CreateGroups}{$preferredGroup} and 3907 (not $altGroup or $altGroup ne $g0); 3908 } else { 3909 # create this directory if any tag is preferred and has a value 3910 # (unless group creation is disabled via the WriteMode option) 3911 $isCreating = 1 if $$nvHash{Value} and $preferredGroup eq $g0 and 3912 not $$nvHash{EditOnly} and $$self{OPTIONS}{WriteMode} =~ /g/; 3913 } 3914 } 3915 # tag belongs to directory specified by WriteGroup, or by 3916 # the Group0 name if WriteGroup not defined 3917 my $dirName = $$nvHash{WriteGroup}; 3918 # remove MIE copy number(s) if they exist 3919 if ($dirName =~ /^MIE\d*(-[a-z]+)?\d*$/i) { 3920 $dirName = 'MIE' . ($1 || ''); 3921 } 3922 my @dirNames; 3923 # allow a group name of '*' to force writing EXIF/IPTC/XMP/PNG (ForceWrite tag) 3924 if ($dirName eq '*' and $$nvHash{Value}) { 3925 my $val = $$nvHash{Value}[0]; 3926 if ($val) { 3927 foreach (qw(EXIF IPTC XMP PNG FixBase)) { 3928 next unless $val =~ /\b($_|All)\b/i; 3929 push @dirNames, $_; 3930 push @dirNames, 'EXIF' if $_ eq 'FixBase'; 3931 $$self{FORCE_WRITE}{$_} = 1; 3932 } 3933 } 3934 $dirName = shift @dirNames; 3935 } elsif ($dirName eq 'QuickTime') { 3936 # write to specific QuickTime group 3937 $dirName = $self->GetGroup($tagInfo, 1); 3938 } 3939 while ($dirName) { 3940 my $parent = $$fileDirs{$dirName}; 3941 if (ref $parent) { 3942 push @dirNames, reverse @$parent; 3943 $parent = pop @dirNames; 3944 } 3945 $$editDirs{$dirName} = $parent; 3946 $$addDirs{$dirName} = $parent if $isCreating and $isCreating != 2; 3947 $dirName = $parent || shift @dirNames 3948 } 3949 } 3950 } 3951 if (%{$$self{DEL_GROUP}}) { 3952 # add delete groups to list of edited groups 3953 foreach (keys %{$$self{DEL_GROUP}}) { 3954 next if /^-/; # ignore excluded groups 3955 my $dirName = $_; 3956 # translate necessary group 0 names 3957 $dirName = $translateWriteGroup{$dirName} if $translateWriteGroup{$dirName}; 3958 # convert XMP group 1 names 3959 $dirName = 'XMP' if $dirName =~ /^XMP-/; 3960 my @dirNames; 3961 while ($dirName) { 3962 my $parent = $$fileDirs{$dirName}; 3963 if (ref $parent) { 3964 push @dirNames, reverse @$parent; 3965 $parent = pop @dirNames; 3966 } 3967 $$editDirs{$dirName} = $parent; 3968 $dirName = $parent || shift @dirNames 3969 } 3970 } 3971 } 3972 # special case to edit JFIF to get resolutions if editing EXIF information 3973 if ($$editDirs{IFD0} and $$fileDirs{JFIF}) { 3974 $$editDirs{JFIF} = 'IFD1'; 3975 $$editDirs{APP0} = undef; 3976 } 3977 3978 if ($$self{OPTIONS}{Verbose}) { 3979 my $out = $$self{OPTIONS}{TextOut}; 3980 print $out " Editing tags in: "; 3981 foreach (sort keys %$editDirs) { print $out "$_ "; } 3982 print $out "\n"; 3983 return unless $$self{OPTIONS}{Verbose} > 1; 3984 print $out " Creating tags in: "; 3985 foreach (sort keys %$addDirs) { print $out "$_ "; } 3986 print $out "\n"; 3987 } 3988} 3989 3990#------------------------------------------------------------------------------ 3991# Write an image directory 3992# Inputs: 0) ExifTool object reference, 1) source directory information reference 3993# 2) tag table reference, 3) optional reference to writing procedure 3994# Returns: New directory data or undefined on error (or empty string to delete directory) 3995sub WriteDirectory($$$;$) 3996{ 3997 my ($self, $dirInfo, $tagTablePtr, $writeProc) = @_; 3998 my ($out, $nvHash, $delFlag); 3999 4000 $tagTablePtr or return undef; 4001 $out = $$self{OPTIONS}{TextOut} if $$self{OPTIONS}{Verbose}; 4002 # set directory name from default group0 name if not done already 4003 my $dirName = $$dirInfo{DirName}; 4004 my $dataPt = $$dirInfo{DataPt}; 4005 my $grp0 = $$tagTablePtr{GROUPS}{0}; 4006 $dirName or $dirName = $$dirInfo{DirName} = $grp0; 4007 if (%{$$self{DEL_GROUP}}) { 4008 my $delGroup = $$self{DEL_GROUP}; 4009 # delete entire directory if specified 4010 my $grp1 = $dirName; 4011 $delFlag = ($$delGroup{$grp0} or $$delGroup{$grp1}) unless $permanentDir{$grp0}; 4012 # (never delete an entire QuickTime group) 4013 if ($delFlag) { 4014 if (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and 4015 $self->IsRawType() and 4016 # allow non-permanent MakerNote directories to be deleted (ie. NikonCapture) 4017 (not $$dirInfo{TagInfo} or not defined $$dirInfo{TagInfo}{Permanent} or 4018 $$dirInfo{TagInfo}{Permanent})) 4019 { 4020 $self->WarnOnce("Can't delete $1 from $$self{FileType}",1); 4021 undef $grp1; 4022 } elsif (not $blockExifTypes{$$self{FILE_TYPE}}) { 4023 # restrict delete logic to prevent entire tiff image from being killed 4024 # (don't allow IFD0 to be deleted, and delete only ExifIFD if EXIF specified) 4025 if ($$self{FILE_TYPE} eq 'PSD') { 4026 # don't delete Photoshop directories from PSD image 4027 undef $grp1 if $grp0 eq 'Photoshop'; 4028 } elsif ($$self{FILE_TYPE} =~ /^(EPS|PS)$/) { 4029 # allow anything to be deleted from PostScript files 4030 } elsif ($grp1 eq 'IFD0') { 4031 my $type = $$self{TIFF_TYPE} || $$self{FILE_TYPE}; 4032 $$delGroup{IFD0} and $self->Warn("Can't delete IFD0 from $type",1); 4033 undef $grp1; 4034 } elsif ($grp0 eq 'EXIF' and $$delGroup{$grp0}) { 4035 undef $grp1 unless $$delGroup{$grp1} or $grp1 eq 'ExifIFD'; 4036 } 4037 } 4038 if ($grp1) { 4039 if ($dataPt or $$dirInfo{RAF}) { 4040 ++$$self{CHANGED}; 4041 $out and print $out " Deleting $grp1\n"; 4042 $self->Warn('ICC_Profile deleted. Image colors may be affected') if $grp1 eq 'ICC_Profile'; 4043 # can no longer validate TIFF_END if deleting an entire IFD 4044 delete $$self{TIFF_END} if $dirName =~ /IFD/; 4045 } 4046 # don't add back into the wrong location 4047 my $right = $$self{ADD_DIRS}{$grp1}; 4048 # (take care because EXIF directory name may be either EXIF or IFD0, 4049 # but IFD0 will be the one that appears in the directory map) 4050 $right = $$self{ADD_DIRS}{IFD0} if not $right and $grp1 eq 'EXIF'; 4051 if ($delFlag == 2 and $right) { 4052 # also check grandparent because some routines create 2 levels in 1 4053 my $right2 = $$self{ADD_DIRS}{$right} || ''; 4054 my $parent = $$dirInfo{Parent}; 4055 if (not $parent or $parent eq $right or $parent eq $right2) { 4056 # prevent duplicate directories from being recreated at the same path 4057 my $path = join '-', @{$$self{PATH}}, $dirName; 4058 $$self{Recreated} or $$self{Recreated} = { }; 4059 if ($$self{Recreated}{$path}) { 4060 my $p = $parent ? " in $parent" : ''; 4061 $self->Warn("Not recreating duplicate $grp1$p",1); 4062 return ''; 4063 } 4064 $$self{Recreated}{$path} = 1; 4065 # empty the directory 4066 my $data = ''; 4067 $$dirInfo{DataPt} = \$data; 4068 $$dirInfo{DataLen} = 0; 4069 $$dirInfo{DirStart} = 0; 4070 $$dirInfo{DirLen} = 0; 4071 delete $$dirInfo{RAF}; 4072 delete $$dirInfo{Base}; 4073 delete $$dirInfo{DataPos}; 4074 } else { 4075 $self->Warn("Not recreating $grp1 in $parent (should be in $right)",1); 4076 return ''; 4077 } 4078 } else { 4079 return '' unless $$dirInfo{NoDelete}; 4080 } 4081 } 4082 } 4083 } 4084 # use default proc from tag table if no proc specified 4085 $writeProc or $writeProc = $$tagTablePtr{WRITE_PROC} or return undef; 4086 4087 # are we rewriting a pre-existing directory? 4088 my $isRewriting = ($$dirInfo{DirLen} or (defined $dataPt and length $$dataPt) or $$dirInfo{RAF}); 4089 4090 # copy or delete new directory as a block if specified 4091 my $blockName = $dirName; 4092 $blockName = 'EXIF' if $blockName eq 'IFD0'; 4093 my $tagInfo = $Image::ExifTool::Extra{$blockName} || $$dirInfo{TagInfo}; 4094 while ($tagInfo and ($nvHash = $$self{NEW_VALUE}{$tagInfo}) and 4095 $self->IsOverwriting($nvHash) and not ($$nvHash{CreateOnly} and $isRewriting)) 4096 { 4097 # protect against writing EXIF to wrong file types, etc 4098 if ($blockName eq 'EXIF') { 4099 unless ($blockExifTypes{$$self{FILE_TYPE}}) { 4100 $self->Warn("Can't write EXIF as a block to $$self{FILE_TYPE} file"); 4101 last; 4102 } 4103 # this can happen if we call WriteDirectory for an EXIF directory without going 4104 # through WriteTIFF as the WriteProc (which happens if conditionally replacing 4105 # the EXIF block and the condition fails), but we never want to do a block write 4106 # in this case because the EXIF block would end up with two TIFF headers 4107 last unless $writeProc eq \&Image::ExifTool::WriteTIFF; 4108 } 4109 last unless $self->IsOverwriting($nvHash, $dataPt ? $$dataPt : ''); 4110 my $verb = 'Writing'; 4111 my $newVal = $self->GetNewValue($nvHash); 4112 unless (defined $newVal and length $newVal) { 4113 return '' unless $dataPt or $$dirInfo{RAF}; # nothing to do if block never existed 4114 # don't allow MakerNotes to be removed from RAW files 4115 if ($blockName eq 'MakerNotes' and $rawType{$$self{FileType}}) { 4116 $self->Warn("Can't delete MakerNotes from $$self{VALUE}{FileType}",1); 4117 return undef; 4118 } 4119 $verb = 'Deleting'; 4120 $newVal = ''; 4121 } 4122 $$dirInfo{BlockWrite} = 1; # set flag indicating we did a block write 4123 $out and print $out " $verb $blockName as a block\n"; 4124 ++$$self{CHANGED}; 4125 return $newVal; 4126 } 4127 # guard against writing the same directory twice 4128 if (defined $dataPt and defined $$dirInfo{DirStart} and defined $$dirInfo{DataPos} and 4129 not $$dirInfo{NoRefTest}) 4130 { 4131 my $addr = $$dirInfo{DirStart} + $$dirInfo{DataPos} + ($$dirInfo{Base}||0) + $$self{BASE}; 4132 # (Phase One P25 IIQ files have ICC_Profile duplicated in IFD0 and IFD1) 4133 if ($$self{PROCESSED}{$addr} and ($dirName ne 'ICC_Profile' or $$self{TIFF_TYPE} ne 'IIQ')) { 4134 if (defined $$dirInfo{DirLen} and not $$dirInfo{DirLen} and $dirName ne $$self{PROCESSED}{$addr}) { 4135 # it is hypothetically possible to have 2 different directories 4136 # with the same address if one has a length of zero 4137 } elsif ($self->Error("$dirName pointer references previous $$self{PROCESSED}{$addr} directory", 2)) { 4138 return undef; 4139 } else { 4140 $self->Warn("Deleting duplicate $dirName directory"); 4141 $out and print $out " Deleting $dirName\n"; 4142 # delete the duplicate directory (don't recreate it when writing new 4143 # tags to prevent propagating a duplicate IFD in cases like when the 4144 # same ExifIFD exists in both IFD0 and IFD1) 4145 return ''; 4146 } 4147 } else { 4148 $$self{PROCESSED}{$addr} = $dirName; 4149 } 4150 } 4151 my $oldDir = $$self{DIR_NAME}; 4152 my @save = @$self{'Compression','SubfileType'}; 4153 my $name; 4154 if ($out) { 4155 $name = ($dirName eq 'MakerNotes' and $$dirInfo{TagInfo}) ? 4156 $$dirInfo{TagInfo}{Name} : $dirName; 4157 if (not defined $oldDir or $oldDir ne $name) { 4158 my $verb = $isRewriting ? 'Rewriting' : 'Creating'; 4159 print $out " $verb $name\n"; 4160 } 4161 } 4162 my $saveOrder = GetByteOrder(); 4163 my $oldChanged = $$self{CHANGED}; 4164 $$self{DIR_NAME} = $dirName; 4165 push @{$$self{PATH}}, $dirName; 4166 $$dirInfo{IsWriting} = 1; 4167 my $newData; 4168 { 4169 no strict 'refs'; 4170 $newData = &$writeProc($self, $dirInfo, $tagTablePtr); 4171 } 4172 pop @{$$self{PATH}}; 4173 # nothing changed if error occurred or nothing was created 4174 $$self{CHANGED} = $oldChanged unless defined $newData and (length($newData) or $isRewriting); 4175 $$self{DIR_NAME} = $oldDir; 4176 @$self{'Compression','SubfileType'} = @save; 4177 SetByteOrder($saveOrder); 4178 print $out " Deleting $name\n" if $out and defined $newData and not length $newData; 4179 return $newData; 4180} 4181 4182#------------------------------------------------------------------------------ 4183# Uncommon utility routines to for reading binary data values 4184# Inputs: 0) data reference, 1) offset into data 4185sub Get64s($$) 4186{ 4187 my ($dataPt, $pos) = @_; 4188 my $pt = GetByteOrder() eq 'MM' ? 0 : 4; # get position of high word 4189 my $hi = Get32s($dataPt, $pos + $pt); # preserve sign bit of high word 4190 my $lo = Get32u($dataPt, $pos + 4 - $pt); 4191 return $hi * 4294967296 + $lo; 4192} 4193sub Get64u($$) 4194{ 4195 my ($dataPt, $pos) = @_; 4196 my $pt = GetByteOrder() eq 'MM' ? 0 : 4; # get position of high word 4197 my $hi = Get32u($dataPt, $pos + $pt); # (unsigned this time) 4198 my $lo = Get32u($dataPt, $pos + 4 - $pt); 4199 return $hi * 4294967296 + $lo; 4200} 4201sub GetFixed64s($$) 4202{ 4203 my ($dataPt, $pos) = @_; 4204 my $val = Get64s($dataPt, $pos) / 4294967296; 4205 # remove insignificant digits 4206 return int($val * 1e10 + ($val>0 ? 0.5 : -0.5)) / 1e10; 4207} 4208# Decode extended 80-bit float used by Apple SANE and Intel 8087 4209# (note: different than the IEEE standard 80-bit float) 4210sub GetExtended($$) 4211{ 4212 my ($dataPt, $pos) = @_; 4213 my $pt = GetByteOrder() eq 'MM' ? 0 : 2; # get position of exponent 4214 my $exp = Get16u($dataPt, $pos + $pt); 4215 my $sig = Get64u($dataPt, $pos + 2 - $pt); # get significand as int64u 4216 my $sign = $exp & 0x8000 ? -1 : 1; 4217 $exp = ($exp & 0x7fff) - 16383 - 63; # (-63 to fractionalize significand) 4218 return $sign * $sig * 2 ** $exp; 4219} 4220 4221#------------------------------------------------------------------------------ 4222# Dump data in hex and ASCII to console 4223# Inputs: 0) data reference, 1) length or undef, 2-N) Options: 4224# Options: Start => offset to start of data (default=0) 4225# Addr => address to print for data start (default=DataPos+Base+Start) 4226# DataPos => position of data within block (relative to Base) 4227# Base => base offset for pointers from start of file 4228# Width => width of printout (bytes, default=16) 4229# Prefix => prefix to print at start of line (default='') 4230# MaxLen => maximum length to dump 4231# Out => output file reference 4232# Len => data length 4233sub HexDump($;$%) 4234{ 4235 my $dataPt = shift; 4236 my $len = shift; 4237 my %opts = @_; 4238 my $start = $opts{Start} || 0; 4239 my $addr = $opts{Addr}; 4240 my $wid = $opts{Width} || 16; 4241 my $prefix = $opts{Prefix} || ''; 4242 my $out = $opts{Out} || \*STDOUT; 4243 my $maxLen = $opts{MaxLen}; 4244 my $datLen = length($$dataPt) - $start; 4245 my $more; 4246 $len = $opts{Len} if defined $opts{Len}; 4247 4248 $addr = $start + ($opts{DataPos} || 0) + ($opts{Base} || 0) unless defined $addr; 4249 $len = $datLen unless defined $len; 4250 if ($maxLen and $len > $maxLen) { 4251 # print one line less to allow for $more line below 4252 $maxLen = int(($maxLen - 1) / $wid) * $wid; 4253 $more = $len - $maxLen; 4254 $len = $maxLen; 4255 } 4256 if ($len > $datLen) { 4257 print $out "$prefix Warning: Attempted dump outside data\n"; 4258 print $out "$prefix ($len bytes specified, but only $datLen available)\n"; 4259 $len = $datLen; 4260 } 4261 my $format = sprintf("%%-%ds", $wid * 3); 4262 my $tmpl = 'H2' x $wid; # ('(H2)*' would have been nice, but older perl versions don't support it) 4263 my $i; 4264 for ($i=0; $i<$len; $i+=$wid) { 4265 $wid > $len-$i and $wid = $len-$i, $tmpl = 'H2' x $wid; 4266 printf $out "$prefix%8.4x: ", $addr+$i; 4267 my $dat = substr($$dataPt, $i+$start, $wid); 4268 my $s = join(' ', unpack($tmpl, $dat)); 4269 printf $out $format, $s; 4270 $dat =~ tr /\x00-\x1f\x7f-\xff/./; 4271 print $out "[$dat]\n"; 4272 } 4273 $more and print $out "$prefix [snip $more bytes]\n"; 4274} 4275 4276#------------------------------------------------------------------------------ 4277# Print verbose tag information 4278# Inputs: 0) ExifTool object reference, 1) tag ID 4279# 2) tag info reference (or undef) 4280# 3-N) extra parms: 4281# Parms: Index => Index of tag in menu (starting at 0) 4282# Value => Tag value 4283# DataPt => reference to value data block 4284# DataPos => location of data block in file 4285# Base => base added to all offsets 4286# Size => length of value data within block 4287# Format => value format string 4288# Count => number of values 4289# Extra => Extra Verbose=2 information to put after tag number 4290# Table => Reference to tag table 4291# --> plus any of these HexDump() options: Start, Addr, Width 4292sub VerboseInfo($$$%) 4293{ 4294 my ($self, $tagID, $tagInfo, %parms) = @_; 4295 my $verbose = $$self{OPTIONS}{Verbose}; 4296 my $out = $$self{OPTIONS}{TextOut}; 4297 my ($tag, $line, $hexID); 4298 4299 # generate hex number if tagID is numerical 4300 if (defined $tagID) { 4301 $tagID =~ /^\d+$/ and $hexID = sprintf("0x%.4x", $tagID); 4302 } else { 4303 $tagID = 'Unknown'; 4304 } 4305 # get tag name 4306 if ($tagInfo and $$tagInfo{Name}) { 4307 $tag = $$tagInfo{Name}; 4308 } else { 4309 my $prefix; 4310 $prefix = $parms{Table}{TAG_PREFIX} if $parms{Table}; 4311 if ($prefix or $hexID) { 4312 $prefix = 'Unknown' unless $prefix; 4313 $tag = $prefix . '_' . ($hexID ? $hexID : $tagID); 4314 } else { 4315 $tag = $tagID; 4316 } 4317 } 4318 my $dataPt = $parms{DataPt}; 4319 my $size = $parms{Size}; 4320 $size = length $$dataPt unless defined $size or not $dataPt; 4321 my $indent = $$self{INDENT}; 4322 4323 # Level 1: print tag/value information 4324 $line = $indent; 4325 my $index = $parms{Index}; 4326 if (defined $index) { 4327 $line .= $index . ') '; 4328 $line .= ' ' if length($index) < 2; 4329 $indent .= ' '; # indent everything else to align with tag name 4330 } 4331 $line .= $tag; 4332 if ($tagInfo and $$tagInfo{SubDirectory}) { 4333 $line .= ' (SubDirectory) -->'; 4334 } else { 4335 my $maxLen = 90 - length($line); 4336 my $val = $parms{Value}; 4337 if (defined $val) { 4338 $val = '[' . join(',',@$val) . ']' if ref $val eq 'ARRAY'; 4339 $line .= ' = ' . $self->Printable($val, $maxLen); 4340 } elsif ($dataPt) { 4341 my $start = $parms{Start} || 0; 4342 $line .= ' = ' . $self->Printable(substr($$dataPt,$start,$size), $maxLen); 4343 } 4344 } 4345 print $out "$line\n"; 4346 4347 # Level 2: print detailed information about the tag 4348 if ($verbose > 1 and ($parms{Extra} or $parms{Format} or 4349 $parms{DataPt} or defined $size or $tagID =~ /\//)) 4350 { 4351 $line = $indent . '- Tag '; 4352 if ($hexID) { 4353 $line .= $hexID; 4354 } else { 4355 $tagID =~ s/([\0-\x1f\x7f-\xff])/sprintf('\\x%.2x',ord $1)/ge; 4356 $line .= "'${tagID}'"; 4357 } 4358 $line .= $parms{Extra} if defined $parms{Extra}; 4359 my $format = $parms{Format}; 4360 if ($format or defined $size) { 4361 $line .= ' ('; 4362 if (defined $size) { 4363 $line .= "$size bytes"; 4364 $line .= ', ' if $format; 4365 } 4366 if ($format) { 4367 $line .= $format; 4368 $line .= '['.$parms{Count}.']' if $parms{Count}; 4369 } 4370 $line .= ')'; 4371 } 4372 $line .= ':' if $verbose > 2 and $parms{DataPt}; 4373 print $out "$line\n"; 4374 } 4375 4376 # Level 3: do hex dump of value 4377 if ($verbose > 2 and $parms{DataPt} and (not $tagInfo or not $$tagInfo{ReadFromRAF})) { 4378 $parms{Out} = $out; 4379 $parms{Prefix} = $indent; 4380 # limit dump length if Verbose < 5 4381 $parms{MaxLen} = $verbose == 3 ? 96 : 2048 if $verbose < 5; 4382 HexDump($dataPt, $size, %parms); 4383 } 4384} 4385 4386#------------------------------------------------------------------------------ 4387# Dump trailer information 4388# Inputs: 0) ExifTool object ref, 1) dirInfo hash (RAF, DirName, DataPos, DirLen) 4389# Notes: Restores current file position before returning 4390sub DumpTrailer($$) 4391{ 4392 my ($self, $dirInfo) = @_; 4393 my $raf = $$dirInfo{RAF}; 4394 my $curPos = $raf->Tell(); 4395 my $trailer = $$dirInfo{DirName} || 'Unknown'; 4396 my $pos = $$dirInfo{DataPos}; 4397 my $verbose = $$self{OPTIONS}{Verbose}; 4398 my $htmlDump = $$self{HTML_DUMP}; 4399 my ($buff, $buf2); 4400 my $size = $$dirInfo{DirLen}; 4401 $pos = $curPos unless defined $pos; 4402 4403 # get full trailer size if not specified 4404 for (;;) { 4405 unless ($size) { 4406 $raf->Seek(0, 2) or last; 4407 $size = $raf->Tell() - $pos; 4408 last unless $size; 4409 } 4410 $raf->Seek($pos, 0) or last; 4411 if ($htmlDump) { 4412 my $num = $raf->Read($buff, $size) or return; 4413 my $desc = "$trailer trailer"; 4414 $desc = "[$desc]" if $trailer eq 'Unknown'; 4415 $self->HDump($pos, $num, $desc, undef, 0x08); 4416 last; 4417 } 4418 my $out = $$self{OPTIONS}{TextOut}; 4419 printf $out "$trailer trailer (%d bytes at offset 0x%.4x):\n", $size, $pos; 4420 last unless $verbose > 2; 4421 my $num = $size; # number of bytes to read 4422 # limit size if not very verbose 4423 if ($verbose < 5) { 4424 my $limit = $verbose < 4 ? 96 : 512; 4425 $num = $limit if $num > $limit; 4426 } 4427 $raf->Read($buff, $num) == $num or return; 4428 # read the end of the trailer too if not done already 4429 if ($size > 2 * $num) { 4430 $raf->Seek($pos + $size - $num, 0); 4431 $raf->Read($buf2, $num); 4432 } elsif ($size > $num) { 4433 $raf->Seek($pos + $num, 0); 4434 $raf->Read($buf2, $size - $num); 4435 $buff .= $buf2; 4436 undef $buf2; 4437 } 4438 HexDump(\$buff, undef, Addr => $pos, Out => $out); 4439 if (defined $buf2) { 4440 print $out " [snip ", $size - $num * 2, " bytes]\n"; 4441 HexDump(\$buf2, undef, Addr => $pos + $size - $num, Out => $out); 4442 } 4443 last; 4444 } 4445 $raf->Seek($curPos, 0); 4446} 4447 4448#------------------------------------------------------------------------------ 4449# Dump unknown trailer information 4450# Inputs: 0) ExifTool ref, 1) dirInfo ref (with RAF, DataPos and DirLen defined) 4451# Notes: changes dirInfo elements 4452sub DumpUnknownTrailer($$) 4453{ 4454 my ($self, $dirInfo) = @_; 4455 my $pos = $$dirInfo{DataPos}; 4456 my $endPos = $pos + $$dirInfo{DirLen}; 4457 # account for preview/MPF image trailer 4458 my $prePos = $$self{VALUE}{PreviewImageStart} || $$self{PreviewImageStart}; 4459 my $preLen = $$self{VALUE}{PreviewImageLength} || $$self{PreviewImageLength}; 4460 my $tag = 'PreviewImage'; 4461 my $mpImageNum = 0; 4462 my (%image, $lastOne); 4463 for (;;) { 4464 # add to Preview block list if valid and in the trailer 4465 $image{$prePos} = [$tag, $preLen] if $prePos and $preLen and $prePos+$preLen > $pos; 4466 last if $lastOne; # checked all images 4467 # look for MPF images (in the the proper order) 4468 ++$mpImageNum; 4469 $prePos = $$self{VALUE}{"MPImageStart ($mpImageNum)"}; 4470 if (defined $prePos) { 4471 $preLen = $$self{VALUE}{"MPImageLength ($mpImageNum)"}; 4472 } else { 4473 $prePos = $$self{VALUE}{'MPImageStart'}; 4474 $preLen = $$self{VALUE}{'MPImageLength'}; 4475 $lastOne = 1; 4476 } 4477 $tag = "MPImage$mpImageNum"; 4478 } 4479 # dump trailer sections in order 4480 $image{$endPos} = [ '', 0 ]; # add terminator "image" 4481 foreach $prePos (sort { $a <=> $b } keys %image) { 4482 if ($pos < $prePos) { 4483 # dump unknown trailer data 4484 $$dirInfo{DirName} = 'Unknown'; 4485 $$dirInfo{DataPos} = $pos; 4486 $$dirInfo{DirLen} = $prePos - $pos; 4487 $self->DumpTrailer($dirInfo); 4488 } 4489 ($tag, $preLen) = @{$image{$prePos}}; 4490 last unless $preLen; 4491 # dump image if verbose (it is htmlDump'd by ExtractImage) 4492 if ($$self{OPTIONS}{Verbose}) { 4493 $$dirInfo{DirName} = $tag; 4494 $$dirInfo{DataPos} = $prePos; 4495 $$dirInfo{DirLen} = $preLen; 4496 $self->DumpTrailer($dirInfo); 4497 } 4498 $pos = $prePos + $preLen; 4499 } 4500} 4501 4502#------------------------------------------------------------------------------ 4503# Find last element in linked list 4504# Inputs: 0) element in list 4505# Returns: Last element in list 4506sub LastInList($) 4507{ 4508 my $element = shift; 4509 while ($$element{Next}) { 4510 $element = $$element{Next}; 4511 } 4512 return $element; 4513} 4514 4515#------------------------------------------------------------------------------ 4516# Print verbose value while writing 4517# Inputs: 0) ExifTool object ref, 1) heading "eg. '+ IPTC:Keywords', 4518# 2) value, 3) [optional] extra text after value 4519sub VerboseValue($$$;$) 4520{ 4521 return unless $_[0]{OPTIONS}{Verbose} > 1; 4522 my ($self, $str, $val, $xtra) = @_; 4523 my $out = $$self{OPTIONS}{TextOut}; 4524 $xtra or $xtra = ''; 4525 my $maxLen = 81 - length($str) - length($xtra); 4526 $val = $self->Printable($val, $maxLen); 4527 print $out " $str = '${val}'$xtra\n"; 4528} 4529 4530#------------------------------------------------------------------------------ 4531# Pack Unicode numbers into UTF8 string 4532# Inputs: 0-N) list of Unicode numbers 4533# Returns: Packed UTF-8 string 4534sub PackUTF8(@) 4535{ 4536 my @out; 4537 while (@_) { 4538 my $ch = pop; 4539 unshift(@out, $ch), next if $ch < 0x80; 4540 unshift(@out, 0x80 | ($ch & 0x3f)); 4541 $ch >>= 6; 4542 unshift(@out, 0xc0 | $ch), next if $ch < 0x20; 4543 unshift(@out, 0x80 | ($ch & 0x3f)); 4544 $ch >>= 6; 4545 unshift(@out, 0xe0 | $ch), next if $ch < 0x10; 4546 unshift(@out, 0x80 | ($ch & 0x3f)); 4547 $ch >>= 6; 4548 unshift(@out, 0xf0 | ($ch & 0x07)); 4549 } 4550 return pack('C*', @out); 4551} 4552 4553#------------------------------------------------------------------------------ 4554# Unpack numbers from UTF8 string 4555# Inputs: 0) UTF-8 string 4556# Returns: List of Unicode numbers (sets $evalWarning on error) 4557sub UnpackUTF8($) 4558{ 4559 my (@out, $pos); 4560 pos($_[0]) = $pos = 0; # start at beginning of string 4561 for (;;) { 4562 my ($ch, $newPos, $val, $byte); 4563 if ($_[0] =~ /([\x80-\xff])/g) { 4564 $ch = ord($1); 4565 $newPos = pos($_[0]) - 1; 4566 } else { 4567 $newPos = length $_[0]; 4568 } 4569 # unpack 7-bit characters 4570 my $len = $newPos - $pos; 4571 push @out, unpack("x${pos}C$len",$_[0]) if $len; 4572 last unless defined $ch; 4573 $pos = $newPos + 1; 4574 # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences 4575 # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte 4576 # sequences), and 0xfe and 0xff are not valid in UTF-8 strings 4577 if ($ch < 0xc2 or $ch >= 0xf8) { 4578 push @out, ord('?'); # invalid UTF-8 4579 $evalWarning = 'Bad UTF-8'; 4580 next; 4581 } 4582 # decode 2, 3 and 4-byte sequences 4583 my $n = 1; 4584 if ($ch < 0xe0) { 4585 $val = $ch & 0x1f; # 2-byte sequence 4586 } elsif ($ch < 0xf0) { 4587 $val = $ch & 0x0f; # 3-byte sequence 4588 ++$n; 4589 } else { 4590 $val = $ch & 0x07; # 4-byte sequence 4591 $n += 2; 4592 } 4593 unless ($_[0] =~ /\G([\x80-\xbf]{$n})/g) { 4594 pos($_[0]) = $pos; # restore position 4595 push @out, ord('?'); # invalid UTF-8 4596 $evalWarning = 'Bad UTF-8'; 4597 next; 4598 } 4599 foreach $byte (unpack 'C*', $1) { 4600 $val = ($val << 6) | ($byte & 0x3f); 4601 } 4602 push @out, $val; # save Unicode character value 4603 $pos += $n; # position at end of UTF-8 character 4604 } 4605 return @out; 4606} 4607 4608#------------------------------------------------------------------------------ 4609# Generate a new, random GUID 4610# Inputs: <none> 4611# Returns: GUID string 4612my $guidCount; 4613sub NewGUID() 4614{ 4615 my @tm = localtime time; 4616 $guidCount = 0 unless defined $guidCount and ++$guidCount < 0x100; 4617 return sprintf('%.4d%.2d%.2d%.2d%.2d%.2d%.2X%.4X%.4X%.4X%.4X', 4618 $tm[5]+1900, $tm[4]+1, $tm[3], $tm[2], $tm[1], $tm[0], $guidCount, 4619 $$ & 0xffff, rand(0x10000), rand(0x10000), rand(0x10000)); 4620} 4621 4622#------------------------------------------------------------------------------ 4623# Make TIFF header for raw data 4624# Inputs: 0) width, 1) height, 2) num colour components, 3) bits, 4) resolution 4625# 5) color-map data for palette-color image (8 or 16 bit) 4626# Returns: TIFF header 4627# Notes: Multi-byte data must be little-endian 4628sub MakeTiffHeader($$$$;$$) 4629{ 4630 my ($w, $h, $cols, $bits, $res, $cmap) = @_; 4631 $res or $res = 72; 4632 my $saveOrder = GetByteOrder(); 4633 SetByteOrder('II'); 4634 if (not $cmap) { 4635 $cmap = ''; 4636 } elsif (length $cmap == 3 * 2**$bits) { 4637 # convert to short 4638 $cmap = pack 'v*', map { $_ | ($_<<8) } unpack 'C*', $cmap; 4639 } elsif (length $cmap != 6 * 2**$bits) { 4640 $cmap = ''; 4641 } 4642 my $cmo = $cmap ? 12 : 0; # offset due to ColorMap IFD entry 4643 my $hdr = 4644 "\x49\x49\x2a\0\x08\0\0\0\x0e\0" . # 0x00 14 menu entries: 4645 "\xfe\x00\x04\0\x01\0\0\0\x00\0\0\0" . # 0x0a SubfileType = 0 4646 "\x00\x01\x04\0\x01\0\0\0" . Set32u($w) . # 0x16 ImageWidth 4647 "\x01\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x22 ImageHeight 4648 "\x02\x01\x03\0" . Set32u($cols) . # 0x2e BitsPerSample 4649 Set32u($cols == 1 ? $bits : 0xb6 + $cmo) . 4650 "\x03\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x3a Compression = 1 4651 "\x06\x01\x03\0\x01\0\0\0" . # 0x46 PhotometricInterpretation 4652 Set32u($cmap ? 3 : $cols == 1 ? 1 : 2) . 4653 "\x11\x01\x04\0\x01\0\0\0" . # 0x52 StripOffsets 4654 Set32u(0xcc + $cmo + length($cmap)) . 4655 "\x15\x01\x03\0\x01\0\0\0" . Set32u($cols) . # 0x5e SamplesPerPixel 4656 "\x16\x01\x04\0\x01\0\0\0" . Set32u($h) . # 0x6a RowsPerStrip 4657 "\x17\x01\x04\0\x01\0\0\0" . # 0x76 StripByteCounts 4658 Set32u($w * $h * $cols * int(($bits+7)/8)) . 4659 "\x1a\x01\x05\0\x01\0\0\0" . Set32u(0xbc + $cmo) . # 0x82 XResolution 4660 "\x1b\x01\x05\0\x01\0\0\0" . Set32u(0xc4 + $cmo) . # 0x8e YResolution 4661 "\x1c\x01\x03\0\x01\0\0\0\x01\0\0\0" . # 0x9a PlanarConfiguration = 1 4662 "\x28\x01\x03\0\x01\0\0\0\x02\0\0\0" . # 0xa6 ResolutionUnit = 2 4663 ($cmap ? # 0xb2 ColorMap [optional] 4664 "\x40\x01\x03\0" . Set32u(3 * 2**$bits) . "\xd8\0\0\0" : '') . 4665 "\0\0\0\0" . # 0xb2+$cmo (no IFD1) 4666 (Set16u($bits) x 3) . # 0xb6+$cmo BitsPerSample value 4667 Set32u($res) . "\x01\0\0\0" . # 0xbc+$cmo XResolution = 72 4668 Set32u($res) . "\x01\0\0\0" . # 0xc4+$cmo YResolution = 72 4669 $cmap; # 0xcc or 0xd8 (cmap and data go here) 4670 SetByteOrder($saveOrder); 4671 return $hdr; 4672} 4673 4674#------------------------------------------------------------------------------ 4675# Return current time in EXIF format 4676# Inputs: 0) [optional] ExifTool ref, 1) flag to include timezone (0 to disable, 4677# undef or 1 to include) 4678# Returns: time string 4679# - a consistent value is returned for each processed file 4680sub TimeNow(;$$) 4681{ 4682 my ($self, $tzFlag) = @_; 4683 my $timeNow; 4684 ref $self or $tzFlag = $self, $self = { }; 4685 if ($$self{Now}) { 4686 $timeNow = $$self{Now}[0]; 4687 } else { 4688 my $time = time(); 4689 my @tm = localtime $time; 4690 my $tz = TimeZoneString(\@tm, $time); 4691 $timeNow = sprintf("%4d:%.2d:%.2d %.2d:%.2d:%.2d", 4692 $tm[5]+1900, $tm[4]+1, $tm[3], 4693 $tm[2], $tm[1], $tm[0]); 4694 $$self{Now} = [ $timeNow, $tz ]; 4695 } 4696 $timeNow .= $$self{Now}[1] if $tzFlag or not defined $tzFlag; 4697 return $timeNow; 4698} 4699 4700#------------------------------------------------------------------------------ 4701# Inverse date/time print conversion (reformat to YYYY:mm:dd HH:MM:SS[.ss][+-HH:MM|Z]) 4702# Inputs: 0) ExifTool object ref, 1) Date/Time string, 2) timezone flag: 4703# 0 - remove timezone and sub-seconds if they exist 4704# 1 - add timezone if it doesn't exist 4705# undef - leave timezone alone 4706# 3) flag to allow date-only (YYYY, YYYY:mm or YYYY:mm:dd) or time without seconds 4707# Returns: formatted date/time string (or undef and issues warning on error) 4708# Notes: currently accepts different separators, but doesn't use DateFormat yet 4709my $strptimeLib; # strptime library name if available 4710sub InverseDateTime($$;$$) 4711{ 4712 my ($self, $val, $tzFlag, $dateOnly) = @_; 4713 my ($rtnVal, $tz); 4714 my $fmt = $$self{OPTIONS}{DateFormat}; 4715 # strip off timezone first if it exists 4716 if (not $fmt and $val =~ s/([+-])(\d{1,2}):?(\d{2})\s*(DST)?$//i) { 4717 $tz = sprintf("$1%.2d:$3", $2); 4718 } elsif (not $fmt and $val =~ s/Z$//i) { 4719 $tz = 'Z'; 4720 } else { 4721 $tz = ''; 4722 # allow special value of 'now' 4723 return $self->TimeNow($tzFlag) if lc($val) eq 'now'; 4724 } 4725 # only convert date if a format was specified and the date is recognizable 4726 if ($fmt) { 4727 unless (defined $strptimeLib) { 4728 if (eval { require POSIX::strptime }) { 4729 $strptimeLib = 'POSIX::strptime'; 4730 } elsif (eval { require Time::Piece }) { 4731 $strptimeLib = 'Time::Piece'; 4732 # (call use_locale() to convert localized date/time, 4733 # only available in Time::Piece 1.32 and later) 4734 eval { Time::Piece->use_locale() }; 4735 } else { 4736 $strptimeLib = ''; 4737 } 4738 } 4739 my ($lib, $wrn, @a); 4740TryLib: for ($lib=$strptimeLib; ; $lib='') { 4741 if (not $lib) { 4742 last unless $$self{OPTIONS}{StrictDate}; 4743 warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n"; 4744 return undef; 4745 } elsif ($lib eq 'POSIX::strptime') { 4746 @a = eval { POSIX::strptime($val, $fmt) }; 4747 } else { 4748 # protect against a negative epoch time, it can cause a hard crash in Windows 4749 if ($^O eq 'MSWin32' and $fmt =~ /%s/ and $val =~ /-\d/) { 4750 warn "Can't convert negative epoch time\n"; 4751 return undef; 4752 } 4753 @a = eval { 4754 my $t = Time::Piece->strptime($val, $fmt); 4755 return ($t->sec, $t->min, $t->hour, $t->mday, $t->_mon, $t->_year); 4756 }; 4757 } 4758 if (defined $a[5] and length $a[5]) { 4759 $a[5] += 1900; # add 1900 to year 4760 } else { 4761 $wrn = "Invalid date/time (no year) using $lib\n"; 4762 next; 4763 } 4764 ++$a[4] if defined $a[4] and length $a[4]; # add 1 to month 4765 my $i; 4766 foreach $i (0..4) { 4767 if (not defined $a[$i] or not length $a[$i]) { 4768 if ($i < 2 or $dateOnly) { # (allow missing minutes/seconds) 4769 $a[$i] = ' '; 4770 } else { 4771 $wrn = "Incomplete date/time specification using $lib\n"; 4772 next TryLib; 4773 } 4774 } elsif (length($a[$i]) < 2) { 4775 $$a[$i] = "0$a[$i]";# pad to 2 digits if necessary 4776 } 4777 } 4778 $val = join(':', @a[5,4,3]) . ' ' . join(':', @a[2,1,0]); 4779 last; 4780 } 4781 } 4782 if ($val =~ /(\d{4})/g) { # get YYYY 4783 my $yr = $1; 4784 my @a = ($val =~ /\d{1,2}/g); # get mm, dd, HH, and maybe MM, SS 4785 length($_) < 2 and $_ = "0$_" foreach @a; # pad to 2 digits if necessary 4786 if (@a >= 3) { 4787 my $ss = $a[4]; # get SS 4788 push @a, '00' while @a < 5; # add MM, SS if not given 4789 # get sub-seconds if they exist (must be after SS, and have leading ".") 4790 my $fs = (@a > 5 and $val =~ /(\.\d+)\s*$/) ? $1 : ''; 4791 # add/remove timezone if necessary 4792 if ($tzFlag) { 4793 if (not $tz) { 4794 if (eval { require Time::Local }) { 4795 # determine timezone offset for this time 4796 my @args = ($a[4],$a[3],$a[2],$a[1],$a[0]-1,$yr); 4797 my $diff = Time::Local::timegm(@args) - TimeLocal(@args); 4798 $tz = TimeZoneString($diff / 60); 4799 } else { 4800 $tz = 'Z'; # don't know time zone 4801 } 4802 } 4803 } elsif (defined $tzFlag) { 4804 $tz = $fs = ''; # remove timezone and sub-seconds 4805 } 4806 if (defined $ss and $ss < 60) { 4807 $ss = ":$ss"; 4808 } elsif ($dateOnly) { 4809 $ss = ''; 4810 } else { 4811 $ss = ':00'; 4812 } 4813 # construct properly formatted date/time string 4814 if ($a[0] < 1 or $a[0] > 12) { 4815 warn "Month '$a[0]' out of range 1..12\n"; 4816 return undef; 4817 } 4818 if ($a[1] < 1 or $a[1] > 31) { 4819 warn "Day '$a[1]' out of range 1..31\n"; 4820 return undef; 4821 } 4822 $a[2] > 24 and warn("Hour '$a[2]' out of range 0..24\n"), return undef; 4823 $a[3] > 59 and warn("Minutes '$a[3]' out of range 0..59\n"), return undef; 4824 $rtnVal = "$yr:$a[0]:$a[1] $a[2]:$a[3]$ss$fs$tz"; 4825 } elsif ($dateOnly) { 4826 $rtnVal = join ':', $yr, @a; 4827 } 4828 } 4829 $rtnVal or warn "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])\n"; 4830 return $rtnVal; 4831} 4832 4833#------------------------------------------------------------------------------ 4834# Set byte order according to our current preferences 4835# Inputs: 0) ExifTool object ref, 1) default byte order 4836# Returns: new byte order ('II' or 'MM') and sets current byte order 4837# Notes: takes the first of the following that is valid: 4838# 1) ByteOrder option 4839# 2) new value for ExifByteOrder 4840# 3) default byte order passed to this routine 4841# 4) makenote byte order from last file read 4842# 5) big endian 4843sub SetPreferredByteOrder($;$) 4844{ 4845 my ($self, $default) = @_; 4846 my $byteOrder = $self->Options('ByteOrder') || 4847 $self->GetNewValue('ExifByteOrder') || 4848 $default || $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; 4849 unless (SetByteOrder($byteOrder)) { 4850 warn "Invalid byte order '${byteOrder}'\n" if $self->Options('Verbose'); 4851 $byteOrder = $$self{MAKER_NOTE_BYTE_ORDER} || 'MM'; 4852 SetByteOrder($byteOrder); 4853 } 4854 return GetByteOrder(); 4855} 4856 4857#------------------------------------------------------------------------------ 4858# Assemble a continuing fraction into a rational value 4859# Inputs: 0) numerator, 1) denominator 4860# 2-N) list of fraction denominators, deepest first 4861# Returns: numerator, denominator (in list context) 4862sub AssembleRational($$@) 4863{ 4864 @_ < 3 and return @_; 4865 my ($num, $denom, $frac) = splice(@_, 0, 3); 4866 return AssembleRational($frac*$num+$denom, $num, @_); 4867} 4868 4869#------------------------------------------------------------------------------ 4870# Convert a floating point number (or 'inf' or 'undef' or a fraction) into a rational 4871# Inputs: 0) floating point number, 1) optional maximum value (defaults to 0x7fffffff) 4872# Returns: numerator, denominator (in list context) 4873# Notes: 4874# - the returned rational will be accurate to at least 8 significant figures if possible 4875# - eg. an input of 3.14159265358979 returns a rational of 104348/33215, 4876# which equals 3.14159265392142 and is accurate to 10 significant figures 4877# - the returned rational will be reduced to the lowest common denominator except when 4878# the input is a fraction in which case the input is returned unchanged 4879# - these routines were a bit tricky, but fun to write! 4880sub Rationalize($;$) 4881{ 4882 my $val = shift; 4883 return (1, 0) if $val eq 'inf'; 4884 return (0, 0) if $val eq 'undef'; 4885 return ($1,$2) if $val =~ m{^([-+]?\d+)/(\d+)$}; # accept fractional values 4886 # Note: Just testing "if $val" doesn't work because '0.0' is true! (ugghh!) 4887 return (0, 1) if $val == 0; 4888 my $sign = $val < 0 ? ($val = -$val, -1) : 1; 4889 my ($num, $denom, @fracs); 4890 my $frac = $val; 4891 my $maxInt = shift || 0x7fffffff; 4892 for (;;) { 4893 my ($n, $d) = AssembleRational(int($frac + 0.5), 1, @fracs); 4894 if ($n > $maxInt or $d > $maxInt) { 4895 last if defined $num; 4896 return ($sign, $maxInt) if $val < 1; 4897 return ($sign * $maxInt, 1); 4898 } 4899 ($num, $denom) = ($n, $d); # save last good values 4900 my $err = ($n/$d-$val) / $val; # get error of this rational 4901 last if abs($err) < 1e-8; # all done if error is small 4902 my $int = int($frac); 4903 unshift @fracs, $int; 4904 last unless $frac -= $int; 4905 $frac = 1 / $frac; 4906 } 4907 return ($num * $sign, $denom); 4908} 4909 4910#------------------------------------------------------------------------------ 4911# Utility routines to for writing binary data values 4912# Inputs: 0) value, 1) data ref, 2) offset 4913# Notes: prototype is (@) so values can be passed from list if desired 4914sub Set16s(@) 4915{ 4916 my $val = shift; 4917 $val < 0 and $val += 0x10000; 4918 return Set16u($val, @_); 4919} 4920sub Set32s(@) 4921{ 4922 my $val = shift; 4923 $val < 0 and $val += 0xffffffff, ++$val; 4924 return Set32u($val, @_); 4925} 4926sub Set64u(@) 4927{ 4928 my $val = $_[0]; 4929 my $hi = int($val / 4294967296); 4930 my $lo = Set32u($val - $hi * 4294967296); 4931 $hi = Set32u($hi); 4932 $val = GetByteOrder() eq 'MM' ? $hi . $lo : $lo . $hi; 4933 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4934 return $val; 4935} 4936sub SetRational64u(@) { 4937 my ($numer,$denom) = Rationalize($_[0],0xffffffff); 4938 my $val = Set32u($numer) . Set32u($denom); 4939 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4940 return $val; 4941} 4942sub SetRational64s(@) { 4943 my ($numer,$denom) = Rationalize($_[0]); 4944 my $val = Set32s($numer) . Set32u($denom); 4945 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4946 return $val; 4947} 4948sub SetRational32u(@) { 4949 my ($numer,$denom) = Rationalize($_[0],0xffff); 4950 my $val = Set16u($numer) . Set16u($denom); 4951 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4952 return $val; 4953} 4954sub SetRational32s(@) { 4955 my ($numer,$denom) = Rationalize($_[0],0x7fff); 4956 my $val = Set16s($numer) . Set16u($denom); 4957 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4958 return $val; 4959} 4960sub SetFixed16u(@) { 4961 my $val = int(shift() * 0x100 + 0.5); 4962 return Set16u($val, @_); 4963} 4964sub SetFixed16s(@) { 4965 my $val = shift; 4966 return Set16s(int($val * 0x100 + ($val < 0 ? -0.5 : 0.5)), @_); 4967} 4968sub SetFixed32u(@) { 4969 my $val = int(shift() * 0x10000 + 0.5); 4970 return Set32u($val, @_); 4971} 4972sub SetFixed32s(@) { 4973 my $val = shift; 4974 return Set32s(int($val * 0x10000 + ($val < 0 ? -0.5 : 0.5)), @_); 4975} 4976sub SetFloat(@) { 4977 my $val = SwapBytes(pack('f',$_[0]), 4); 4978 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4979 return $val; 4980} 4981sub SetDouble(@) { 4982 # swap 32-bit words (ARM quirk) and bytes if necessary 4983 my $val = SwapBytes(SwapWords(pack('d',$_[0])), 8); 4984 $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val; 4985 return $val; 4986} 4987#------------------------------------------------------------------------------ 4988# hash lookups for writing binary data values 4989my %writeValueProc = ( 4990 int8s => \&Set8s, 4991 int8u => \&Set8u, 4992 int16s => \&Set16s, 4993 int16u => \&Set16u, 4994 int16uRev => \&Set16uRev, 4995 int32s => \&Set32s, 4996 int32u => \&Set32u, 4997 int64u => \&Set64u, 4998 rational32s => \&SetRational32s, 4999 rational32u => \&SetRational32u, 5000 rational64s => \&SetRational64s, 5001 rational64u => \&SetRational64u, 5002 fixed16u => \&SetFixed16u, 5003 fixed16s => \&SetFixed16s, 5004 fixed32u => \&SetFixed32u, 5005 fixed32s => \&SetFixed32s, 5006 float => \&SetFloat, 5007 double => \&SetDouble, 5008 ifd => \&Set32u, 5009); 5010# verify that we can write floats on this platform 5011{ 5012 my %writeTest = ( 5013 float => [ -3.14159, 'c0490fd0' ], 5014 double => [ -3.14159, 'c00921f9f01b866e' ], 5015 ); 5016 my $format; 5017 my $oldOrder = GetByteOrder(); 5018 SetByteOrder('MM'); 5019 foreach $format (keys %writeTest) { 5020 my ($val, $hex) = @{$writeTest{$format}}; 5021 # add floating point entries if we can write them 5022 next if unpack('H*', &{$writeValueProc{$format}}($val)) eq $hex; 5023 delete $writeValueProc{$format}; # we can't write them 5024 } 5025 SetByteOrder($oldOrder); 5026} 5027 5028#------------------------------------------------------------------------------ 5029# write binary data value (with current byte ordering) 5030# Inputs: 0) value, 1) format string 5031# 2) number of values: 5032# undef = 1 for numerical types, or data length for string/undef types 5033# -1 = number of space-delimited values in the input string 5034# 3) optional data reference, 4) value offset (may be negative for bytes from end) 5035# Returns: packed value (and sets value in data) or undef on error 5036# Notes: May modify input value to round for integer formats 5037sub WriteValue($$;$$$$) 5038{ 5039 my ($val, $format, $count, $dataPt, $offset) = @_; 5040 my $proc = $writeValueProc{$format}; 5041 my $packed; 5042 5043 if ($proc) { 5044 my @vals = split(' ',$val); 5045 if ($count) { 5046 $count = @vals if $count < 0; 5047 } else { 5048 $count = 1; # assume 1 if count not specified 5049 } 5050 $packed = ''; 5051 while ($count--) { 5052 $val = shift @vals; 5053 return undef unless defined $val; 5054 # validate numerical formats 5055 if ($format =~ /^int/) { 5056 unless (IsInt($val) or IsHex($val)) { 5057 return undef unless IsFloat($val); 5058 # round to nearest integer 5059 $val = int($val + ($val < 0 ? -0.5 : 0.5)); 5060 $_[0] = $val; 5061 } 5062 } elsif (not IsFloat($val)) { 5063 return undef unless $format =~ /^rational/ and ($val eq 'inf' or 5064 $val eq 'undef' or IsRational($val)); 5065 } 5066 $packed .= &$proc($val); 5067 } 5068 } elsif ($format eq 'string' or $format eq 'undef') { 5069 $format eq 'string' and $val .= "\0"; # null-terminate strings 5070 if ($count and $count > 0) { 5071 my $diff = $count - length($val); 5072 if ($diff) { 5073 #warn "wrong string length!\n"; 5074 # adjust length of string to match specified count 5075 if ($diff < 0) { 5076 if ($format eq 'string') { 5077 return undef unless $count; 5078 $val = substr($val, 0, $count - 1) . "\0"; 5079 } else { 5080 $val = substr($val, 0, $count); 5081 } 5082 } else { 5083 $val .= "\0" x $diff; 5084 } 5085 } 5086 } else { 5087 $count = length($val); 5088 } 5089 $dataPt and substr($$dataPt, $offset, $count) = $val; 5090 return $val; 5091 } else { 5092 warn "Sorry, Can't write $format values on this platform\n"; 5093 return undef; 5094 } 5095 $dataPt and substr($$dataPt, $offset, length($packed)) = $packed; 5096 return $packed; 5097} 5098 5099#------------------------------------------------------------------------------ 5100# Encode bit mask (the inverse of DecodeBits()) 5101# Inputs: 0) value to encode, 1) Reference to hash for encoding (or undef) 5102# 2) optional number of bits per word (defaults to 32), 3) total bits 5103# Returns: bit mask or undef on error (plus error string in list context) 5104sub EncodeBits($$;$$) 5105{ 5106 my ($val, $lookup, $bits, $num) = @_; 5107 $bits or $bits = 32; 5108 $num or $num = $bits; 5109 my $words = int(($num + $bits - 1) / $bits); 5110 my @outVal = (0) x $words; 5111 if ($val ne '(none)') { 5112 my @vals = split /\s*,\s*/, $val; 5113 foreach $val (@vals) { 5114 my $bit; 5115 if ($lookup) { 5116 $bit = ReverseLookup($val, $lookup); 5117 # (Note: may get non-numerical $bit values from Unknown() tags) 5118 unless (defined $bit) { 5119 if ($val =~ /\[(\d+)\]/) { # numerical bit specification 5120 $bit = $1; 5121 } else { 5122 # don't return error string unless more than one value 5123 return undef unless @vals > 1 and wantarray; 5124 return (undef, "no match for '${val}'"); 5125 } 5126 } 5127 } else { 5128 $bit = $val; 5129 } 5130 unless (IsInt($bit) and $bit < $num) { 5131 return undef unless wantarray; 5132 return (undef, IsInt($bit) ? 'bit number too high' : 'not an integer'); 5133 } 5134 my $word = int($bit / $bits); 5135 $outVal[$word] |= (1 << ($bit - $word * $bits)); 5136 } 5137 } 5138 return "@outVal"; 5139} 5140 5141#------------------------------------------------------------------------------ 5142# get current position in output file (or end of file if a scalar reference) 5143# Inputs: 0) file or scalar reference 5144# Returns: Current position or -1 on error 5145sub Tell($) 5146{ 5147 my $outfile = shift; 5148 if (UNIVERSAL::isa($outfile,'GLOB')) { 5149 return tell($outfile); 5150 } else { 5151 return length($$outfile); 5152 } 5153} 5154 5155#------------------------------------------------------------------------------ 5156# write to file or memory 5157# Inputs: 0) file or scalar reference, 1-N) list of stuff to write 5158# Returns: true on success 5159sub Write($@) 5160{ 5161 my $outfile = shift; 5162 if (UNIVERSAL::isa($outfile,'GLOB')) { 5163 return print $outfile @_; 5164 } elsif (ref $outfile eq 'SCALAR') { 5165 $$outfile .= join('', @_); 5166 return 1; 5167 } 5168 return 0; 5169} 5170 5171#------------------------------------------------------------------------------ 5172# Write trailer buffer to file (applying fixups if necessary) 5173# Inputs: 0) ExifTool object ref, 1) trailer dirInfo ref, 2) output file ref 5174# Returns: 1 on success 5175sub WriteTrailerBuffer($$$) 5176{ 5177 my ($self, $trailInfo, $outfile) = @_; 5178 if ($$self{DEL_GROUP}{Trailer}) { 5179 $self->VPrint(0, " Deleting trailer ($$trailInfo{Offset} bytes)\n"); 5180 ++$$self{CHANGED}; 5181 return 1; 5182 } 5183 my $pos = Tell($outfile); 5184 my $trailPt = $$trailInfo{OutFile}; 5185 # apply fixup if necessary (AFCP requires this) 5186 if ($$trailInfo{Fixup}) { 5187 if ($pos > 0) { 5188 # shift offsets to final AFCP location and write it out 5189 $$trailInfo{Fixup}{Shift} += $pos; 5190 $$trailInfo{Fixup}->ApplyFixup($trailPt); 5191 } else { 5192 $self->Error("Can't get file position for trailer offset fixup",1); 5193 } 5194 } 5195 return Write($outfile, $$trailPt); 5196} 5197 5198#------------------------------------------------------------------------------ 5199# Add trailers as a block 5200# Inputs: 0) ExifTool object ref, 1) [optional] trailer data raf, 5201# 1 or 2-N) trailer types to add (or none to add all) 5202# Returns: new trailer ref, or undef 5203# - increments CHANGED if trailer was added 5204sub AddNewTrailers($;@) 5205{ 5206 my ($self, @types) = @_; 5207 my $trailPt; 5208 ref $types[0] and $trailPt = shift @types; 5209 $types[0] or shift @types; # (in case undef data ref is passed) 5210 # add all possible trailers if none specified (currently only CanonVRD) 5211 @types or @types = qw(CanonVRD CanonDR4); 5212 # add trailers as a block (if not done already) 5213 my $type; 5214 foreach $type (@types) { 5215 next unless $$self{NEW_VALUE}{$Image::ExifTool::Extra{$type}}; 5216 next if $$self{"Did$type"}; 5217 my $val = $self->GetNewValue($type) or next; 5218 # DR4 record must be wrapped in VRD trailer package 5219 if ($type eq 'CanonDR4') { 5220 next if $$self{DidCanonVRD}; # (only allow one VRD trailer) 5221 require Image::ExifTool::CanonVRD; 5222 $val = Image::ExifTool::CanonVRD::WrapDR4($val); 5223 $$self{DidCanonVRD} = 1; 5224 } 5225 my $verb = $trailPt ? 'Writing' : 'Adding'; 5226 $self->VPrint(0, " $verb $type as a block\n"); 5227 if ($trailPt) { 5228 $$trailPt .= $val; 5229 } else { 5230 $trailPt = \$val; 5231 } 5232 $$self{"Did$type"} = 1; 5233 ++$$self{CHANGED}; 5234 } 5235 return $trailPt; 5236} 5237 5238#------------------------------------------------------------------------------ 5239# Write segment, splitting up into multiple segments if necessary 5240# Inputs: 0) file or scalar reference, 1) segment marker 5241# 2) segment header, 3) segment data ref, 4) segment type 5242# Returns: number of segments written, or 0 on error 5243# Notes: Writes a single empty segment if data is empty 5244sub WriteMultiSegment($$$$;$) 5245{ 5246 my ($outfile, $marker, $header, $dataPt, $type) = @_; 5247 $type or $type = ''; 5248 my $len = length($$dataPt); 5249 my $hdr = "\xff" . chr($marker); 5250 my $count = 0; 5251 my $maxLen = $maxSegmentLen - length($header); 5252 $maxLen -= 2 if $type eq 'ICC'; # leave room for segment counters 5253 my $num = int(($len + $maxLen - 1) / $maxLen); # number of segments to write 5254 my $n = 0; 5255 # write data, splitting into multiple segments if necessary 5256 # (each segment gets its own header) 5257 for (;;) { 5258 ++$count; 5259 my $size = $len - $n; 5260 if ($size > $maxLen) { 5261 $size = $maxLen; 5262 # avoid starting an Extended EXIF segment with a valid TIFF header 5263 # (because we would interpret that as a separate EXIF segment) 5264 --$size if $type eq 'EXIF' and $n+$maxLen <= $len-4 and 5265 substr($$dataPt, $n+$maxLen, 4) =~ /^(MM\0\x2a|II\x2a\0)/; 5266 } 5267 my $buff = substr($$dataPt,$n,$size); 5268 $n += $size; 5269 $size += length($header); 5270 if ($type eq 'ICC') { 5271 $buff = pack('CC', $count, $num) . $buff; 5272 $size += 2; 5273 } 5274 # write the new segment with appropriate header 5275 my $segHdr = $hdr . pack('n', $size + 2); 5276 Write($outfile, $segHdr, $header, $buff) or return 0; 5277 last if $n >= $len; 5278 } 5279 return $count; 5280} 5281 5282#------------------------------------------------------------------------------ 5283# Write XMP segment(s) to JPEG file 5284# Inputs: 0) ExifTool object ref, 1) outfile ref, 2) XMP data ref, 5285# 3) extended XMP data ref, 4) 32-char extended XMP GUID (or undef if no extended data) 5286# Returns: true on success, false on write error 5287sub WriteMultiXMP($$$$$) 5288{ 5289 my ($self, $outfile, $dataPt, $extPt, $guid) = @_; 5290 my $success = 1; 5291 5292 # write main XMP segment 5293 my $size = length($$dataPt) + length($xmpAPP1hdr); 5294 if ($size > $maxXMPLen) { 5295 $self->Error("XMP block too large for JPEG segment! ($size bytes)", 1); 5296 return 1; 5297 } 5298 my $app1hdr = "\xff\xe1" . pack('n', $size + 2); 5299 Write($outfile, $app1hdr, $xmpAPP1hdr, $$dataPt) or $success = 0; 5300 # write extended XMP segment(s) if necessary 5301 if (defined $guid) { 5302 $size = length($$extPt); 5303 my $maxLen = $maxXMPLen - 75; # maximum size without 75-byte header 5304 my $off; 5305 for ($off=0; $off<$size; $off+=$maxLen) { 5306 # header(75) = signature(35) + guid(32) + size(4) + offset(4) 5307 my $len = $size - $off; 5308 $len = $maxLen if $len > $maxLen; 5309 $app1hdr = "\xff\xe1" . pack('n', $len + 75 + 2); 5310 $self->VPrint(0, "Writing extended XMP segment ($len bytes)\n"); 5311 Write($outfile, $app1hdr, $xmpExtAPP1hdr, $guid, pack('N2', $size, $off), 5312 substr($$extPt, $off, $len)) or $success = 0; 5313 } 5314 } 5315 return $success; 5316} 5317 5318#------------------------------------------------------------------------------ 5319# WriteJPEG : Write JPEG image 5320# Inputs: 0) ExifTool object reference, 1) dirInfo reference 5321# Returns: 1 on success, 0 if this wasn't a valid JPEG file, or -1 if 5322# an output file was specified and a write error occurred 5323sub WriteJPEG($$) 5324{ 5325 my ($self, $dirInfo) = @_; 5326 my $outfile = $$dirInfo{OutFile}; 5327 my $raf = $$dirInfo{RAF}; 5328 my ($ch, $s, $length,$err, %doneDir, $isEXV, $creatingEXV); 5329 my $verbose = $$self{OPTIONS}{Verbose}; 5330 my $out = $$self{OPTIONS}{TextOut}; 5331 my $rtnVal = 0; 5332 my %dumpParms = ( Out => $out ); 5333 my ($writeBuffer, $oldOutfile); # used to buffer writing until PreviewImage position is known 5334 5335 # check to be sure this is a valid JPG or EXV file 5336 unless ($raf->Read($s,2) == 2 and $s eq "\xff\xd8") { 5337 if (defined $s and length $s) { 5338 return 0 unless $s eq "\xff\x01" and $raf->Read($s,5) == 5 and $s eq 'Exiv2'; 5339 } else { 5340 return 0 unless $$self{FILE_TYPE} eq 'EXV'; 5341 $s = 'Exiv2'; 5342 $creatingEXV = 1; 5343 } 5344 Write($outfile,"\xff\x01") or $err = 1; 5345 $isEXV = 1; 5346 } 5347 $dumpParms{MaxLen} = 128 unless $verbose > 3; 5348 5349 delete $$self{PREVIEW_INFO}; # reset preview information 5350 delete $$self{DEL_PREVIEW}; # reset flag to delete preview 5351 5352 Write($outfile, $s) or $err = 1; 5353 # figure out what segments we need to write for the tags we have set 5354 my $addDirs = $$self{ADD_DIRS}; 5355 my $editDirs = $$self{EDIT_DIRS}; 5356 my $delGroup = $$self{DEL_GROUP}; 5357 my $path = $$self{PATH}; 5358 my $pn = scalar @$path; 5359 5360 # set input record separator to 0xff (the JPEG marker) to make reading quicker 5361 local $/ = "\xff"; 5362# 5363# pre-scan image to determine if any create-able segment already exists 5364# 5365 my $pos = $raf->Tell(); 5366 my ($marker, @dirOrder, %dirCount); 5367 Prescan: for (;;) { 5368 # read up to next marker (JPEG markers begin with 0xff) 5369 $raf->ReadLine($s) or last; 5370 # JPEG markers can be padded with unlimited 0xff's 5371 for (;;) { 5372 $raf->Read($ch, 1) or last Prescan; 5373 $marker = ord($ch); 5374 last unless $marker == 0xff; 5375 } 5376 my $dirName; 5377 # stop pre-scan at SOS (end of meta information) or EOI (end of image) 5378 if ($marker == 0xda or $marker == 0xd9) { 5379 $dirName = $jpegMarker{$marker}; 5380 push(@dirOrder, $dirName); 5381 $dirCount{$dirName} = 1; 5382 last; 5383 } 5384 # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) 5385 if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { 5386 last unless $raf->Seek(7, 1); 5387 # read data for all markers except stand-alone 5388 # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, RST0-RST7) 5389 } elsif ($marker!=0x00 and $marker!=0x01 and ($marker<0xd0 or $marker>0xd7)) { 5390 # read record length word 5391 last unless $raf->Read($s, 2) == 2; 5392 my $len = unpack('n',$s); # get data length 5393 last unless defined($len) and $len >= 2; 5394 $len -= 2; # subtract size of length word 5395 if (($marker & 0xf0) == 0xe0) { # is this an APP segment? 5396 my $n = $len < 64 ? $len : 64; 5397 $raf->Read($s, $n) == $n or last; 5398 $len -= $n; 5399 # Note: only necessary to recognize APP segments that we can create, 5400 # or delete as a group (and the names below should match @delGroups) 5401 if ($marker == 0xe0) { 5402 $s =~ /^JFIF\0/ and $dirName = 'JFIF'; 5403 $s =~ /^JFXX\0\x10/ and $dirName = 'JFXX'; 5404 $s =~ /^(II|MM).{4}HEAPJPGM/s and $dirName = 'CIFF'; 5405 } elsif ($marker == 0xe1) { 5406 if ($s =~ /^(.{0,4})$exifAPP1hdr(.{1,4})/is) { 5407 $dirName = 'IFD0'; 5408 my ($junk, $bytes) = ($1, $2); 5409 # support multi-segment EXIF 5410 if (@dirOrder and $dirOrder[-1] =~ /^(IFD0|ExtendedEXIF)$/ and 5411 not length $junk and $bytes !~ /^(MM\0\x2a|II\x2a\0)/) 5412 { 5413 $dirName = 'ExtendedEXIF'; 5414 } 5415 } 5416 $s =~ /^$xmpAPP1hdr/ and $dirName = 'XMP'; 5417 $s =~ /^$xmpExtAPP1hdr/ and $dirName = 'XMP'; 5418 } elsif ($marker == 0xe2) { 5419 $s =~ /^ICC_PROFILE\0/ and $dirName = 'ICC_Profile'; 5420 $s =~ /^FPXR\0/ and $dirName = 'FlashPix'; 5421 $s =~ /^MPF\0/ and $dirName = 'MPF'; 5422 } elsif ($marker == 0xe3) { 5423 $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta'; 5424 } elsif ($marker == 0xe5) { 5425 $s =~ /^RMETA\0/ and $dirName = 'RMETA'; 5426 } elsif ($marker == 0xec) { 5427 $s =~ /^Ducky/ and $dirName = 'Ducky'; 5428 } elsif ($marker == 0xed) { 5429 $s =~ /^$psAPP13hdr/ and $dirName = 'Photoshop'; 5430 } elsif ($marker == 0xee) { 5431 $s =~ /^Adobe/ and $dirName = 'Adobe'; 5432 } 5433 # initialize doneDir as a flag that the directory exists 5434 # (unless we are deleting it anyway) 5435 $doneDir{$dirName} = 0 if defined $dirName and not $$delGroup{$dirName}; 5436 } 5437 $raf->Seek($len, 1) or last; 5438 } 5439 $dirName or $dirName = JpegMarkerName($marker); 5440 $dirCount{$dirName} = ($dirCount{$dirName} || 0) + 1; 5441 push @dirOrder, $dirName; 5442 } 5443 unless ($marker and $marker == 0xda) { 5444 $isEXV or $self->Error('Corrupted JPEG image'), return 1; 5445 $marker and $marker != 0xd9 and $self->Error('Corrupted EXV file'), return 1; 5446 } 5447 $raf->Seek($pos, 0) or $self->Error('Seek error'), return 1; 5448# 5449# re-write the image 5450# 5451 my ($combinedSegData, $segPos, $firstSegPos, %extendedXMP); 5452 my (@iccChunk, $iccChunkCount, $iccChunksTotal); 5453 # read through each segment in the JPEG file 5454 Marker: for (;;) { 5455 5456 # read up to next marker (JPEG markers begin with 0xff) 5457 my $segJunk; 5458 $raf->ReadLine($segJunk) or $segJunk = ''; 5459 # remove the 0xff but write the rest of the junk up to this point 5460 # (this will handle the data after the first 7 bytes of SOF segments) 5461 chomp($segJunk); 5462 Write($outfile, $segJunk) if length $segJunk; 5463 # JPEG markers can be padded with unlimited 0xff's 5464 for (;;) { 5465 if ($raf->Read($ch, 1)) { 5466 $marker = ord($ch); 5467 last unless $marker == 0xff; 5468 } elsif ($creatingEXV) { 5469 # create EXV from scratch 5470 $marker = 0xd9; # EOI 5471 push @dirOrder, 'EOI'; 5472 $dirCount{EOI} = 1; 5473 last; 5474 } else { 5475 $self->Error('Format error'); 5476 return 1; 5477 } 5478 } 5479 # read the segment data 5480 my $segData; 5481 # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) 5482 if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { 5483 last unless $raf->Read($segData, 7) == 7; 5484 # read data for all markers except stand-alone 5485 # markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, EOI, RST0-RST7) 5486 } elsif ($marker!=0x00 and $marker!=0x01 and $marker!=0xd9 and 5487 ($marker<0xd0 or $marker>0xd7)) 5488 { 5489 # read record length word 5490 last unless $raf->Read($s, 2) == 2; 5491 my $len = unpack('n',$s); # get data length 5492 last unless defined($len) and $len >= 2; 5493 $segPos = $raf->Tell(); 5494 $len -= 2; # subtract size of length word 5495 last unless $raf->Read($segData, $len) == $len; 5496 } 5497 # initialize variables for this segment 5498 my $hdr = "\xff" . chr($marker); # segment header 5499 my $markerName = JpegMarkerName($marker); 5500 my $dirName = shift @dirOrder; # get directory name 5501# 5502# create all segments that must come before this one 5503# (nothing comes before SOI or after SOS) 5504# 5505 while ($markerName ne 'SOI') { 5506 if (exists $$addDirs{JFIF} and not defined $doneDir{JFIF}) { 5507 $doneDir{JFIF} = 1; 5508 if (defined $doneDir{Adobe}) { 5509 # JFIF overrides Adobe APP14 colour components, so don't allow this 5510 # (ref https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html) 5511 $self->Warn('Not creating JFIF in JPEG with Adobe APP14'); 5512 } else { 5513 if ($verbose) { 5514 print $out "Creating APP0:\n"; 5515 print $out " Creating JFIF with default values\n"; 5516 } 5517 my $jfif = "\x01\x02\x01\0\x48\0\x48\0\0"; 5518 SetByteOrder('MM'); 5519 my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); 5520 my %dirInfo = ( 5521 DataPt => \$jfif, 5522 DirStart => 0, 5523 DirLen => length $jfif, 5524 Parent => 'JFIF', 5525 ); 5526 # must temporarily remove JFIF from DEL_GROUP so we can 5527 # delete JFIF and add it back again in a single step 5528 my $delJFIF = $$delGroup{JFIF}; 5529 delete $$delGroup{JFIF}; 5530 $$path[$pn] = 'JFIF'; 5531 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5532 $$delGroup{JFIF} = $delJFIF if defined $delJFIF; 5533 if (defined $newData and length $newData) { 5534 my $app0hdr = "\xff\xe0" . pack('n', length($newData) + 7); 5535 Write($outfile,$app0hdr,"JFIF\0",$newData) or $err = 1; 5536 } 5537 } 5538 } 5539 # don't create anything before APP0 or APP1 EXIF (containing IFD0) 5540 last if $markerName eq 'APP0' or $dirCount{IFD0} or $dirCount{ExtendedEXIF}; 5541 # EXIF information must come immediately after APP0 5542 if (exists $$addDirs{IFD0} and not defined $doneDir{IFD0}) { 5543 $doneDir{IFD0} = 1; 5544 $verbose and print $out "Creating APP1:\n"; 5545 # write new EXIF data 5546 $$self{TIFF_TYPE} = 'APP1'; 5547 my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); 5548 my %dirInfo = ( 5549 DirName => 'IFD0', 5550 Parent => 'APP1', 5551 ); 5552 $$path[$pn] = 'APP1'; 5553 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 5554 if (defined $buff and length $buff) { 5555 if (length($buff) + length($exifAPP1hdr) > $maxSegmentLen) { 5556 if ($self->Options('NoMultiExif')) { 5557 $self->Error('EXIF is too large for JPEG segment'); 5558 } else { 5559 $self->Warn('Creating multi-segment EXIF',1); 5560 } 5561 } 5562 # switch to buffered output if required 5563 if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { 5564 $writeBuffer = ''; 5565 $oldOutfile = $outfile; 5566 $outfile = \$writeBuffer; 5567 # account for segment, EXIF and TIFF headers 5568 $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; 5569 $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; 5570 } 5571 # write as multi-segment 5572 my $n = WriteMultiSegment($outfile, 0xe1, $exifAPP1hdr, \$buff, 'EXIF'); 5573 if (not $n) { 5574 $err = 1; 5575 } elsif ($n > 1 and $oldOutfile) { 5576 # (punt on this because updating the pointers would be a real pain) 5577 $self->Error("Can't write multi-segment EXIF with external pointers"); 5578 } 5579 ++$$self{CHANGED}; 5580 } 5581 } 5582 # APP13 Photoshop segment next 5583 last if $dirCount{Photoshop}; 5584 if (exists $$addDirs{Photoshop} and not defined $doneDir{Photoshop}) { 5585 $doneDir{Photoshop} = 1; 5586 $verbose and print $out "Creating APP13:\n"; 5587 # write new APP13 Photoshop record to memory 5588 my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); 5589 my %dirInfo = ( 5590 Parent => 'APP13', 5591 ); 5592 $$path[$pn] = 'APP13'; 5593 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5594 if (defined $buff and length $buff) { 5595 WriteMultiSegment($outfile, 0xed, $psAPP13hdr, \$buff) or $err = 1; 5596 ++$$self{CHANGED}; 5597 } 5598 } 5599 # then APP1 XMP segment 5600 last if $dirCount{XMP}; 5601 if (exists $$addDirs{XMP} and not defined $doneDir{XMP}) { 5602 $doneDir{XMP} = 1; 5603 $verbose and print $out "Creating APP1:\n"; 5604 # write new XMP data 5605 my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); 5606 my %dirInfo = ( 5607 Parent => 'APP1', 5608 # specify MaxDataLen so XMP is split if required 5609 MaxDataLen => $maxXMPLen - length($xmpAPP1hdr), 5610 ); 5611 $$path[$pn] = 'APP1'; 5612 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5613 if (defined $buff and length $buff) { 5614 WriteMultiXMP($self, $outfile, \$buff, $dirInfo{ExtendedXMP}, 5615 $dirInfo{ExtendedGUID}) or $err = 1; 5616 } 5617 } 5618 # then APP2 ICC_Profile segment 5619 last if $dirCount{ICC_Profile}; 5620 if (exists $$addDirs{ICC_Profile} and not defined $doneDir{ICC_Profile}) { 5621 $doneDir{ICC_Profile} = 1; 5622 next if $$delGroup{ICC_Profile} and $$delGroup{ICC_Profile} != 2; 5623 $verbose and print $out "Creating APP2:\n"; 5624 # write new ICC_Profile data 5625 my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); 5626 my %dirInfo = ( 5627 Parent => 'APP2', 5628 ); 5629 $$path[$pn] = 'APP2'; 5630 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5631 if (defined $buff and length $buff) { 5632 WriteMultiSegment($outfile, 0xe2, "ICC_PROFILE\0", \$buff, 'ICC') or $err = 1; 5633 ++$$self{CHANGED}; 5634 } 5635 } 5636 # then APP12 Ducky segment 5637 last if $dirCount{Ducky}; 5638 if (exists $$addDirs{Ducky} and not defined $doneDir{Ducky}) { 5639 $doneDir{Ducky} = 1; 5640 $verbose and print $out "Creating APP12 Ducky:\n"; 5641 # write new Ducky segment data 5642 my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::Ducky'); 5643 my %dirInfo = ( 5644 Parent => 'APP12', 5645 ); 5646 $$path[$pn] = 'APP12'; 5647 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5648 if (defined $buff and length $buff) { 5649 my $size = length($buff) + 5; 5650 if ($size <= $maxSegmentLen) { 5651 # write the new segment with appropriate header 5652 my $app12hdr = "\xff\xec" . pack('n', $size + 2); 5653 Write($outfile, $app12hdr, 'Ducky', $buff) or $err = 1; 5654 } else { 5655 $self->Warn("APP12 Ducky segment too large! ($size bytes)"); 5656 } 5657 } 5658 } 5659 # then APP14 Adobe segment 5660 last if $dirCount{Adobe}; 5661 if (exists $$addDirs{Adobe} and not defined $doneDir{Adobe}) { 5662 $doneDir{Adobe} = 1; 5663 my $buff = $self->GetNewValue('Adobe'); 5664 if ($buff) { 5665 $verbose and print $out "Creating APP14:\n Creating Adobe segment\n"; 5666 my $size = length($buff); 5667 if ($size <= $maxSegmentLen) { 5668 # write the new segment with appropriate header 5669 my $app14hdr = "\xff\xee" . pack('n', $size + 2); 5670 Write($outfile, $app14hdr, $buff) or $err = 1; 5671 ++$$self{CHANGED}; 5672 } else { 5673 $self->Warn("APP14 Adobe segment too large! ($size bytes)"); 5674 } 5675 } 5676 } 5677 # finally, COM segment 5678 last if $dirCount{COM}; 5679 if (exists $$addDirs{COM} and not defined $doneDir{COM}) { 5680 $doneDir{COM} = 1; 5681 next if $$delGroup{File} and $$delGroup{File} != 2; 5682 my $newComment = $self->GetNewValue('Comment'); 5683 if (defined $newComment) { 5684 if ($verbose) { 5685 print $out "Creating COM:\n"; 5686 $self->VerboseValue('+ Comment', $newComment); 5687 } 5688 WriteMultiSegment($outfile, 0xfe, '', \$newComment) or $err = 1; 5689 ++$$self{CHANGED}; 5690 } 5691 } 5692 last; # didn't want to loop anyway 5693 } 5694 $$path[$pn] = $markerName; 5695 # decrement counter for this directory since we are about to process it 5696 --$dirCount{$dirName}; 5697# 5698# rewrite existing segments 5699# 5700 # handle SOF markers: SOF0-SOF15, except DHT(0xc4), JPGA(0xc8) and DAC(0xcc) 5701 if (($marker & 0xf0) == 0xc0 and ($marker == 0xc0 or $marker & 0x03)) { 5702 $verbose and print $out "JPEG $markerName:\n"; 5703 Write($outfile, $hdr, $segData) or $err = 1; 5704 next; 5705 } elsif ($marker == 0xda) { # SOS 5706 pop @$path; 5707 $verbose and print $out "JPEG SOS\n"; 5708 # write SOS segment 5709 $s = pack('n', length($segData) + 2); 5710 Write($outfile, $hdr, $s, $segData) or $err = 1; 5711 my ($buff, $endPos, $trailInfo); 5712 my $delPreview = $$self{DEL_PREVIEW}; 5713 $trailInfo = IdentifyTrailer($raf) unless $$delGroup{Trailer}; 5714 my $nvTrail = $self->GetNewValueHash($Image::ExifTool::Extra{Trailer}); 5715 unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer} or $nvTrail) { 5716 # blindly copy the rest of the file 5717 while ($raf->Read($buff, 65536)) { 5718 Write($outfile, $buff) or $err = 1, last; 5719 } 5720 $rtnVal = 1; # success unless we have a file write error 5721 last; # all done 5722 } 5723 # write the rest of the image (as quickly as possible) up to the EOI 5724 my $endedWithFF; 5725 for (;;) { 5726 my $n = $raf->Read($buff, 65536) or last Marker; 5727 if (($endedWithFF and $buff =~ m/^\xd9/sg) or 5728 $buff =~ m/\xff\xd9/sg) 5729 { 5730 $rtnVal = 1; # the JPEG is OK 5731 # write up to the EOI 5732 my $pos = pos($buff); 5733 Write($outfile, substr($buff, 0, $pos)) or $err = 1; 5734 $buff = substr($buff, $pos); 5735 last; 5736 } 5737 unless ($n == 65536) { 5738 $self->Error('JPEG EOI marker not found'); 5739 last Marker; 5740 } 5741 Write($outfile, $buff) or $err = 1; 5742 $endedWithFF = substr($buff, 65535, 1) eq "\xff" ? 1 : 0; 5743 } 5744 # remember position of last data copied 5745 $endPos = $raf->Tell() - length($buff); 5746 # write new trailer if specified 5747 if ($nvTrail) { 5748 # access new value directly to avoid copying a potentially very large data block 5749 if ($$nvTrail{Value} and $$nvTrail{Value}[0]) { # (note: "0" will also delete the trailer) 5750 $self->VPrint(0, ' Writing new trailer'); 5751 Write($outfile, $$nvTrail{Value}[0]) or $err = 1; 5752 ++$$self{CHANGED}; 5753 } elsif ($raf->Seek(0, 2) and $raf->Tell() != $endPos) { 5754 $self->VPrint(0, ' Deleting trailer (', $raf->Tell() - $endPos, ' bytes)'); 5755 ++$$self{CHANGED}; # changed if there was previously a trailer 5756 } 5757 last; # all done 5758 } 5759 # rewrite existing trailers 5760 if ($trailInfo) { 5761 my $tbuf = ''; 5762 $raf->Seek(-length($buff), 1); # seek back to just after EOI 5763 $$trailInfo{OutFile} = \$tbuf; # rewrite the trailer 5764 $$trailInfo{ScanForAFCP} = 1; # scan if necessary 5765 $self->ProcessTrailers($trailInfo) or undef $trailInfo; 5766 } 5767 if (not $oldOutfile) { 5768 # do nothing special 5769 } elsif ($$self{LeicaTrailer}) { 5770 my $trailLen; 5771 if ($trailInfo) { 5772 $trailLen = $$trailInfo{DataPos} - $endPos; 5773 } else { 5774 $raf->Seek(0, 2) or $err = 1; 5775 $trailLen = $raf->Tell() - $endPos; 5776 } 5777 my $fixup = $$self{LeicaTrailer}{Fixup}; 5778 $$self{LeicaTrailer}{TrailPos} = $endPos; 5779 $$self{LeicaTrailer}{TrailLen} = $trailLen; 5780 # get _absolute_ position of new Leica trailer 5781 my $absPos = Tell($oldOutfile) + length($$outfile); 5782 require Image::ExifTool::Panasonic; 5783 my $dat = Image::ExifTool::Panasonic::ProcessLeicaTrailer($self, $absPos); 5784 # allow some junk before Leica trailer (just in case) 5785 my $junk = $$self{LeicaTrailerPos} - $endPos; 5786 # set MakerNote pointer and size (subtract 10 for segment and EXIF headers) 5787 $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', length($$outfile) - 10 + $junk); 5788 # use this fixup to set the size too (sneaky) 5789 my $trailSize = defined($dat) ? length($dat) - $junk : $$self{LeicaTrailer}{Size}; 5790 $$fixup{Start} -= 4; $$fixup{Shift} += 4; 5791 $fixup->SetMarkerPointers($outfile, 'LeicaTrailer', $trailSize) if defined $trailSize; 5792 $$fixup{Start} += 4; $$fixup{Shift} -= 4; 5793 # clean up and write the buffered data 5794 $outfile = $oldOutfile; 5795 undef $oldOutfile; 5796 Write($outfile, $writeBuffer) or $err = 1; 5797 undef $writeBuffer; 5798 if (defined $dat) { 5799 Write($outfile, $dat) or $err = 1; # write new Leica trailer 5800 $delPreview = 1; # delete existing Leica trailer 5801 } 5802 } else { 5803 # locate preview image and fix up preview offsets 5804 my $scanLen = $$self{Make} =~ /^SONY/i ? 65536 : 1024; 5805 if (length($buff) < $scanLen) { # make sure we have enough trailer to scan 5806 my $buf2; 5807 $buff .= $buf2 if $raf->Read($buf2, $scanLen - length($buff)); 5808 } 5809 # get new preview image position, relative to EXIF base 5810 my $newPos = length($$outfile) - 10; # (subtract 10 for segment and EXIF headers) 5811 my $junkLen; 5812 # adjust position if image isn't at the start (eg. Olympus E-1/E-300) 5813 if ($buff =~ /(\xff\xd8\xff.|.\xd8\xff\xdb)(..)/sg) { 5814 my ($jpegHdr, $segLen) = ($1, $2); 5815 $junkLen = pos($buff) - 6; 5816 # Sony previewimage trailer has a 32 byte header 5817 if ($$self{Make} =~ /^SONY/i and $junkLen > 32) { 5818 # with some newer Sony models, the makernotes preview pointer 5819 # points to JPEG at end of EXIF inside MPImage preview (what a pain!) 5820 if ($jpegHdr eq "\xff\xd8\xff\xe1") { # is the first segment EXIF? 5821 $segLen = unpack('n', $segLen); # the EXIF segment length 5822 # Sony PreviewImage starts with last 2 bytes of EXIF segment 5823 # (and first byte is usually "\0", not "\xff", so don't check this) 5824 if (length($buff) > $junkLen + $segLen + 6 and 5825 substr($buff, $junkLen + $segLen + 3, 3) eq "\xd8\xff\xdb") 5826 { 5827 $junkLen += $segLen + 2; 5828 # (note: this will not copy the trailer after PreviewImage, 5829 # which is a 14kB block full of zeros for the A77) 5830 } 5831 } 5832 $junkLen -= 32; 5833 } 5834 $newPos += $junkLen; 5835 } 5836 # fix up the preview offsets to point to the start of the new image 5837 my $previewInfo = $$self{PREVIEW_INFO}; 5838 delete $$self{PREVIEW_INFO}; 5839 my $fixup = $$previewInfo{Fixup}; 5840 $newPos += ($$previewInfo{BaseShift} || 0); 5841 # adjust to absolute file offset if necessary (Samsung STMN) 5842 $newPos += Tell($oldOutfile) + 10 if $$previewInfo{Absolute}; 5843 if ($$previewInfo{Relative}) { 5844 # adjust for our base by looking at how far the pointer got shifted 5845 $newPos -= ($fixup->GetMarkerPointers($outfile, 'PreviewImage') || 0); 5846 } elsif ($$previewInfo{ChangeBase}) { 5847 # Leica S2 uses relative offsets for the preview only (leica sucks) 5848 my $makerOffset = $fixup->GetMarkerPointers($outfile, 'LeicaTrailer'); 5849 $newPos -= $makerOffset if $makerOffset; 5850 } 5851 $fixup->SetMarkerPointers($outfile, 'PreviewImage', $newPos); 5852 # clean up and write the buffered data 5853 $outfile = $oldOutfile; 5854 undef $oldOutfile; 5855 Write($outfile, $writeBuffer) or $err = 1; 5856 undef $writeBuffer; 5857 # write preview image 5858 if ($$previewInfo{Data} ne 'LOAD_PREVIEW') { 5859 # write any junk that existed before the preview image 5860 Write($outfile, substr($buff,0,$junkLen)) or $err = 1 if $junkLen; 5861 # write the saved preview image 5862 Write($outfile, $$previewInfo{Data}) or $err = 1; 5863 delete $$previewInfo{Data}; 5864 # (don't increment CHANGED because we could be rewriting existing preview) 5865 $delPreview = 1; # remove old preview 5866 } 5867 } 5868 # copy over preview image if necessary 5869 unless ($delPreview) { 5870 my $extra; 5871 if ($trailInfo) { 5872 # copy everything up to start of first processed trailer 5873 $extra = $$trailInfo{DataPos} - $endPos; 5874 } else { 5875 # copy everything up to end of file 5876 $raf->Seek(0, 2) or $err = 1; 5877 $extra = $raf->Tell() - $endPos; 5878 } 5879 if ($extra > 0) { 5880 if ($$delGroup{Trailer}) { 5881 $verbose and print $out " Deleting unknown trailer ($extra bytes)\n"; 5882 ++$$self{CHANGED}; 5883 } else { 5884 # copy over unknown trailer 5885 $verbose and print $out " Preserving unknown trailer ($extra bytes)\n"; 5886 $raf->Seek($endPos, 0) or $err = 1; 5887 CopyBlock($raf, $outfile, $extra) or $err = 1; 5888 } 5889 } 5890 } 5891 # write trailer if necessary 5892 if ($trailInfo) { 5893 $self->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1; 5894 undef $trailInfo; 5895 } 5896 last; # all done parsing file 5897 5898 } elsif ($marker==0xd9 and $isEXV) { 5899 # write EXV EOI (any trailer will be lost) 5900 Write($outfile, "\xff\xd9") or $err = 1; 5901 $rtnVal = 1; 5902 last; 5903 5904 } elsif ($marker==0x00 or $marker==0x01 or ($marker>=0xd0 and $marker<=0xd7)) { 5905 $verbose and $marker and print $out "JPEG $markerName:\n"; 5906 # handle stand-alone markers 0x00, 0x01 and 0xd0-0xd7 (NULL, TEM, RST0-RST7) 5907 Write($outfile, $hdr) or $err = 1; 5908 next; 5909 } 5910 # 5911 # NOTE: A 'next' statement after this point will cause $$segDataPt 5912 # not to be written if there is an output file, so in this case 5913 # the $$self{CHANGED} flags must be updated 5914 # 5915 my $segDataPt = \$segData; 5916 $length = length($segData); 5917 if ($verbose) { 5918 print $out "JPEG $markerName ($length bytes):\n"; 5919 if ($verbose > 2 and $markerName =~ /^APP/) { 5920 HexDump($segDataPt, undef, %dumpParms); 5921 } 5922 } 5923 # group delete of APP segments 5924 if ($$delGroup{$dirName}) { 5925 $verbose and print $out " Deleting $dirName segment\n"; 5926 $self->Warn('ICC_Profile deleted. Image colors may be affected') if $dirName eq 'ICC_Profile'; 5927 ++$$self{CHANGED}; 5928 next Marker; 5929 } 5930 my ($segType, $del); 5931 # rewrite this segment only if we are changing a tag which is contained in its 5932 # directory (or deleting '*', in which case we need to identify the segment type) 5933 while (exists $$editDirs{$markerName} or $$delGroup{'*'}) { 5934 if ($marker == 0xe0) { # APP0 (JFIF, CIFF) 5935 if ($$segDataPt =~ /^JFIF\0/) { 5936 $segType = 'JFIF'; 5937 $$delGroup{JFIF} and $del = 1, last; 5938 last unless $$editDirs{JFIF}; 5939 SetByteOrder('MM'); 5940 my $tagTablePtr = GetTagTable('Image::ExifTool::JFIF::Main'); 5941 my %dirInfo = ( 5942 DataPt => $segDataPt, 5943 DataPos => $segPos, 5944 DataLen => $length, 5945 DirStart => 5, # directory starts after identifier 5946 DirLen => $length-5, 5947 Parent => $markerName, 5948 ); 5949 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 5950 if (defined $newData and length $newData) { 5951 $$segDataPt = "JFIF\0" . $newData; 5952 } 5953 } elsif ($$segDataPt =~ /^JFXX\0\x10/) { 5954 $segType = 'JFXX'; 5955 $$delGroup{JFIF} and $del = 1; 5956 } elsif ($$segDataPt =~ /^(II|MM).{4}HEAPJPGM/s) { 5957 $segType = 'CIFF'; 5958 $$delGroup{CIFF} and $del = 1, last; 5959 last unless $$editDirs{CIFF}; 5960 my $newData = ''; 5961 my %dirInfo = ( 5962 RAF => new File::RandomAccess($segDataPt), 5963 OutFile => \$newData, 5964 ); 5965 require Image::ExifTool::CanonRaw; 5966 if (Image::ExifTool::CanonRaw::WriteCRW($self, \%dirInfo) > 0) { 5967 if (length $newData) { 5968 $$segDataPt = $newData; 5969 } else { 5970 undef $segDataPt; 5971 $del = 1; # delete this segment 5972 } 5973 } 5974 } 5975 } elsif ($marker == 0xe1) { # APP1 (EXIF, XMP) 5976 # check for EXIF data 5977 if ($$segDataPt =~ /^(.{0,4})$exifAPP1hdr/is) { 5978 my $hdrLen = length $exifAPP1hdr; 5979 if (length $1) { 5980 $hdrLen += length $1; 5981 $self->Error('Unknown garbage at start of EXIF segment',1); 5982 } elsif ($$segDataPt !~ /^Exif\0/) { 5983 $self->Error('Incorrect EXIF segment identifier',1); 5984 } 5985 $segType = 'EXIF'; 5986 last unless $$editDirs{IFD0}; 5987 # add this data to the combined data if it exists 5988 if (defined $combinedSegData) { 5989 $combinedSegData .= substr($$segDataPt,$hdrLen); 5990 $segDataPt = \$combinedSegData; 5991 $segPos = $firstSegPos; 5992 $length = length $combinedSegData; # update length 5993 } 5994 # peek ahead to see if the next segment is extended EXIF 5995 if ($dirOrder[0] eq 'ExtendedEXIF') { 5996 # initialize combined data if necessary 5997 unless (defined $combinedSegData) { 5998 $combinedSegData = $$segDataPt; 5999 $firstSegPos = $segPos; 6000 $self->Warn('File contains multi-segment EXIF',1); 6001 } 6002 next Marker; # get the next segment to combine 6003 } 6004 $doneDir{IFD0} and $self->Warn('Multiple APP1 EXIF records'); 6005 $doneDir{IFD0} = 1; 6006 # check del groups now so we can change byte order in one step 6007 if ($$delGroup{IFD0} or $$delGroup{EXIF}) { 6008 delete $doneDir{IFD0}; # delete so we will create a new one 6009 $del = 1; 6010 last; 6011 } 6012 # rewrite EXIF as if this were a TIFF file in memory 6013 my %dirInfo = ( 6014 DataPt => $segDataPt, 6015 DataPos => -$hdrLen, # (remember: relative to Base!) 6016 DirStart => $hdrLen, 6017 Base => $segPos + $hdrLen, 6018 Parent => $markerName, 6019 DirName => 'IFD0', 6020 ); 6021 # write new EXIF data to memory 6022 my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main'); 6023 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 6024 if (defined $buff) { 6025 undef $$segDataPt; # free the old buffer 6026 $segDataPt = \$buff; 6027 } else { 6028 last Marker unless $self->Options('IgnoreMinorErrors'); 6029 } 6030 # delete segment if IFD contains no entries 6031 length $$segDataPt or $del = 1, last; 6032 if (length($$segDataPt) + length($exifAPP1hdr) > $maxSegmentLen) { 6033 if ($self->Options('NoMultiExif')) { 6034 $self->Error('EXIF is too large for JPEG segment'); 6035 } else { 6036 $self->Warn('Writing multi-segment EXIF',1); 6037 } 6038 } 6039 # switch to buffered output if required 6040 if (($$self{PREVIEW_INFO} or $$self{LeicaTrailer}) and not $oldOutfile) { 6041 $writeBuffer = ''; 6042 $oldOutfile = $outfile; 6043 $outfile = \$writeBuffer; 6044 # must account for segment, EXIF and TIFF headers 6045 $$self{PREVIEW_INFO}{Fixup}{Start} += 18 if $$self{PREVIEW_INFO}; 6046 $$self{LeicaTrailer}{Fixup}{Start} += 18 if $$self{LeicaTrailer}; 6047 } 6048 # write as multi-segment 6049 my $n = WriteMultiSegment($outfile, $marker, $exifAPP1hdr, $segDataPt, 'EXIF'); 6050 if (not $n) { 6051 $err = 1; 6052 } elsif ($n > 1 and $oldOutfile) { 6053 # (punt on this because updating the pointers would be a real pain) 6054 $self->Error("Can't write multi-segment EXIF with external pointers"); 6055 } 6056 undef $combinedSegData; 6057 undef $$segDataPt; 6058 next Marker; 6059 # check for XMP data 6060 } elsif ($$segDataPt =~ /^($xmpAPP1hdr|$xmpExtAPP1hdr)/) { 6061 $segType = 'XMP'; 6062 $$delGroup{XMP} and $del = 1, last; 6063 $doneDir{XMP} = ($doneDir{XMP} || 0) + 1; 6064 last unless $$editDirs{XMP}; 6065 if ($doneDir{XMP} + $dirCount{XMP} > 1) { 6066 # must assemble all XMP segments before writing 6067 my ($guid, $extXMP); 6068 if ($$segDataPt =~ /^$xmpExtAPP1hdr/) { 6069 # save extended XMP data 6070 if (length $$segDataPt < 75) { 6071 $extendedXMP{Error} = 'Truncated data'; 6072 } else { 6073 my ($size, $off) = unpack('x67N2', $$segDataPt); 6074 $guid = substr($$segDataPt, 35, 32); 6075 if ($guid =~ /[^A-Za-z0-9]/) { # (technically, should be uppercase) 6076 $extendedXMP{Error} = 'Invalid GUID'; 6077 } else { 6078 # remember extended data for each GUID 6079 $extXMP = $extendedXMP{$guid}; 6080 if ($extXMP) { 6081 $size == $$extXMP{Size} or $extendedXMP{Error} = 'Inconsistent size'; 6082 } else { 6083 $extXMP = $extendedXMP{$guid} = { }; 6084 } 6085 $$extXMP{Size} = $size; 6086 $$extXMP{$off} = substr($$segDataPt, 75); 6087 } 6088 } 6089 } else { 6090 # save all main XMP segments (should normally be only one) 6091 $extendedXMP{Main} = [] unless $extendedXMP{Main}; 6092 push @{$extendedXMP{Main}}, substr($$segDataPt, length $xmpAPP1hdr); 6093 } 6094 # continue processing only if we have read all the segments 6095 next Marker if $dirCount{XMP}; 6096 # reconstruct an XMP super-segment 6097 $$segDataPt = $xmpAPP1hdr; 6098 my $goodGuid = ''; 6099 foreach (@{$extendedXMP{Main}}) { 6100 # get the HasExtendedXMP GUID if it exists 6101 if (/:HasExtendedXMP\s*(=\s*['"]|>)(\w{32})/) { 6102 # warn of subsequent XMP blocks specifying a different 6103 # HasExtendedXMP (have never seen this) 6104 if ($goodGuid and $goodGuid ne $2) { 6105 $self->WarnOnce('Multiple XMP segments specifying different extended XMP GUID'); 6106 } 6107 $goodGuid = $2; # GUID for the standard extended XMP 6108 } 6109 $$segDataPt .= $_; 6110 } 6111 # GUID of the extended XMP that we want to read 6112 my $readGuid = $$self{OPTIONS}{ExtendedXMP} || 0; 6113 $readGuid = $goodGuid if $readGuid eq '1'; 6114 foreach $guid (sort keys %extendedXMP) { 6115 next unless length $guid == 32; # ignore other (internal) keys 6116 if ($guid ne $readGuid and $readGuid ne '2') { 6117 my $non = $guid eq $goodGuid ? '' : 'non-'; 6118 $self->Warn("Ignored ${non}standard extended XMP (GUID $guid)"); 6119 next; 6120 } 6121 if ($guid ne $goodGuid) { 6122 $self->Warn("Reading non-standard extended XMP (GUID $guid)"); 6123 } 6124 $extXMP = $extendedXMP{$guid}; 6125 next unless ref $extXMP eq 'HASH'; # (just to be safe) 6126 my $size = $$extXMP{Size}; 6127 my (@offsets, $off); 6128 for ($off=0; $off<$size; ) { 6129 last unless defined $$extXMP{$off}; 6130 push @offsets, $off; 6131 $off += length $$extXMP{$off}; 6132 } 6133 if ($off == $size) { 6134 # add all XMP to super-segment 6135 $$segDataPt .= $$extXMP{$_} foreach @offsets; 6136 } else { 6137 $self->Error("Incomplete extended XMP (GUID $guid)", 1); 6138 } 6139 } 6140 $self->Error("$extendedXMP{Error} in extended XMP", 1) if $extendedXMP{Error}; 6141 } 6142 my $start = length $xmpAPP1hdr; 6143 my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main'); 6144 my %dirInfo = ( 6145 DataPt => $segDataPt, 6146 DirStart => $start, 6147 Parent => $markerName, 6148 # limit XMP size and create extended XMP if necessary 6149 MaxDataLen => $maxXMPLen - length($xmpAPP1hdr), 6150 ); 6151 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 6152 if (defined $newData) { 6153 undef %extendedXMP; 6154 if (length $newData) { 6155 # write multi-segment XMP (XMP plus extended XMP if necessary) 6156 WriteMultiXMP($self, $outfile, \$newData, $dirInfo{ExtendedXMP}, 6157 $dirInfo{ExtendedGUID}) or $err = 1; 6158 undef $$segDataPt; # free the old buffer 6159 next Marker; 6160 } else { 6161 $$segDataPt = ''; # delete the XMP 6162 } 6163 } else { 6164 $verbose and print $out " [XMP rewritten with no changes]\n"; 6165 if ($doneDir{XMP} > 1) { 6166 # re-write original multi-segment XMP 6167 my ($dat, $guid, $extXMP, $off); 6168 foreach $dat (@{$extendedXMP{Main}}) { # main XMP 6169 next unless length $dat; 6170 $s = pack('n', length($xmpAPP1hdr) + length($dat) + 2); 6171 Write($outfile, $hdr, $s, $xmpAPP1hdr, $dat) or $err = 1; 6172 } 6173 foreach $guid (sort keys %extendedXMP) { # extended XMP 6174 next unless length $guid == 32; 6175 $extXMP = $extendedXMP{$guid}; 6176 next unless ref $extXMP eq 'HASH'; 6177 my $size = $$extXMP{Size} or next; 6178 for ($off=0; defined $$extXMP{$off}; $off += length $$extXMP{$off}) { 6179 $s = pack('n', length($xmpExtAPP1hdr) + length($$extXMP{$off}) + 42); 6180 Write($outfile, $hdr, $s, $xmpExtAPP1hdr, $guid, 6181 pack('N2', $size, $off), $$extXMP{$off}) or $err = 1; 6182 } 6183 } 6184 undef $$segDataPt; # free the old buffer 6185 undef %extendedXMP; 6186 next Marker; 6187 } 6188 # continue on to re-write original single-segment XMP 6189 } 6190 $del = 1 unless length $$segDataPt; 6191 } elsif ($$segDataPt =~ /^http/ or $$segDataPt =~ /<exif:/) { 6192 $self->Warn('Ignored APP1 XMP segment with non-standard header', 1); 6193 } 6194 } elsif ($marker == 0xe2) { # APP2 (ICC Profile, FPXR, MPF) 6195 if ($$segDataPt =~ /^ICC_PROFILE\0/ and $length >= 14) { 6196 $segType = 'ICC_Profile'; 6197 $$delGroup{ICC_Profile} and $del = 1, last; 6198 # must concatenate blocks of profile 6199 my $chunkNum = Get8u($segDataPt, 12); 6200 my $chunksTot = Get8u($segDataPt, 13); 6201 if (defined $iccChunksTotal) { 6202 # abort parsing ICC_Profile if the total chunk count is inconsistent 6203 if ($chunksTot != $iccChunksTotal and defined $iccChunkCount) { 6204 # an error because the accumulated profile data will be lost 6205 $self->Error('Inconsistent ICC_Profile chunk count', 1); 6206 undef $iccChunkCount; # abort ICC_Profile parsing 6207 undef $chunkNum; # avoid 2nd warning below 6208 ++$$self{CHANGED}; # we are deleting the bad chunks before this one 6209 } 6210 } else { 6211 $iccChunkCount = 0; 6212 $iccChunksTotal = $chunksTot; 6213 $self->Warn('ICC_Profile chunk count is zero') if !$chunksTot; 6214 } 6215 if (defined $iccChunkCount) { 6216 # save this chunk 6217 if (defined $iccChunk[$chunkNum]) { 6218 $self->Warn("Duplicate ICC_Profile chunk number $chunkNum"); 6219 $iccChunk[$chunkNum] .= substr($$segDataPt, 14); 6220 } else { 6221 $iccChunk[$chunkNum] = substr($$segDataPt, 14); 6222 } 6223 # continue accumulating chunks unless we have all of them 6224 next Marker unless ++$iccChunkCount >= $iccChunksTotal; 6225 undef $iccChunkCount; # prevent reprocessing 6226 $doneDir{ICC_Profile} = 1; 6227 # combine the ICC_Profile chunks 6228 my $icc_profile = ''; 6229 defined $_ and $icc_profile .= $_ foreach @iccChunk; 6230 undef @iccChunk; # free memory 6231 $segDataPt = \$icc_profile; 6232 $length = length $icc_profile; 6233 my $tagTablePtr = GetTagTable('Image::ExifTool::ICC_Profile::Main'); 6234 my %dirInfo = ( 6235 DataPt => $segDataPt, 6236 DataPos => $segPos + 14, 6237 DataLen => $length, 6238 DirStart => 0, 6239 DirLen => $length, 6240 Parent => $markerName, 6241 ); 6242 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 6243 if (defined $newData) { 6244 undef $$segDataPt; # free the old buffer 6245 $segDataPt = \$newData; 6246 } 6247 length $$segDataPt or $del = 1, last; 6248 # write as ICC multi-segment 6249 WriteMultiSegment($outfile, $marker, "ICC_PROFILE\0", $segDataPt, 'ICC') or $err = 1; 6250 undef $$segDataPt; 6251 next Marker; 6252 } elsif (defined $chunkNum) { 6253 $self->WarnOnce('Invalid or extraneous ICC_Profile chunk(s)'); 6254 # fall through to preserve this extra profile... 6255 } 6256 } elsif ($$segDataPt =~ /^FPXR\0/) { 6257 $segType = 'FPXR'; 6258 $$delGroup{FlashPix} and $del = 1; 6259 } elsif ($$segDataPt =~ /^MPF\0/) { 6260 $segType = 'MPF'; 6261 $$delGroup{MPF} and $del = 1; 6262 } 6263 } elsif ($marker == 0xe3) { # APP3 (Kodak Meta) 6264 if ($$segDataPt =~ /^(Meta|META|Exif)\0\0/) { 6265 $segType = 'Kodak Meta'; 6266 $$delGroup{Meta} and $del = 1, last; 6267 $doneDir{Meta} and $self->Warn('Multiple APP3 Meta segments'); 6268 $doneDir{Meta} = 1; 6269 last unless $$editDirs{Meta}; 6270 # rewrite Meta IFD as if this were a TIFF file in memory 6271 my %dirInfo = ( 6272 DataPt => $segDataPt, 6273 DataPos => -6, # (remember: relative to Base!) 6274 DirStart => 6, 6275 Base => $segPos + 6, 6276 Parent => $markerName, 6277 DirName => 'Meta', 6278 ); 6279 # write new data to memory 6280 my $tagTablePtr = GetTagTable('Image::ExifTool::Kodak::Meta'); 6281 my $buff = $self->WriteDirectory(\%dirInfo, $tagTablePtr, \&WriteTIFF); 6282 if (defined $buff) { 6283 # update segment with new data 6284 $$segDataPt = substr($$segDataPt,0,6) . $buff; 6285 } else { 6286 last Marker unless $self->Options('IgnoreMinorErrors'); 6287 } 6288 # delete segment if IFD contains no entries 6289 $del = 1 unless length($$segDataPt) > 6; 6290 } 6291 } elsif ($marker == 0xe5) { # APP5 (Ricoh RMETA) 6292 if ($$segDataPt =~ /^RMETA\0/) { 6293 $segType = 'Ricoh RMETA'; 6294 $$delGroup{RMETA} and $del = 1; 6295 } 6296 } elsif ($marker == 0xec) { # APP12 (Ducky) 6297 if ($$segDataPt =~ /^Ducky/) { 6298 $segType = 'Ducky'; 6299 $$delGroup{Ducky} and $del = 1, last; 6300 $doneDir{Ducky} and $self->Warn('Multiple APP12 Ducky segments'); 6301 $doneDir{Ducky} = 1; 6302 last unless $$editDirs{Ducky}; 6303 my $tagTablePtr = GetTagTable('Image::ExifTool::APP12::Ducky'); 6304 my %dirInfo = ( 6305 DataPt => $segDataPt, 6306 DataPos => $segPos, 6307 DataLen => $length, 6308 DirStart => 5, # directory starts after identifier 6309 DirLen => $length-5, 6310 Parent => $markerName, 6311 ); 6312 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 6313 if (defined $newData) { 6314 undef $$segDataPt; # free the old buffer 6315 # add header to new segment unless empty 6316 $newData = 'Ducky' . $newData if length $newData; 6317 $segDataPt = \$newData; 6318 } 6319 $del = 1 unless length $$segDataPt; 6320 } 6321 } elsif ($marker == 0xed) { # APP13 (Photoshop) 6322 if ($$segDataPt =~ /^$psAPP13hdr/) { 6323 $segType = 'Photoshop'; 6324 # add this data to the combined data if it exists 6325 if (defined $combinedSegData) { 6326 $combinedSegData .= substr($$segDataPt,length($psAPP13hdr)); 6327 $segDataPt = \$combinedSegData; 6328 $length = length $combinedSegData; # update length 6329 } 6330 # peek ahead to see if the next segment is photoshop data too 6331 if ($dirOrder[0] eq 'Photoshop') { 6332 # initialize combined data if necessary 6333 $combinedSegData = $$segDataPt unless defined $combinedSegData; 6334 next Marker; # get the next segment to combine 6335 } 6336 if ($doneDir{Photoshop}) { 6337 $self->Warn('Multiple Photoshop records'); 6338 # only rewrite the first Photoshop segment when deleting this group 6339 # (to remove multiples when deleting and adding back in one step) 6340 $$delGroup{Photoshop} and $del = 1, last; 6341 } 6342 $doneDir{Photoshop} = 1; 6343 # process APP13 Photoshop record 6344 my $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); 6345 my %dirInfo = ( 6346 DataPt => $segDataPt, 6347 DataPos => $segPos, 6348 DataLen => $length, 6349 DirStart => 14, # directory starts after identifier 6350 DirLen => $length-14, 6351 Parent => $markerName, 6352 ); 6353 my $newData = $self->WriteDirectory(\%dirInfo, $tagTablePtr); 6354 if (defined $newData) { 6355 undef $$segDataPt; # free the old buffer 6356 $segDataPt = \$newData; 6357 } 6358 length $$segDataPt or $del = 1, last; 6359 # write as multi-segment 6360 WriteMultiSegment($outfile, $marker, $psAPP13hdr, $segDataPt) or $err = 1; 6361 undef $combinedSegData; 6362 undef $$segDataPt; 6363 next Marker; 6364 } 6365 } elsif ($marker == 0xee) { # APP14 (Adobe) 6366 if ($$segDataPt =~ /^Adobe/) { 6367 $segType = 'Adobe'; 6368 # delete it and replace it later if editing 6369 if ($$delGroup{Adobe} or $$editDirs{Adobe}) { 6370 $del = 1; 6371 undef $doneDir{Adobe}; # so we can add it back again above 6372 } 6373 } 6374 } elsif ($marker == 0xfe) { # COM (JPEG comment) 6375 my $newComment; 6376 unless ($doneDir{COM}) { 6377 $doneDir{COM} = 1; 6378 unless ($$delGroup{File} and $$delGroup{File} != 2) { 6379 my $tagInfo = $Image::ExifTool::Extra{Comment}; 6380 my $nvHash = $self->GetNewValueHash($tagInfo); 6381 my $val = $segData; 6382 $val =~ s/\0+$//; # allow for stupid software that adds NULL terminator 6383 if ($self->IsOverwriting($nvHash, $val) or $$delGroup{File}) { 6384 $newComment = $self->GetNewValue($nvHash); 6385 } else { 6386 delete $$editDirs{COM}; # we aren't editing COM after all 6387 last; 6388 } 6389 } 6390 } 6391 $self->VerboseValue('- Comment', $$segDataPt); 6392 if (defined $newComment) { 6393 # write out the comments 6394 $self->VerboseValue('+ Comment', $newComment); 6395 WriteMultiSegment($outfile, 0xfe, '', \$newComment) or $err = 1; 6396 } else { 6397 $verbose and print $out " Deleting COM segment\n"; 6398 } 6399 ++$$self{CHANGED}; # increment the changed flag 6400 undef $segDataPt; # don't write existing comment 6401 } 6402 last; # didn't want to loop anyway 6403 } 6404 6405 # delete necessary segments (including unknown segments if deleting all) 6406 if ($del or ($$delGroup{'*'} and not $segType and $marker>=0xe0 and $marker<=0xef)) { 6407 $segType = 'unknown' unless $segType; 6408 $verbose and print $out " Deleting $markerName $segType segment\n"; 6409 ++$$self{CHANGED}; 6410 next Marker; 6411 } 6412 # write out this segment if $segDataPt is still defined 6413 if (defined $segDataPt and defined $$segDataPt) { 6414 # write the data for this record (the data could have been 6415 # modified, so recalculate the length word) 6416 my $size = length($$segDataPt); 6417 if ($size > $maxSegmentLen) { 6418 $segType or $segType = 'Unknown'; 6419 $self->Error("$segType $markerName segment too large! ($size bytes)"); 6420 $err = 1; 6421 } else { 6422 $s = pack('n', length($$segDataPt) + 2); 6423 Write($outfile, $hdr, $s, $$segDataPt) or $err = 1; 6424 } 6425 undef $$segDataPt; # free the buffer 6426 undef $segDataPt; 6427 } 6428 } 6429 # make sure the ICC_Profile was complete 6430 $self->Error('Incomplete ICC_Profile record', 1) if defined $iccChunkCount; 6431 pop @$path if @$path > $pn; 6432 # if oldOutfile is still set, there was an error copying the JPEG 6433 $oldOutfile and return 0; 6434 if ($rtnVal) { 6435 # add any new trailers we are creating 6436 my $trailPt = $self->AddNewTrailers(); 6437 Write($outfile, $$trailPt) or $err = 1 if $trailPt; 6438 } 6439 # set return value to -1 if we only had a write error 6440 $rtnVal = -1 if $rtnVal and $err; 6441 if ($creatingEXV and $rtnVal > 0 and not $$self{CHANGED}) { 6442 $self->Error('Nothing written'); 6443 $rtnVal = -1; 6444 } 6445 return $rtnVal; 6446} 6447 6448#------------------------------------------------------------------------------ 6449# Validate an image for writing 6450# Inputs: 0) ExifTool object reference, 1) raw value reference 6451# Returns: error string or undef on success 6452sub CheckImage($$) 6453{ 6454 my ($self, $valPtr) = @_; 6455 if (length($$valPtr) and $$valPtr!~/^\xff\xd8/ and not 6456 $self->Options('IgnoreMinorErrors')) 6457 { 6458 return '[Minor] Not a valid image'; 6459 } 6460 return undef; 6461} 6462 6463#------------------------------------------------------------------------------ 6464# check a value for validity 6465# Inputs: 0) value reference, 1) format string, 2) optional count 6466# Returns: error string, or undef on success 6467# Notes: May modify value (if a count is specified for a string, it is null-padded 6468# to the specified length, and floating point values are rounded to integer if required) 6469sub CheckValue($$;$) 6470{ 6471 my ($valPtr, $format, $count) = @_; 6472 my (@vals, $val, $n); 6473 6474 if ($format eq 'string' or $format eq 'undef') { 6475 return undef unless $count and $count > 0; 6476 my $len = length($$valPtr); 6477 if ($format eq 'string') { 6478 $len >= $count and return 'String too long'; 6479 } else { 6480 $len > $count and return 'Data too long'; 6481 } 6482 if ($len < $count) { 6483 $$valPtr .= "\0" x ($count - $len); 6484 } 6485 return undef; 6486 } 6487 if ($count and $count != 1) { 6488 @vals = split(' ',$$valPtr); 6489 $count < 0 and ($count = @vals or return undef); 6490 } else { 6491 $count = 1; 6492 @vals = ( $$valPtr ); 6493 } 6494 if (@vals != $count) { 6495 my $str = @vals > $count ? 'Too many' : 'Not enough'; 6496 return "$str values specified ($count required)"; 6497 } 6498 for ($n=0; $n<$count; ++$n) { 6499 $val = shift @vals; 6500 if ($format =~ /^int/) { 6501 # make sure the value is integer 6502 unless (IsInt($val)) { 6503 if (IsHex($val)) { 6504 $val = $$valPtr = hex($val); 6505 } else { 6506 # round single floating point values to the nearest integer 6507 return 'Not an integer' unless IsFloat($val) and $count == 1; 6508 $val = $$valPtr = int($val + ($val < 0 ? -0.5 : 0.5)); 6509 } 6510 } 6511 my $rng = $intRange{$format} or return "Bad int format: $format"; 6512 return "Value below $format minimum" if $val < $$rng[0]; 6513 # (allow 0xfeedfeed code as value for 16-bit pointers) 6514 return "Value above $format maximum" if $val > $$rng[1] and $val != 0xfeedfeed; 6515 } elsif ($format =~ /^rational/ or $format eq 'float' or $format eq 'double') { 6516 # make sure the value is a valid floating point number 6517 unless (IsFloat($val)) { 6518 # allow 'inf', 'undef' and fractional rational values 6519 if ($format =~ /^rational/) { 6520 next if $val eq 'inf' or $val eq 'undef'; 6521 if ($val =~ m{^([-+]?\d+)/(\d+)$}) { 6522 next unless $1 < 0 and $format =~ /u$/; 6523 return 'Must be an unsigned rational'; 6524 } 6525 } 6526 return 'Not a floating point number'; 6527 } 6528 if ($format =~ /^rational\d+u$/ and $val < 0) { 6529 return 'Must be a positive number'; 6530 } 6531 } 6532 } 6533 return undef; # success! 6534} 6535 6536#------------------------------------------------------------------------------ 6537# check new value for binary data block 6538# Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref 6539# Returns: error string or undef (and may modify value) on success 6540sub CheckBinaryData($$$) 6541{ 6542 my ($self, $tagInfo, $valPtr) = @_; 6543 my $format = $$tagInfo{Format}; 6544 unless ($format) { 6545 my $table = $$tagInfo{Table}; 6546 if ($table and $$table{FORMAT}) { 6547 $format = $$table{FORMAT}; 6548 } else { 6549 # use default 'int8u' unless specified 6550 $format = 'int8u'; 6551 } 6552 } 6553 my $count; 6554 if ($format =~ /(.*)\[(.*)\]/) { 6555 $format = $1; 6556 $count = $2; 6557 # can't evaluate $count now because we don't know $size yet 6558 undef $count if $count =~ /\$size/; 6559 } 6560 return CheckValue($valPtr, $format, $count); 6561} 6562 6563#------------------------------------------------------------------------------ 6564# Rename a file (with patch for Windows Unicode file names, and other problem) 6565# Inputs: 0) ExifTool ref, 1) old name, 2) new name 6566# Returns: true on success 6567sub Rename($$$) 6568{ 6569 my ($self, $old, $new) = @_; 6570 my ($result, $try, $winUni); 6571 6572 if ($self->EncodeFileName($old)) { 6573 $self->EncodeFileName($new, 1); 6574 $winUni = 1; 6575 } elsif ($self->EncodeFileName($new)) { 6576 $old = $_[1]; 6577 $self->EncodeFileName($old, 1); 6578 $winUni = 1; 6579 } 6580 for (;;) { 6581 if ($winUni) { 6582 $result = eval { Win32API::File::MoveFileExW($old, $new, 6583 Win32API::File::MOVEFILE_REPLACE_EXISTING() | 6584 Win32API::File::MOVEFILE_COPY_ALLOWED()) }; 6585 } else { 6586 $result = rename($old, $new); 6587 } 6588 last if $result or $^O ne 'MSWin32'; 6589 # keep trying for up to 0.5 seconds 6590 # (patch for Windows denial-of-service susceptibility) 6591 $try = ($try || 1) + 1; 6592 last if $try > 50; 6593 select(undef,undef,undef,0.01); # sleep for 0.01 sec 6594 } 6595 return $result; 6596} 6597 6598#------------------------------------------------------------------------------ 6599# Delete a file (with patch for Windows Unicode file names) 6600# Inputs: 0) ExifTool ref, 1-N) names of files to delete 6601# Returns: number of files deleted 6602sub Unlink($@) 6603{ 6604 my $self = shift; 6605 my $result = 0; 6606 while (@_) { 6607 my $file = shift; 6608 if ($self->EncodeFileName($file)) { 6609 ++$result if eval { Win32API::File::DeleteFileW($file) }; 6610 } else { 6611 ++$result if unlink $file; 6612 } 6613 } 6614 return $result; 6615} 6616 6617#------------------------------------------------------------------------------ 6618# Set file times (Unix seconds since the epoch) 6619# Inputs: 0) ExifTool ref, 1) file name or ref, 2) access time, 3) modification time, 6620# 4) inode change or creation time (or undef for any time to avoid setting) 6621# 5) flag to suppress warning 6622# Returns: 1 on success, 0 on error 6623my $k32SetFileTime; 6624sub SetFileTime($$;$$$$) 6625{ 6626 my ($self, $file, $atime, $mtime, $ctime, $noWarn) = @_; 6627 my $saveFile; 6628 local *FH; 6629 6630 # open file by name if necessary 6631 unless (ref $file) { 6632 # (file will be automatically closed when *FH goes out of scope) 6633 $self->Open(\*FH, $file, '+<') or $self->Warn('Error opening file for update'), return 0; 6634 $saveFile = $file; 6635 $file = \*FH; 6636 } 6637 # on Windows, try to work around incorrect file times when daylight saving time is in effect 6638 if ($^O eq 'MSWin32') { 6639 if (not eval { require Win32::API }) { 6640 $self->WarnOnce('Install Win32::API for proper handling of Windows file times'); 6641 } elsif (not eval { require Win32API::File }) { 6642 $self->WarnOnce('Install Win32API::File for proper handling of Windows file times'); 6643 } else { 6644 # get Win32 handle, needed for SetFileTime 6645 my $win32Handle = eval { Win32API::File::GetOsFHandle($file) }; 6646 unless ($win32Handle) { 6647 $self->Warn('Win32API::File::GetOsFHandle returned invalid handle'); 6648 return 0; 6649 } 6650 # convert Unix seconds to FILETIME structs 6651 my $time; 6652 foreach $time ($atime, $mtime, $ctime) { 6653 # set to NULL if not defined (i.e. do not change) 6654 defined $time or $time = 0, next; 6655 # convert to 100 ns intervals since 0:00 UTC Jan 1, 1601 6656 # (89 leap years between 1601 and 1970) 6657 my $wt = ($time + (((1970-1601)*365+89)*24*3600)) * 1e7; 6658 my $hi = int($wt / 4294967296); 6659 $time = pack 'LL', int($wt - $hi * 4294967296), $hi; # pack FILETIME struct 6660 } 6661 unless ($k32SetFileTime) { 6662 return 0 if defined $k32SetFileTime; 6663 $k32SetFileTime = new Win32::API('KERNEL32', 'SetFileTime', 'NPPP', 'I'); 6664 unless ($k32SetFileTime) { 6665 $self->Warn('Error calling Win32::API::SetFileTime'); 6666 $k32SetFileTime = 0; 6667 return 0; 6668 } 6669 } 6670 unless ($k32SetFileTime->Call($win32Handle, $ctime, $atime, $mtime)) { 6671 $self->Warn('Win32::API::SetFileTime returned ' . Win32::GetLastError()); 6672 return 0; 6673 } 6674 return 1; 6675 } 6676 } 6677 # other OS (or Windows fallback) 6678 if (defined $atime and defined $mtime) { 6679 my $success; 6680 local $SIG{'__WARN__'} = \&SetWarning; # (this may not be necessary) 6681 for (;;) { 6682 undef $evalWarning; 6683 # (this may fail on the first try if futimes is not implemented) 6684 $success = eval { utime($atime, $mtime, $file) }; 6685 last if $success or not defined $saveFile; 6686 close $file; 6687 $file = $saveFile; 6688 undef $saveFile; 6689 } 6690 unless ($noWarn) { 6691 if ($@ or $evalWarning) { 6692 $self->Warn(CleanWarning($@ || $evalWarning)); 6693 } elsif (not $success) { 6694 $self->Warn('Error setting file time'); 6695 } 6696 } 6697 return $success; 6698 } 6699 return 1; # (nothing to do) 6700} 6701 6702#------------------------------------------------------------------------------ 6703# Copy data block from RAF to output file in max 64kB chunks 6704# Inputs: 0) RAF ref, 1) outfile ref, 2) block size 6705# Returns: 1 on success, 0 on read error, undef on write error 6706sub CopyBlock($$$) 6707{ 6708 my ($raf, $outfile, $size) = @_; 6709 my $buff; 6710 for (;;) { 6711 last unless $size > 0; 6712 my $n = $size > 65536 ? 65536 : $size; 6713 $raf->Read($buff, $n) == $n or return 0; 6714 Write($outfile, $buff) or return undef; 6715 $size -= $n; 6716 } 6717 return 1; 6718} 6719 6720#------------------------------------------------------------------------------ 6721# Copy image data from one file to another 6722# Inputs: 0) ExifTool object reference 6723# 1) reference to list of image data [ position, size, pad bytes ] 6724# 2) output file ref 6725# Returns: true on success 6726sub CopyImageData($$$) 6727{ 6728 my ($self, $imageDataBlocks, $outfile) = @_; 6729 my $raf = $$self{RAF}; 6730 my ($dataBlock, $err); 6731 my $num = @$imageDataBlocks; 6732 $self->VPrint(0, " Copying $num image data blocks\n") if $num; 6733 foreach $dataBlock (@$imageDataBlocks) { 6734 my ($pos, $size, $pad) = @$dataBlock; 6735 $raf->Seek($pos, 0) or $err = 'read', last; 6736 my $result = CopyBlock($raf, $outfile, $size); 6737 $result or $err = defined $result ? 'read' : 'writ'; 6738 # pad if necessary 6739 Write($outfile, "\0" x $pad) or $err = 'writ' if $pad; 6740 last if $err; 6741 } 6742 if ($err) { 6743 $self->Error("Error ${err}ing image data"); 6744 return 0; 6745 } 6746 return 1; 6747} 6748 6749#------------------------------------------------------------------------------ 6750# Write to binary data block 6751# Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref 6752# Returns: Binary data block or undefined on error 6753sub WriteBinaryData($$$) 6754{ 6755 my ($self, $dirInfo, $tagTablePtr) = @_; 6756 $self or return 1; # allow dummy access to autoload this package 6757 6758 # get default format ('int8u' unless specified) 6759 my $dataPt = $$dirInfo{DataPt} or return undef; 6760 my $defaultFormat = $$tagTablePtr{FORMAT} || 'int8u'; 6761 my $increment = FormatSize($defaultFormat); 6762 unless ($increment) { 6763 warn "Unknown format $defaultFormat\n"; 6764 return undef; 6765 } 6766 # extract data members first if necessary 6767 my @varOffsets; 6768 if ($$tagTablePtr{DATAMEMBER}) { 6769 $$dirInfo{DataMember} = $$tagTablePtr{DATAMEMBER}; 6770 $$dirInfo{VarFormatData} = \@varOffsets; 6771 $self->ProcessBinaryData($dirInfo, $tagTablePtr); 6772 delete $$dirInfo{DataMember}; 6773 delete $$dirInfo{VarFormatData}; 6774 } 6775 my $dirStart = $$dirInfo{DirStart} || 0; 6776 my $dirLen = $$dirInfo{DirLen} || length($$dataPt) - $dirStart; 6777 my $newData = substr($$dataPt, $dirStart, $dirLen) or return undef; 6778 my $dirName = $$dirInfo{DirName}; 6779 my $varSize = 0; 6780 my @varInfo = @varOffsets; 6781 my $tagInfo; 6782 $dataPt = \$newData; 6783 foreach $tagInfo (sort { $$a{TagID} <=> $$b{TagID} } $self->GetNewTagInfoList($tagTablePtr)) { 6784 my $tagID = $$tagInfo{TagID}; 6785 # evaluate conditional tags now if necessary 6786 if (ref $$tagTablePtr{$tagID} eq 'ARRAY' or $$tagInfo{Condition}) { 6787 my $writeInfo = $self->GetTagInfo($tagTablePtr, $tagID); 6788 next unless $writeInfo and $writeInfo eq $tagInfo; 6789 } 6790 # add offsets for variable-sized tags if necessary 6791 while (@varInfo and $varInfo[0][0] < $tagID) { 6792 $varSize = $varInfo[0][1]; # get accumulated variable size 6793 shift @varInfo; 6794 } 6795 my $count = 1; 6796 my $format = $$tagInfo{Format}; 6797 my $entry = int($tagID) * $increment + $varSize; # relative offset of this entry 6798 if ($format) { 6799 if ($format =~ /(.*)\[(.*)\]/) { 6800 $format = $1; 6801 $count = $2; 6802 my $size = $dirLen; # used in eval 6803 # evaluate count to allow count to be based on previous values 6804 #### eval Format size ($size, $self) - NOTE: %val not supported for writing 6805 $count = eval $count; 6806 $@ and warn($@), next; 6807 } elsif ($format eq 'string') { 6808 # string with no specified count runs to end of block 6809 $count = ($dirLen > $entry) ? $dirLen - $entry : 0; 6810 } 6811 } else { 6812 $format = $defaultFormat; 6813 } 6814 # read/write using variable format if changed in Hook 6815 $format = $varInfo[0][2] if @varInfo and $varInfo[0][0] == $tagID; 6816 my $val = ReadValue($dataPt, $entry, $format, $count, $dirLen-$entry); 6817 next unless defined $val; 6818 my $nvHash = $self->GetNewValueHash($tagInfo, $$self{CUR_WRITE_GROUP}); 6819 next unless $self->IsOverwriting($nvHash, $val) > 0; 6820 my $newVal = $self->GetNewValue($nvHash); 6821 next unless defined $newVal; # can't delete from a binary table 6822 # update DataMember with new value if necessary 6823 $$self{$$tagInfo{DataMember}} = $newVal if $$tagInfo{DataMember}; 6824 # only write masked bits if specified 6825 my $mask = $$tagInfo{Mask}; 6826 $newVal = (($newVal << $$tagInfo{BitShift}) & $mask) | ($val & ~$mask) if $mask; 6827 # set the size 6828 if ($$tagInfo{DataTag} and not $$tagInfo{IsOffset}) { 6829 warn 'Internal error' unless $newVal == 0xfeedfeed; 6830 my $data = $self->GetNewValue($$tagInfo{DataTag}); 6831 $newVal = length($data) if defined $data; 6832 my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; 6833 if ($format =~ /^int16/ and $newVal > 0xffff) { 6834 $self->Error("$$tagInfo{DataTag} is too large (64 kB max. for this file)"); 6835 } 6836 } 6837 my $rtnVal = WriteValue($newVal, $format, $count, $dataPt, $entry); 6838 if (defined $rtnVal) { 6839 $self->VerboseValue("- $dirName:$$tagInfo{Name}", $val); 6840 $self->VerboseValue("+ $dirName:$$tagInfo{Name}", $newVal); 6841 ++$$self{CHANGED}; 6842 } 6843 } 6844 # add necessary fixups for any offsets 6845 if ($$tagTablePtr{IS_OFFSET} and $$dirInfo{Fixup}) { 6846 $varSize = 0; 6847 @varInfo = @varOffsets; 6848 my $fixup = $$dirInfo{Fixup}; 6849 my $tagID; 6850 foreach $tagID (@{$$tagTablePtr{IS_OFFSET}}) { 6851 $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID) or next; 6852 while (@varInfo and $varInfo[0][0] < $tagID) { 6853 $varSize = $varInfo[0][1]; 6854 shift @varInfo; 6855 } 6856 my $entry = $tagID * $increment + $varSize; # (no offset to dirStart for new dir data) 6857 next unless $entry <= $dirLen - 4; 6858 # (Ricoh has 16-bit preview image offsets, so can't just assume int32u) 6859 my $format = $$tagInfo{Format} || $$tagTablePtr{FORMAT} || 'int32u'; 6860 my $offset = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); 6861 # ignore if offset is zero (eg. Ricoh DNG uses this to indicate no preview) 6862 next unless $offset; 6863 $fixup->AddFixup($entry, $$tagInfo{DataTag}, $format); 6864 # handle the preview image now if this is a JPEG file 6865 next unless $$self{FILE_TYPE} eq 'JPEG' and $$tagInfo{DataTag} and 6866 $$tagInfo{DataTag} eq 'PreviewImage' and defined $$tagInfo{OffsetPair}; 6867 # NOTE: here we assume there are no var-sized tags between the 6868 # OffsetPair tags. If this ever becomes possible we must recalculate 6869 # $varSize for the OffsetPair tag here! 6870 $entry = $$tagInfo{OffsetPair} * $increment + $varSize; 6871 my $size = ReadValue($dataPt, $entry, $format, 1, $dirLen-$entry); 6872 my $previewInfo = $$self{PREVIEW_INFO}; 6873 $previewInfo or $previewInfo = $$self{PREVIEW_INFO} = { 6874 Fixup => new Image::ExifTool::Fixup, 6875 }; 6876 # set flag indicating we are using short pointers 6877 $$previewInfo{IsShort} = 1 unless $format eq 'int32u'; 6878 $$previewInfo{Absolute} = 1 if $$tagInfo{IsOffset} and $$tagInfo{IsOffset} eq '3'; 6879 # get the value of the Composite::PreviewImage tag 6880 $$previewInfo{Data} = $self->GetNewValue(GetCompositeTagInfo('PreviewImage')); 6881 unless (defined $$previewInfo{Data}) { 6882 if ($offset >= 0 and $offset + $size <= $$dirInfo{DataLen}) { 6883 $$previewInfo{Data} = substr(${$$dirInfo{DataPt}},$offset,$size); 6884 } else { 6885 $$previewInfo{Data} = 'LOAD_PREVIEW'; # flag to load preview later 6886 } 6887 } 6888 } 6889 } 6890 # write any necessary SubDirectories 6891 if ($$tagTablePtr{IS_SUBDIR}) { 6892 $varSize = 0; 6893 @varInfo = @varOffsets; 6894 my $tagID; 6895 foreach $tagID (@{$$tagTablePtr{IS_SUBDIR}}) { 6896 my $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID); 6897 next unless defined $tagInfo; 6898 while (@varInfo and $varInfo[0][0] < $tagID) { 6899 $varSize = $varInfo[0][1]; 6900 shift @varInfo; 6901 } 6902 my $entry = int($tagID) * $increment + $varSize; 6903 last if $entry >= $dirLen; 6904 # get value for Condition if necessary 6905 unless ($tagInfo) { 6906 my $more = $dirLen - $entry; 6907 $more = 128 if $more > 128; 6908 my $v = substr($newData, $entry, $more); 6909 $tagInfo = $self->GetTagInfo($tagTablePtr, $tagID, \$v); 6910 next unless $tagInfo; 6911 } 6912 next unless $$tagInfo{SubDirectory}; # (just to be safe) 6913 my %subdirInfo = ( DataPt => \$newData, DirStart => $entry ); 6914 my $subTablePtr = GetTagTable($$tagInfo{SubDirectory}{TagTable}); 6915 my $dat = $self->WriteDirectory(\%subdirInfo, $subTablePtr); 6916 substr($newData, $entry) = $dat if defined $dat and length $dat; 6917 } 6918 } 6919 return $newData; 6920} 6921 6922#------------------------------------------------------------------------------ 6923# Write TIFF as a directory 6924# Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref 6925# Returns: New directory data or undefined on error 6926sub WriteTIFF($$$) 6927{ 6928 my ($self, $dirInfo, $tagTablePtr) = @_; 6929 $self or return 1; # allow dummy access 6930 my $buff = ''; 6931 $$dirInfo{OutFile} = \$buff; 6932 return $buff if $self->ProcessTIFF($dirInfo, $tagTablePtr) > 0; 6933 return undef; 6934} 6935 69361; # end 6937 6938__END__ 6939 6940=head1 NAME 6941 6942Image::ExifTool::Writer.pl - ExifTool routines for writing meta information 6943 6944=head1 SYNOPSIS 6945 6946These routines are autoloaded by Image::ExifTool when required. 6947 6948=head1 DESCRIPTION 6949 6950This module contains ExifTool write routines and other infrequently 6951used routines. 6952 6953=head1 AUTHOR 6954 6955Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com) 6956 6957This library is free software; you can redistribute it and/or modify it 6958under the same terms as Perl itself. 6959 6960=head1 SEE ALSO 6961 6962L<Image::ExifTool(3pm)|Image::ExifTool> 6963 6964=cut 6965