@uswriting/exiftool
Version:
ExifTool powered by WebAssembly to extract and write metadata from files in browsers and Node.js environments using zeroperl
5 lines • 119 kB
JavaScript
var j=`use strict;use warnings;require 5.004;my$version='13.30';$^W=1;my$exePath;BEGIN {$exePath=@ARGV && lc($ARGV[0])eq '-xpath' && shift()? $^X : $0;my$exeDir=($exePath =~ /(.*)[\\\\\\/]/)? $1 : '.';my$incDir=($0 =~ /(.*)[\\\\\\/]/)? "$1/lib" : './lib';if (-l $0){my$lnk=eval {readlink $0};if (defined$lnk){my$lnkDir=($lnk =~ /(.*)[\\\\\\/]/)? $1 : '.';$exeDir=(($lnk =~ m(^/))? '' : $exeDir .'/').$lnkDir;$incDir="$exeDir/lib"}}$Image::ExifTool::exeDir=$exeDir;unshift@INC,$incDir;while (@ARGV and lc($ARGV[0])eq '-config'){shift;push@Image::ExifTool::configFiles,shift}}use Image::ExifTool qw{:Public};sub SigInt();sub SigCont();sub Cleanup();sub GetImageInfo($$);sub SetImageInfo($$$);sub DoHardLink($$$$$);sub CleanXML($);sub EncodeXML($);sub FormatXML($$$);sub EscapeJSON($;$);sub FormatJSON($$$;$);sub PrintCSV(;$);sub AddGroups($$$$);sub ConvertBinary($);sub IsEqual($$);sub Printable($);sub LengthUTF8($);sub Infile($;$);sub AddSetTagsFile($;$);sub Warning($$);sub DoSetFromFile($$$);sub CleanFilename($);sub HasWildcards($);sub SetWindowTitle($);sub ProcessFiles($;$);sub ScanDir($$;$);sub FindFileWindows($$);sub FileNotFound($);sub PreserveTime();sub AbsPath($);sub MyConvertFileName($$);sub SuggestedExtension($$$);sub LoadPrintFormat($;$);sub FilenameSPrintf($;$@);sub NextUnusedFilename($;$);sub CreateDirectory($);sub OpenOutputFile($;@);sub AcceptFile($);sub SlurpFile($$);sub FilterArgfileLine($);sub ReadStayOpen($);sub Progress($$);sub PrintTagList($@);sub PrintErrors($$$);$SIG{INT}='SigInt';$SIG{CONT}='SigCont';END {Cleanup()}my@commonArgs;my@condition;my@csvFiles;my@csvTags;my@delFiles;my@dynamicFiles;my@efile;my@exclude;my (@echo3,@echo4);my@files;my@moreArgs;my@newValues;my@requestTags;my@srcFmt;my@tags;my%altFile;my%appended;my%countLink;my%created;my%csvTags;my%database;my%filterExt;my%ignore;my$ignoreHidden;my%outComma;my%outTrailer;my%preserveTime;my%printFmt;my%seqFileDir;my%setTags;my%setTagsList;my%usedFileName;my%utf8FileName;my%warnedOnce;my%wext;my$allGroup;my$altEnc;my$argFormat;my$binaryOutput;my$binaryStdout;my$binSep;my$binTerm;my$comma;my$count;my$countBad;my$countBadCr;my$countBadWr;my$countCopyWr;my$countDir;my$countFailed;my$countGoodCr;my$countGoodWr;my$countNewDir;my$countSameWr;my$critical;my$csv;my$csvAdd;my$csvDelim;my$csvSaveCount;my$deleteOrig;my$diff;my$disableOutput;my$doSetFileName;my$doUnzip;my ($end,$endDir,%endDir);my$escapeC;my$escapeHTML;my$evalWarning;my$executeID;my$failCondition;my$fastCondition;my$fileHeader;my$fileTrailer;my$filtered;my$filterFlag;my$fixLen;my$forcePrint;my$geoOnly;my$helped;my$html;my$interrupted;my$isBinary;my$isWriting;my$joinLists;my$json;my$langOpt;my$listDir;my$listItem;my$listSep;my$mt;my$multiFile;my$noBinary;my$outFormat;my$outOpt;my$overwriteOrig;my$pause;my$plot;my$preserveTime;my$progress;my$progressCount;my$progressIncr;my$progressMax;my$progressNext;my$progStr;my$quiet;my$rafStdin;my$recurse;my$rtnVal;my$rtnValPrev;my$saveCount;my$scanWritable;my$sectHeader;my$sectTrailer;my$seqFileDir;my$seqFileNum;my$setCharset;my$showGroup;my$showTagID;my$stayOpenBuff='';my$stayOpenFile;my$structOpt;my$tabFormat;my$tagOut;my$textOut;my$textOut2;my$textOverwrite;my$tmpFile;my$tmpText;my$validFile;my$verbose;my$vout;my$windowTitle;my%wroteHEAD;my$xml;my$stayOpen=0;my$rtnValApp=0;my$curTitle='';my$isCRLF={MSWin32=>1,os2=>1,dos=>1 }->{$^O};my%jsonChar=('"'=>'"','\\\\'=>'\\\\',"\\t"=>'t',"\\n"=>'n',"\\r"=>'r');my%escC=("\\n"=>'\\n',"\\r"=>'\\r',"\\t"=>'\\t','\\\\'=>'\\\\\\\\');my%unescC=(a=>"\\a",b=>"\\b",f=>"\\f",n=>"\\n",r=>"\\r",t=>"\\t",0=>"\\0",'\\\\'=>'\\\\');my%optArgs=('-tagsfromfile'=>1,'-addtagsfromfile'=>1,'-alltagsfromfile'=>1,'-@'=>1,'-api'=>1,'-c'=>1,'-coordformat'=>1,'-charset'=>0,'-config'=>1,'-csvdelim'=>1,'-d'=>1,'-dateformat'=>1,'-D'=>0,'-diff'=>1,'-echo'=>1,'-echo#'=>1,'-efile'=>1,'-efile#'=>1,'-efile!'=>1,'-efile#!'=>1,'-ext'=>1,'--ext'=>1,'-ext+'=>1,'--ext+'=>1,'-extension'=>1,'--extension'=>1,'-extension+'=>1,'--extension+'=>1,'-fileorder'=>1,'-fileorder#'=>1,'-file#'=>1,'-geotag'=>1,'-globaltimeshift'=>1,'-i'=>1,'-ignore'=>1,'-if'=>1,'-if#'=>1,'-lang'=>0,'-listitem'=>1,'-o'=>1,'-out'=>1,'-p'=>1,'-printformat'=>1,'-p-'=>1,'-printformat-'=>1,'-P'=>0,'-password'=>1,'-require'=>1,'-sep'=>1,'-separator'=>1,'-srcfile'=>1,'-stay_open'=>1,'-use'=>1,'-userparam'=>1,'-w'=>1,'-w!'=>1,'-w+'=>1,'-w+!'=>1,'-w!+'=>1,'-textout'=>1,'-textout!'=>1,'-textout+'=>1,'-textout+!'=>1,'-textout!+'=>1,'-tagout'=>1,'-tagout!'=>1,'-tagout+'=>1,'-tagout+!'=>1,'-tagout!+'=>1,'-wext'=>1,'-wm'=>1,'-writemode'=>1,'-x'=>1,'-exclude'=>1,'-X'=>0,);my@recommends=qw(Archive::Zip Compress::Zlib Digest::MD5 Digest::SHA IO::Compress::Bzip2 POSIX::strptime Time::Local Unicode::LineBreak Compress::Raw::Lzma IO::Compress::RawDeflate IO::Uncompress::RawInflate IO::Compress::Brotli IO::Uncompress::Brotli Win32::API Win32::FindFile Win32API::File);my%altRecommends=('POSIX::strptime'=>'Time::Piece',);my%unescapeChar=('t'=>"\\t",'n'=>"\\n",'r'=>"\\r");sub Image::ExifTool::EndDir() {return$endDir=1}sub Image::ExifTool::End() {return$end=1}sub Exit {if ($pause){if (eval {require Term::ReadKey}){print STDERR "-- press any key --";Term::ReadKey::ReadMode('cbreak');Term::ReadKey::ReadKey(0);Term::ReadKey::ReadMode(0);print STDERR "\\b \\b" x 20}else {print STDERR "-- press RETURN --\\n";<STDIN>}}exit shift}sub Warn {if ($quiet < 2 or $_[0]=~ /^Error/){my$oldWarn=$SIG{'__WARN__'};delete$SIG{'__WARN__'};warn(@_);$SIG{'__WARN__'}=$oldWarn if defined$oldWarn}}sub Error {Warn @_;$rtnVal=1}sub WarnOnce($) {Warn(@_)and $warnedOnce{$_[0]}=1 unless$warnedOnce{$_[0]}}sub SigInt() {$critical and $interrupted=1,return;Cleanup();exit 1}sub SigCont() {}sub Cleanup() {$mt->Unlink($tmpFile)if defined$tmpFile;$mt->Unlink($tmpText)if defined$tmpText;undef$tmpFile;undef$tmpText;PreserveTime()if%preserveTime;SetWindowTitle('')}if (grep /^-common_args$/i,@ARGV){my (@newArgs,$common,$end);for (@ARGV){if (/^-common_args$/i and not $end){$common=1}elsif ($common){push@commonArgs,$_}else {$end=1 if $_ eq '--';push@newArgs,$_}}@ARGV=@newArgs if$common}Command: for (;;){if (@echo3){my$str=join("\\n",@echo3)."\\n";$str =~ s/\\$\\{status\\}/$rtnVal/ig;print STDOUT$str}if (@echo4){my$str=join("\\n",@echo4)."\\n";$str =~ s/\\$\\{status\\}/$rtnVal/ig;print STDERR$str}$rafStdin->Close()if$rafStdin;undef$rafStdin;$rtnValPrev=$rtnVal;$rtnValApp=$rtnVal if$rtnVal;last unless@ARGV or not defined$rtnVal or $stayOpen >= 2 or @commonArgs;if ($binaryStdout){binmode(STDOUT,':crlf')if $] >= 5.006 and $isCRLF;$binaryStdout=0}if ($stayOpen >= 2){if ($quiet and not defined$executeID){eval {require IO::Handle}and STDERR->flush(),STDOUT->flush()}else {eval {require IO::Handle}and STDERR->flush();my$id=defined$executeID ? $executeID : '';my$save=$|;$|=1;print "{ready$id}\\n";$|=$save}}undef@condition;undef@csvFiles;undef@csvTags;undef@delFiles;undef@dynamicFiles;undef@echo3;undef@echo4;undef@efile;undef@exclude;undef@files;undef@newValues;undef@srcFmt;undef@tags;undef%appended;undef%countLink;undef%created;undef%csvTags;undef%database;undef%endDir;undef%filterExt;undef%ignore;undef%outComma;undef%outTrailer;undef%preserveTime;undef%printFmt;undef%seqFileDir;undef%setTags;undef%setTagsList;undef%usedFileName;undef%utf8FileName;undef%warnedOnce;undef%wext;undef$allGroup;undef$altEnc;undef$argFormat;undef$binaryOutput;undef$binSep;undef$binTerm;undef$comma;undef$csv;undef$csvAdd;undef$deleteOrig;undef$diff;undef$disableOutput;undef$doSetFileName;undef$doUnzip;undef$end;undef$endDir;undef$escapeHTML;undef$escapeC;undef$evalWarning;undef$executeID;undef$failCondition;undef$fastCondition;undef$fileHeader;undef$filtered;undef$fixLen;undef$forcePrint;undef$geoOnly;undef$ignoreHidden;undef$joinLists;undef$langOpt;undef$listItem;undef$multiFile;undef$noBinary;undef$outOpt;undef$preserveTime;undef$progress;undef$progressCount;undef$progressIncr;undef$progressMax;undef$progressNext;undef$recurse;undef$scanWritable;undef$sectHeader;undef$setCharset;undef$showGroup;undef$showTagID;undef$structOpt;undef$tagOut;undef$textOut;undef$textOverwrite;undef$tmpFile;undef$tmpText;undef$validFile;undef$verbose;undef$windowTitle;$count=0;$countBad=0;$countBadCr=0;$countBadWr=0;$countCopyWr=0;$countDir=0;$countFailed=0;$countGoodCr=0;$countGoodWr=0;$countNewDir=0;$countSameWr=0;$csvDelim=',';$csvSaveCount=0;$fileTrailer='';$filterFlag=0;$html=0;$isWriting=0;$json=0;$listSep=', ';$outFormat=0;$overwriteOrig=0;$progStr='';$quiet=0;$rtnVal=0;$saveCount=0;$sectTrailer='';$seqFileDir=0;$seqFileNum=0;$tabFormat=0;$vout=\\*STDOUT;$xml=0;my@fileOrder;my$fileOrderFast;my$addGeotime;my$doGlob;my$endOfOpts;my$escapeXML;my$setTagsFile;my$sortOpt;my$srcStdin;my$useMWG;my ($argsLeft,@nextPass,$badCmd);my$pass=0;if ($^O eq 'MSWin32' and eval {require File::Glob}){import File::Glob qw(:globally :nocase);$doGlob=1}$mt=Image::ExifTool->new;$mt->Options(Duplicates=>0)unless%Image::ExifTool::UserDefined::Options and defined$Image::ExifTool::UserDefined::Options{Duplicates};$joinLists=1 if defined$mt->Options('List')and not $mt->Options('List');if (not $preserveTime and $^O eq 'MSWin32'){$preserveTime=2 if eval {require Win32::API}and eval {require Win32API::File}}if (@Image::ExifTool::UserDefined::Arguments){unshift@ARGV,@Image::ExifTool::UserDefined::Arguments}if ($version ne $Image::ExifTool::VERSION){Warn "Application version $version does not match Image::ExifTool library version $Image::ExifTool::VERSION\\n"}for (;;){if (not @ARGV or ($ARGV[0]=~ /^(-|\\xe2\\x88\\x92)execute(\\d+)?$/i and not $endOfOpts)){if (@ARGV){$executeID=$2;$helped=1;$badCmd and shift,$rtnVal=1,next Command}elsif ($stayOpen >= 2){ReadStayOpen(\\@ARGV);next}elsif ($badCmd){undef@commonArgs;$rtnVal=1;next Command}if ($pass==0){if (@commonArgs and not defined$argsLeft){$argsLeft=scalar(@ARGV)+ scalar(@moreArgs);unshift@ARGV,@commonArgs;undef@commonArgs unless$argsLeft;next}if (defined$argsLeft and $argsLeft < scalar(@ARGV)+ scalar(@moreArgs)){Warn "Ignoring -common_args from $ARGV[0] onwards to avoid infinite recursion\\n";while ($argsLeft < scalar(@ARGV)+ scalar(@moreArgs)){@ARGV and shift(@ARGV),next;shift@moreArgs}}$useMWG=1 if not $useMWG and grep /^([--_0-9A-Z]+:)*1?mwg:/i,@tags,@requestTags;if ($useMWG){require Image::ExifTool::MWG;Image::ExifTool::MWG::Load()}if (defined$forcePrint){unless (defined$mt->Options('MissingTagValue')){$mt->Options(MissingTagValue=>'-')}$forcePrint=$mt->Options('MissingTagValue')}}if (@nextPass){unshift@ARGV,@nextPass;undef@nextPass;undef$endOfOpts;++$pass;next}@ARGV and shift;last}$_=shift;next if$badCmd;if (not $endOfOpts and s/^(-|\\xe2\\x88\\x92)//){s/^\\xe2\\x88\\x92/-/;if ($_ eq '-'){$pass or push@nextPass,'--';$endOfOpts=1;next}my$a=lc $_;if (/^list([wfrdx]|wf|g(\\d*)|geo)?$/i){$pass or push@nextPass,"-$_";my$type=lc($1 || '');if (not $type or $type eq 'w' or $type eq 'x'){my$group;if ($ARGV[0]and $ARGV[0]=~ /^(-|\\xe2\\x88\\x92)(.+):(all|\\*)$/i){if ($pass==0){$useMWG=1 if lc($2)eq 'mwg';push@nextPass,shift;next}$group=$2;shift;$group =~ /IFD/i and Warn("Can't list tags for specific IFD\\n"),$helped=1,next;$group =~ /^(all|\\*)$/ and undef$group}else {$pass or next}$helped=1;if ($type eq 'x'){require Image::ExifTool::TagInfoXML;my%opts;$opts{Flags}=1 if defined$forcePrint;$opts{NoDesc}=1 if$outFormat > 0;$opts{Lang}=$langOpt;Image::ExifTool::TagInfoXML::Write(undef,$group,%opts);next}my$wr=($type eq 'w');my$msg=($wr ? 'Writable' : 'Available').($group ? " $group" : '').' tags';PrintTagList($msg,$wr ? GetWritableTags($group): GetAllTags($group));next if$group or $wr;my@tagList=GetShortcuts();PrintTagList('Command-line shortcuts',@tagList)if@tagList;next}$pass or next;$helped=1;if ($type eq 'wf'){my@wf;CanWrite($_)and push@wf,$_ foreach GetFileType();PrintTagList('Writable file extensions',@wf)}elsif ($type eq 'f'){PrintTagList('Supported file extensions',GetFileType())}elsif ($type eq 'r'){PrintTagList('Recognized file extensions',GetFileType(undef,0))}elsif ($type eq 'd'){PrintTagList('Deletable groups',GetDeleteGroups())}elsif ($type eq 'geo'){require Image::ExifTool::Geolocation;my ($i,$entry);print "Geolocation database:\\n" unless$quiet;my$isAlt=$mt->Options('GeolocAltNames')? ',AltNames' : '';$isAlt='' if$isAlt and not Image::ExifTool::Geolocation::ReadAltNames();print "City,Region,Subregion,CountryCode,Country,TimeZone,FeatureCode,Population,Latitude,Longitude$isAlt\\n";Image::ExifTool::Geolocation::SortDatabase('City')if$sortOpt;my$minPop=$mt->Options('GeolocMinPop');my$feature=$mt->Options('GeolocFeature')|| '';my$neg=$feature =~ s/^-//;my%fcodes=map {lc($_)=>1}split /\\s*,\\s*/,$feature;my@isUTF8=(0,1,2,4);push@isUTF8,10 if$isAlt;for ($i=0;;++$i){my@entry=Image::ExifTool::Geolocation::GetEntry($i,$langOpt,1)or last;$#entry=9;next if$minPop and $entry[7]< $minPop;next if%fcodes and $neg ? $fcodes{lc$entry[6]}: not $fcodes{lc$entry[6]};push@entry,Image::ExifTool::Geolocation::GetAltNames($i,1)if$isAlt;$_=defined $_ ? $mt->Decode($_,'UTF8'): '' foreach@entry[@isUTF8];pop@entry if$isAlt and not $entry[10];print join(',',@entry),"\\n"}}else {my$family=$2 || 0;PrintTagList("Groups in family $family",$mt->GetAllGroups($family))}next}if ($a eq 'ver'){$pass or push(@nextPass,'-ver'),next;my$libVer=$Image::ExifTool::VERSION;my$str=$libVer eq $version ? '' : " [Warning: Library version is $libVer]";if ($verbose){print "ExifTool version $version$str$Image::ExifTool::RELEASE\\n";printf "Perl version %s%s\\n",$],(defined \${^UNICODE} ? " (-C\${^UNICODE})" : '');print "Platform: $^O\\n";if ($verbose > 8){print "Current Dir: " .Cwd::getcwd()."\\n" if (eval {require Cwd});print "Script Name: $0\\n";print "Exe Name: $^X\\n";print "Exe Dir: $Image::ExifTool::exeDir\\n";print "Exe Path: $exePath\\n"}print "Optional libraries:\\n";for (@recommends){next if /^Win32/ and $^O ne 'MSWin32';my$ver=eval "require $_ and \\$\${_}::VERSION";my$alt=$altRecommends{$_};$ver=eval "require $alt and \\$\${alt}::VERSION" and $_=$alt if not $ver and $alt;printf " %-28s %s\\n",$_,$ver || '(not installed)'}if ($verbose > 1){print "Include directories:\\n";ref $_ or print " $_\\n" foreach@INC}}else {print "$version$str$Image::ExifTool::RELEASE\\n"}$helped=1;next}if (/^(all|add)?tagsfromfile(=.*)?$/i){$setTagsFile=$2 ? substr($2,1): (@ARGV ? shift : '');if ($setTagsFile eq ''){Error("File must be specified for -tagsFromFile option\\n");$badCmd=1;next}AddSetTagsFile($setTagsFile,{Replace=>($1 and lc($1)eq 'add')? 0 : 1 });next}if ($a eq '@'){my$argFile=shift or Error("Expecting filename for -\\@ option\\n"),$badCmd=1,next;if ($stayOpen==1){@moreArgs=@ARGV;undef@ARGV}elsif ($stayOpen==3){if ($stayOpenFile and $stayOpenFile ne '-' and $argFile eq $stayOpenFile){$stayOpen=2;Warn "Ignoring request to switch to the same -stay_open ARGFILE ($argFile)\\n";next}close STAYOPEN;$stayOpen=1}my$fp=($stayOpen==1 ? \\*STAYOPEN : \\*ARGFILE);unless ($mt->Open($fp,$argFile)){unless ($argFile !~ /^\\// and $mt->Open($fp,"$Image::ExifTool::exeDir/$argFile")){Error "Error opening arg file $argFile\\n";$badCmd=1;next}}if ($stayOpen==1){$stayOpenFile=$argFile;$stayOpenBuff='';$stayOpen=2;$helped=1;ReadStayOpen(\\@ARGV);next}my (@newArgs,$didBOM);for (<ARGFILE>){unless ($didBOM){s/^\\xef\\xbb\\xbf//;$didBOM=1}$_=FilterArgfileLine($_);push@newArgs,$_ if defined $_}close ARGFILE;unshift@ARGV,@newArgs;next}/^(-?)(a|duplicates)$/i and $mt->Options(Duplicates=>($1 ? 0 : 1)),next;if ($a eq 'api'){my$opt=shift;if (defined$opt and length$opt){my$val=($opt =~ s/=(.*)//s)? $1 : 1;$val=undef unless$opt =~ s/\\^$// or length$val;$mt->Options($opt=>$val)}else {print "Available API Options:\\n";my$availableOptions=Image::ExifTool::AvailableOptions();$$_[3]or printf(" %-17s - %s\\n",$$_[0],$$_[2])foreach @$availableOptions;$helped=1}next}/^arg(s|format)$/i and $argFormat=1,next;if (/^(-?)b(inary)?$/i){($binaryOutput,$noBinary)=$1 ? (undef,1): (1,undef);$mt->Options(Binary=>$binaryOutput,NoPDFList=>$binaryOutput);next}if (/^c(oordFormat)?$/i){my$fmt=shift;$fmt or Error("Expecting coordinate format for -c option\\n"),$badCmd=1,next;$mt->Options('CoordFormat',$fmt);next}if ($a eq 'charset'){my$charset=(@ARGV and $ARGV[0]!~ /^(-|\\xe2\\x88\\x92)/)? shift : undef;if (not $charset){$pass or push(@nextPass,'-charset'),next;my%charsets;$charsets{$_}=1 foreach values%Image::ExifTool::charsetName;PrintTagList('Available character sets',sort keys%charsets);$helped=1}elsif ($charset !~ s/^(\\w+)=// or lc($1)eq 'exiftool'){{local$SIG{'__WARN__'}=sub {$evalWarning=$_[0]};undef$evalWarning;$mt->Options(Charset=>$charset)}if ($evalWarning){Warn$evalWarning}else {$setCharset=$mt->Options('Charset')}}else {my$type={id3=>'ID3',iptc=>'IPTC',exif=>'EXIF',filename=>'FileName',photoshop=>'Photoshop',quicktime=>'QuickTime',riff=>'RIFF' }->{lc $1};$type or Warn("Unknown type for -charset option: $1\\n"),next;$mt->Options("Charset$type"=>$charset)}next}/^config$/i and Warn("Ignored -config option (not first on command line)\\n"),shift,next;if (/^csv(\\+?=.*)?$/i){my$csvFile=$1;unless ($pass){push@nextPass,"-$_";if ($csvFile){push@newValues,{SaveCount=>++$saveCount };$csvSaveCount=$saveCount}next}if ($csvFile){$csvFile =~ s/^(\\+?=)//;$csvAdd=2 if $1 eq '+=';$vout=\\*STDERR if$srcStdin;$verbose and print$vout "Reading CSV file $csvFile\\n";my$msg;if ($mt->Open(\\*CSVFILE,$csvFile)){binmode CSVFILE;require Image::ExifTool::Import;$msg=Image::ExifTool::Import::ReadCSV(\\*CSVFILE,\\%database,$forcePrint,$csvDelim);close(CSVFILE)}else {$msg="Error opening CSV file '\${csvFile}'"}$msg and Warn("$msg\\n");$isWriting=1}$csv='CSV';next}if (/^csvdelim$/i){$csvDelim=shift;defined$csvDelim or Error("Expecting argument for -csvDelim option\\n"),$badCmd=1,next;$csvDelim =~ /"/ and Error("CSV delimiter can not contain a double quote\\n"),$badCmd=1,next;my%unescape=('t'=>"\\t",'n'=>"\\n",'r'=>"\\r",'\\\\'=>'\\\\');$csvDelim =~ s/\\\\(.)/$unescape{$1}||"\\\\$1"/sge;$mt->Options(CSVDelim=>$csvDelim);next}if (/^d$/ or $a eq 'dateformat'){my$fmt=shift;$fmt or Error("Expecting date format for -d option\\n"),$badCmd=1,next;$mt->Options('DateFormat',$fmt);next}(/^D$/ or $a eq 'decimal')and $showTagID='D',next;if (/^diff$/i){$diff=shift;defined$diff or Error("Expecting file name for -$_ option\\n"),$badCmd=1;next}/^delete_original(!?)$/i and $deleteOrig=($1 ? 2 : 1),next;/^list_dir$/i and $listDir=1,next;(/^e$/ or $a eq '-composite')and $mt->Options(Composite=>0),next;(/^-e$/ or $a eq 'composite')and $mt->Options(Composite=>1),next;(/^E$/ or $a eq 'escapehtml')and require Image::ExifTool::HTML and $escapeHTML=1,next;($a eq 'ec' or $a eq 'escapec')and $escapeC=1,next;($a eq 'ex' or $a eq 'escapexml')and $escapeXML=1,next;if (/^echo(\\d)?$/i){my$n=$1 || 1;my$arg=shift;next unless defined$arg;$n > 4 and Warn("Invalid -echo number\\n"),next;if ($n > 2){$n==3 ? push(@echo3,$arg): push(@echo4,$arg)}else {print {$n==2 ? \\*STDERR : \\*STDOUT}$arg,"\\n"}$helped=1;next}if (/^(ee|extractembedded)(\\d*)$/i){$mt->Options(ExtractEmbedded=>$2 || 1);$mt->Options(Duplicates=>1);next}if (/^efile(\\d+)?(!)?$/i){my$arg=shift;defined$arg or Error("Expecting file name for -$_ option\\n"),$badCmd=1,next;$efile[0]=$arg if not $1 or $1 & 0x01;$efile[1]=$arg if $1 and $1 & 0x02;$efile[2]=$arg if $1 and $1 & 0x04;$efile[3]=$arg if $1 and $1 & 0x08;$efile[4]=$arg if $1 and $1 & 0x016;unlink$arg if $2;next}if (/^-?ext(ension)?(\\+)?$/i){my$ext=shift;defined$ext or Error("Expecting extension for -ext option\\n"),$badCmd=1,next;my$flag=/^-/ ? 0 : ($2 ? 2 : 1);$filterFlag |= (0x01 << $flag);$ext =~ s/^\\.//;$filterExt{uc($ext)}=$flag ? 1 : 0;next}if (/^f$/ or $a eq 'forceprint'){$forcePrint=1;next}if (/^F([-+]?\\d*)$/ or /^fixbase([-+]?\\d*)$/i){$mt->Options(FixBase=>$1);next}if (/^fast(\\d*)$/i){$mt->Options(FastScan=>(length $1 ? $1 : 1));next}if (/^(file\\d+)$/i){$altFile{lc $1}=shift or Error("Expecting file name for -file option\\n"),$badCmd=1,next;next}if (/^fileorder(\\d*)$/i){push@fileOrder,shift if@ARGV;my$num=$1 || 0;$fileOrderFast=$num if not defined$fileOrderFast or $fileOrderFast > $num;next}$a eq 'globaltimeshift' and $mt->Options(GlobalTimeShift=>shift),next;if (/^(g)(roupHeadings|roupNames)?([\\d:]*)$/i){$showGroup=$3 || 0;$allGroup=($2 ? lc($2)eq 'roupnames' : $1 eq 'G');$mt->Options(SavePath=>1)if$showGroup =~ /\\b5\\b/;$mt->Options(SaveFormat=>1)if$showGroup =~ /\\b6\\b/;next}if ($a eq 'geotag'){my$trkfile=shift;unless ($pass){push@nextPass,'-geotag',$trkfile;next}$trkfile or Error("Expecting file name for -geotag option\\n"),$badCmd=1,next;if (HasWildcards($trkfile)){my@trks;if ($^O eq 'MSWin32' and eval {require Win32::FindFile}){@trks=FindFileWindows($mt,$trkfile)}elsif (eval {require File::Glob}){@trks=File::Glob::bsd_glob($trkfile)}else {@trks=glob($trkfile)}@trks or Error("No matching file found for -geotag option\\n"),$badCmd=1,next;push@newValues,'geotag='.shift(@trks)while@trks > 1;$trkfile=pop(@trks)}$_="geotag=$trkfile"}if (/^h$/ or $a eq 'htmlformat'){require Image::ExifTool::HTML;$html=$escapeHTML=1;$json=$xml=0;next}(/^H$/ or $a eq 'hex')and $showTagID='H',next;if (/^htmldump([-+]?\\d+)?$/i){$verbose=($verbose || 0)+ 1;$html=2;$mt->Options(HtmlDumpBase=>$1)if defined $1;next}if (/^i(gnore)?$/i){my$dir=shift;defined$dir or Error("Expecting directory name for -i option\\n"),$badCmd=1,next;$ignore{$dir}=1;$dir eq 'HIDDEN' and $ignoreHidden=1;next}if (/^if(\\d*)$/i){my$cond=shift;my$fast=length($1)? $1 : undef;defined$cond or Error("Expecting expression for -if option\\n"),$badCmd=1,next;if (not @condition or not defined$fast or (defined$fastCondition and $fastCondition > $fast)){$fastCondition=$fast}$cond =~ /^\\s*(not\\s*)\\$ok\\s*$/i and ($1 xor $rtnValPrev)and $failCondition=1;push@requestTags,$cond =~ /\\$\\{?((?:[-_0-9A-Z]+:)*[-_0-9A-Z?*]+)/ig;push@condition,$cond;next}if (/^j(son)?(\\+?=.*)?$/i){if ($2){unless ($pass){push@nextPass,"-$_";push@newValues,{SaveCount=>++$saveCount };$csvSaveCount=$saveCount;next}my$jsonFile=$2;$jsonFile =~ s/^(\\+?=)//;$csvAdd=2 if $1 eq '+=';$vout=\\*STDERR if$srcStdin;$verbose and print$vout "Reading JSON file $jsonFile\\n";my$chset=$mt->Options('Charset');my$msg;if ($mt->Open(\\*JSONFILE,$jsonFile)){binmode JSONFILE;require Image::ExifTool::Import;$msg=Image::ExifTool::Import::ReadJSON(\\*JSONFILE,\\%database,$forcePrint,$chset);close(JSONFILE)}else {$msg="Error opening JSON file '\${jsonFile}'"}$msg and Warn("$msg\\n");$isWriting=1;$csv='JSON'}else {$json=1;$html=$xml=0;$mt->Options(Duplicates=>1);require Image::ExifTool::XMP}next}/^(k|pause)$/i and $pause=1,next;(/^l$/ or $a eq 'long')and --$outFormat,next;(/^L$/ or $a eq 'latin')and $mt->Options(Charset=>'Latin'),next;if ($a eq 'lang'){$langOpt=(@ARGV and $ARGV[0]!~ /^(-|\\xe2\\x88\\x92)/)? shift : undef;if ($langOpt){$langOpt =~ tr/-A-Z/_a-z/;$mt->Options(Lang=>$langOpt);next if$langOpt eq $mt->Options('Lang')}else {$pass or push(@nextPass,'-lang'),next}my$langs=$quiet ? '' : "Available languages:\\n";$langs .= " $_ - $Image::ExifTool::langName{$_}\\n" foreach@Image::ExifTool::langs;$langs =~ tr/_/-/;$langs=Image::ExifTool::HTML::EscapeHTML($langs)if$escapeHTML;$langs=$mt->Decode($langs,'UTF8');$langOpt and Error("Invalid or unsupported language '\${langOpt}'.\\n$langs"),$badCmd=1,next;print$langs;$helped=1;next}if ($a eq 'listitem'){my$li=shift;defined$li and Image::ExifTool::IsInt($li)or Warn("Expecting integer for -listItem option\\n"),next;$mt->Options(ListItem=>$li);$listItem=$li;next}/^(m|ignoreminorerrors)$/i and $mt->Options(IgnoreMinorErrors=>1),next;/^(n|-printconv)$/i and $mt->Options(PrintConv=>0),next;/^(-n|printconv)$/i and $mt->Options(PrintConv=>1),next;$a eq 'nop' and $helped=1,next;if (/^o(ut)?$/i){$outOpt=shift;defined$outOpt or Error("Expected output file or directory name for -o option\\n"),$badCmd=1,next;CleanFilename($outOpt);$vout=\\*STDERR if$vout =~ /^-(\\.\\w+)?$/;next}/^overwrite_original$/i and $overwriteOrig=1,next;/^overwrite_original_in_place$/i and $overwriteOrig=2,next;/^plot$/i and require Image::ExifTool::Plot and $plot=Image::ExifTool::Plot->new,next;if (/^p(-?)$/ or /^printformat(-?)$/i){my$fmt=shift;if ($pass){LoadPrintFormat($fmt,$1 || $binaryOutput);if (not $useMWG and grep /^([-_0-9A-Z]+:)*1?mwg:/i,@requestTags){$useMWG=1;require Image::ExifTool::MWG;Image::ExifTool::MWG::Load()}}else {push@nextPass,"-$_",$fmt}next}(/^P$/ or $a eq 'preserve')and $preserveTime=1,next;/^password$/i and $mt->Options(Password=>shift),next;if (/^progress(\\d*)(:.*)?$/i){$progressIncr=$1 || 1;$progressNext=0;if ($2){$windowTitle=substr $2,1;$windowTitle='ExifTool %p%%' unless length$windowTitle;$windowTitle =~ /%\\d*[bpr]/ and $progress=0 unless defined$progress}else {$progress=1;$verbose=0 unless defined$verbose}$progressCount=0;next}/^q(uiet)?$/i and ++$quiet,next;/^r(ecurse)?(\\.?)$/i and $recurse=($2 ? 2 : 1),next;if ($a eq 'require'){my$ver=shift;unless (defined$ver and Image::ExifTool::IsFloat($ver)){Error("Expecting version number for -require option\\n");$badCmd=1;next}unless ($Image::ExifTool::VERSION >= $ver){Error("Requires ExifTool version $ver or later\\n");$badCmd=1}next}/^restore_original$/i and $deleteOrig=0,next;(/^S$/ or $a eq 'veryshort')and $outFormat+=2,next;/^s(hort)?(\\d*)$/i and $outFormat=$2 eq '' ? $outFormat + 1 : $2,next;/^scanforxmp$/i and $mt->Options(ScanForXMP=>1),next;if (/^sep(arator)?$/i){my$sep=$listSep=shift;defined$listSep or Error("Expecting list item separator for -sep option\\n"),$badCmd=1,next;$sep =~ s/\\\\(.)/$unescapeChar{$1}||$1/sge;(defined$binSep ? $binTerm : $binSep)=$sep;$mt->Options(ListSep=>$listSep);$joinLists=1;my$listSplit=quotemeta$listSep;$listSplit =~ s/(\\\\ )+/\\\\s\\*/g;$listSplit='\\\\s+' if$listSplit eq '\\\\s*';$mt->Options(ListSplit=>$listSplit);next}/^(-)?sort$/i and $sortOpt=$1 ? 0 : 1,next;if ($a eq 'srcfile'){@ARGV or Warn("Expecting FMT for -srcfile option\\n"),next;push@srcFmt,shift;next}if ($a eq 'stay_open'){my$arg=shift;defined$arg or Warn("Expecting argument for -stay_open option\\n"),next;if ($arg =~ /^(1|true)$/i){if (not $stayOpen){$stayOpen=1}elsif ($stayOpen==2){$stayOpen=3}else {Warn "-stay_open already active\\n"}}elsif ($arg =~ /^(0|false)$/i){if ($stayOpen >= 2){close STAYOPEN;push@ARGV,@moreArgs;undef@moreArgs}elsif (not $stayOpen){Warn("-stay_open wasn't active\\n")}$stayOpen=0}else {Warn "Invalid argument for -stay_open\\n"}next}if (/^(-)?struct$/i){$mt->Options(Struct=>$1 ? 0 : 1);next}/^t(ab)?$/ and $tabFormat=1,next;if (/^T$/ or $a eq 'table'){$tabFormat=$forcePrint=1;$outFormat+=2;++$quiet;next}if (/^(u)(nknown(2)?)?$/i){my$inc=($3 or (not $2 and $1 eq 'U'))? 2 : 1;$mt->Options(Unknown=>$mt->Options('Unknown')+ $inc);next}if ($a eq 'use'){my$module=shift;$module or Error("Expecting module name for -use option\\n"),$badCmd=1,next;lc$module eq 'mwg' and $useMWG=1,next;$module =~ /[^\\w:]/ and Error("Invalid module name: $module\\n"),$badCmd=1,next;local$SIG{'__WARN__'}=sub {$evalWarning=$_[0]};unless (eval "require Image::ExifTool::$module" or eval "require $module" or eval "require '\${module}'"){Error("Error using module $module\\n");$badCmd=1}next}if ($a eq 'userparam'){my$opt=shift;defined$opt or Error("Expected parameter for -userParam option\\n"),$badCmd=1,next;$opt =~ /=/ or $opt .= '=1';$mt->Options(UserParam=>$opt);next}if (/^v(erbose)?(\\d*)$/i){$verbose=($2 eq '')? ($verbose || 0)+ 1 : $2;next}if (/^(w|textout|tagout)([!+]*)$/i){$textOut=shift || Warn("Expecting argument for -$_ option\\n");my ($t1,$t2)=($1,$2);$textOverwrite=0;$textOverwrite += 1 if$t2 =~ /!/;$textOverwrite += 2 if$t2 =~ /\\+/;if ($t1 ne 'W' and lc($t1)ne 'tagout'){undef$tagOut}elsif ($textOverwrite >= 2 and $textOut !~ /%[-+]?\\d*[.:]?\\d*[lu]?[tgso]/){$tagOut=0}else {$tagOut=1}next}if (/^(-?)(wext|tagoutext)$/i){my$ext=shift;defined$ext or Error("Expecting extension for -wext option\\n"),$badCmd=1,next;my$flag=1;$1 and $wext{'*'}=1,$flag=-1;$ext =~ s/^\\.//;$wext{lc$ext}=$flag;next}if ($a eq 'wm' or $a eq 'writemode'){my$wm=shift;defined$wm or Error("Expecting argument for -$_ option\\n"),$badCmd=1,next;$wm =~ /^[wcg]*$/i or Error("Invalid argument for -$_ option\\n"),$badCmd=1,next;$mt->Options(WriteMode=>$wm);next}if (/^x$/ or $a eq 'exclude'){my$tag=shift;defined$tag or Error("Expecting tag name for -x option\\n"),$badCmd=1,next;$tag =~ s/\\ball\\b/\\*/ig;if ($setTagsFile){push @{$setTags{$setTagsFile}},"-$tag"}else {push@exclude,$tag}next}(/^X$/ or $a eq 'xmlformat')and $xml=1,$html=$json=0,$mt->Options(Duplicates=>1),next;if (/^php$/i){$json=2;$html=$xml=0;$mt->Options(Duplicates=>1);next}if (/^z(ip)?$/i){$doUnzip=1;$mt->Options(Compress=>1,XMPShorthand=>1);$mt->Options(Compact=>1)unless$mt->Options('Compact');next}$_ eq '' and push(@files,'-'),$srcStdin=1,next;length $_ eq 1 and $_ ne '*' and Error("Unknown option -$_\\n"),$badCmd=1,next;if (/^[^<]+(<?)=(.*)/s){my$val=$2;if ($1 and length($val)and ($val eq '@' or not defined FilenameSPrintf($val))){push@newValues,{SaveCount=>++$saveCount }}push@newValues,$_;if (/^([-_0-9A-Z]+:)*1?mwg:/i){$useMWG=1}elsif (/^([-_0-9A-Z]+:)*(filename|directory|testname)\\b/i){$doSetFileName=1}elsif (/^([-_0-9A-Z]+:)*(geotag|geotime|geosync|geolocate)\\b/i){if (lc $2 eq 'geotime'){$addGeotime=''}else {unshift@newValues,pop@newValues;if (lc $2 eq 'geotag' and (not defined$addGeotime or $addGeotime)and length$val){$addGeotime=($1 || '').q[Geotime<\${DateTimeOriginal#;$_=$self->GetValue('SubSecDateTimeOriginal','ValueConv') || $_}]}}}}else {AddSetTagsFile($setTagsFile='@')if not $setTagsFile and /(<|>)/;if ($setTagsFile){push @{$setTags{$setTagsFile}},$_;if ($1 eq '>'){$useMWG=1 if /^(.*>\\s*)?([-_0-9A-Z]+:)*1?mwg:/si;if (/\\b(filename|directory|testname)#?$/i){$doSetFileName=1}elsif (/\\bgeotime#?$/i){$addGeotime=''}}else {$useMWG=1 if /^([^<]+<\\s*(.*\\$\\{?)?)?([-_0-9A-Z]+:)*1?mwg:/si;if (/^([-_0-9A-Z]+:)*(filename|directory|testname)\\b/i){$doSetFileName=1}elsif (/^([-_0-9A-Z]+:)*geotime\\b/i){$addGeotime=''}}}else {my$lst=s/^-// ? \\@exclude : \\@tags;Warn(qq(Invalid TAG name: "$_"\\n))unless /^([-_0-9A-Z*]+:)*([-_0-9A-Z*?]+)#?$/i;push @$lst,$_}}}else {unless ($pass){push@nextPass,$_;next}if ($doGlob and HasWildcards($_)){if ($^O eq 'MSWin32' and eval {require Win32::FindFile}){push@files,FindFileWindows($mt,$_)}else {push@files,File::Glob::bsd_glob($_)}$doGlob=2}else {push@files,$_;$srcStdin=1 if $_ eq '-'}}}$mt->Options(UserParam=>'OK=' .(not $rtnValPrev));$vout=\\*STDERR if$srcStdin and ($isWriting or @newValues);$mt->Options(TextOut=>$vout)if$vout eq \\*STDERR;if ($useMWG and not defined$mt->Options('CharsetEXIF')){$mt->Options(CharsetEXIF=>'UTF8')}if (not @files and not $outOpt and not @newValues){my$loc=$mt->Options('Geolocation');$loc and $loc ne '1' and push(@files,qq(\\@JSON:{})),$geoOnly=1}unless ((@tags and not $outOpt)or @files or @newValues or $geoOnly){if ($doGlob and $doGlob==2){Error "No matching files\\n";next}$outOpt and Error("Nothing to write\\n"),next;unless ($helped){local$SIG{'__WARN__'}=sub {$evalWarning=$_[0]};my$dummy=\\*SAVEERR;unless ($^O eq 'os2'){open SAVEERR,">&STDERR";open STDERR,'>/dev/null'}if (system('perldoc',$0)){print "Syntax: exiftool [OPTIONS] FILE\\n\\n";print "Consult the exiftool documentation for a full list of options.\\n"}unless ($^O eq 'os2'){close STDERR;open STDERR,'>&SAVEERR'}}next}if (defined$deleteOrig and (@newValues or @tags)){if (not @newValues){my$verb=$deleteOrig ? 'deleting' : 'restoring from';Error "Can't specify tags when $verb originals\\n"}elsif ($deleteOrig){Error "Can't use -delete_original when writing.\\n";Error "Maybe you meant -overwrite_original ?\\n"}else {Error "It makes no sense to use -restore_original when writing\\n"}next}if ($overwriteOrig > 1 and $outOpt){Error "Can't overwrite in place when -o option is used\\n";next}if (($tagOut or defined$diff)and ($csv or $json or %printFmt or $tabFormat or $xml or $plot or ($verbose and $html))){my$opt=$tagOut ? '-W' : '-diff';Error "Sorry, $opt may not be combined with -csv, -htmlDump, -j, -p, -t or -X\\n";next}if ($csv and $csv eq 'CSV' and not $isWriting){$json=0;if ($textOut){$textOut2=$textOut;undef$textOut}if ($binaryOutput){$binaryOutput=0;$setCharset='default' unless defined$setCharset}if (%printFmt){Warn "The -csv option has no effect when -p is used\\n";undef$csv}require Image::ExifTool::XMP if$setCharset}if ($plot and $textOut){$textOut2=$textOut;undef$textOut}if ($textOut2){if ($textOverwrite > 1){Error "Can not append to multi-file output format\\n";undef$textOut2;next}if (not $textOverwrite and $mt->Exists($textOut2,1)){Error "Output file $textOut2 already exists\\n";undef$textOut2;next}CreateDirectory($textOut2);if ($mt->Open(\\*OUTFILE,$textOut2,'>')){close(\\*OUTFILE);unlink($textOut2)}else {Error("Error creating $textOut2\\n");undef$textOut2;next}}if ($escapeHTML or $json){$mt->Options(Charset=>'UTF8')if$json;$mt->Options(Escape=>'HTML')if$escapeHTML and not $xml}elsif ($escapeXML and not $xml){$mt->Options(Escape=>'XML')}if ($sortOpt){my$sort=($outFormat > 0 or $xml or $json or $csv or $plot)? 'Tag' : 'Descr';$mt->Options(Sort=>$sort,Sort2=>$sort)}if ($mt->Options('Struct')and not $structOpt){$structOpt=$mt->Options('Struct');require 'Image/ExifTool/XMPStruct.pl'}if ($plot){undef$joinLists;$mt->Options(List=>1);$plot->Settings($mt->Options('Plot'))}elsif ($xml){require Image::ExifTool::XMP;my$charset=$mt->Options('Charset');my%encoding=(UTF8=>'UTF-8',Latin=>'windows-1252',Latin2=>'windows-1250',Cyrillic=>'windows-1251',Greek=>'windows-1253',Turkish=>'windows-1254',Hebrew=>'windows-1255',Arabic=>'windows-1256',Baltic=>'windows-1257',Vietnam=>'windows-1258',MacRoman=>'macintosh',);unless ($encoding{$charset}){$charset='UTF8';$mt->Options(Charset=>$charset)}$fileHeader="<?xml version='1.0' encoding='$encoding{$charset}'?>\\n" ."<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>\\n";$fileTrailer="</rdf:RDF>\\n";$joinLists=1 if$outFormat > 0;$mt->Options(List=>1)unless$joinLists;$showGroup=$allGroup=1;$binaryOutput=($outFormat > 0 ? undef : 0)if$binaryOutput;$showTagID='D' if$tabFormat and not $showTagID}elsif ($json){if ($json==1){$fileHeader='[';$fileTrailer="]\\n"}else {$fileHeader='Array(';$fileTrailer=");\\n"}if ($binaryOutput){$binaryOutput=0;require Image::ExifTool::XMP if$json==1}$mt->Options(List=>1)unless$joinLists;$showTagID='D' if$tabFormat and not $showTagID}elsif ($structOpt){$mt->Options(List=>1)}else {$joinLists=1}if ($argFormat){$outFormat=3;$allGroup=1 if defined$showGroup}if (Image::ExifTool::IsPC()){tr/\\\\/\\// foreach@files}unless (@files){unless ($outOpt){if ($doGlob and $doGlob==2){Error "No matching files\\n"}else {Error "No file specified\\n"}next}push@files,''}if ($verbose){$disableOutput=1 unless@tags or @exclude or $tagOut;undef$binaryOutput unless$tagOut;if ($html){$html=2;$mt->Options(HtmlDump=>$verbose)}else {$mt->Options(Verbose=>$verbose)unless$tagOut}}elsif (defined$verbose){require FileHandle;STDOUT->autoflush(1);STDERR->autoflush(1)}my$needSave=1;if (@newValues){if ($addGeotime){AddSetTagsFile($setTagsFile='@')unless$setTagsFile and $setTagsFile eq '@';push @{$setTags{$setTagsFile}},$addGeotime;$verbose and print$vout qq(Using default "-$addGeotime"\\n)}my%setTagsIndex;my%addDelOpt=('+'=>'AddValue','-'=>'DelValue',"\\xe2\\x88\\x92"=>'DelValue');$saveCount=0;for (@newValues){if (ref $_ eq 'HASH'){if ($$_{SaveCount}){$saveCount=$mt->SaveNewValues();$needSave=0;push@dynamicFiles,\\$csv if $$_{SaveCount}==$csvSaveCount}next}/(.*?)=(.*)/s or next;
my ($tag, $newVal) = ($1, $2);
$tag =~ s/\\ball\\b/\\*/ig; # replace 'all' with '*' in tag names
$newVal eq '' and undef $newVal unless $tag =~ s/\\^([-+]*)$/$1/; # undefined to delete tag
if ($tag =~ /^(All)?TagsFromFile$/i){defined$newVal or Error("Need file name for -tagsFromFile\\n"),next Command;++$isWriting;if ($newVal eq '@' or not defined FilenameSPrintf($newVal)or grep /\\bfile\\d+:/i,@{$setTags{$newVal}}){push@dynamicFiles,$newVal;next}unless ($mt->Exists($newVal)or $newVal eq '-'){Error "File '\${newVal}' does not exist for -tagsFromFile option\\n";next Command}my$setTags=$setTags{$newVal};if ($setTagsList{$newVal}){my$i=$setTagsIndex{$newVal}|| 0;$setTagsIndex{$newVal}=$i + 1;$setTags=$setTagsList{$newVal}[$i]if$setTagsList{$newVal}[$i]}unless (DoSetFromFile($mt,$newVal,$setTags)){$rtnVal=1;next Command}$needSave=1;next}my%opts=(Shift=>0);$opts{Protected}=1 unless$tag =~ /[?*]/;if ($tag =~ s/<// and defined$newVal){if (defined FilenameSPrintf($newVal)){SlurpFile($newVal,\\$newVal)or next}else {$tag =~ s/([-+]|\\xe2\\x88\\x92)$// and $opts{$addDelOpt{$1}}=1;my$result=Image::ExifTool::IsWritable($tag);if ($result){$opts{ProtectSaved}=$saveCount;push@dynamicFiles,[$tag,$newVal,\\%opts ];++$isWriting}elsif (defined$result){Warn "Tag '\${tag}' is not writable\\n"}else {Warn "Tag '\${tag}' does not exist\\n"}next}}if ($tag =~ s/([-+]|\\xe2\\x88\\x92)$//){$opts{$addDelOpt{$1}}=1;$newVal='' if $1 eq '-' and not defined$newVal}if ($escapeC and defined$newVal){$newVal =~ s/\\\\(x([0-9a-fA-F]{2})|.)/$2 ? chr(hex($2)) : $unescC{$1} || $1/seg}my ($rtn,$wrn)=$mt->SetNewValue($tag,$newVal,%opts);$needSave=1;++$isWriting if$rtn;$wrn and Warning($mt,$wrn);}unless ($csv){for (@exclude){$mt->SetNewValue($_,undef,Replace=>2);$needSave=1}}unless ($isWriting or $outOpt or @tags){Error "Nothing to do.\\n";next}}elsif (grep /^(\\*:)?\\*$/,@exclude){Error "All tags excluded -- nothing to do.\\n";next}if ($isWriting){if (defined$diff){Error "Can't use -diff option when writing tags\\n";next}elsif ($plot){Error "Can't use -plot option when writing tags\\n";next}elsif (@tags and not $outOpt and not $csv){my ($tg,$s)=@tags > 1 ? ("$tags[0] ...",'s'): ($tags[0],'');Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\\n"}}$mt->SaveNewValues()if$outOpt or (@dynamicFiles and $needSave);$multiFile=1 if@files > 1;@exclude and $mt->Options(Exclude=>\\@exclude);undef$binaryOutput if$html;if ($binaryOutput){$outFormat=99;$mt->Options(PrintConv=>0);unless ($textOut or $binaryStdout){binmode(STDOUT);$binaryStdout=1;$mt->Options(TextOut=>($vout=\\*STDERR))}undef$showGroup}if (defined$showGroup and not (@tags and ($allGroup or $csv))and ($sortOpt or not defined$sortOpt)){$mt->Options(Sort=>"Group$showGroup")}if ($textOut){CleanFilename($textOut);$textOut=".$textOut" unless$textOut =~ /[.%]/ or defined$tagOut}if ($outOpt){my$type=GetFileType($outOpt);if ($type){my$canWrite=CanWrite($outOpt);unless ($canWrite){if (defined$canWrite and $canWrite eq ''){$type=Image::ExifTool::GetFileExtension($outOpt);$type=uc($outOpt)unless defined$type}Error "Can't write $type files\\n";next}$scanWritable=$type unless CanCreate($type)}else {$scanWritable=1}$isWriting=1}elsif ($isWriting or defined$deleteOrig){$scanWritable=1}$altEnc=$mt->Options('Charset');undef$altEnc if$altEnc eq 'UTF8';if (not $altEnc and $mt->Options('Lang')ne 'en'){$fixLen=eval {require Unicode::GCString}? 2 : 1}if (@fileOrder){my@allFiles;ProcessFiles($mt,\\@allFiles);my$sortTool=Image::ExifTool->new;$sortTool->Options(FastScan=>$fileOrderFast)if$fileOrderFast;$sortTool->Options(PrintConv=>$mt->Options('PrintConv'));$sortTool->Options(Duplicates=>0);my (%sortBy,%isFloat,@rev,$file);push@rev,(s/^-// ? 1 : 0)foreach@fileOrder;for$file (@allFiles){my@tags;my$info=$sortTool->ImageInfo(Infile($file,1),@fileOrder,\\@tags);for (@tags){$_=$$info{$_};defined $_ or $_='~',next;$isFloat{$_}=Image::ExifTool::IsFloat($_);s/(\\d+)/(length($1) < 12 ? '0'x(12-length($1)) : '') . $1/eg unless$isFloat{$_}}$sortBy{$file}=\\@tags}@files=sort {my ($i,$cmp);for ($i=0;$i<@rev;++$i){my$u=$sortBy{$a}[$i];my$v=$sortBy{$b}[$i];if (not $isFloat{$u}and not $isFloat{$v}){$cmp=$u cmp $v}elsif ($isFloat{$u}and $isFloat{$v}){$cmp=$u <=> $v}else {$cmp=$isFloat{$u}? -1 : 1}return$rev[$i]? -$cmp : $cmp if$cmp}return$a cmp $b}@allFiles}elsif (defined$progress){my@allFiles;ProcessFiles($mt,\\@allFiles);@files=@allFiles}$progressMax=scalar@files if defined$progress;my@dbKeys=keys%database;if (@dbKeys){if (eval {require Cwd}){undef$evalWarning;local$SIG{'__WARN__'}=sub {$evalWarning=$_[0]};for (@dbKeys){my$db=$database{$_};tr/\\\\/\\// and $database{$_}=$db;my$absPath=AbsPath($_);if (defined$absPath){$database{$absPath}=$db unless$database{$absPath};if ($verbose and $verbose > 1){print$vout "Imported entry for '\${_}' (full path: '\${absPath}')\\n"}}elsif ($verbose and $verbose > 1){print$vout "Imported entry for '\${_}' (no full path)\\n"}}}}ProcessFiles($mt);Error "No file with specified extension\\n" if$filtered and not $validFile;if ($textOut){for (keys%outTrailer){next unless$outTrailer{$_};if ($mt->Open(\\*OUTTRAIL,$_,'>>')){my$fp=\\*OUTTRAIL;print$fp $outTrailer{$_};close$fp}else {Error("Error appending to $_\\n")}}}else {print$sectTrailer if$sectTrailer;print$fileTrailer if$fileTrailer and not $fileHeader;my ($fp,$err);if ($textOut2){if ($mt->Open(\\*OUTFILE,$textOut2,'>')){$fp=\\*OUTFILE}else {Error("Error creating $textOut2\\n");$err=1}}unless ($err){PrintCSV($fp)if$csv and not $isWriting;if ($plot){$plot->Draw($fp || \\*STDOUT);if ($$plot{Error}){Error("Error: $$plot{Error}\\n");$err=1}elsif ($$plot{Warn}){Warn("Warning: $$plot{Warn}\\n")}}}if ($fp){close($fp)or $err=1;if ($err){$mt->Unlink($textOut2)}else {$created{$textOut2}=1}}}my$totWr=$countGoodWr + $countBadWr + $countSameWr + $countCopyWr + $countGoodCr + $countBadCr;if (defined$deleteOrig){unless ($quiet){printf "%5d directories scanned\\n",$countDir if$countDir;printf "%5d directories created\\n",$countNewDir if$countNewDir;printf "%5d files failed condition\\n",$countFailed if$countFailed;printf "%5d image files found\\n",$count}if (@delFiles){if ($deleteOrig==1){printf '%5d originals will be deleted! Are you sure [y/n]? ',scalar(@delFiles);my$response=<STDIN>;unless ($response =~ /^(y|yes)\\s*$/i){Warn "Originals not deleted.\\n";next}}$countGoodWr=$mt->Unlink(@delFiles);$countBad=scalar(@delFiles)- $countGoodWr}if ($quiet){}elsif ($count and not $countGoodWr and not $countBad){printf "%5d original files found\\n",$countGoodWr}elsif ($deleteOrig){printf "%5d original files deleted\\n",$countGoodWr if$count;printf "%5d originals not deleted due to errors\\n",$countBad if$countBad}else {printf "%5d image files restored from original\\n",$countGoodWr if$count;printf "%5d files not restored due to errors\\n",$countBad if$countBad}}elsif ((not $binaryStdout or $verbose)and not $quiet){my$tot=$count + $countBad;if ($countDir or $totWr or $countFailed or $tot > 1 or $textOut or %countLink){my$o=(($html or $json or $xml or %printFmt or $csv or $plot)and not $textOut)? \\*STDERR : $vout;printf($o "%5d directories scanned\\n",$countDir)if$countDir;printf($o "%5d directories created\\n",$countNewDir)if$countNewDir;printf($o "%5d files failed condition\\n",$countFailed)if$countFailed;printf($o "%5d image files created\\n",$countGoodCr)if$countGoodCr;printf($o "%5d image files updated\\n",$countGoodWr)if$totWr - $countGoodCr - $countBadCr - $countCopyWr;printf($o "%5d image files unchanged\\n",$countSameWr)if$countSameWr;printf($o "%5d image files %s\\n",$countCopyWr,$overwriteOrig ? 'moved' : 'copied')if$countCopyWr;printf($o "%5d files weren't updated due to errors\\n",$countBadWr)if$countBadWr;printf($o "%5d files weren't created due to errors\\n",$countBadCr)if$countBadCr;printf($o "%5d image files read\\n",$count)if ($tot+$countFailed)>1 or ($countDir and not $totWr);printf($o "%5d files could not be read\\n",$countBad)if$countBad;printf($o "%5d output files created\\n",scalar(keys%created))if$textOut or $textOut2;printf($o "%5d output files appended\\n",scalar(keys%appended))if%appended;printf($o "%5d hard links created\\n",$countLink{Hard}|| 0)if$countLink{Hard}or $countLink{BadHard};printf($o "%5d hard links could not be created\\n",$countLink{BadHard})if$countLink{BadHard};printf($o "%5d symbolic links created\\n",$countLink{Sym}|| 0)if$countLink{Sym}or $countLink{BadSym};printf($o "%5d symbolic links could not be created\\n",$countLink{BadSym})if$countLink{BadSym}}}if ($countBadWr or $countBadCr or $countBad){$rtnVal=1}elsif ($countFailed and not ($count or $totWr)and not $rtnVal){$rtnVal=2}Cleanup();}close STAYOPEN if$stayOpen >= 2;Exit$rtnValApp;sub GetImageInfo($$) {my ($et,$orig)=@_;my (@foundTags,@found2,$info,$info2,$et2,$file,$file2,$ind,$g8);if (defined$windowTitle){if ($progressCount >= $progressNext){my$prog=$progressMax ? "$progressCount/$progressMax" : '0/0';my$title=$windowTitle;my ($num,$denom)=split '/',$prog;my$frac=$num / ($denom || 1);my$n=$title =~ s/%(\\d+)b/%b/ ? $1 : 20;my$bar=int($frac * $n + 0.5);my%lkup=(b=>('I' x $bar).('.' x ($n - $bar)),f=>$orig,p=>int(100 * $frac + 0.5),r=>$prog,'%'=>'%',);$title =~ s/%([%bfpr])/$lkup{$1}/eg;SetWindowTitle($title);if (defined$progressMax){undef$progressNext}else {$progressNext += $progressIncr}}++$progressCount unless defined$progressMax}unless (length$orig or $outOpt){Warn qq(Error: Zero-length file name - ""\\n);++$countBad;return}if (@srcFmt){my ($fmt,$first);for$fmt (@srcFmt){$file=$fmt eq '@' ? $orig : FilenameSPrintf($fmt,$orig);$et->Exists($file)and undef($first),last;$verbose and print$vout "Source file $file does not exist\\n";$first=$file unless defined$first}$file=$first if defined$first;my ($d,$f)=Image::ExifTool::SplitFileName($orig);$et->Options(UserParam=>"OriginalDirectory#=$d");$et->Options(UserParam=>"OriginalFileName#=$f")}else {$file=$orig}for$g8 (sort keys%altFile){my$altName=$orig;$altName =~ s/\\$/\\$\\$/g;$altName=FilenameSPrintf($altFile{$g8},$altName);$et->SetAlternateFile($g8,$altName)}my$pipe=$file;if ($doUnzip){if ($file =~ /\\.(gz|bz2)$/i){my$type=lc $1;if ($file =~ /[^-_.'A-Za-z0-9\\/\\\\]/){Warn "Error: Insecure zip file name. Skipped\\n";EFile($file);++$countBad;return}if ($type eq 'gz'){$pipe=qq{gzip -dc "$file" |}}else {$pipe=qq{bzip2 -dc "$file" |}}$$et{TRUST_PIPE}=1}}if (@condition){unless ($file eq '-' or $et->Exists($file)){Warn "Error: File not found - $file\\n";EFile($file);FileNotFound($file);++$countBad;return}my$result;unless ($failCondition){undef$evalWarning;local$SIG{'__WARN__'}=sub {$evalWarning=$_[0]};my (%info,$condition);my$opts={Duplicates=>1,RequestTags=>\\@requestTags,Verbose=>0,HtmlDump=>0 };$$opts{FastScan}=$fastCondition if defined$fastCondition;@foundTags=('*',@tags)if@tags;$info=$et->ImageInfo(Infile($pipe,$isWriting),\\@foundTags,$opts);for$condition (@condition){my$cond=$et->InsertTagValues($condition,\\@foundTags,\\%info);{package Image::ExifTool;my$self=$et;$result=eval$cond;$@ and $evalWarning=$@}if ($evalWarning){undef$result;if ($verbose){chomp$evalWarning;$evalWarning =~ s/ at \\(eval .*//s;Warn "Condition: $evalWarning - $file\\n"}}last unless$result}undef@foundTags if$fastCondition}unless ($result){Progress($vout,"-------- $file (failed condition)")if$verbose;EFile($file,2);++$countFailed;return}if ($isWriting or $verbose or defined$fastCondition or defined$diff){undef$info;--$$et{FILE_SEQUENCE}}}elsif ($file =~ s/^(\\@JSON:)(.*)/$1/){my$dat=$2;$info=$et->ImageInfo(\\$dat,\\@foundTags);if ($geoOnly){/^Geolocation/ or delete $$info{$_}foreach keys %$info;$file=' '}}if (defined$deleteOrig){Progress($vout,"======== $file")if defined$verbose;++$count;my$original="\${file}_original";$et->Exists($original)or return;if ($deleteOrig){$verbose and print$vout "Scheduled for deletion: $original\\n";push@delFiles,$original}elsif ($et->Rename($original,$file)){$verbose and print$vout "Restored from $original\\n";EFile($file,3);++$countGoodWr}else {Warn "Error renaming $original\\n";EFile($file);++$countBad}return}++$seqFileNum;my ($dir)=Image::ExifTool::SplitFileName($orig);$seqFileDir=$seqFileDir{$dir}=($seqFileDir{$dir}|| 0)+ 1;my$lineCount=0;my ($fp,$outfile,$append);if ($textOut and ($verbose or $et->Options('PrintCSV'))and not ($tagOut or defined$diff or $plot)){($fp,$outfile,$append)=OpenOutputFile($orig);$fp or EFile($file),++$countBad,return;$tmpText=$outfile unless$append;$et->Options(TextOut=>$fp)}if ($isWriting){Progress($vout,"======== $file")if defined$verbose;SetImageInfo($et,$file,$orig);$info=$et->GetInfo('Warning','Error');PrintErrors($et,$info,$file);if (defined$outfile){undef$tmpText;close($fp);$et->Options(TextOut=>$vout);if ($info->{Error}){$et->Unlink($outfile)}elsif ($append){$appended{$outfile}=1 unless$created{$outfile}}else {$created{$outfile}=1}}return}unless ($file eq '-' or $et->Exists($file)or $info){Warn "Error: File not found - $file\\n";FileNotFound($file);defined$outfile and close($fp),undef($tmpText),$et->Unlink($outfile);EFile($file);++$countBad;return}my$o;unless ($binaryOutput or $textOut or %printFmt or $html > 1 or $csv or $plot){if ($html){require Image::ExifTool::HTML;my$f=Image::ExifTool::HTML::EscapeHTML($file);print "<!-- $f -->\\n"}elsif (not ($json or $xml or defined$diff)){$o=\\*STDOUT if ($multiFile and not $quiet)or $progress}}$o=\\*STDERR if$progress and not $o;Progress($o,"======== $file")if$o;if ($info){if (@tags and not %printFmt){@foundTags=@tags;$info=$et->GetInfo(\\@foundTags)}}else {my$oldDups=$et->Options('Duplicates');if (%printFmt){$et->Options(Duplicates=>1);$et->Options(RequestTags=>\\@requestTags);if ($printFmt{SetTags}){$$et{TAGS_FROM_FILE}=1;$et->Options(MakerNotes=>1);$et->Options(Struct=>2);$et->Options(List=>1);$et->Options(CoordFormat=>'%d %d %.8f')unless$et->Options('CoordFormat')}}else {@foundTags=@tags}if (defined$diff){$file2=FilenameSPrintf($diff,$orig);if ($file eq $file2){Warn "Error: Diffing file with itself - $file2\\n";EFile($file);++$countBad;return}if ($et->Exists($file2)){$showGroup=1 unless defined$showGroup;$allGroup=1 unless defined$allGroup;$et->Options(Duplicates=>1,Sort=>"Group$showGroup",Verbose=>0);$et2=Image::ExifTool->new;$et2->Options(%{$$et{OPTIONS}});$et2->Options(ListSep=>$$et{OPTIONS}{ListSep});$et2->Options(ListSplit=>$$et{OPTIONS}{ListSplit});@found2=@foundTags;$info2=$et2->ImageInfo($file2,\\@found2)}else {$info2={Error=>"Diff file not found" }}if ($$info2{Error}){Warn "Error: $$info2{Error} - $file2\\n";EFile($file);++$countBad;return}}$info=$et->ImageInfo(Infile($pipe),\\@foundTags);$et->Options(Duplicates=>$oldDups)}if ($fp){if (define