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