home *** CD-ROM | disk | FTP | other *** search
Text File | 2010-05-24 | 159.8 KB | 4,036 lines |
- #!/usr/bin/perl -w
-
- eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
- if 0; # not running under some shell
- #------------------------------------------------------------------------------
- # File: exiftool
- #
- # Description: Read/write meta information
- #
- # Revisions: Nov. 12/03 - P. Harvey Created
- # (See html/history.html for revision history)
- #
- # References: ATV - Alexander Vonk, private communication
- #------------------------------------------------------------------------------
- use strict;
- require 5.004;
-
- my $version = '8.15';
-
- # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool'
- my $exeDir;
- BEGIN {
- # get exe directory
- $exeDir = ($0 =~ /(.*)[\\\/]/) ? $1 : '.';
- # add lib directory at start of include path
- unshift @INC, "$exeDir/lib";
- # disable config file if specified
- if (@ARGV and lc($ARGV[0]) eq '-config') {
- shift;
- $Image::ExifTool::configFile = shift;
- }
- }
- use Image::ExifTool qw{:Public};
-
- sub SigInt();
- sub Cleanup();
- sub CleanXML($);
- sub EncodeXML($);
- sub FormatXML($$$);
- sub EscapeJSON($;$);
- sub PrintJSON($$$);
- sub GetImageInfo($$);
- sub SetImageInfo($$);
- sub DoSetFromFile($$$);
- sub CleanFilename($);
- sub ProcessFiles($;$);
- sub ScanDir($$;$);
- sub LoadPrintFormat($);
- sub FilenameSPrintf($;$);
- sub NextUnusedFilename($;$);
- sub CreateDirectory($);
- sub SourceFile($);
- sub OpenOutputFile($);
- sub AcceptFile($);
- sub SlurpFile($$);
- sub PrintTagList($@);
- sub PrintErrors($$$);
-
- # do cleanup on Ctrl-C
- $SIG{INT} = 'SigInt';
-
- END {
- Cleanup();
- }
-
- # declare all static variables
- my @commonArgs; # arguments common to all commands
- my @dynamicFiles; # list of -tagsFromFile files with dynamic names
- my @dynamicValues; # list of -TAG<=FMT pairs for setting new values
- my @exclude; # list of excluded tags
- my @files; # list of files and directories to scan
- my @fileOrder; # tags to use for ordering of input files
- my @ignore; # directory names to ignore
- my @newValues; # list of new tag values to set
- my @tags; # list of tags to extract
- my %excludeGrp; # hash of tags excluded by group
- my %filterExt; # lookup for filtered extensions
- my %printFmt; # the contents of the print format file
- my %setTags; # hash of list references for tags to set from files
- my %warnedOnce; # lookup for once-only warnings
- my $addGeotime; # automatically added geotime argument
- my $allGroup; # show group name for all tags
- my $allInGroup; # flag to show all tags in a group
- my $binaryOutput; # flag for binary output
- my $binaryStdout; # flag set if we output binary to stdout
- my $comma; # flag set if we need a comma in JSON output
- my $condition; # conditional processing of files
- my $count; # count of files scanned
- my $countBad; # count of files with errors
- my $countBadCr; # count files not created due to errors
- my $countBadWr; # count write errors
- my $countCreated; # count output files created
- my $countDir; # count of directories scanned
- my $countFailed; # count files that failed condition
- my $countGoodCr; # count files created OK
- my $countGoodWr; # count files written OK
- my $countNewDir; # count of directories created
- my $countSameWr; # count files written OK but not changed
- my $countCopyWr; # count of files copied without being changed
- my $disableOutput; # flag to disable normal output
- my $doGlob; # flag set to do filename wildcard expansion
- my $doSetFileName; # flag set if FileName may be written
- my $doUnzip; # flag to extract info from .gz and .bz2 files
- my $escapeHTML; # flag to escape printed values for html
- my $escapeXML; # flag to escape printed values for xml
- my $evalWarning; # warning from eval
- my $fileHeader; # header to print to output file (or console, once)
- my $fileTrailer; # trailer for output file
- my $filtered; # flag indicating file was filtered by name
- my $filterFlag; # file filter flag (0x01=deny extensions, 0x02=allow extensions)
- my $fixLen; # flag to fix description lengths when writing alternate languages
- my $forcePrint; # force printing of tags whose values weren't found
- my $helped; # flag to avoid printing help if no tags specified
- my $html; # flag for html-formatted output (2=html dump)
- my $isWriting; # flag set if we are writing tags
- my $joinLists; # flag set to join list values into a single string
- my $json; # flag for JSON-formatted output
- my $listSep; # list item separator (', ' by default)
- my $mainTool; # main ExifTool object
- my $multiFile; # non-zero if we are scanning multiple files
- my $outFormat; # -1=Canon format, 0=same-line, 1=tag names, 2=values only
- my $outOpt; # output file or directory name
- my $outputExt; # extension for output file (or undef for no output)
- my $overwriteOrig; # flag to overwrite original file
- my $pause; # pause before returning
- my $preserveTime; # flag to preserve times of updated files
- my $quiet; # flag to disable printing of informational messages / warnings
- my $recurse; # recurse into subdirectories
- my $rtnVal; # command return value (0=success)
- my $scanWritable; # flag to process only writable file types
- my $seqFileNum; # sequential file number used for %C
- my $setTagsFile; # filename for last TagsFromFile option
- my $showGroup; # number of group to show (may be zero or '')
- my $showTagID; # non-zero to show tag ID's
- my $srcFmt; # source file name format string
- my $structOpt; # output structured XMP information (JSON and XML output only)
- my $tabFormat; # non-zero for tab output format
- my $tmpFile; # temporary file to delete on exit
- my $tmpText; # temporary text file
- my $validFile; # flag indicating we processed a valid file
- my $verbose; # verbose setting
- my $utf8; # flag set if we are using UTF-8 encoding
- my $useMWG; # flag set if we are using any MWG tag
- my $xml; # flag for XML-formatted output
-
- # lookup for O/S names which may use a backslash as a directory separator
- # (ref File::Spec of PathTools-3.2701)
- my %hasBackslash = ( MSWin32 => 1, os2 => 1, dos => 1, NetWare => 1, symbian => 1, cygwin => 1 );
-
- # lookup for JSON characters that we escape specially
- my %jsonChar = ( '"'=>'"', '\\'=>'\\', "\t"=>'t', "\n"=>'n', "\r"=>'r' );
-
- # exit routine
- sub Exit {
- if ($pause) {
- if (eval 'require Term::ReadKey') {
- print STDERR "-- press any key --";
- Term::ReadKey::ReadMode('cbreak');
- Term::ReadKey::ReadKey(0);
- print STDERR "\b \b" x 20;
- } else {
- print STDERR "-- press RETURN --\n";
- <STDIN>;
- }
- }
- exit shift;
- }
- # my warn and die routines
- sub Warn { warn(@_) if $quiet < 2 or $_[0] =~ /^Error/; }
- sub Die { Warn @_; Exit 1; }
- sub WarnOnce($) {
- Warn(@_) and $warnedOnce{$_[0]} = 1 unless $warnedOnce{$_[0]};
- }
-
- # define signal handlers and cleanup routine
- sub SigInt() { Cleanup(); exit 1; }
- sub Cleanup() {
- unlink $tmpFile if defined $tmpFile;
- unlink $tmpText if defined $tmpText;
- }
-
- #------------------------------------------------------------------------------
- # main script
- #
-
- # separate arguments common to all commands
- if (grep /^-common_args$/i, @ARGV) {
- my (@newArgs, $common);
- foreach (@ARGV) {
- if (/^-common_args$/i) {
- $common = 1;
- next;
- } elsif ($common) {
- push @commonArgs, $_;
- }
- push @newArgs, $_;
- }
- @ARGV = @newArgs;
- }
-
- # loop over sets of command-line arguments separated by "-execute"
- Command: while (@ARGV or not defined $rtnVal)
- {
-
- $rtnVal = 0 unless defined $rtnVal;
-
- # initialize all static variables
- # (not done: @commonArgs, $helped, $mainTool, $pause, $rtnVal)
- undef @dynamicFiles;
- undef @dynamicValues;
- undef @exclude;
- undef @files;
- undef @fileOrder;
- undef @ignore;
- undef @newValues;
- undef @tags;
- undef %excludeGrp;
- undef %filterExt;
- undef %printFmt;
- undef %setTags;
- undef %warnedOnce;
- undef $addGeotime;
- undef $allGroup;
- undef $allInGroup;
- undef $binaryOutput;
- undef $binaryStdout;
- undef $comma;
- undef $condition;
- undef $disableOutput;
- undef $doGlob;
- undef $doSetFileName;
- undef $doUnzip;
- undef $escapeHTML;
- undef $escapeXML;
- undef $evalWarning;
- undef $fileHeader;
- undef $fileTrailer;
- undef $filtered;
- undef $fixLen;
- undef $forcePrint;
- undef $joinLists;
- undef $multiFile;
- undef $outOpt;
- undef $outputExt;
- undef $preserveTime;
- undef $recurse;
- undef $scanWritable;
- undef $setTagsFile;
- undef $showGroup;
- undef $showTagID;
- undef $srcFmt;
- undef $structOpt;
- undef $tmpFile;
- undef $tmpText;
- undef $useMWG;
- undef $validFile;
- undef $verbose;
-
- $count = 0;
- $countBad = 0;
- $countBadCr = 0;
- $countBadWr = 0;
- $countCreated = 0;
- $countDir = 0;
- $countFailed = 0;
- $countGoodCr = 0;
- $countGoodWr = 0;
- $countNewDir = 0;
- $countSameWr = 0;
- $countCopyWr = 0;
- $filterFlag = 0;
- $html = 0;
- $isWriting = 0;
- $json = 0;
- $listSep = ', ';
- $outFormat = 0;
- $overwriteOrig = 0;
- $quiet = 0;
- $seqFileNum = 0;
- $tabFormat = 0;
- $utf8 = 1;
- $xml = 0;
-
- # for Windows, use globbing for wildcard expansion if available - MK/20061010
- if ($^O eq 'MSWin32' and eval "require File::Glob") {
- # override the core glob forcing case insensitivity
- import File::Glob qw(:globally :nocase);
- $doGlob = 1;
- }
-
- $mainTool = new Image::ExifTool; # create ExifTool object
-
- # don't extract duplicates by default unless set by UserDefined::Options
- $mainTool->Options(Duplicates => 0) unless %Image::ExifTool::UserDefined::Options
- and defined $Image::ExifTool::UserDefined::Options{Duplicates};
-
- # local variables
- my ($doneCommonArgs, @nextPass);
- my $pass = 0;
-
- # parse command-line options in 2 passes...
- # pass 1: set all of our ExifTool options
- # pass 2: print all of our help and informational output (-list, -ver, etc)
- for (;;) {
- if (not @ARGV) {
- if ($pass == 0) {
- # require MWG module if used in any argument
- $useMWG = 1 if not $useMWG and grep /^mwg:/i, @tags; # (also covers -p option)
- require Image::ExifTool::MWG if $useMWG;
- }
- last unless @nextPass;
- # process arguments which were deferred to the next pass
- @ARGV = @nextPass;
- undef @nextPass;
- ++$pass;
- }
- $_ = shift;
- if (s/^-//) {
- my $a = lc $_;
- if (/^list(w|f|wf|d|g(\d*)|x)?$/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] =~ /^-(.+):(all|\*)$/i) {
- if ($pass == 0) {
- $useMWG = 1 if lc($1) eq 'mwg';
- push(@nextPass, shift);
- next;
- }
- $group = $1;
- shift;
- $group =~ /IFD/i and Warn("Can't list tags for specific IFD\n"), 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 $forcePrint;
- $opts{NoDesc} = 1 if $outFormat > 0;
- 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));
- # also print shortcuts if listing all tags
- 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('Recognized file extensions', GetFileType());
- } elsif ($type eq 'd') {
- PrintTagList('Deletable groups', GetDeleteGroups());
- } else { # 'g(\d*)'
- # list all groups in specified family
- my $family = $2 || 0;
- PrintTagList("Groups in family $family", 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]";
- print("$version$str$Image::ExifTool::RELEASE\n");
- $helped = 1;
- next;
- }
- if (/^(all|add)?tagsfromfile(=.*)?$/i) {
- $setTagsFile = $2 ? substr($2,1) : (@ARGV ? shift : '');
- $setTagsFile eq '' and Die "File must be specified for -tagsFromFile option\n";
- push(@newValues, "TagsFromFile=$setTagsFile");
- $setTags{$setTagsFile} or $setTags{$setTagsFile} = [];
- push @{$setTags{$setTagsFile}}, { Replace => ($1 and lc($1) eq 'add') ? 0 : 1 };
- next;
- }
- if ($a eq '@') {
- my $argFile = shift or Die "Expecting filename for -\@ option\n";
- unless (open(ARGFILE, "<$argFile")) {
- unless ($argFile !~ /^\// and open(ARGFILE, "<$exeDir/$argFile")) {
- Die "Error opening arg file $argFile\n";
- }
- }
- my (@newArgs, $didBOM);
- foreach (<ARGFILE>) {
- # filter Byte Order Mark if it exists from start of UTF-8 text file
- unless ($didBOM) {
- s/^\xef\xbb\xbf//;
- $didBOM = 1;
- }
- s/^\s+//; s/\s+$//s; # remove leading/trailing white space
- # remove white space before, and single space after '=', '+=', '-=' or '<='
- s/^([-\w]+)\s*([-+<]?=) ?/$1$2/;
- push @newArgs, $_ unless $_ eq '' or /^#/;
- }
- close(ARGFILE);
- unshift @ARGV, @newArgs;
- next;
- }
- /^(-?)(a|duplicates)$/i and $mainTool->Options(Duplicates => ($1 ? 0 : 1)), next;
- /^b(inary)?$/i and $mainTool->Options(Binary => 1), $binaryOutput = 1, next;
- /^c(oordFormat)?$/i and $mainTool->Options('CoordFormat', shift || Die "Expecting coordinate format for -c option\n"), next;
- if ($a eq 'charset') {
- my $charset = (@ARGV and $ARGV[0] !~ /^-/) ? shift : undef;
- if (not $charset or $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') {
- $mainTool->Options(Charset => $charset);
- $utf8 = ($mainTool->Options('Charset') eq 'UTF8');
- } elsif (lc($1) eq 'iptc') {
- $mainTool->Options(CharsetIPTC => $charset);
- } elsif (lc($1) eq 'id3') {
- $mainTool->Options(CharsetID3 => $charset);
- } else {
- warn "Unknown type for -charset option: $1\n";
- }
- next;
- }
- /^config$/i and Warn("Ignored -config option (not first on command line)\n"), shift, next;
- (/^d$/ or $a eq 'dateformat') and $mainTool->Options('DateFormat', shift || Die "Expecting date format for -d option\n"), next;
- (/^D$/ or $a eq 'decimal') and $showTagID = 'D', next;
- (/^e$/ or $a eq '-composite') and $mainTool->Options(Composite => 0), next;
- (/^-e$/ or $a eq 'composite') and $mainTool->Options(Composite => 1), next;
- (/^E$/ or $a eq 'escapehtml') and require Image::ExifTool::HTML and $escapeHTML = 1, next;
- ($a eq 'ex' or $a eq 'escapexml') and $escapeXML = 1, next;
- if ($a eq 'echo') { # undocumented debugging option
- unless ($pass) {
- push @nextPass, '-echo';
- push @nextPass, shift if @ARGV;
- next;
- }
- print(shift,"\n") if @ARGV;
- $helped = 1;
- next;
- }
- if (/^(ee|extractembedded)$/i) {
- $mainTool->Options(ExtractEmbedded => 1);
- $mainTool->Options(Duplicates => 1);
- next;
- }
- if ($a eq 'execute') {
- if (@nextPass) {
- unshift @ARGV, @nextPass, '-execute';
- undef @nextPass;
- ++$pass;
- next;
- }
- $helped = 1;
- last if $doneCommonArgs or not @commonArgs;
- # execute common arguments for each command
- unshift @ARGV, @commonArgs, '-execute';
- $doneCommonArgs = 1;
- next;
- }
- if (/^-?ext(ension)?$/i) {
- my $ext = shift;
- defined $ext or Die "Expecting extension for -ext option\n";
- $ext =~ s/^\.//; # remove leading '.' if it exists
- my $flag = /^-/ ? 0 : 1;
- $filterFlag |= (0x01 << $flag);
- $filterExt{uc($ext)} = $flag;
- next;
- }
- if (/^f$/ or $a eq 'forceprint') {
- $forcePrint = 1;
- $mainTool->Options(MissingTagValue => '-');
- next;
- }
- if (/^F([-+]?\d*)$/ or /^fixbase([-+]?\d*)$/i) {
- $mainTool->Options(FixBase => $1);
- next;
- }
- if (/^fast(\d*)$/i) {
- $mainTool->Options(FastScan => (length $1 ? $1 : 1));
- next;
- }
- if ($a eq 'fileorder') {
- push @fileOrder, shift if @ARGV;
- next;
- }
- if (/^(g)(roupHeadings|roupNames)?([\d:]*)$/i) {
- $showGroup = $3 || 0;
- $allGroup = ($2 ? lc($2) eq 'roupnames' : $1 eq 'G');
- next;
- }
- if ($a eq 'geotag') {
- my $trkfile = shift or Die "Expecting file name for -geotag option\n";
- # allow wildcards in filename
- if ($trkfile =~ /[*?]/) {
- my @trks = $doGlob ? File::Glob::bsd_glob($trkfile) : glob($trkfile);
- @trks or Die "No matching file found for -geotag option\n";
- push @newValues, 'geotag='.shift(@trks) while @trks > 1;
- $trkfile = pop(@trks);
- }
- $_ = "geotag=$trkfile";
- # (fall through!)
- }
- 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;
- $mainTool->Options(HtmlDumpBase => $1) if defined $1;
- next;
- }
- /^i(gnore)?$/i and push(@ignore,shift || Die "Expecting directory name for -i option\n"), next;
- if ($a eq 'if') {
- my $cond = shift;
- defined $cond or Die("Expecting expression for -if option\n");
- $useMWG = 1 if $cond =~ /\$\{?mwg:/i;
- if (defined $condition) {
- $condition .= " and $cond";
- } else {
- $condition = $cond;
- }
- next;
- }
- /^j(son)?$/i and $json = 1, $html = $xml = 0, $mainTool->Options(Duplicates => 1), next;
- /^(k|pause)$/i and $pause = 1, next;
- (/^l$/ or $a eq 'long') and --$outFormat, next;
- (/^L$/ or $a eq 'latin') and $utf8 = 0, $mainTool->Options(Charset => 'Latin'), next;
- if ($a eq 'lang') {
- my $lang = (@ARGV and $ARGV[0] !~ /^-/) ? shift : undef;
- if ($lang) {
- # make lower case and use underline as a separator (ie. 'en_ca')
- $lang =~ tr/-A-Z/_a-z/;
- $mainTool->Options(Lang => $lang);
- next if $lang eq $mainTool->Options('Lang');
- } else {
- $pass or push(@nextPass, '-lang'), next;
- }
- my $langs = "Available languages:\n";
- $langs .= " $_ - $Image::ExifTool::langName{$_}\n" foreach @Image::ExifTool::langs;
- $langs = $mainTool->Decode($langs, 'UTF8');
- $langs = Image::ExifTool::HTML::EscapeHTML($langs) if $escapeHTML;
- Die "Invalid or unsupported language '$lang'.\n$langs" if $lang;
- print $langs;
- $helped = 1;
- next;
- }
- /^(m|ignoreminorerrors)$/i and $mainTool->Options(IgnoreMinorErrors => 1), next;
- /^(n|-printconv)$/i and $mainTool->Options(PrintConv => 0), next;
- /^(-n|printconv)$/i and $mainTool->Options(PrintConv => 1), next;
- if (/^o(ut)?$/i) {
- $outOpt = shift || Die("Expected output file or directory name for -o option\n");
- CleanFilename($outOpt);
- next;
- }
- /^overwrite_original$/i and $overwriteOrig = 1, next;
- /^overwrite_original_in_place$/i and $overwriteOrig = 2, next;
- (/^p$/ or $a eq 'printformat') and LoadPrintFormat(shift), next;
- (/^P$/ or $a eq 'preserve') and $preserveTime = 1, next;
- /^q(uiet)?$/i and ++$quiet, next;
- /^r(ecurse)?$/i and $recurse = 1, next;
- (/^s$/ or $a eq 'short') and ++$outFormat, next;
- (/^S$/ or $a eq 'veryshort') and $outFormat+=2, next;
- /^scanforxmp$/i and $mainTool->Options(ScanForXMP => 1), next;
- if (/^sep(arator)?$/i) {
- $listSep = shift;
- defined $listSep or Die("Expecting list item separator for -sep option\n");
- $mainTool->Options(ListSep => $listSep);
- $joinLists = 1;
- # also split when writing values
- my $listSplit = quotemeta $listSep;
- # a space in the string matches zero or more whitespace characters
- $listSplit =~ s/(\\ )+/\\s\*/g;
- # but a single space alone matches one or more whitespace characters
- $listSplit = '\\s+' if $listSplit eq '\\s*';
- $mainTool->Options(ListSplit => $listSplit);
- next;
- }
- /^srcfile$/i and $srcFmt = shift || Die("Expecting FMT for -srcfile option\n"), next;
- /^struct$/i and $structOpt = 1, next;
- /^t(ab)?$/ and $tabFormat = 1, next;
- if (/^T$/ or $a eq 'table') {
- $tabFormat = 1; $outFormat+=2; ++$quiet; $forcePrint = 1;
- $mainTool->Options(MissingTagValue => '-');
- next;
- }
- if (/^(u)(nknown(2)?)?$/i) {
- my $inc = ($3 or (not $2 and $1 eq 'U')) ? 2 : 1;
- $mainTool->Options(Unknown => $mainTool->Options('Unknown') + $inc);
- next;
- }
- if ($a eq 'use') {
- my $module = shift || Die("Expecting module name for -use option\n");
- lc $module eq 'mwg' and $useMWG = 1, next;
- local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] };
- unless (eval "require Image::ExifTool::$module" or
- eval "require $module" or
- eval "require '$module'")
- {
- delete $SIG{'__WARN__'};
- Die("Error using module $module\n");
- }
- next;
- }
- if (/^v(erbose)?(\d*)$/i) {
- $verbose = ($2 eq '') ? ($verbose || 0) + 1 : $2;
- next;
- }
- /^(w|textout)$/i and $outputExt = shift || Die("Expecting output extension for -w option\n"), next;
- if (/^x$/ or $a eq 'exclude') {
- my $tag = shift;
- defined $tag or Die "Expecting tag name for -x option\n";
- $tag =~ s/\ball\b/\*/ig; # replace 'all' with '*' in tag names
- if ($setTagsFile) {
- push @{$setTags{$setTagsFile}}, "-$tag";
- } else {
- push @exclude, $tag;
- }
- next;
- }
- (/^X$/ or $a eq 'xmlformat') and $xml = 1, $html = $json = 0, $mainTool->Options(Duplicates => 1), next;
- /^z(ip)?$/i and $doUnzip = 1, $mainTool->Options(Compress => 1), next;
- $_ eq '' and push(@files, '-'), next; # read STDIN
- length $_ eq 1 and Die "Unknown option -$_\n";
- if (/^[^<]+(<?)=(.*)/s) {
- my $val = $2;
- push @newValues, $_;
- if (/^mwg:/i) {
- $useMWG = 1;
- } elsif (/^([-\w]+:)*(filename|directory)\b/i) {
- $doSetFileName = 1;
- } elsif (/^([-\w]+:)*(geotag|geotime)\b/i) {
- if (lc $2 eq 'geotag') {
- if (not defined $addGeotime or $addGeotime) {
- $addGeotime = ($1 || '') . 'Geotime';
- $addGeotime .= length $val ? '<DateTimeOriginal' : '=';
- }
- } else {
- $addGeotime = '';
- }
- }
- } else {
- if (not $setTagsFile and /(<|>)/) {
- # assume '-tagsFromFile @' if tags are being redirected
- # and -tagsFromFile hasn't already been specified
- $setTagsFile = '@';
- push(@newValues, "TagsFromFile=@");
- $setTags{$setTagsFile} or $setTags{$setTagsFile} = [];
- }
- if ($setTagsFile) {
- push @{$setTags{$setTagsFile}}, $_;
- if (/>/) {
- $useMWG = 1 if /^(.*>\s*)?mwg:/si;
- if (/\b(filename|directory)#?$/i) {
- $doSetFileName = 1;
- } elsif (/\bgeotime#?$/i) {
- $addGeotime = '';
- }
- } else {
- $useMWG = 1 if /^([^<]+<\s*(.*\$\{?)?)?mwg:/si;
- if (/^([-\w]+:)*(filename|directory)\b/i) {
- $doSetFileName = 1;
- } elsif (/^([-\w]+:)*geotime\b/i) {
- $addGeotime = '';
- }
- }
- } elsif (/^-(.*)/) {
- push @exclude, $1;
- } else {
- push @tags, $_;
- }
- }
- } elsif ($doGlob and /[*?]/) {
- # glob each filespec if necessary - MK/20061010
- push @files, File::Glob::bsd_glob($_);
- $doGlob = 2;
- } else {
- push @files, $_;
- }
- }
-
- # print help
- unless ((@tags and not $outOpt) or @files or @newValues) {
- if ($doGlob and $doGlob == 2) {
- Warn "No matching files\n";
- $rtnVal = 1;
- next;
- }
- if ($outOpt) {
- Warn "Nothing to write\n";
- $rtnVal = 1;
- next;
- }
- unless ($helped) {
- # catch warnings if we have problems running perldoc
- local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] };
- my $dummy = \*SAVEERR; # avoid "used only once" warning
- 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";
- }
- close STDERR;
- open STDERR, '>&SAVEERR';
- }
- next;
- }
-
- if ($overwriteOrig > 1 and $outOpt) {
- Warn "Can't overwrite in place when -o option is used\n";
- $rtnVal = 1;
- next;
- }
-
- if ($escapeHTML or $json) {
- # must be UTF8 for HTML conversion and JSON output
- $mainTool->Options(Charset => 'UTF8');
- # use Escape option to do our HTML escaping unless XML output
- $mainTool->Options(Escape => 'HTML') if $escapeHTML and not $xml;
- } elsif ($escapeXML and not $xml) {
- $mainTool->Options(Escape => 'XML');
- }
-
- # set up for RDF/XML and JSON output formats
- if ($xml) {
- require Image::ExifTool::XMP; # for EscapeXML()
- my $charset = $mainTool->Options('Charset');
- # standard XML encoding names for supported Charset settings
- # (ref http://www.iana.org/assignments/character-set)
- 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',
- );
- # switch to UTF-8 if we don't have a standard encoding name
- unless ($encoding{$charset}) {
- $charset = 'UTF8';
- $mainTool->Options(Charset => $charset);
- }
- # set file header/trailer for XML output
- $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";
- # extract as a list unless short output format
- $joinLists = 1 if $outFormat > 0;
- $mainTool->Options(List => 1) unless $joinLists;
- $mainTool->Options(Struct => 1) if $structOpt and $outFormat <= 0;
- $showGroup = $allGroup = 1; # always show group 1
- # set binaryOutput flag to 0 or undef (0 = output encoded binary in XML)
- $binaryOutput = ($outFormat > 0 ? undef : 0) if $binaryOutput;
- $showTagID = 'D' if $tabFormat and not $showTagID;
- } elsif ($json) {
- $fileHeader = "[";
- $fileTrailer = "]\n";
- $mainTool->Options(List => 1) unless $joinLists;
- $mainTool->Options(Duplicates => 0) unless defined $showGroup;
- $mainTool->Options(Struct => 1) if $structOpt;
- undef $binaryOutput; # can't currently use -b with -json
- } else {
- $joinLists = 1; # join lists for all other output formats
- }
-
- # change to forward slashes if necessary in all filenames (like CleanFilename)
- if ($hasBackslash{$^O}) {
- tr/\\/\// foreach @files;
- }
-
- # can't do anything if no file specified
- unless (@files) {
- unless ($outOpt) {
- Warn "No file specified\n";
- $rtnVal = 1;
- next;
- }
- push @files, ''; # create file from nothing
- }
-
- # set Verbose and HtmlDump options
- if ($verbose) {
- $disableOutput = 1 unless @tags or @exclude;
- undef $binaryOutput; # disable conflicting option
- if ($html) {
- $html = 2; # flag for html dump
- $mainTool->Options(HtmlDump => $verbose);
- } else {
- $mainTool->Options(Verbose => $verbose);
- }
- } elsif (defined $verbose) {
- # auto-flush output when -v0 is used
- require FileHandle;
- STDOUT->autoflush(1);
- STDERR->autoflush(1);
- }
-
- # validate all tags we're writing
- if (@newValues) {
- # assume -geotime value if -geotag specified without -geotime
- if ($addGeotime) {
- if ($addGeotime =~ /=$/) {
- push @newValues, $addGeotime; # deleting
- } else {
- unless ($setTagsFile and $setTagsFile eq '@') {
- $setTagsFile = '@';
- push(@newValues, "TagsFromFile=@");
- $setTags{$setTagsFile} or $setTags{$setTagsFile} = [];
- }
- push @{$setTags{$setTagsFile}}, $addGeotime;
- }
- $verbose and print qq{Argument "-$addGeotime" is assumed\n};
- }
- my %addDelOpt = ( '+' => 'AddValue', '-' => 'DelValue' ); # add/delete option lookup
- foreach (@newValues) {
- /(.*?)=(.*)/s or next;
- my ($tag, $newVal) = ($1, $2);
- $tag =~ s/\ball\b/\*/ig; # replace 'all' with '*' in tag names
- $newVal eq '' and undef $newVal; # undefined to delete tag
- if ($tag =~ /^(All)?TagsFromFile$/i) {
- Die "Need file name for -tagsFromFile\n" unless defined $newVal;
- ++$isWriting;
- if ($newVal eq '@' or not defined FilenameSPrintf($newVal)) {
- push @dynamicFiles, $newVal unless grep /^$newVal$/, @dynamicFiles;
- next; # set tags from dynamic file later
- }
- unless (-e $newVal) {
- Warn "File '$newVal' does not exist for -tagsFromFile option\n";
- $rtnVal = 1;
- next Command;
- }
- # set specified tags from this file
- $verbose and print("Setting new values from $newVal\n");
- my $info = $mainTool->SetNewValuesFromFile($newVal, @{$setTags{$newVal}});
- if ($info->{Error}) {
- my $err = $info->{Error};
- delete $info->{Error};
- delete $info->{Warning};
- if (%$info) {
- $info->{Warning} = $err; # downgrade to a warning
- } else {
- # no tags set so error must have been fatal
- Warn "Error: $err - $newVal\n";
- $rtnVal = 1;
- next Command;
- }
- }
- if ($info->{Warning}) {
- Warn "Warning: $info->{Warning} - $newVal\n";
- delete $info->{Warning};
- }
- %$info or Warn "No writable tags found - $newVal\n";
- next;
- } elsif ($tag =~ /^PreviewImage$/i) {
- # can't delete preview image, so we can set it to ''
- $newVal = '' unless defined $newVal;
- }
- my %opts = (
- Protected => 1, # allow writing of 'unsafe' tags
- Shift => 0, # shift values if possible instead of adding/deleting
- );
- if ($tag =~ s/<// and defined $newVal) {
- if (defined FilenameSPrintf($newVal)) {
- SlurpFile($newVal, \$newVal) or next;
- } else {
- $tag =~ s/([-+])$// and $opts{$addDelOpt{$1}} = 1;
- # verify that this tag can be written
- my $result = Image::ExifTool::IsWritable($tag);
- if ($result) {
- # add to list of dynamic tag values
- push @dynamicValues, [ $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/([-+])$//) {
- $opts{$addDelOpt{$1}} = 1; # set AddValue or DelValue option
- # set $newVal to '' if deleting nothing
- $newVal = '' if $1 eq '-' and not defined $newVal;
- }
- my ($rtn, $wrn) = $mainTool->SetNewValue($tag, $newVal, %opts);
- ++$isWriting if $rtn;
- $wrn and Warn "Warning: $wrn\n";
- }
- # exclude specified tags
- foreach (@exclude) {
- $mainTool->SetNewValue($_, undef, Replace => 2);
- }
- unless ($isWriting or $outOpt or @tags) {
- Warn "Nothing to do.\n";
- $rtnVal = 1;
- next;
- }
- } elsif (grep /^(\*:)?\*$/, @exclude) {
- Warn "All tags excluded -- nothing to do.\n";
- $rtnVal = 1;
- next;
- }
- if ($isWriting and @tags and not $outOpt) {
- my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], '');
- Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n";
- }
- # save current state of new values if setting values from target file
- # or if we may be translating to a different format
- $mainTool->SaveNewValues() if $outOpt or @dynamicFiles or @dynamicValues;
-
- $multiFile = 1 if @files > 1;
- @exclude and $mainTool->Options(Exclude => \@exclude);
-
- if ($binaryOutput) {
- $outFormat = 99; # shortest possible output format
- $mainTool->Options(PrintConv => 0);
- binmode(STDOUT);
- $binaryStdout = 1;
- # disable conflicting options
- undef $showGroup;
- $html = 0;
- }
-
- # sort by groups to look nicer depending on options
- if (defined $showGroup and not (@tags and $allGroup)) {
- $mainTool->Options(Sort => "Group$showGroup"),
- }
-
- if (defined $outputExt) {
- CleanFilename($outputExt); # make all forward slashes
- # add '.' before output extension if necessary
- $outputExt = ".$outputExt" unless $outputExt =~ /[.%]/;
- }
-
- # determine if we should scan for only writable files
- if ($outOpt) {
- my $type = GetFileType($outOpt);
- if ($type) {
- unless (CanWrite($type)) {
- Warn "Can't write $type files\n";
- $rtnVal = 1;
- next;
- }
- $scanWritable = $type unless CanCreate($type);
- } else {
- $scanWritable = 1;
- }
- $isWriting = 1; # set writing flag
- } elsif ($isWriting) {
- $scanWritable = 1;
- }
-
- # set flag to fix description lengths if necessary
- $fixLen = ($utf8 and $mainTool->Options('Lang') ne 'en' and eval 'require Encode');
-
- # sort input files if specified
- if (@fileOrder) {
- my @allFiles;
- ProcessFiles(undef, \@allFiles);
- my $sortTool = new Image::ExifTool;
- $sortTool->Options(PrintConv => $mainTool->Options('PrintConv'));
- $sortTool->Options(Duplicates => 0);
- my (%sortBy, %isFloat, @rev, $file);
- # save reverse sort flags
- push @rev, (s/^-// ? 1 : 0) foreach @fileOrder;
- foreach $file (@allFiles) {
- my @tags;
- my $info = $sortTool->ImageInfo($file, @fileOrder, \@tags);
- # get values of all tags (or '~' to sort last if not defined)
- foreach (@tags) {
- $_ = $$info{$_};
- defined $_ or $_ = '~', next;
- $isFloat{$_} = 1 if /^[+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;
- }
- $sortBy{$file} = \@tags; # save tag values for each file
- }
- # sort in specified order
- @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; # alphabetically
- } elsif ($isFloat{$u} and $isFloat{$v}) {
- $cmp = $u <=> $v; # numerically
- } else {
- $cmp = $isFloat{$u} ? -1 : 1; # numbers first
- }
- return $rev[$i] ? -$cmp : $cmp if $cmp;
- }
- return $a cmp $b; # default to sort by name
- } @allFiles;
- }
-
- # process all specified files
- ProcessFiles($mainTool);
-
- if ($filtered and not $validFile) {
- Warn "No file with specified extension\n";
- $rtnVal = 1;
- }
-
- # print file trailer if necessary
- print $fileTrailer if $fileTrailer and not $outputExt and not $fileHeader;
-
- # print summary and exit
- my $tot = $count + $countBad;
- my $totWr = $countGoodWr + $countBadWr + $countSameWr + $countCopyWr +
- $countGoodCr + $countBadCr;
- if (($countDir or $totWr or $countFailed or $tot > 1 or $outputExt) and
- not ($binaryStdout or $quiet))
- {
- my $o = (($html or $json or $xml or %printFmt) and not $outputExt) ? \*STDERR : \*STDOUT;
- 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;
- printf($o "%5d image files unchanged\n", $countSameWr) if $countSameWr;
- printf($o "%5d image files copied\n", $countCopyWr) 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>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", $countCreated) if $outputExt;
- }
-
- # set error status if we had any errors
- $rtnVal = 1 if $countBadWr or $countBadCr or $countBad or ($countFailed and not $count);
-
- } # end "Command" loop
-
- Exit $rtnVal; # all done
-
-
- #------------------------------------------------------------------------------
- # Get image information from EXIF data in file
- # Inputs: 0) ExifTool object reference, 1) file name
- sub GetImageInfo($$)
- {
- my ($exifTool, $orig) = @_;
- my (@foundTags, $info, $writeOnly);
- my $file = SourceFile($orig);
- my $pipe = $file;
- my $ind;
-
- if ($doUnzip) {
- # pipe through gzip or bzip2 if necessary
- if ($file =~ /\.gz$/i) {
- $pipe = qq{gzip -dc "$file" |};
- } elsif ($file =~ /\.bz2$/i) {
- $pipe = qq{bzip2 -dc "$file" |};
- }
- }
- # evaluate -if expression for conditional processing
- if (defined $condition) {
- # catch run time errors as well as compile errors
- undef $evalWarning;
- local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] };
-
- my %info;
- # extract information and build expression for evaluation
- my $opts = { Duplicates => 1, Verbose => 0, HtmlDump => 0 };
- # return all tags but explicitly mention tags on command line so
- # requested images will generate the appropriate warnings
- @foundTags = ('*', @tags) if @tags;
- $info = $exifTool->ImageInfo($pipe, \@foundTags, $opts);
- my $cond = $exifTool->InsertTagValues(\@foundTags, $condition, \%info);
-
- #### eval "-if" condition (%info)
- my $result = eval $cond;
-
- $@ and $evalWarning = $@;
- if ($evalWarning) {
- # fail condition if warning is issued
- undef $result;
- if ($verbose) {
- chomp $evalWarning;
- $evalWarning =~ s/ at \(eval .*//s;
- delete $SIG{'__WARN__'};
- Warn "Condition: $evalWarning - $file\n";
- }
- }
- unless ($result) {
- $verbose and print "-------- $file (failed condition)\n";
- ++$countFailed;
- return;
- }
- # can't make use of $info if verbose because we must reprocess
- # the file anyway to generate the verbose output
- undef $info if $verbose;
- }
- my $lineCount = 0;
- my ($fp, $outfile);
- if ($outputExt and $verbose) {
- ($fp, $outfile) = OpenOutputFile($orig);
- $fp or ++$countBad, return;
- $tmpText = $outfile; # deletes file if we exit prematurely
- $exifTool->Options(TextOut => $fp);
- }
-
- if ($isWriting) {
- print "======== $file\n" if defined $verbose;
- my $success = SetImageInfo($exifTool, $orig);
- $info = $exifTool->GetInfo('Warning', 'Error');
- PrintErrors($exifTool, $info, $file);
- # close output text file if necessary
- if ($outfile) {
- undef $tmpText;
- close($fp);
- $exifTool->Options(TextOut => \*STDOUT);
- if ($info->{Error}) {
- unlink $outfile; # erase bad file
- } else {
- ++$countCreated;
- }
- }
- return;
- } else {
- my $tag;
- # extract EXIF information from this file
- unless ($file eq '-' or -e $file) {
- Warn "File not found: $file\n";
- $outfile and close($fp), undef($tmpText), unlink($outfile);
- ++$countBad;
- return;
- }
- unless ($binaryOutput or $outputExt or %printFmt or $html > 1) {
- if ($html) {
- require Image::ExifTool::HTML;
- my $f = Image::ExifTool::HTML::EscapeHTML($file);
- print "<!-- $f -->\n";
- } elsif (not ($json or $xml)) {
- print "======== $file\n" if $multiFile and not $quiet;
- }
- }
- if ($info) {
- # get the information we wanted
- if (@tags and not %printFmt) {
- @foundTags = @tags;
- $info = $exifTool->GetInfo(\@foundTags);
- }
- } else {
- # request specified tags unless using print format option
- my $oldDups = $exifTool->Options('Duplicates');
- if (%printFmt) {
- $exifTool->Options(Duplicates => 1);
- } else {
- @foundTags = @tags;
- }
- # extract the information
- $info = $exifTool->ImageInfo($pipe, \@foundTags);
- $exifTool->Options(Duplicates => $oldDups);
- }
- # all done now if we already wrote output text file (ie. verbose option)
- if ($fp) {
- if ($outfile) {
- $exifTool->Options(TextOut => \*STDOUT);
- undef $tmpText;
- if ($info->{Error}) {
- close($fp);
- unlink $outfile; # erase bad file
- } else {
- ++$lineCount; # output text file (likely) is not empty
- }
- }
- if ($info->{Error}) {
- Warn "Error: $info->{Error} - $file\n";
- ++$countBad;
- return;
- }
- }
- }
- # print warnings to stderr if using binary output
- # (because we are likely ignoring them and piping stdout to file)
- # or if there is none of the requested information available
- if ($binaryOutput or not %$info) {
- my $errs = $exifTool->GetInfo('Warning', 'Error');
- PrintErrors($exifTool, $errs, $file);
- }
-
- # open output file (or stdout if no output file) if not done already
- unless ($fp) {
- ($fp, $outfile) = OpenOutputFile($orig);
- $fp or ++$countBad, return;
- }
-
- # print the results for this file
- if (%printFmt) {
- # output using print format file (-p) option
- my ($type, $doc, $grp);
- undef $fileTrailer;
- # repeat for each embedded document if necessary
- my $lastDoc = $exifTool->Options('ExtractEmbedded') ? $exifTool->{DOC_COUNT} : 0;
- foreach $type ('HEAD', 'BODY', 'TAIL') {
- my $prf = $printFmt{$type} or next;
- for ($doc=0; $doc<=$lastDoc; ++$doc) {
- if ($lastDoc) {
- if ($doc) {
- last unless $type eq 'BODY'; # only repeat BODY lines
- $grp = "Doc$doc:";
- } else {
- $grp = 'Main:';
- }
- # change tag groups to print next document by adding "Main:" or "Doc#:"
- # to all tags which don't already start with a family 3 group name
- $prf = [ @{$printFmt{$type}} ];
- s/((^|[^\$])(\$\$)*\$\{?)((?!(Main|Doc\d+):)[\w])/$1$grp$4/ig foreach @$prf;
- }
- my @lines;
- foreach (@$prf) {
- my $line = $exifTool->InsertTagValues(\@foundTags, $_, 'Warn');
- push @lines, $line if defined $line;
- }
- $lineCount += scalar @lines;
- if ($type eq 'TAIL') {
- $fileTrailer = '' unless defined $fileTrailer;
- $fileTrailer .= join '', @lines;
- } elsif (@lines) {
- print $fp @lines;
- }
- }
- }
- delete $printFmt{HEAD} unless $outfile; # print header only once per output file
- my $errs = $exifTool->GetInfo('Warning', 'Error');
- PrintErrors($exifTool, $errs, $file);
- } elsif (not $disableOutput) {
- # print file header (only once)
- if ($fileHeader) {
- print $fp $fileHeader;
- undef $fileHeader unless $outputExt;
- }
- my ($tag, $line, %noDups);
- if ($html) {
- print $fp "<table>\n";
- } elsif ($xml) {
- my $f = $file;
- CleanXML(\$f);
- print $fp "\n<rdf:Description rdf:about='$f'";
- print $fp "\n xmlns:et='http://ns.exiftool.ca/1.0/'";
- print $fp " et:toolkit='Image::ExifTool $Image::ExifTool::VERSION'";
- # define namespaces for all tag groups
- my %groups;
- foreach $tag (@foundTags) {
- my ($grp, $grp1) = $exifTool->GetGroup($tag);
- unless ($grp1) {
- next unless $forcePrint;
- $grp = $grp1 = 'Unknown';
- }
- next if $groups{$grp1};
- $groups{$grp1} = 1;
- # include family 0 and 1 groups in URI except for internal tags
- # (this will put internal tags in the "XML" group on readback)
- $grp .= "/$grp1" unless $grp eq $grp1 and
- $grp =~ /^(ExifTool|File|Composite|Unknown)$/;
- print $fp "\n xmlns:$grp1='http://ns.exiftool.ca/$grp/1.0/'";
- }
- print $fp '>' if $outFormat < 1; # finish rdf:Description token unless short format
- $ind = $outFormat >= 0 ? ' ' : ' ';
- } elsif ($json) {
- print $fp ",\n" if $comma;
- print $fp qq({\n "SourceFile": ), EscapeJSON($file, 1);
- $comma = 1;
- $ind = (defined $showGroup and not $allGroup) ? ' ' : ' ';
- }
- # suppress duplicates manually in JSON and short XML output
- my $noDups = ($json or ($xml and $outFormat > 0));
- my $printConv = $mainTool->Options('PrintConv');
- my $lastGroup = '';
- foreach $tag (@foundTags) {
- my $tagName = GetTagName($tag);
- my $group;
- # make sure this tag has a value
- my $val = $info->{$tag};
- if (not defined $val) {
- # ignore tags that weren't found unless necessary
- next if $binaryOutput;
- next unless $forcePrint;
- $val = '-'; # forced to print all tag values
- }
- if (defined $showGroup) {
- $group = $exifTool->GetGroup($tag, $showGroup);
- # look ahead to see if this tag may suppress a priority tag in
- # the same group, and if so suppress this tag instead
- next if $noDups and $tag =~ /(\S+) / and defined $$info{$1} and
- $group eq $exifTool->GetGroup($1, $showGroup);
- $group = 'Unknown' if not $group and ($xml or $json);
- unless ($allGroup) {
- if ($lastGroup ne $group) {
- if ($html) {
- my $cols = 1;
- ++$cols if $outFormat==0 or $outFormat==1;
- ++$cols if $showTagID;
- print $fp "<tr><td colspan=$cols bgcolor='#dddddd'>$group</td></tr>\n";
- } elsif ($json) {
- print $fp "\n }" if $lastGroup;
- print $fp ',' if $lastGroup or $comma;
- print $fp qq(\n "$group": {);
- undef $comma;
- undef %noDups; # allow duplicate names in different groups
- } else {
- print $fp "---- $group ----\n";
- }
- $lastGroup = $group;
- }
- undef $group; # undefine so we don't print it below
- }
- }
-
- ++$lineCount; # we are printing something meaningful
-
- if ($binaryOutput) {
- # translate scalar reference to actual binary data
- if (ref $val eq 'SCALAR') {
- $val = $$val;
- } elsif (ref $val eq 'ARRAY') {
- $val = join "\n", @$val;
- }
- print $fp $val;
- next;
- }
- if (ref $val eq 'SCALAR') {
- my $bOpt = $json ? '' : ', use -b option to extract';
- if ($$val =~ /^Binary data/) {
- $val = "($$val$bOpt)";
- } elsif (defined $binaryOutput) { # this happens when -X is used with -b
- # avoid writing Protected binary tags (ie. data blocks) [insider information]
- next if $exifTool->{TAG_INFO}{$tag}{Protected};
- $val = $$val;
- } else {
- $val = '(Binary data ' . length($$val) . " bytes$bOpt)";
- }
- } elsif (ref $val eq 'ARRAY' and $joinLists and not ref $$val[0]) {
- # join arrays of simple values
- $val = join($listSep, @$val) if ref $val eq 'ARRAY';
- }
-
- # get description if we need it (use tag name if $outFormat > 0)
- my $desc = $outFormat > 0 ? $tagName : $exifTool->GetDescription($tag);
-
- if ($xml) {
- # RDF/XML output format
- my $tok = "$group:$tagName";
- if ($outFormat > 0) {
- if ($escapeHTML) {
- $val =~ tr/\0-\x08\x0b\x0c\x0e-\x1f/./;
- Image::ExifTool::XMP::FixUTF8(\$val) if $utf8;
- $val = Image::ExifTool::HTML::EscapeHTML($val);
- } else {
- CleanXML(\$val);
- }
- unless ($noDups{$tok}) {
- print $fp "\n $tok='$val'";
- # XML does not allow duplicate attributes
- $noDups{$tok} = 1;
- }
- next;
- }
- my ($xtra, $valNum, $descClose);
- if ($showTagID) {
- my ($id, $lang) = $exifTool->GetTagID($tag);
- if ($id =~ /^\d+$/) {
- $id = sprintf("0x%.4x", $id) if $showTagID eq 'H';
- } else {
- $id = Image::ExifTool::XMP::FullEscapeXML($id);
- }
- $xtra = " et:id='$id'";
- $xtra .= " xml:lang='$lang'" if $lang;
- } else {
- $xtra = '';
- }
- if ($tabFormat) {
- my $table = $exifTool->GetTableName($tag);
- my $index = $exifTool->GetTagIndex($tag);
- $xtra .= " et:table='$table'";
- $xtra .= " et:index='$index'" if defined $index;
- }
- my $lastVal = $val;
- for ($valNum=0; $valNum<2; ++$valNum) {
- $val = FormatXML($val, $ind, $group);
- if ($outFormat >= 0) {
- # normal output format (note: this will give
- # non-standard RDF/XML if there are any attributes)
- print $fp "\n <$tok$xtra$val</$tok>";
- last;
- } elsif ($valNum == 0) {
- CleanXML(\$desc);
- if ($xtra) {
- print $fp "\n <$tok>";
- print $fp "\n <rdf:Description$xtra>";
- $descClose = "\n </rdf:Description>";
- } else {
- print $fp "\n <$tok rdf:parseType='Resource'>";
- $descClose = '';
- }
- # print tag Description
- print $fp "\n <et:desc>$desc</et:desc>";
- if ($printConv) {
- # print PrintConv value
- print $fp "\n <et:prt$val</et:prt>";
- $val = $exifTool->GetValue($tag, 'ValueConv');
- # go back to print ValueConv value only if different
- next if ref $val ne 'SCALAR' and $val ne $lastVal;
- print $fp "$descClose\n </$tok>";
- last;
- }
- }
- # print ValueConv value
- print $fp "\n <et:val$val</et:val>";
- print $fp "$descClose\n </$tok>";
- last;
- }
- next;
- } elsif ($json) {
- # JSON output format
- my $tok = $allGroup ? "$group:$tagName" : $tagName;
- next if $noDups{$tok};
- $noDups{$tok} = 1;
- print $fp ',' if $comma;
- print $fp qq(\n$ind"$tok": );
- PrintJSON($fp, $val, $ind);
- $comma = 1;
- next;
- }
- my $id;
- if ($showTagID) {
- $id = $exifTool->GetTagID($tag);
- if ($id =~ /^(\d+)(\.\d+)?$/) { # only print numeric ID's
- $id = sprintf("0x%.4x", $1) if $showTagID eq 'H';
- } else {
- $id = '-';
- }
- }
-
- # translate unprintable chars in value and remove trailing spaces
- $val =~ tr/\x01-\x1f\x7f/./;
- $val =~ s/\x00//g;
- $val =~ s/\s+$//;
-
- if ($html) {
- print $fp "<tr>";
- print $fp "<td>$group</td>" if defined $group;
- print $fp "<td>$id</td>" if $showTagID;
- print $fp "<td>$desc</td>" if $outFormat <= 1;
- print $fp "<td>$val</td></tr>\n";
- } else {
- my $buff = '';
- if ($tabFormat) {
- $buff = "$group\t" if defined $group;
- $buff .= "$id\t" if $showTagID;
- if ($outFormat <= 1) {
- $buff .= "$desc\t$val\n";
- } elsif (defined $line) {
- $line .= "\t$val";
- } else {
- $line = $val;
- }
- } elsif ($outFormat < 0) { # long format
- $buff = "[$group] " if defined $group;
- $buff .= "$id " if $showTagID;
- $buff .= "$desc\n $val\n";
- } elsif ($outFormat == 0 or $outFormat == 1) {
- my $wid;
- my $len = 0;
- if (defined $group) {
- $buff = sprintf("%-15s ", "[$group]");
- $len = 16;
- }
- if ($showTagID) {
- $wid = ($showTagID eq 'D') ? 5 : 6;
- $len += $wid + 1;
- ($wid = $len - length($buff) - 1) < 1 and $wid = 1;
- $buff .= sprintf "%${wid}s ", $id;
- }
- $wid = 32 - (length($buff) - $len);
- # pad description to a constant length
- # (get actual character length when using alternate languages
- # because these descriptions may contain UTF8-encoded characters)
- my $padLen = $wid - length($fixLen ? Encode::decode_utf8($desc) : $desc);
- $padLen = 0 if $padLen < 0;
- $buff .= $desc . (' ' x $padLen) . ": $val\n";
- } elsif ($outFormat == 2) {
- $buff = "[$group] " if defined $group;
- $buff .= "$id " if $showTagID;
- $buff .= "$tagName: $val\n";
- } else {
- $buff = "$group " if defined $group;
- $buff .= "$id " if $showTagID;
- $buff .= "$val\n";
- }
- print $fp $buff;
- }
- }
- if ($html) {
- print $fp "</table>\n";
- } elsif ($xml) {
- # close rdf:Description element
- print $fp $outFormat < 1 ? "\n</rdf:Description>\n" : "/>\n";
- } elsif ($json) {
- print $fp "\n }" if $lastGroup;
- print $fp "\n}";
- $comma = 1;
- } elsif ($tabFormat and $outFormat > 1) {
- print $fp "$line\n" if defined $line;
- }
- }
- if ($outfile) {
- print $fp $fileTrailer if $fileTrailer; # write file trailer
- close($fp);
- if ($lineCount) {
- ++$countCreated;
- } else {
- unlink $outfile; # don't keep empty output files
- }
- }
- ++$count;
- }
-
- #------------------------------------------------------------------------------
- # Set information in file
- # Inputs: 0) ExifTool object reference
- # 1) original source file name ('' to create from scratch)
- # Returns: true on success
- sub SetImageInfo($$)
- {
- my ($exifTool, $orig) = @_;
- my ($outfile, $restored, $isTemporary, $isStdout);
- my $file = SourceFile($orig);
- my $infile = $file; # save infile in case we change it again
- my $tagsFromSrc;
-
- undef $tmpFile; # make sure this isn't defined
-
- # clear any existing errors or warnings since we check these on return
- delete $exifTool->{VALUE}->{Error};
- delete $exifTool->{VALUE}->{Warning};
-
- # first, try to determine our output file name so we can return quickly
- # if it already exists (note: this test must be delayed until after we
- # set tags from dynamic files if writing FileName or Directory)
- if (defined $outOpt) {
- if ($outOpt eq '-') {
- $isStdout = 1;
- } else {
- $outfile = FilenameSPrintf($outOpt, $orig);
- if ($outfile eq '') {
- Warn "Can't create file with zero-length name from $orig\n";
- ++$countBadCr;
- return 0;
- }
- if (-d $outfile or $outfile =~ /\/$/) {
- $outfile .= '/' unless $outfile =~ /\/$/;
- my $name = $file;
- $name =~ s/.*\///; # remove directory name
- $outfile .= $name;
- } else {
- my $srcType = GetFileType($file) || '';
- my $outType = GetFileType($outfile);
- if ($outType and ($srcType ne $outType or $outType eq 'ICC')) {
- unless (CanCreate($outfile)) {
- my $what = $srcType ? 'other types' : 'scratch';
- WarnOnce "Error: Can't create $outType files from $what\n";
- ++$countBadCr;
- return 0;
- }
- if ($file ne '') {
- # restore previous new values unless done already
- $exifTool->RestoreNewValues();
- $restored = 1;
- # translate to this type by setting specified tags from file
- my @setTags = @tags;
- foreach (@exclude) {
- push @setTags, "-$_";
- }
- # force some tags to be copied for certain file types
- my %forceCopy = (
- ICC => 'ICC_Profile',
- VRD => 'CanonVRD',
- );
- push @setTags, $forceCopy{$outType} if $forceCopy{$outType};
- # assume "-tagsFromFile @" unless -tagsFromFile already specified
- # (%setTags won't be empty if -tagsFromFile used)
- if (not %setTags or (@setTags and not $setTags{'@'})) {
- return 0 unless DoSetFromFile($exifTool, $file, \@setTags);
- } elsif (@setTags) {
- # add orphaned tags to existing "-tagsFromFile @" for this file only
- push @setTags, @{$setTags{'@'}};
- $tagsFromSrc = \@setTags;
- }
- # all done with source file -- create from meta information alone
- $file = '';
- }
- }
- }
- $outfile = NextUnusedFilename($outfile);
- if (-e $outfile and not $doSetFileName) {
- Warn "Error: '$outfile' already exists - $infile\n";
- ++$countBadWr;
- return 0;
- }
- }
- } elsif ($file eq '-') {
- $isStdout = 1;
- }
- # set new values with dynamic file names
- if (@dynamicValues) {
- # restore previous values if necessary
- $exifTool->RestoreNewValues() unless $restored;
- $restored = 1;
- my ($dyVal, $buff);
- foreach $dyVal (@dynamicValues) {
- my $dyfile = FilenameSPrintf($$dyVal[1], $orig);
- my ($rtn, $wrn);
- if (defined $dyfile and SlurpFile($dyfile, \$buff)) {
- $verbose and print "Reading $$dyVal[0] from $dyfile\n";
- ($rtn, $wrn) = $mainTool->SetNewValue($$dyVal[0], $buff, %{$$dyVal[2]});
- $wrn and Warn "$wrn\n";
- }
- # remove this tag if we couldn't set it properly
- $rtn or $mainTool->SetNewValue($$dyVal[0], undef, Replace => 2);
- }
- }
- # set tags from destination file if required
- if (@dynamicFiles) {
- # restore previous values if necessary
- $exifTool->RestoreNewValues() unless $restored;
- my $dyFile;
- foreach $dyFile (@dynamicFiles) {
- my ($fromFile, $setTags);
- if ($dyFile eq '@') {
- $fromFile = $orig;
- $setTags = $tagsFromSrc || $setTags{$dyFile};
- } else {
- $fromFile = FilenameSPrintf($dyFile, $orig);
- ++$countBadWr, return 0 unless defined $fromFile;
- $setTags = $setTags{$dyFile};
- }
- # set new values values from file
- return 0 unless DoSetFromFile($exifTool, $fromFile, $setTags);
- }
- }
- if ($isStdout) {
- # write to STDOUT
- $outfile = \*STDOUT;
- binmode(STDOUT);
- $binaryStdout = 1;
- } else {
- # determine what our output file name should be
- my $newFileName = $exifTool->GetNewValues('FileName');
- my $newDir = $exifTool->GetNewValues('Directory');
- if ($newFileName or $newDir or ($doSetFileName and defined $outfile)) {
- if ($newFileName) {
- $newFileName = FilenameSPrintf($newFileName, $orig);
- if (defined $outfile) {
- $outfile = Image::ExifTool::GetNewFileName($orig, $outfile) if $orig ne '';
- $outfile = Image::ExifTool::GetNewFileName($outfile, $newFileName);
- } elsif ($orig ne '') {
- $outfile = Image::ExifTool::GetNewFileName($orig, $newFileName);
- }
- }
- if ($newDir) {
- $newDir = FilenameSPrintf($newDir, $orig);
- $outfile = Image::ExifTool::GetNewFileName(defined $outfile ? $outfile : $orig, $newDir);
- }
- $outfile = NextUnusedFilename($outfile, $infile);
- if (-e $outfile) {
- if ($infile ne $outfile) {
- Warn "Error: '$outfile' already exists - $infile\n";
- ++$countBadWr;
- return 0;
- }
- undef $outfile; # not changing the file name after all
- }
- }
- if (defined $outfile) {
- $verbose and print "'$infile' --> '$outfile'\n";
- # create output directory if necessary
- CreateDirectory($outfile);
- # set temporary file (automatically erased on abnormal exit)
- $tmpFile = $outfile if defined $outOpt;
- }
- unless (defined $tmpFile) {
- # count the number of tags and pseudo-tags we are writing
- my ($numSet, $numPseudo) = $exifTool->CountNewValues();
- if (-e $file) {
- unless ($numSet) {
- # no need to write if no tags set
- print "Nothing changed in $file\n" if defined $verbose;
- ++$countSameWr;
- return 1;
- }
- } elsif (CanCreate($file)) {
- if ($numSet == $numPseudo) {
- # no need to write if no real tags
- Warn("Error: Nothing to write - $file\n");
- ++$countBadWr;
- return 0;
- }
- unless (defined $outfile) {
- # create file from scratch
- $outfile = $file;
- $file = '';
- }
- } else {
- # file doesn't exist, and we can't create it
- Warn("Error: File not found - $file\n");
- ++$countBadWr;
- return 0;
- }
- # quickly rename file and/or set file date if this is all we are doing
- if ($numSet == $numPseudo) {
- my $r1 = $exifTool->SetFileModifyDate($file);
- my $r2 = 0;
- $r2 = $exifTool->SetFileName($file, $outfile) if defined $outfile;
- if ($r1 > 0 or $r2 > 0) {
- ++$countGoodWr;
- } elsif ($r1 < 0 or $r2 < 0) {
- ++$countBadWr;
- return 0;
- } else {
- ++$countSameWr;
- }
- return 1;
- }
- unless (defined $outfile) {
- # write to a truly temporary file
- $outfile = "${file}_exiftool_tmp";
- if (-e $outfile) {
- Warn("Error: Temporary file already exists: $outfile\n");
- ++$countBadWr;
- return 0;
- }
- $isTemporary = 1;
- }
- # new output file is temporary until we know it has been written properly
- $tmpFile = $outfile;
- }
- }
- # rewrite the file
- my $success = $exifTool->WriteInfo($file, $outfile);
-
- # get file time if preserving it
- my ($modTime, $accTime);
- if ($preserveTime and $success and -e $file) {
- $modTime = $^T - (-M $file) * (24 * 3600);
- $accTime = $^T - (-A $file) * (24 * 3600);
- }
- if ($success == 1) {
- # preserve the original file times
- if (defined $tmpFile) {
- if (-e $file) {
- if ($preserveTime and not utime($accTime, $modTime, $tmpFile)) {
- Warn "Error setting file time - $file\n";
- }
- if ($isTemporary) {
- # move original out of the way
- my $original = "${file}_original";
- if (not $overwriteOrig and not -e $original) {
- # rename the file and check again to be sure the file doesn't exist
- # (in case, say, the filesystem truncated the file extension)
- if (not rename($file, $original) or -e $file) {
- Die "Error renaming $file\n";
- }
- }
- if ($overwriteOrig > 1) {
- # copy temporary file over top of original to preserve attributes
- my ($err, $buff);
- my $newFile = $tmpFile;
- open(NEW_FILE, "<$newFile") or Die "Error opening $newFile\n";
- binmode(NEW_FILE);
- # must not erase temporary file now if interrupted
- undef $tmpFile;
- if (open(ORIG_FILE, ">$file")) {
- $tmpFile = $file; # original is now our temporary file
- binmode(ORIG_FILE);
- while (read(NEW_FILE, $buff, 65536)) {
- print ORIG_FILE $buff or $err = 1;
- }
- close(NEW_FILE);
- close(ORIG_FILE) or $err = 1;
- if ($err) {
- Warn "Couldn't overwrite in place - $file\n";
- undef $tmpFile;
- unless (rename($newFile, $file) or
- (unlink $file and rename($newFile, $file)))
- {
- Die "Error renaming $newFile to $file\n";
- }
- } else {
- undef $tmpFile;
- unlink $newFile;
- if ($preserveTime and not utime($accTime, $modTime, $file)) {
- Warn "Error setting file time - $file\n";
- }
- }
- ++$countGoodWr;
- } else {
- close(NEW_FILE);
- Warn "Error opening $file for writing\n";
- unlink $newFile;
- ++$countBadWr;
- }
- # simply rename temporary file to replace original
- # (if we didn't already rename it to add "_original")
- } elsif (rename($tmpFile, $file)) {
- ++$countGoodWr;
- } else {
- my $newFile = $tmpFile;
- undef $tmpFile; # (avoid deleting file if we get interrupted)
- # unlink may fail if already renamed or no permission
- if (not unlink($file)) {
- Warn "Error renaming temporary file to $file\n";
- unlink $newFile;
- ++$countBadWr;
- } elsif (not rename($newFile, $file)) {
- Warn "Error renaming temporary file to $file\n";
- # (don't delete tmp file now because it is all we have left)
- ++$countBadWr;
- } else {
- ++$countGoodWr;
- }
- }
- } elsif ($overwriteOrig) {
- # erase original file
- unlink $file or Warn "Error erasing original $file\n";
- ++$countGoodWr;
- } else {
- ++$countGoodCr;
- }
- } else {
- # this file was created from scratch, not edited
- ++$countGoodCr;
- }
- } else {
- ++$countGoodWr;
- }
- } elsif ($success) {
- if ($isTemporary) {
- # just erase the temporary file since no changes were made
- unlink $tmpFile;
- ++$countSameWr;
- } else {
- if (defined $accTime and not utime($accTime, $modTime, $outfile)) {
- Warn "Error setting file time - $file\n";
- }
- if ($overwriteOrig) {
- unlink $file or Warn "Error erasing original $file\n";
- }
- ++$countCopyWr;
- }
- print "Nothing changed in $file\n" if defined $verbose;
- } else {
- unlink $tmpFile if defined $tmpFile;
- ++$countBadWr;
- }
- undef $tmpFile;
- return $success;
- }
-
- #------------------------------------------------------------------------------
- # Clean string for XML (also removes invalid control chars and malformed UTF-8)
- # Inputs: 0) string ref
- # Returns: nothing, but input string is escaped
- sub CleanXML($)
- {
- my $strPt = shift;
- # translate control characters that are invalid in XML
- $$strPt =~ tr/\0-\x08\x0b\x0c\x0e-\x1f/./;
- # fix malformed UTF-8 characters
- Image::ExifTool::XMP::FixUTF8($strPt) if $utf8;
- # escape necessary characters for XML
- $$strPt = Image::ExifTool::XMP::EscapeXML($$strPt);
- }
-
- #------------------------------------------------------------------------------
- # Encode string for XML
- # Inputs: 0) string ref
- # Returns: encoding used (and input string is translated)
- sub EncodeXML($)
- {
- my $strPt = shift;
- if ($$strPt =~ /[\0-\x08\x0b\x0c\x0e-\x1f]/ or
- ($utf8 and Image::ExifTool::XMP::IsUTF8($strPt) < 0))
- {
- # encode binary data and non-UTF8 with special characters as base64
- $$strPt = Image::ExifTool::XMP::EncodeBase64($$strPt);
- return 'http://www.w3.org/2001/XMLSchema#base64Binary'; #ATV
- } elsif ($escapeHTML) {
- $$strPt = Image::ExifTool::HTML::EscapeHTML($$strPt);
- } else {
- $$strPt = Image::ExifTool::XMP::EscapeXML($$strPt);
- }
- return ''; # not encoded
- }
-
- #------------------------------------------------------------------------------
- # Format value for XML output
- # Inputs: 0) value, 1) indentation, 2) group
- # Returns: formatted value
- sub FormatXML($$$)
- {
- local $_;
- my ($val, $ind, $grp) = @_;
- my $gt = '>';
- if (ref $val eq 'ARRAY') {
- # convert ARRAY into an rdf:Bag
- my $val2 = "\n$ind <rdf:Bag>";
- foreach (@$val) {
- $val2 .= "\n$ind <rdf:li" . FormatXML($_, "$ind ", $grp) . "</rdf:li>";
- }
- $val = "$val2\n$ind </rdf:Bag>\n$ind";
- } elsif (ref $val eq 'HASH') {
- $gt = " rdf:parseType='Resource'>";
- my $val2 = '';
- foreach (sort keys %$val) {
- my $tok = $grp . ':' . $_;
- $val2 .= "\n$ind <$tok" . FormatXML($$val{$_}, "$ind ", $grp) . "</$tok>";
- }
- $val = "$val2\n$ind";
- } else {
- my $enc = EncodeXML(\$val);
- $gt = " rdf:datatype='$enc'>\n" if $enc; #ATV
- }
- return $gt . $val;
- }
-
- #------------------------------------------------------------------------------
- # Escape string for JSON
- # Inputs: 0) string, 1) flag to force numbers to be quoted too
- # Returns: Escaped string (quoted if necessary)
- sub EscapeJSON($;$)
- {
- my ($str, $quote) = @_;
- unless ($quote) {
- # JSON boolean (true or false)
- return lc($str) if $str =~ /^(true|false)$/i;
- # JSON number (see json.org for numerical format)
- return $str if $str =~ /^-?(\d|[1-9]\d+)(\.\d+)?(e[-+]?\d+)?$/i;
- }
- # escape special characters
- $str =~ s/(["\t\n\r\\])/\\$jsonChar{$1}/sg;
- # escape other control characters with \u
- $str =~ s/([\0-\x1f])/sprintf("\\u%.4X",ord $1)/sge;
- return '"' . $str . '"'; # return the quoted string
- }
-
- #------------------------------------------------------------------------------
- # Print JSON value
- # Inputs: 0) file reference, 1) value, 2) indentation
- sub PrintJSON($$$)
- {
- local $_;
- my ($fp, $val, $ind) = @_;
- my $comma;
- if (not ref $val) {
- print $fp EscapeJSON($val);
- } elsif (ref $val eq 'ARRAY') {
- if ($joinLists and not ref $$val[0]) {
- print $fp EscapeJSON(join($listSep,@$val));
- } else {
- print $fp '[';
- foreach (@$val) {
- print $fp ',' if $comma;
- PrintJSON($fp, $_, $ind);
- $comma = 1,
- }
- print $fp ']',
- }
- } elsif (ref $val eq 'HASH') {
- print $fp '{';
- foreach (sort keys %$val) {
- print $fp ',' if $comma;
- print $fp qq(\n$ind "$_": );
- PrintJSON($fp, $$val{$_}, "$ind ");
- $comma = 1,
- }
- print $fp "\n$ind}",
- } elsif (ref $val eq 'SCALAR') {
- if ($binaryOutput) {
- print $fp EscapeJSON($val);
- } else {
- print $fp '(Binary data ' . length($$val) . ' bytes)';
- }
- } else {
- print $fp '<err>';
- }
- }
-
- #------------------------------------------------------------------------------
- # Set new values from file
- # Inputs: 0) exiftool ref, 1) filename, 2) reference to list of values to set
- # Returns: 0 on error (and increments $countBadWr)
- sub DoSetFromFile($$$)
- {
- my ($exifTool, $file, $setTags) = @_;
- $verbose and print "Setting new values from $file\n";
- my $info = $exifTool->SetNewValuesFromFile($file, @$setTags);
- PrintErrors($exifTool, $info, $file);
- $info->{Error} and ++$countBadWr, return 0;
- return 1;
- }
-
- #------------------------------------------------------------------------------
- # Translate backslashes to forward slashes in filename if necessary
- # Inputs: 0) Filename
- # Returns: nothing, but changes filename if necessary
- sub CleanFilename($)
- {
- $_[0] =~ tr/\\/\// if $hasBackslash{$^O};
- }
-
- #------------------------------------------------------------------------------
- # process files in our @files list
- # Inputs: 0) ExifTool ref, 1) list ref to just return full file names
- # Notes: arg 0 is not used if arg 1 is defined
- sub ProcessFiles($;$)
- {
- my ($exifTool, $list) = @_;
- my $file;
- foreach $file (@files) {
- if (-d $file) {
- $multiFile = $validFile = 1;
- ScanDir($mainTool, $file, $list);
- } elsif ($filterFlag and not AcceptFile($file)) {
- if (-e $file) {
- $filtered = 1;
- $verbose and print "-------- $file (wrong extension)\n";
- } else {
- Warn "File not found: $file\n";
- $rtnVal = 1;
- }
- } else {
- $validFile = 1;
- $list and push(@$list, $file), next;
- GetImageInfo($exifTool, $file);
- }
- }
- }
-
- #------------------------------------------------------------------------------
- # Scan directory for image files
- # Inputs: 0) ExifTool ref, 1) directory name, 2) list ref to return file names
- sub ScanDir($$;$)
- {
- my ($exifTool, $dir, $list) = @_;
- opendir(DIR_HANDLE, $dir) or Warn("Error opening directory $dir\n"), return;
- my @fileList = readdir(DIR_HANDLE);
- closedir(DIR_HANDLE);
-
- my $file;
- $dir =~ /\/$/ or $dir .= '/';
- foreach $file (@fileList) {
- my $path = "$dir$file";
- if (-d $path) {
- next if $file =~ /^\./; # ignore dirs starting with "."
- next if grep /^$file$/, @ignore;
- $recurse and ScanDir($exifTool, $path, $list);
- next;
- }
- # apply rules from -ext options
- my $accepted;
- if ($filterFlag) {
- $accepted = AcceptFile($file) or next;
- # must be specifically accepted to bypass selection logic
- $accepted &= 0x01;
- }
- unless ($accepted) {
- # read/write this file if it is a recognized type
- if ($scanWritable) {
- if ($scanWritable eq '1') {
- next unless CanWrite($file);
- } else {
- my $type = GetFileType($file);
- next unless defined $type and $type eq $scanWritable;
- }
- } elsif (not GetFileType($file)) {
- next unless $doUnzip;
- next unless $file =~ /\.(gz|bz2)$/i;
- }
- }
- $list and push(@$list, $path), next;
- GetImageInfo($exifTool, $path);
- }
- ++$countDir;
- }
-
- #------------------------------------------------------------------------------
- # Add print format entry
- # Inputs: 0) expression string
- sub AddPrintFormat($)
- {
- my $expr = shift;
- my $type;
- if ($expr =~ /^#/) {
- $expr =~ s/^#\[(HEAD|BODY|TAIL)\]// or return; # ignore comments
- $type = $1;
- } else {
- $type = 'BODY';
- }
- $printFmt{$type} or $printFmt{$type} = [ ];
- push @{$printFmt{$type}}, $expr;
- # add to list of requested tags
- push @tags, $expr =~ /\$((?:[-\w]+:)*[-\w]+#?)/g;
- # tags contained in braces too...
- push @tags, $expr =~ /\$\{((?:[-\w]+:)*[-\w]+#?)\}/g;
- }
-
- #------------------------------------------------------------------------------
- # Load print format file
- # Inputs: 0) file name
- # - saves lines of file to %printFmt list
- # - adds tag names to @tags list
- sub LoadPrintFormat($)
- {
- my $arg = shift || Die "Must specify file or expression for -p option\n";
- if (-f $arg and open(FMT_FILE, "<$arg")) {
- foreach (<FMT_FILE>) {
- AddPrintFormat($_);
- }
- close(FMT_FILE);
- } else {
- AddPrintFormat($arg . "\n");
- }
- }
-
- #------------------------------------------------------------------------------
- # A sort of sprintf for filenames
- # Inputs: 0) format string (%d=dir, %f=file name, %e=ext),
- # 1) source filename or undef to test format string
- # Returns: new filename or undef on error (or if no file and fmt contains token)
- sub FilenameSPrintf($;$)
- {
- my ($fmt, $file) = @_;
- # return format string straight away if no tokens
- return $fmt unless $fmt =~ /%[-+]?\d*\.?\d*[lu]?[dfe]/;
- return undef unless defined $file;
- CleanFilename($file); # make sure we are using forward slashes
- # split filename into directory, file, extension
- my @parts = ($file =~ /^(.*?)([^\/]*?)(\.[^.\/]*)?$/);
- $parts[2] = $parts[2] ? substr($parts[2], 1) : '';
- @parts or Warn("Error: Bad pattern match for file $file\n"), return undef;
- my %part;
- foreach ('d','f','e') {
- $part{$_} = shift @parts;
- }
- my ($filename, $pos) = ('', 0);
- while ($fmt =~ /(%([-+]?)(\d*)\.?(\d*)([lu]?)([dfe]))/g) {
- $filename .= substr($fmt, $pos, pos($fmt) - $pos - length($1));
- $pos = pos($fmt);
- my ($sign, $wid, $skip, $mod, $code) = ($2, $3, $4 || 0, $5, $6);
- my $len = length $part{$code};
- next unless $skip < $len;
- $wid = $len - $skip if $wid eq '' or $wid + $skip > $len;
- $skip = $len - $wid - $skip if $sign eq '-';
- my $part = substr($part{$code}, $skip, $wid);
- $part = ($mod eq 'u') ? uc($part) : lc($part) if $mod;
- $filename .= $part;
- }
- $filename .= substr($fmt, $pos); # add rest of file name
- $filename =~ s{//}{/}g; # remove double slashes
- return $filename;
- }
-
- #------------------------------------------------------------------------------
- # Convert number to alphabetical index: a, b, c, ... z, aa, ab ...
- # Inputs: 0) number
- # Returns: alphabetical index string
- sub Num2Alpha($)
- {
- my $num = shift;
- my $alpha = chr(97 + ($num %26));
- while ($num >= 26) {
- $num = int($num / 26) - 1;
- $alpha = chr(97 + ($num % 26)) . $alpha;
- }
- return $alpha;
- }
-
- #------------------------------------------------------------------------------
- # Expand '%c' and '%C' codes if filename to get next unused file name
- # Inputs: 0) file name format string, 1) filename ok to use even if it exists
- # Returns: new file name
- sub NextUnusedFilename($;$)
- {
- my ($fmt, $okfile) = @_;
- return $fmt unless $fmt =~ /%[-+]?\d*\.?\d*[lun]?[cC]/;
- my %sep = ( '-' => '-', '+' => '_' );
- my ($copy, $alpha) = (0, 'a');
- for (;;) {
- my ($filename, $pos) = ('', 0);
- while ($fmt =~ /(%([-+]?)(\d*)(\.?)(\d*)([lun]?)([cC]))/g) {
- $filename .= substr($fmt, $pos, pos($fmt) - $pos - length($1));
- $pos = pos($fmt);
- my ($sign, $wid, $dec, $wid2, $mod, $tok) = ($2, $3 || 0, $4, $5 || 0, $6, $7);
- if ($tok eq 'C') {
- $seqFileNum = $wid if $wid and not $seqFileNum;
- $wid = $wid2;
- } else {
- next unless $dec or $copy;
- $wid = $wid2 if $wid < $wid2;
- }
- # add dash or underline separator if '-' or '+' specified
- $filename .= $sep{$sign} if $sign;
- if ($mod and $mod ne 'n') {
- my $a = $tok eq 'C' ? Num2Alpha($seqFileNum++) : $alpha;
- my $str = ($wid and $wid > length $a) ? 'a' x ($wid - length($a)) : '';
- $str .= $a;
- $str = uc $str if $mod eq 'u';
- $filename .= $str;
- } else {
- my $c = $tok eq 'C' ? $seqFileNum++ : $copy;
- my $num = $c + ($mod ? 1 : 0);
- $filename .= $wid ? sprintf("%.${wid}d",$num) : $num;
- }
- }
- $filename .= substr($fmt, $pos); # add rest of file name
- # return now with filename unless file exists
- return $filename unless -e $filename;
- return $filename if defined $okfile and $filename eq $okfile;
- ++$copy;
- ++$alpha;
- }
- }
-
- #------------------------------------------------------------------------------
- # Create directory for specified file
- # Inputs: 0) complete file name including path
- # Returns: true if a directory was created (dies if dir can't be created)
- sub CreateDirectory($)
- {
- my $file = shift;
- my ($dir, $created);
- ($dir = $file) =~ s/[^\/]*$//; # remove filename from path specification
- if ($dir and not -d $dir) {
- my @parts = split /\//, $dir;
- $dir = '';
- foreach (@parts) {
- $dir .= $_;
- if (length $dir and not -d $dir) {
- # create directory since it doesn't exist
- mkdir($dir, 0777) or Die "Error creating directory $dir\n";
- $verbose and print "Created directory $dir\n";
- $created = 1;
- }
- $dir .= '/';
- }
- }
- ++$countNewDir if $created;
- return $created;
- }
-
- #------------------------------------------------------------------------------
- # Open output text file
- # Inputs: 0) file name format string
- # Returns: 0) file reference (or undef on error), 1) file name if opened
- sub OpenOutputFile($)
- {
- my $file = shift;
- my ($fp, $outfile);
- if ($outputExt) {
- $outfile = $file;
- CleanFilename($outfile);
- if ($outputExt =~ /%[-+]?\d*\.?\d*[lun]?[dfecC]/) {
- # make filename from printf-like $outputExt
- $outfile = FilenameSPrintf($outputExt, $file);
- return () unless defined $outfile;
- $outfile = NextUnusedFilename($outfile);
- CreateDirectory($outfile); # create directory if necessary
- } else {
- $outfile =~ s/\.[^.\/]*$//; # remove extension if it exists
- $outfile .= $outputExt;
- }
- if (-e $outfile) {
- Warn "Output file $outfile already exists for $file\n";
- return ();
- }
- open(OUTFILE, ">$outfile") or Die "Error creating $outfile\n";
- binmode(OUTFILE) if $binaryOutput;
- $fp = \*OUTFILE;
- } else {
- $fp = \*STDOUT;
- }
- return($fp, $outfile);
- }
-
- #------------------------------------------------------------------------------
- # Filter files based on extension
- # Inputs: 0) file name
- # Returns: 0 = rejected, 1 = specifically accepted, 2 = accepted by default
- # Notes: This routine should only be called if $filterFlag is set
- sub AcceptFile($)
- {
- my $file = shift;
- my $ext = ($file =~ /.*\.(.+)$/) ? uc($1) : '';
- return $filterExt{$ext} if defined $filterExt{$ext};
- return 0 if $filterFlag & 0x02; # reject if accepting specific extensions
- return 2; # accept by default
- }
-
- #------------------------------------------------------------------------------
- # Slurp file into buffer
- # Inputs: 0) file name, 1) buffer reference
- # Returns: 1 on success
- sub SlurpFile($$)
- {
- my ($file, $buffPt) = @_;
- open(INFILE, "<$file") or Warn("Error opening file $file\n"), return 0;
- binmode(INFILE);
- my $bsize = 1024 * 1024;
- my $num = read(INFILE, $$buffPt, $bsize);
- unless ($num) {
- close(INFILE);
- Warn("Error reading $file\n");
- return 0;
- }
- my $bmax = 64 * $bsize;
- while ($num == $bsize) {
- $bsize *= 2 if $bsize < $bmax;
- my $buff;
- $num = read(INFILE, $buff, $bsize);
- last unless $num;
- $$buffPt .= $buff;
- }
- close(INFILE);
- return 1;
- }
-
- #------------------------------------------------------------------------------
- # Print list of tags
- # Inputs: 0) message, 1-N) list of tag names
- sub PrintTagList($@)
- {
- my $msg = shift;
- print $msg, ":\n ";
- my $len = 1;
- my $tag;
- foreach $tag (@_) {
- my $taglen = length($tag);
- if ($len + $taglen > 78) {
- print "\n ";
- $len = 1;
- }
- print " $tag";
- $len += $taglen + 1;
- }
- @_ or print ' [empty list]';
- print "\n";
- }
-
- #------------------------------------------------------------------------------
- # Get source file name for specified file
- # Inputs: 0) file name
- sub SourceFile($)
- {
- my $file = shift;
- return $file unless $srcFmt;
- return FilenameSPrintf($srcFmt, $file);
- }
-
- #------------------------------------------------------------------------------
- # Print warnings and errors from info hash
- # Inputs: 0) ExifTool object ref, 1) info hash, 2) file name
- sub PrintErrors($$$)
- {
- my ($exifTool, $info, $file) = @_;
- my ($tag, $key);
- foreach $tag (qw(Warning Error)) {
- next unless $$info{$tag};
- my @keys = ( $tag );
- push @keys, sort(grep /$tag /, keys %$info) if $exifTool->Options('Duplicates');
- foreach $key (@keys) {
- Warn "$tag: $info->{$key} - $file\n";
- }
- }
- }
-
- __END__
-
- =head1 NAME
-
- exiftool - Read and write meta information in files
-
- =head1 SYNOPSIS
-
- B<exiftool> [I<OPTIONS>] [-I<TAG>...] [--I<TAG>...] I<FILE>...
-
- B<exiftool> [I<OPTIONS>] -I<TAG>[+-E<lt>]=[I<VALUE>]... I<FILE>...
-
- B<exiftool> [I<OPTIONS>] B<-tagsFromFile> I<SRCFILE>
- [-I<SRCTAG>[E<gt>I<DSTTAG>]...] I<FILE>...
-
- B<exiftool> [ B<-ver> | B<-list>[B<w>|B<f>|B<wf>|B<g>[I<NUM>]|B<d>|B<x>] ]
-
- For specific examples, see the L<EXAMPLES|/READING EXAMPLES> sections below.
-
- =head1 DESCRIPTION
-
- A command-line interface to L<Image::ExifTool|Image::ExifTool>, used for
- reading and writing meta information in image, audio and video files.
- I<FILE> is a source file name, directory name, or C<-> for the standard
- input. Information is read from the source file and output in readable form
- to the console (or written to an output text file with the B<-w> option).
-
- To write or copy information, new tag values are specified with the
- -I<TAG>=[I<VALUE>] syntax or the B<-tagsFromFile> or B<-geotag> options.
- This causes I<FILE> to be rewritten, and by default the original file is
- preserved with C<_original> appended to the file name. (Be sure to verify
- that the new file is OK before erasing the original.) Once in write mode,
- exiftool will ignore any read-specific options.
-
- Note: If I<FILE> is a directory name, then only file types with recognized
- extensions are processed when reading, and only writable types are processed
- when any tag is written. However, a filename may be specified or the
- B<-ext> option may be used to force processing of files with any extension.
-
- Below is a list of file types and meta information formats currently
- supported by ExifTool (r = read, w = write, c = create):
-
- File Types
- ------------+-------------+-------------+-------------+------------
- 3FR r | DOC r | K25 r | PAGES r | RWZ r
- 3G2 r | DOCX r | KDC r | PBM r/w | RM r
- 3GP r | DVB r | KEY r | PDF r/w | SO r
- ACR r | DYLIB r | LNK r | PEF r/w | SR2 r/w
- AFM r | EIP r | M2TS r | PFA r | SRF r
- AI r/w | EPS r/w | M4A/V r | PFB r | SRW r/w
- AIFF r | ERF r/w | MEF r/w | PFM r | SVG r
- APE r | EXE r | MIE r/w/c | PGM r/w | SWF r
- ARW r/w | EXIF r/w/c | MIFF r | PICT r | THM r/w
- ASF r | F4A/V r | MNG r/w | PNG r/w | TIFF r/w
- AVI r | FLA r | MOS r/w | PPM r/w | TTC r
- BMP r | FLAC r | MOV r | PPT r | TTF r
- BTF r | FLV r | MP3 r | PPTX r | VRD r/w/c
- COS r | FPX r | MP4 r | PS r/w | WAV r
- CR2 r/w | GIF r/w | MPC r | PSB r/w | WDP r/w
- CRW r/w | GZ r | MPG r | PSD r/w | WMA r
- CS1 r/w | HDP r/w | MPO r/w | PSP r | WMV r
- DCM r | HTML r | MQV r | QTIF r | X3F r
- DCP r/w | ICC r/w/c | MRW r/w | RA r | XLS r
- DCR r | IIQ r | NEF r/w | RAF r/w | XLSX r
- DFONT r | IND r/w | NRW r/w | RAM r | XMP r/w/c
- DIVX r | ITC r | NUMBERS r | RAW r/w | ZIP r
- DJVU r | JNG r/w | OGG r | RIFF r |
- DLL r | JP2 r/w | ORF r/w | RW2 r/w |
- DNG r/w | JPEG r/w | OTF r | RWL r/w |
-
- Meta Information
- ----------------------+----------------------+---------------------
- EXIF r/w/c | JPEG 2000 r | APE r
- GPS r/w/c | DICOM r | Vorbis r
- IPTC r/w/c | Flash r | SPIFF r
- XMP r/w/c | FlashPix r | DjVu r
- MakerNotes r/w/c | QuickTime r | M2TS r
- Photoshop IRB r/w/c | GeoTIFF r | PE/COFF r
- ICC Profile r/w/c | PrintIM r | AVCHD r
- MIE r/w/c | ID3 r | ZIP r
- JFIF r/w/c | Kodak Meta r | (and more)
- Ducky APP12 r/w/c | Ricoh RMETA r |
- PDF r/w/c | Picture Info r |
- CIFF r/w | Adobe APP14 r |
- AFCP r/w | MPF r |
- PhotoMechanic r/w | Stim r |
-
- =head1 OPTIONS
-
- Case is not significant for any command-line option (including tag and group
- names), except for single-character options when the corresponding
- upper-case option is defined. Many single-character options have equivalent
- long-name versions (shown in brackets), and some options have inverses which
- are invoked with a leading double-dash. Note that multiple single-character
- options may NOT be combined into one argument because this would be
- interpreted as a tag name.
-
- =head2 Option Summary
-
- -TAG or --TAG Extract or exclude specified tag
- -TAG[+-]=[VALUE] Write new value for tag
- -TAG[+-]<=DATFILE Write tag value from contents of file
- -TAG[+-]<SRCTAG Copy tag value (see -tagsFromFile)
-
- -@ ARGFILE Read command-line arguments from file
- -a (-duplicates) Allow duplicate tag names in output
- -b (-binary) Output data in binary format
- -c FMT (-coordFormat) Set format for GPS coordinates
- -charset [[TYPE=]CHARSET] Specify encoding for special characters
- -d FMT (-dateFormat) Set format for date/time values
- -D (-decimal) Show tag ID numbers in decimal
- -e (--composite) Do not calculate composite tags
- -E, -ex (-escape(HTML|XML)) Escape values for HTML (-E) or XML (-ex)
- -ee (-extractEmbedded) Extract information from embedded files
- -ext EXT (-extension) Process files with specified extension
- -f (-forcePrint) Force printing of all specified tags
- -F[OFFSET] (-fixBase) Fix the base for maker notes offsets
- -fast[NUM] Increase speed for slow devices
- -fileOrder [-]TAG Set file processing order
- -g[NUM...] (-groupHeadings) Organize output by tag group
- -G[NUM...] (-groupNames) Print group name for each tag
- -geotag TRKFILE Geotag images from specified GPS log
- -h (-htmlFormat) Use HMTL formatting for output
- -H (-hex) Show tag ID number in hexadecimal
- -htmlDump[OFFSET] Generate HTML-format binary dump
- -i DIR (-ignore) Ignore specified directory name
- -if EXPR Conditionally process files
- -j (-json) Use JSON output format
- -k (-pause) Pause before terminating
- -l (-long) Use long 2-line output format
- -L (-latin) Use Windows Latin1 encoding
- -lang [LANG] Set current language
- -list[w|f|wf|g[NUM]|d|x] List various exiftool attributes
- -m (-ignoreMinorErrors) Ignore minor errors and warnings
- -n (--printConv) Disable print conversion
- -o OUTFILE (-out) Set output file or directory name
- -overwrite_original Overwrite original by renaming tmp file
- -overwrite_original_in_place Overwrite original by copying tmp file
- -p FMTFILE (-printFormat) Print output in specified format
- -P (-preserve) Preserve date/time of original file
- -q (-quiet) Quiet processing
- -r (-recurse) Recursively process subdirectories
- -s (-short) Short output format
- -S (-veryShort) Very short output format
- -scanForXMP Brute force XMP scan
- -sep STR (-separator) Set separator string for list items
- -struct Use structured XML or JSON output
- -t (-tab) Output in tab-delimited list format
- -T (-table) Output in tabular format
- -tagsFromFile SRCFILE Copy tag values from file
- -u (-unknown) Extract unknown tags
- -U (-unknown2) Extract unknown binary tags too
- -use MODULE Add features from plug-in module
- -v[NUM] (-verbose) Print verbose messages
- -ver Print exiftool version number
- -w EXT (-textOut) Write console output to file
- -x TAG (-exclude) Exclude specified tag
- -X (-xmlFormat) Use RDF/XML output format
- -z (-zip) Read/write compressed information
-
- -common_args Define common arguments
- -config CFGFILE Specify configuration file name
- -execute Execute multiple commands on one line
- -srcfile FMT Set different source file name
-
- =head2 Option Details
-
- =over 5
-
- =item B<->I<TAG>
-
- Extract information for specified tag (ie. C<-CreateDate>). A tag name
- is the handle by which a piece of information is referenced. See
- L<Image::ExifTool::TagNames|Image::ExifTool::TagNames> for documentation on
- available tag names. A tag name may include leading group names separated
- by colons (ie. C<-EXIF:CreateDate>, or C<-Doc1:XMP:Creator>), and each group
- name may be prefixed by a digit to specify family number (ie.
- C<-1IPTC:City>). Use the B<-listg> option to list available group names by
- family.
-
- A special tag name of C<All> may be used to indicate all meta information.
- This is particularly useful when a group name is specified to extract all
- information in a group. (C<*> is a synonym for C<All>, but must be quoted
- if used on the command line to prevent shell globbing.)
-
- A C<#> may be appended to the tag name to disable the print conversion on a
- per-tag basis (see the B<-n> option). This may also be used when writing or
- copying tags.
-
- If no tags are specified, all available information is extracted.
-
- Note: Descriptions, not tag names, are shown by default when extracting
- information. Use the B<-s> option to see the tag names instead.
-
- =item B<-->I<TAG>
-
- Exclude specified tag from extracted information. Same as the B<-x> option.
- May also be used following a B<-tagsFromFile> option to exclude tags from
- being copied, or to exclude groups from being deleted when deleting all
- information (ie. C<-all= --exif:all> deletes all but EXIF information). But
- note that this will not exclude individual tags from a group delete.
- Instead, the tags must be recovered using the B<-tagsFromFile> option (ie.
- C<-all= -tagsfromfile @ -artist>).
-
- =item B<->I<TAG>[+-]B<=>[I<VALUE>]
-
- Write a new value for the specified tag (ie. C<-comment=wow>), or delete the
- tag if no I<VALUE> is given (ie. C<-comment=>). C<+=> and C<-=> are used to
- add or remove existing entries from a list, or to shift date/time values
- (see L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> for details),
- and C<-=> may be used to conditionally remove or replace a tag (see
- L</WRITING EXAMPLES> for examples).
-
- I<TAG> may contain a leading family 0 or 1 group name separated by a colon.
- If no group name is specified, the tag is created in the preferred group,
- and updated in any other location where a same-named tag already exists.
- The preferred group is the first group in the following list where I<TAG> is
- valid: 1) EXIF, 2) IPTC, 3) XMP.
-
- The special C<All> tag may be used in this syntax only if a I<VALUE> is NOT
- given. This causes all meta information to be deleted (or all information
- in a group if C<-GROUP:All=> is used). Note that not all groups are
- deletable. Use the B<-listd> option for a complete list of deletable
- groups. Also, within an image some groups may be contained within others,
- and these groups are removed if the containing group is deleted:
-
- JPEG Image:
- - Deleting EXIF or IFD0 also deletes ExifIFD, GlobParamIFD,
- GPS, IFD1, InteropIFD, MakerNotes, PrintIM and SubIFD.
- - Deleting ExifIFD also deletes InteropIFD and MakerNotes.
- - Deleting Photoshop also deletes IPTC.
-
- TIFF Image:
- - Deleting EXIF only removes ExifIFD which also deletes
- InteropIFD and MakerNotes.
-
- Note: MakerNotes tags may be edited, but not created or deleted
- individually. This avoids many potential problems including the inevitable
- compatibility problems with OEM software which may be very inflexible about
- the information it expects to find in the maker notes.
-
- =item B<->I<TAG>E<lt>=I<DATFILE> or B<->I<TAG>E<lt>=I<FMT>
-
- Set the value of a tag from the contents of file I<DATFILE>. The file name
- may also be given by a I<FMT> string where %d, %f and %e represent the
- directory, file name and extension of the original I<FILE> (see the B<-w>
- option for more details). Note that quotes are required around this
- argument to prevent shell redirection since it contains a C<E<lt>> symbol.
- C<+E<lt>=> or C<-E<lt>=> may also be used to add or delete specific list
- entries, or to shift date/time values.
-
- =item B<-@> I<ARGFILE>
-
- Read command-line arguments from the specified file. The file contains one
- argument per line (NOT one option per line -- some options require
- additional arguments which must be placed on separate lines). Blank lines
- and lines beginning with C<#> and are ignored. Normal shell processing of
- arguments is not performed, which among other things means that arguments
- should not be quoted. I<ARGFILE> may exist relative to either the current
- directory or the exiftool directory unless an absolute pathname is given.
-
- For example, the following I<ARGFILE> will set the value of Copyright to
- "Copyright YYYY, Phil Harvey", where "YYYY" is the year of CreateDate:
-
- -d
- %Y
- -copyright<Copyright $createdate, Phil Harvey
-
- =item B<-a>, B<--a> (B<-duplicates>, B<--duplicates>)
-
- Allow (B<-a>) or suppress (B<--a>) duplicate tag names in the output. By
- default, duplicate tags are suppressed unless the B<-ee> or B<-X> options
- are used or the Duplicates option is enabled in the configuration file.
-
- =item B<-b> (B<-binary>)
-
- Output requested data in binary format without tag names or descriptions.
- This option is mainly used for extracting embedded images or other binary
- data, but it may also be useful for some text strings since control
- characters (such as newlines) are not replaced by '.' as they are in the
- default output. Also valid in combination with the C<-X> option.
-
- =item B<-c> I<FMT> (B<-coordFormat>)
-
- Set the print format for GPS coordinates. I<FMT> uses the same syntax as
- the C<printf> format string. The specifiers correspond to degrees, minutes
- and seconds in that order, but minutes and seconds are optional. For
- example, the following table gives the output for the same coordinate using
- various formats:
-
- FMT Output
- ------------------- ------------------
- "%d deg %d' %.2f"\" 54 deg 59' 22.80" (default for reading)
- "%d %d %.8f" 54 59 22.80000000 (default for copying)
- "%d deg %.4f min" 54 deg 59.3800 min
- "%.6f degrees" 54.989667 degrees
-
- Note: To avoid loss of precision, the default coordinate format is
- different when copying tags using the B<-tagsFromFile> option.
-
- =item B<-charset> [[I<TYPE>=]I<CHARSET>]
-
- If I<TYPE> is C<ExifTool> or not specified, this option sets the ExifTool
- character encoding for output tag values when reading and input values when
- writing. The default ExifTool encoding is C<UTF8>. I<TYPE> may also be
- C<ID3> to specify the internal encoding of ID3v1 information, or C<IPTC> to
- set the internal IPTC encoding when IPTC:CodedCharacterSet is not defined.
- The default ID3 and IPTC charsets are C<Latin>. If no I<CHARSET> is given,
- a list of available character sets is returned. See
- L<http://owl.phy.queensu.ca/~phil/exiftool/faq.html#Q10> for more
- information about character sets. Valid I<CHARSET> values are:
-
- CHARSET Alias(es) Description
- ---------- --------------- ----------------------------------
- UTF8 cp65001, UTF-8 UTF-8 characters
- Latin cp1252, Latin1 Windows Latin1 (West European)
- Latin2 cp1250 Windows Latin2 (Central European)
- Cyrillic cp1251, Russian Windows Cyrillic
- Greek cp1253 Windows Greek
- Turkish cp1254 Windows Turkish
- Hebrew cp1255 Windows Hebrew
- Arabic cp1256 Windows Arabic
- Baltic cp1257 Windows Baltic
- Vietnam cp1258 Windows Vietnamese
- Thai cp874 Windows Thai
- MacRoman cp10000, Roman Macintosh Roman
- MacLatin2 cp10029 Macintosh Latin2 (Central Europe)
- MacCyrillic cp10007 Macintosh Cyrillic
- MacGreek cp10006 Macintosh Greek
- MacTurkish cp10081 Macintosh Turkish
- MacRomanian cp10010 Macintosh Romanian
- MacIceland cp10079 Macintosh Icelandic
- MacCroatian cp10082 Macintosh Croatian
-
- =item B<-d> I<FMT> (B<-dateFormat>)
-
- Set the format for date/time tag values. Consult C<strftime> man page for
- I<FMT> syntax. The default format is equivalent to "%Y:%m:%d %H:%M:%S".
- This option has no effect on date-only or time-only tags and ignores
- timezone information if present. Only one B<-d> option may be used per
- command. The inverse operation (ie. un-formatting a date/time value) is
- currently not applied when writing a date/time tag.
-
- =item B<-D> (B<-decimal>)
-
- Show tag ID number in decimal when extracting information.
-
- =item B<-e> (B<--composite>)
-
- Print existing tags only -- don't calculate composite tags.
-
- =item B<-E>, B<-ex> (B<-escapeHTML>, B<-escapeXML>)
-
- Escape characters in output values for HTML (B<-E>) or XML (B<-ex>). For
- HTML, all characters with Unicode code points above U+007F are escaped as
- well as the following 5 characters: & (&) E<39> (') E<quot> (")
- E<gt> (>) and E<lt> (<). For XML, only these 5 characters are
- escaped. The B<-E> option is implied with B<-h>, and B<-ex> is implied with
- B<-X>. The inverse conversion is applied when writing tags.
-
- =item B<-ee> (B<-extractEmbedded>)
-
- Extract information from embedded documents in EPS and PDF files, embedded
- MPF images in JPEG and MPO files, streaming metadata from AVCHD videos, and
- the resource fork of Macintosh files. Implies the B<-a> option. Use B<-g3>
- or B<-G3> to identify the originating document for extracted information.
- Embedded documents containing sub-documents are indicated with dashes in the
- family 3 group name. (ie. C<Doc2-3> is the 3rd sub-document of the 2nd
- embedded document.)
-
- =item B<-ext> I<EXT>, B<--ext> I<EXT> (B<-extension>)
-
- Process only files with (B<-ext>) or without (B<--ext>) a specified
- extension. There may be multiple B<-ext> and B<--ext> options. Extensions
- may begin with a leading '.', and case is not significant. For example:
-
- exiftool -ext .JPG DIR # process only JPG files
- exiftool --ext crw --ext dng DIR # process all but CRW and DNG
- exiftool --ext . DIR # ignore if no extension
-
- =item B<-f> (B<-forcePrint>)
-
- Force printing of tags even if their values are not found. This option only
- applies when tag names are specified. May also be used to add a 'flags'
- attribute to the B<-listx> output.
-
- =item B<-F>[I<OFFSET>] (B<-fixBase>)
-
- Fix the base for maker notes offsets. A common problem with some image
- editors is that offsets in the maker notes are not adjusted properly when
- the file is modified. This may cause the wrong values to be extracted for
- some maker note entries when reading the edited file. This option allows an
- integer I<OFFSET> to be specified for adjusting the maker notes base offset.
- If no I<OFFSET> is given, ExifTool takes its best guess at the correct base.
- Note that exiftool will automatically fix the offsets for images which store
- original offset information (ie. newer Canon models). Offsets are fixed
- permanently if B<-F> is used when writing EXIF to an image. ie)
-
- exiftool -F -exif:resolutionunit=inches image.jpg
-
- =item B<-fast>[I<NUM>]
-
- Increase speed of extracting information from JPEG images. With this
- option, ExifTool will not scan to the end of a JPEG image to check for an
- AFCP or PreviewImage trailer, or past the first comment in GIF images or the
- audio/video data in WAV/AVI files to search for additional metadata. These
- speed benefits are small when reading images directly from disk, but can be
- substantial if piping images through a network connection. For more
- substantial speed benefits, B<-fast2> also causes exiftool to avoid
- extracting any EXIF MakerNote information.
-
- =item B<-fileOrder> [-]I<TAG>
-
- Set file processing order according to the sorted value of the specified
- I<TAG>. For example, to process files in order of date:
-
- exiftool -fileOrder DateTimeOriginal DIR
-
- Additional B<-fileOrder> options may be added as secondary sort keys.
- Floating point values are sorted numerically, and all other values are
- sorted alphabetically. The sort order may be reversed by prefixing the tag
- name with a C<-> (ie. C<-fileOrder -createdate>). Note that this option has
- a large performance impact since it involves an additional processing pass
- of each file.
-
- =item B<-g>[I<NUM>][:I<NUM>...] (B<-groupHeadings>)
-
- Organize output by tag group. I<NUM> specifies a group family number, and
- may be 0 (general location), 1 (specific location), 2 (category), 3
- (document number) or 4 (instance number). Multiple families may be
- specified by separating them with colons. By default the resulting group
- name is simplified by removing any leading C<Main:> and collapsing adjacent
- identical group names, but this can avoided by placing a colon before the
- first family number (ie. B<-g:3:1>). If I<NUM> is not specified, B<-g0> is
- assumed. Use the B<-listg> option to list group names for a specified
- family.
-
- =item B<-G>[I<NUM>][:I<NUM>...] (B<-groupNames>)
-
- Same as B<-g> but print group name for each tag.
-
- =item B<-geotag> I<TRKFILE>
-
- Geotag images from the specified GPS track log file. Using the B<-geotag>
- option is equivalent to writing a value to the C<Geotag> tag. After the
- B<-geotag> option has been specified, the value of the C<Geotime> tag is
- written to define a date/time for the position interpolation. If C<Geotime>
- is not specified, the value is copied from C<DateTimeOriginal>. For
- example, the following two commands are equivalent:
-
- exiftool -geotag track.log image.jpg
- exiftool -geotag "-Geotime<DateTimeOriginal" image.jpg
-
- When the C<Geotime> value is converted to UTC, the local system timezone is
- assumed unless the date/time value contains a timezone. Writing C<Geotime>
- causes the following 8 EXIF tags to be created: GPSLatitude,
- GPSLatitudeRef, GPSLongitude, GPSLongitudeRef, GPSAltitude, GPSAltitudeRef,
- GPSDateStamp and GPSTimeStamp. Alternately C<XMP:Geotime> may be written to
- create the following 5 XMP tags: GPSLatitude, GPSLongitude, GPSAltitude,
- GPSAltitudeRef and GPSDateTime.
-
- The C<Geosync> tag may be used to specify a time correction which is applied
- to each C<Geotime> value for synchronization with GPS time. For example,
- the following command compensates for image times which are 1 minute and 20
- seconds behind GPS:
-
- exiftool -geosync=+1:20 -geotag a.log DIR
-
- Multiple B<-geotag> options may be used to concatinate GPS track log data.
- Also, a single B<-geotag> option may be used to load multiple track log
- files by using wildcards in the I<TRKFILE> name, but note that in this case
- I<TRKFILE> must be quoted on most systems (with the notable exception of
- Windows) to prevent filename expansion. For example:
-
- exiftool -geotag "TRACKDIR/*.log" IMAGEDIR
-
- Currently supported track file formats are GPX, NMEA RMC/GGA/GLL, KML, IGC,
- Garmin XML and TCX, and Magellan PMGNTRK. See L</GEOTAGGING EXAMPLES> for
- examples. Also see "geotag.html" in the full ExifTool distribution and the
- L<Image::ExifTool Options|Image::ExifTool/Options> for more details and
- for information about geotag configuration options.
-
- =item B<-h> (B<-htmlFormat>)
-
- Use HTML table formatting for output. Implies the B<-E> option. The
- formatting options B<-D>, B<-H>, B<-g>, B<-G>, B<-l> and B<-s> may be used
- in combination with B<-h> to influence the HTML format.
-
- =item B<-H> (B<-hex>)
-
- Show tag ID number in hexadecimal when extracting information.
-
- =item B<-htmlDump>[I<OFFSET>]
-
- Generate a dynamic web page containing a hex dump of the EXIF information.
- This can be a very powerful tool for low-level analysis of EXIF information.
- The B<-htmlDump> option is also invoked if the B<-v> and B<-h> options are
- used together. The verbose level controls the maximum length of the blocks
- dumped. An I<OFFSET> may be given to specify the base for displayed
- offsets. If not provided, the EXIF/TIFF base offset is used. Use
- B<-htmlDump0> for absolute offsets. Currently only EXIF/TIFF and JPEG
- information is dumped, but the -u option can be used to give a raw hex dump
- of other file formats.
-
- =item B<-i> I<DIR> (B<-ignore>)
-
- Ignore specified directory name. May be multiple B<-i> options.
-
- =item B<-if> I<EXPR>
-
- Specify a condition to be evaluated before processing each I<FILE>. I<EXPR>
- is a Perl-like expression containing tag names prefixed by C<$> symbols. It
- is evaluated with the tags from each I<FILE> in turn, and the file is
- processed only if the expression returns true. Unlike Perl variable names,
- tag names are not case sensitive and may contain a hyphen. As well, tag
- names may have a leading group name separated by a colon, and/or a trailing
- C<#> character to disable print conversion. When multiple B<-if> options
- are used, all conditions must be satisfied to process the file. Returns an
- exit status of 1 if all files fail the condition. Below are a few examples:
-
- # extract shutterspeed from all Canon images in a directory
- exiftool -shutterspeed -if '$make eq "Canon"' dir
-
- # add one hour to all images created on or after Apr. 2, 2006
- exiftool -alldates+=1 -if '$CreateDate ge "2006:04:02"' dir
-
- # set EXIF ISO value if possible, unless it is set already
- exiftool '-exif:iso<iso' -if 'not $exif:iso' dir
-
- # find images containing a specific keyword (case insensitive)
- exiftool -if '$keywords =~ /harvey/i' -filename dir
-
- =item B<-j> (B<-json>)
-
- Use JSON (JavaScript Object Notation) formatting for console output. This
- option may be combined with B<-g> to organize the output into objects by
- group, or B<-G> to add group names to each tag. List-type tags with
- multiple items are output as JSON arrays unless B<-sep> is used. By default
- XMP structures are flattened into individual tags in the JSON output, but
- the original structure may be preserved with the B<-struct> option (this
- also causes all List-type XMP tags to be output as JSON arrays, while
- without this option single-item lists are output as simple strings). The
- B<-a> option is implied if the B<-g> or B<-G> options are used, otherwise it
- is ignored and duplicate tags are suppressed. The B<-b>, B<-L> and
- B<-charset> options have no effect on the JSON output.
-
- =item B<-k> (B<-pause>)
-
- Pause with the message C<-- press any key --> before terminating. This
- option is used to prevent the command window from closing when run as a
- Windows drag and drop application.
-
- =item B<-l> (B<-long>)
-
- Use long 2-line Canon-style output format. Adds a description and
- unconverted value to the XML output when B<-X> is used.
-
- =item B<-L> (B<-latin>)
-
- Use Windows Latin1 encoding (cp1252) for output tag values instead of the
- default UTF-8. When writing, B<-L> specifies that input text values are
- Latin1 instead of UTF-8. Equivalent to C<-charset latin>.
-
- =item B<-lang> [I<LANG>]
-
- Set current language for tag descriptions and converted values. I<LANG> is
- C<de>, C<fr>, C<ja>, etc. Use B<-lang> with no other arguments to get a
- list of available languages. The default language is C<en> if B<-lang> is
- not specified. Note that tag/group names are always English, independent of
- the B<-lang> setting, and translation of warning/error messages has not yet
- been implemented.
-
- By default, ExifTool uses UTF-8 encoding for special characters, but the
- the B<-L> or B<-charset> option may be used to invoke other encodings.
-
- Currently, the language support is not complete, but users are welcome to
- help improve this by submitting their own translations. To submit a set of
- translations, first use the B<-listx> option and redirect the output to a
- file to generate an XML tag database, then add entries for other languages,
- zip this file, and email it to phil at owl.phy.queensu.ca for inclusion in
- ExifTool.
-
- =item B<-list>, B<-listw>, B<-listf>, B<-listwf>, B<-listg>[I<NUM>],
- B<-listd>, B<-listx>
-
- Print a list of all valid tag names (B<-list>), all writable tag names
- (B<-listw>), all recognized file extensions (B<-listf>), all writable file
- extensions (B<-listwf>), all tag groups [in a specified family]
- (B<-listg>[I<NUM>]), all deletable tag groups (B<-listd>), or an XML
- database of tag details (B<-listx>). The B<-list>, B<-listw> and B<-listx>
- options may be followed by an additional argument of the form C<-GROUP:All>
- to list all tags in a specific group, where C<GROUP> is one or more family
- 0-2 group names (excepting EXIF IFD groups) separated by colons. With
- B<-listg>, I<NUM> may be given to specify the group family, otherwise family
- 0 is assumed. The B<-s> and B<-f> options may be used with B<-listx> to
- shorten the output by omitting the descriptions and values, and to add a
- 'flags' attribute. Here are some examples:
-
- -list # list all tag names
- -list -EXIF:All # list all EXIF tags
- -list -xmp:time:all # list all XMP tags relating to time
- -listw -XMP-dc:All # list all writable XMP-dc tags
- -listf # list all recognized file extensions
- -listwf # list all writable file extensions
- -listg1 # list all groups in family 1
- -listd # list all deletable groups
- -listx -EXIF:All # list database of EXIF tags in XML format
- -listx -XMP:All -s # list short XML database of XMP tags
-
- Note that none of the B<-list> options require an input I<FILE>.
-
- =item B<-m> (B<-ignoreMinorErrors>)
-
- Ignore minor errors and warnings. This enables writing to files with minor
- errors and disables some validation checks which could result in minor
- warnings.
-
- =item B<-n> (B<--printConv>)
-
- Read and write values as numbers instead of words. By default, extracted
- values are converted to a more human-readable format for printing, but the
- B<-n> option disables this print conversion for all tags. For example:
-
- > exiftool -Orientation -S a.jpg
- Orientation: Rotate 90 CW
- > exiftool -Orientation -S -n a.jpg
- Orientation: 6
-
- The print conversion may also be disabled on a per-tag basis by suffixing
- the tag name with a C<#> character:
-
- > exiftool -Orientation# -Orientation -S a.jpg
- Orientation: 6
- Orientation: Rotate 90 CW
-
- These techniques may also be used to disable the inverse print conversion
- when writing. For example, the following commands all have the same effect:
-
- > exiftool -Orientation='Rotate 90 CW' a.jpg
- > exiftool -Orientation=6 -n a.jpg
- > exiftool -Orientation#=6 a.jpg
-
- =item B<-o> I<OUTFILE> or I<FMT> (B<-out>)
-
- Set the output file or directory name when writing information. (Without
- this option, the original file is renamed to C<FILE_original> and output is
- sent to I<FILE>.) The output file name may also be specified using a I<FMT>
- string in which %d, %f and %e represent the directory, file name and
- extension of I<FILE>. Also, %c may be used to add a copy number. See the
- B<-w> option for I<FMT> string examples.
-
- The output file is taken to be a directory name if it already exists as a
- directory or if the name ends with '/'. Output directories are created if
- necessary. Existing files will not be overwritten. Combining the
- B<-overwrite_original> option with B<-o> causes the original source file to
- be erased after the output file is successfully written.
-
- A special feature of this option allows it to be used to create certain
- types of files from scratch. Currently, this can only be done with XMP,
- ICC/ICM, MIE, VRD and EXIF files by specifying the appropriate extension for
- I<OUTFILE>. The file is created from a combination of information in
- I<FILE> (as if the B<-tagsFromFile> option was used), and tag values
- assigned on the command line. If no I<FILE> is specified, the output file
- may be created from scratch using only tags assigned on the command line.
-
- =item B<-overwrite_original>
-
- Overwrite the original I<FILE> (instead of preserving it by adding
- C<_original> to the file name) when writing information to an image.
- Caution: This option should only be used if you already have separate backup
- copies of your image files. The overwrite is implemented by renaming a
- temporary file to replace the original. This deletes the original file and
- replaces it with the edited version in a single operation. When combined
- with B<-o>, this option causes the original file to be deleted if the output
- file was successfully written.
-
- =item B<-overwrite_original_in_place>
-
- Similar to B<-overwrite_original> except that an extra step is added to
- allow the original file attributes to be preserved. For example, on a Mac
- this causes the original file creation date, ownership, permissions, type,
- creator, icon and resource fork to be preserved. This is implemented by
- opening the original file in update mode and replacing its data with a copy
- of a temporary file before deleting the temporary. The extra step results
- in slower performance, so the B<-overwrite_original> option should be used
- instead unless necessary.
-
- =item B<-p> I<FMTFILE> or I<STR> (B<-printFormat>)
-
- Print output in the format specified by the given file or string (and ignore
- other format options). Tag names in the format file or string begin with a
- C<$> symbol and may contain a leading group name and/or a trailing C<#>.
- Case is not significant. Braces C<{}> may be used around the tag name to
- separate it from subsequent text. Use C<$$> to represent a C<$> symbol, and
- C<$/> for a newline. Multiple B<-p> options may be used, each contributing
- a line of text to the output. Lines beginning with C<#[HEAD]> and
- C<#[TAIL]> are output only for the first and last processed files
- respectively. Lines beginning with C<#[BODY]> and lines not beginning with
- C<#> are output for each processed file. Other lines beginning with C<#>
- are ignored. For example, this format file:
-
- # this is a comment line
- #[HEAD]# Generated by ExifTool $exifToolVersion
- File: $FileName - $DateTimeOriginal
- (f/$Aperture, ${ShutterSpeed}s, ISO $EXIF:ISO)
- #[TAIL]# end
-
- with this command:
-
- exiftool -p test.fmt a.jpg b.jpg
-
- produces output like this:
-
- # Generated by ExifTool 8.10
- File: a.jpg - 2003:10:31 15:44:19
- (f/5.6, 1/60s, ISO 100)
- File: b.jpg - 2006:05:23 11:57:38
- (f/8.0, 1/13s, ISO 100)
- # end
-
- When B<-ee> (B<-extractEmbedded>) is combined with B<-p>, embedded documents
- are effectively processed as separate input files.
-
- If a specified tag does not exist, a minor warning is issued and the line
- with the missing tag is not printed. However, the B<-f> option may be used
- to set the value of missing tags to '-', or the B<-m> option may be used to
- ignore minor warnings and leave the missing values empty.
-
- =item B<-P> (B<-preserve>)
-
- Preserve the filesystem modification date/time of the original file
- (C<FileModifyDate>) when writing. Note that some filesystems (ie. Mac and
- Windows) store a creation date which is not preserved by this option. For
- these systems, the B<-overwrite_original_in_place> option may be used to
- preserve the creation date.
-
- =item B<-q> (B<-quiet>)
-
- Quiet processing. One B<-q> suppresses normal informational messages, and a
- second B<-q> suppresses warnings as well. Error messages can not be
- suppressed, although minor errors may be downgraded to warnings with the
- B<-m> option.
-
- =item B<-r> (B<-recurse>)
-
- Recursively process files in subdirectories. Only meaningful if I<FILE> is
- a directory name.
-
- =item B<-s> (B<-short>)
-
- Short output format. Prints tag names instead of descriptions. Add up to 3
- B<-s> options for even shorter formats:
-
- -s - print tag names instead of descriptions
- -s -s - no extra spaces to column-align values
- -s -s -s - print values only
-
- Also effective when combined with B<-t>, B<-h>, B<-X> or B<-listx> options.
-
- =item B<-S> (B<-veryShort>)
-
- Very short format. The same as two B<-s> options. Extra spaces used to
- column-align values are not printed.
-
- =item B<-scanForXMP>
-
- Scan all files (even unrecognized formats) for XMP information unless found
- already. When combined with the B<-fast> option, only unrecognized file
- types are scanned. Warning: It can be time consuming to scan large files.
-
- =item B<-sep> I<STR> (B<-separator>)
-
- Specify separator string for items in List-type tags. When reading, the
- default is ", ". When writing, this option causes values assigned to
- list-type tags to be split into individual items at each substring matching
- specified separator. Space characters in the separator string match zero or
- more whitespace characters.
-
- =item B<-struct>
-
- Output structured information instead of flattening to individual tags.
- Effective only for XML and JSON output formats (B<-X> and B<-j>) when
- extracting XMP information.
-
- =item B<-t> (B<-tab>)
-
- Output a tab-delimited list of description/values (useful for database
- import). May be combined with B<-s> to print tag names instead of
- descriptions, or B<-S> to print tag values only, tab-delimited on a single
- line. The B<-t> option may also be used to add tag table information to the
- B<-X> option output.
-
- =item B<-T> (B<-table>)
-
- Output tag values in table form. (Equivalent to B<-t -S -q -f>.)
-
- =item B<-tagsFromFile> I<SRCFILE> or I<FMT>
-
- Copy tag values from I<SRCFILE> to I<FILE>. Tag names on the command line
- after this option specify the tags to be copied, or excluded from the copy.
- If no tags are specified, then all tags from the source file are copied.
- More than one B<-tagsFromFile> option may be used to copy tags from multiple
- files.
-
- By default, this option will commute information between same-named tags in
- different groups and write each tag to the preferred group. This allows
- some information to be automatically translated when copying between images
- of different formats. However, if a group name is specified for a tag then
- the information is written to the original group (unless redirected to
- another group, see below). This works even if C<All> is used as a group
- name, so C<-All:All> is used to specify that all information be copied to
- the same group in the destination file.
-
- I<SRCFILE> may be the same as I<FILE> to move information around within a
- file. In this case, C<@> may be used to represent the source file (ie.
- C<-tagsFromFile @>), permitting this feature to be used for batch processing
- multiple files (see note 3 below). Specified tags are then copied from each
- file in turn as it is rewritten. For advanced batch use, the source file
- name may also be specified using a I<FMT> string in which %d, %f and %e
- represent the directory, file name and extension of I<FILE>. See B<-w>
- option for I<FMT> string examples.
-
- A powerful redirection feature allows a destination tag to be specified for
- each extracted tag. With this feature, information may be written to a tag
- with a different name or group. This is done using
- E<quot>'-I<SRCTAG>E<gt>I<DSTTAG>'E<quot> on the command line after
- B<-tagsFromFile> (E<quot>'-I<DSTTAG>E<lt>I<SRCTAG>'E<quot> also works).
- Note that this argument must be quoted to prevent shell redirection, and
- there is no C<=> sign as when assigning new values. Both source and
- destination tags may be prefixed by a group name, and C<All> or C<*> may be
- used as a tag or group name. If no destination group is specified, the
- information is written to the preferred group. As a convenience,
- C<-tagsFromFile @> is assumed for any redirected tags which are specified
- without a prior B<-tagsFromFile> option. Copied tags may also be added or
- deleted from a list with arguments of the form
- E<quot>'-I<SRCTAG>+E<gt>I<DSTTAG>'E<quot> or
- E<quot>'-I<SRCTAG>-E<gt>I<DSTTAG>'E<quot>.
-
- An extension of the redirection feature allows strings involving tag names
- to be used on the right hand side of the C<E<lt>> symbol with the syntax
- E<quot>'-I<DSTTAG>E<lt>I<STR>'E<quot>, where tag names in I<STR> are
- prefixed with a C<$> symbol. See the B<-p> option for more details about
- this syntax. Strings starting with a C<=> sign must insert a single space
- after the C<E<lt>> to avoid confusion with the C<E<lt>=> syntax which would
- otherwise attempt to set the tag value from the contents of a file. A
- single space at the start of the string is removed if it exists, but all
- other whitespace is preserved.
-
- See L</COPYING EXAMPLES> for examples using B<-tagsFromFile>.
-
- Notes:
-
- 1) Be aware of the difference between excluding a tag from being copied
- (--I<TAG>), and deleting a tag (-I<TAG>=). Excluding a tag prevents it from
- being copied to the destination image, but deleting will remove a
- pre-existing tag from the image.
-
- 2) The maker note information is copied as a block, so it isn't affected
- like other information by subsequent tag assignments on the command line.
- Also, since the PreviewImage referenced from the maker notes may be rather
- large, it is not copied, and must be transferred separately if desired.
-
- 3) When performing complex batch processing, it is important to note that
- the order of operations is different for tags copied in batch mode. In
- general, tags are copied from batch-mode files after all other command-line
- arguments have been applied. For example, the following two commands are
- not equivalent:
-
- # (not batch mode): Sets xmp:title to 'NEW'
- exiftool -tagsfromfile a.jpg -xmp:title -xmp:title=NEW a.jpg
-
- # (batch mode): Preserves original title if it exists
- exiftool -tagsfromfile @ -xmp:title -xmp:title=NEW a.jpg
-
- 4) The normal behaviour of copied tags differs subtly from that of assigned
- tags for List-type tags. When copying to a list, each copied tag overrides
- any previous operations on the list. While this avoids duplicate list items
- when copying groups of tags from a file containing redundant information, it
- also prevents values of different tags from being copied into the same list
- when this is the intent. So a B<-addTagsFromFile> option is provided which
- allows copying of multiple tags into the same list. ie)
-
- exiftool -addtagsfromfile @ '-subject<make' '-subject<model' ...
-
- Other than this difference, the B<-tagsFromFile> and B<-addTagsFromFile>
- options are equivalent.
-
- =item B<-u> (B<-unknown>)
-
- Extract values of unknown tags. Add another B<-u> to also extract unknown
- information from binary data blocks. This option applies to tags with
- numerical tag ID's, and causes tag names like "Exif_0xc5d9" to be generated
- for unknown information. It has no effect on information types which have
- human-readable tag ID's (such as XMP), since unknown tags are extracted
- automatically from these formats.
-
- =item B<-U> (B<-unknown2>)
-
- Extract values of unknown tags as well as unknown information from some
- binary data blocks. This is the same as two B<-u> options.
-
- =item B<-use> I<MODULE>
-
- Add features from specified plug-in I<MODULE>. Currently, the MWG module is
- the only plug-in module distributed with exiftool. This module adds
- read/write support for tags as recommended by the Metadata Working Group.
- To save typing, C<-use MWG> is assumed if the C<MWG> group is specified for
- any tag on the command line. See the
- L<MWG Tags documentation|Image::ExifTool::TagNames/MWG Tags> for more
- details.
-
- =item B<-v>[I<NUM>] (B<-verbose>)
-
- Print verbose messages. I<NUM> specifies the level of verbosity in the
- range 0-5, with higher numbers being more verbose. If I<NUM> is not given,
- then each B<-v> option increases the level of verbosity by 1. With any
- level greater than 0, most other options are ignored and normal console
- output is suppressed unless specific tags are extracted. Using B<-v0>
- causes the console output buffer to be flushed after each line (which may be
- useful to avoid delays when piping exiftool output), and prints the name of
- each processed file when writing.
-
- =item B<-ver>
-
- Print exiftool version number.
-
- =item B<-w> I<EXT> or I<FMT> (B<-textOut>)
-
- Write console output to a file with name ending in I<EXT> for each source
- file. The output file name is obtained by replacing the source file
- extension (including the '.') with the specified extension (and a '.' is
- added to the start of I<EXT> if it doesn't already contain one).
- Alternatively, a I<FMT> string may be used to give more control over the
- output file name and directory. In the format string, %d, %f and %e
- represent the directory, filename and extension of the source file, and %c
- represents a copy number which is automatically incremented if the file
- already exists. %d includes the trailing '/' if necessary, but %e does not
- include the leading '.'. For example:
-
- -w %d%f.txt # same effect as "-w txt"
- -w dir/%f_%e.out # write files to "dir" as "FILE_EXT.out"
- -w dir2/%d%f.txt # write to "dir2", keeping dir structure
- -w a%c.txt # write to "a.txt" or "a1.txt" or "a2.txt"...
-
- Existing files will not be overwritten, and output directories are created
- automatically if necessary.
-
- Notes:
-
- 1) In a Windows BAT file the C<%> character is represented by C<%%>, so an
- argument like C<%d%f.txt> is written as C<%%d%%f.txt>.
-
- 2) It is not possible to specify a simple filename as an argument for B<-w>.
- Instead, this simple case is accomplished using shell redirection:
-
- exiftool FILE > out.txt
-
- Advanced features: A substring of the original file name, directory or
- extension may be taken by specifying a field width immediately following the
- '%' character. If the width is negative, the substring is taken from the
- end. The substring position (characters to ignore at the start or end of
- the string) may be given by a second optional value after a decimal point.
- For example:
-
- Input File Name Format Specifier Output File Name
- ---------------- ---------------- ----------------
- Picture-123.jpg %7f.txt Picture.txt
- Picture-123.jpg %-.4f.out Picture.out
- Picture-123.jpg %7f.%-3f Picture.123
- Picture-123a.jpg Meta%-3.1f.txt Meta123.txt
-
- For %c, these modifiers have a different effects. If a field width is
- given, the copy number is padded with zeros to the specified width. A
- leading '-' adds a dash before the copy number, and a '+' adds an underline.
- By default, a copy number of zero is omitted, but this can be changed by
- adding a decimal point to the modifier. For example:
-
- -w A%-cZ.txt # AZ.txt, A-1Z.txt, A-2Z.txt ...
- -w B%5c.txt # B.txt, B00001.txt, B00002.txt ...
- -w C%.c.txt # C0.txt, C1.txt, C2.txt ...
- -w D%-.c.txt # D-0.txt, D-1.txt, D-2.txt ...
- -w E%-.4c.txt # E-0000.txt, E-0001.txt, E-0002.txt ...
- -w F%-.4nc.txt # F-0001.txt, F-0002.txt, F-0003.txt ...
- -w G%+c.txt # G.txt, G_1.txt G_2.txt ...
- -w H%-lc.txt # H.txt, H-b.txt, H-c.txt ...
-
- A special feature allows the copy number to be incremented for each
- processed file by using %C (upper case) instead of %c. This allows a
- sequential number to be added to output file names, even if the names are
- different. For %C, the number before the decimal place gives the starting
- index, and the number after the decimal place gives the field width. The
- following examples show the output filenames when used with the command
- C<exiftool rose.jpg star.jpg jet.jpg ...>:
-
- -w %C%f.txt # 0rose.txt, 1star.txt, 2jet.txt
- -w %f-%10C.txt # rose-10.txt, star-11.txt, jet-12.txt
- -w %.3C-%f.txt # 000-rose.txt, 001-star.txt, 002-jet.txt
- -w %57.4C%f.txt # 0057rose.txt, 0058star.txt, 0059jet.txt
-
- All format codes may be modified by 'l' or 'u' to specify lower or upper
- case respectively (ie. C<%le> for a lower case file extension). When used
- to modify %c or %C, the numbers are changed to an alphabetical base (see
- example H above). Also, %c may be modified by 'n' to count using natural
- numbers starting from 1, instead of 0 (see example F).
-
- This same I<FMT> syntax is used with the B<-o> and B<-tagsFromFile> options,
- although %c is only valid for output file names.
-
- =item B<-x> I<TAG> (B<-exclude>)
-
- Exclude the specified tag. There may be multiple B<-x> options. This has
- the same effect as --I<TAG> on the command line. May also be used following
- a B<-tagsFromFile> option to exclude tags from being copied.
-
- =item B<-X> (B<-xmlFormat>)
-
- Use RDF/XML formatting for console output. Implies the B<-a> option, so
- duplicate tags are extracted. The formatting options B<-b>, B<-D>, B<-H>,
- B<-l>, B<-s>, B<-sep>, B<-struct> and B<-t> may be used in combination with
- B<-X> to affect the output, but note that the tag ID (B<-D>, B<-H> and
- B<-t>), binary data (B<-b>) and structured output (B<-struct>) options are
- not effective for the short output (B<-s>). Another restriction of B<-s> is
- that only one tag with a given group and name may appear in the output.
- Note that the tag ID options (B<-D>, B<-H> and B<-t>) will produce
- non-standard RDF/XML unless the B<-l> option is also used. By default,
- list-type tags with multiple values are formatted as an RDF Bag, but they
- are combined into a single string when B<-s> or B<-sep> is used. Using
- B<-L> changes the XML encoding from "UTF-8" to "windows-1252". Other
- B<-charset> settings change the encoding only if there is a corresponding
- standard XML character set. The B<-b> option causes binary data values to
- be written, encoded in base64 if necessary. The B<-t> option adds tag table
- information to the output (table C<name>, decimal tag C<id>, and C<index>
- for cases where multiple conditional tags exist with the same ID).
-
- =item B<-z> (B<-zip>)
-
- When reading, causes information to be extracted from .gz and .bz2
- compressed images. (Only one image per archive. Requires gzip and bzip2 to
- be installed on the system.) When writing, causes compressed information to
- be written if supported by the image format. (ie. The PNG format supports
- compressed text.)
-
- =back
-
- =head3 Advanced Options
-
- Among other things, the advanced options allow complex processing to be
- performed from a single command without the need for additional scripting.
- This may be particularly useful for implementations such as Windows
- drag-and-drop applications. These options may also be used to improve
- performance in multi-pass processing by reducing the overhead required to
- load exiftool for each invocation.
-
- =over 5
-
- =item B<-common_args>
-
- Specifies that all arguments following this option are common to all
- executed commands when B<-execute> is used. This and the B<-config> option
- are the only options that may not be used inside a B<-@> I<ARGFILE>.
-
- =item B<-config> I<CFGFILE>
-
- Load specified configuration file instead of the default ".ExifTool_config".
- If used, this option must come before all other arguments on the command
- line. The I<CFGFILE> name must include the full path specification, or may
- be set to an empty string ("") to disable loading of the config file. See
- the sample configuration file and "config.html" in the full ExifTool
- distribution for more information about the ExifTool configuration file.
-
- =item B<-execute>
-
- Execute command for all arguments up to this point on the command line.
- Allows multiple commands to be executed from a single command line.
-
- =item B<-srcfile> I<FMT>
-
- Specify a different source file to be processed based on the name of the
- original file. This may be useful in some special situations for processing
- related preview images or sidecar files. See the B<-w> option for a
- description of the I<FMT> syntax. Note that file name I<FMT> strings for
- all options are based on the original file name specified on the command
- line, not the name of the source file specified by B<-srcfile>.
-
- =back
-
- =head1 READING EXAMPLES
-
- B<Note>: Beware when cutting and pasting these examples into your terminal!
- Some characters such as single and double quotes and hyphens may have been
- changed into similar-looking but functionally-different characters by the
- text formatter used to display this documentation.
-
- =over 5
-
- =item exiftool -a -u -g1 a.jpg
-
- Print all meta information in an image, including duplicate and unknown
- tags, sorted by group (for family 1).
-
- =item exiftool -common dir
-
- Print common meta information for all images in C<dir>.
-
- =item exiftool -T -createdate -aperture -shutterspeed -iso dir > out.txt
-
- List meta information in tab-delimited column form for all images in C<dir>
- to an output text file named "out.txt".
-
- =item exiftool -s -ImageSize -ExposureTime b.jpg
-
- Print ImageSize and ExposureTime tag names and values.
-
- =item exiftool -l -canon c.jpg d.jpg
-
- Print standard Canon information from two image files.
-
- =item exiftool -r -w .txt -common pictures
-
- Recursively extract common meta information from files in C<pictures>
- directory, writing text output into files with the same names but with a
- C<.txt> extension.
-
- =item exiftool -b -ThumbnailImage image.jpg > thumbnail.jpg
-
- Save thumbnail image from C<image.jpg> to a file called C<thumbnail.jpg>.
-
- =item exiftool -b -PreviewImage 118_1834.JPG > preview.jpg
-
- Extract preview image from JPG file and write it to C<preview.jpg>.
-
- =item exiftool -b -JpgFromRaw -w _JFR.JPG -ext CRW -r .
-
- Recursively extract JPG image from all Canon CRW files in the current
- directory, adding C<_JFR.JPG> for the name of the output JPG files.
-
- =item exiftool -d '%r %a, %B %e, %Y' -DateTimeOriginal -S -s *.jpg
-
- Print formatted date/time for all JPG files in a directory.
-
- =item exiftool -IFD1:XResolution -IFD1:YResolution image.jpg
-
- Extract image resolution from EXIF IFD1 information (thumbnail image IFD).
-
- =item exiftool -xmp:author:all -a image.jpg
-
- Extract all author-related XMP information from an image.
-
- =item exiftool -xmp -b a.jpg > out.xmp
-
- Extract complete XMP data record intact from C<a.jpg> and write it to
- C<out.xmp> using the special C<XMP> tag (see the Extra tags in
- L<Image::ExifTool::TagNames|Image::ExifTool::TagNames>).
-
- =item exiftool -p '$filename has date $dateTimeOriginal' -q -f dir
-
- Print one line of output containing the file name and DateTimeOriginal for
- each image in directory C<dir>.
-
- =item exiftool -ee -p '$gpslatitude, $gpslongitude, $gpstimestamp' a.m2ts
-
- Extract all GPS positions from an AVCHD video.
-
- =item exiftool -icc_profile -b -w icc image.jpg
-
- Save complete ICC_Profile from an image to an output file with the same name
- and an extension of C<.icc>.
-
- =item exiftool -htmldump -w tmp/%f_%e.html t/images
-
- Generate HTML pages from a hex dump of EXIF information in all images from
- the C<t/images> directory. The output HTML files are written to the C<tmp>
- directory (which is created if it didn't exist), with names of the form
- 'FILENAME_EXT.html'.
-
- =back
-
- =head1 WRITING EXAMPLES
-
- Note that quotes are necessary around arguments which contain certain
- special characters such as C<E<gt>>, C<E<lt>> or any white space. These
- quoting techniques are shell dependent, but the examples below will work for
- most Unix shells. With the Windows cmd shell however, double quotes should
- be used (ie. -Comment=E<34>This is a new commentE<34>).
-
- =over 5
-
- =item exiftool -Comment='This is a new comment' dst.jpg
-
- Write new comment to a JPG image (replaces any existing comment).
-
- =item exiftool -comment= -o newdir *.jpg
-
- Remove comment from all JPG images in the current directory, writing the
- modified images to a new directory.
-
- =item exiftool -keywords=EXIF -keywords=editor dst.jpg
-
- Replace existing keyword list with two new keywords (C<EXIF> and C<editor>).
-
- =item exiftool -Keywords+=word -o newfile.jpg src.jpg
-
- Copy a source image to a new file, and add a keyword (C<word>) to the
- current list of keywords.
-
- =item exiftool -credit-=xxx dir
-
- Delete Credit information from all files in a directory where the Credit
- value was (C<xxx>).
-
- =item exiftool -xmp:description-de='kühl' -E dst.jpg
-
- Write alternate language for XMP:Description, using HTML character escaping
- to input special characters.
-
- =item exiftool -all= dst.jpg
-
- Delete all meta information from an image. Note: You should NOT do this to
- RAW images (except DNG) since proprietary RAW image formats often contain
- information in the makernotes that is necessary for converting the image.
-
- =item exiftool -all= -comment='lonely' dst.jpg
-
- Delete all meta information from an image and add a comment back in. (Note
- that the order is important: C<-comment='lonely' -all=> would also delete
- the new comment.)
-
- =item exiftool -all= --jfif:all dst.jpg
-
- Delete all meta information except JFIF group from an image.
-
- =item exiftool -Photoshop:All= dst.jpg
-
- Delete Photoshop meta information from an image (note that the Photoshop
- information also includes IPTC).
-
- =item exiftool -r -XMP-crss:all= DIR
-
- Recursively delete all XMP-crss information from images in a directory.
-
- =item exiftool '-ThumbnailImageE<lt>=thumb.jpg' dst.jpg
-
- Set the thumbnail image from specified file (Note: The quotes are neccessary
- to prevent shell redirection).
-
- =item exiftool '-JpgFromRawE<lt>=%d%f_JFR.JPG' -ext CRW -r .
-
- Recursively write JPEG images with filenames ending in C<_JFR.JPG> to the
- JpgFromRaw tag of like-named files with extension C<.CRW> in the current
- directory. (This is the inverse of the C<-JpgFromRaw> command of the
- L</READING EXAMPLES> section above.)
-
- =item exiftool -DateTimeOriginal-='0:0:0 1:30:0' dir
-
- Adjust original date/time of all images in directory C<dir> by subtracting
- one hour and 30 minutes. (This is equivalent to C<-DateTimeOriginal-=1.5>.
- See L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> for details.)
-
- =item exiftool -createdate+=3 -modifydate+=3 a.jpg b.jpg
-
- Add 3 hours to the CreateDate and ModifyDate timestamps of two images.
-
- =item exiftool -AllDates+=1:30 -if '$make eq E<34>CanonE<34>' dir
-
- Shift the values of DateTimeOriginal, CreateDate and ModifyDate forward by 1
- hour and 30 minutes for all Canon images in a directory. (The AllDates tag
- is provided as a shortcut for these three tags, allowing them to be accessed
- via a single tag.)
-
- =item exiftool -xmp:city=Kingston image1.jpg image2.nef
-
- Write a tag to the XMP group of two images. (Without the C<xmp:> this tag
- would get written to the IPTC group since C<City> exists in both, and IPTC
- is preferred by default.)
-
- =item exiftool -LightSource-='Unknown (0)' dst.tiff
-
- Delete C<LightSource> tag only if it is unknown with a value of 0.
-
- =item exiftool -whitebalance-=auto -WhiteBalance=tung dst.jpg
-
- Set C<WhiteBalance> to C<Tungsten> only if it was previously C<Auto>.
-
- =item exiftool -comment-= -comment='new comment' a.jpg
-
- Write a new comment only if the image doesn't have one already.
-
- =item exiftool -o %d%f.xmp dir
-
- Create XMP meta information data files for all images in C<dir>.
-
- =item exiftool -o test.xmp -owner=Phil -title='XMP File'
-
- Create an XMP data file only from tags defined on the command line.
-
- =item exiftool '-ICC_Profile<=%d%f.icc' image.jpg
-
- Write ICC_Profile to an image from a C<.icc> file of the same name.
-
- =item exiftool -trailer:all= image.jpg
-
- Delete any trailer found after the end of image (EOI) in a JPEG file. A
- number of digital cameras store a large PreviewImage after the JPEG EOI, and
- the file size may be reduced significantly by deleting this trailer. See
- the L<JPEG Tags documentation|Image::ExifTool::TagNames/JPEG Tags> for a
- list of recognized JPEG trailers.
-
- =back
-
- =head1 COPYING EXAMPLES
-
- These examples demonstrate the ability to copy tag values between files.
-
- =over 5
-
- =item exiftool -tagsFromFile src.crw dst.jpg
-
- Copy the values of all writable tags from C<src.crw> to C<dst.jpg>, writing
- the information to the preferred groups.
-
- =item exiftool -TagsFromFile src.jpg -all:all dst.jpg
-
- Copy the values of all writable tags from C<src.jpg> to C<dst.jpg>,
- preserving the original tag groups.
-
- =item exiftool -all= -tagsfromfile src.jpg -exif:all dst.jpg
-
- Erase all meta information from C<dst.jpg> image, then copy EXIF tags from
- C<src.jpg>.
-
- =item exiftool -exif:all= -tagsfromfile @ -all:all -unsafe bad.jpg
-
- Rebuild all EXIF meta information from scratch in an image. This technique
- can be used in JPEG images to repair corrupted EXIF information which
- otherwise could not be written due to errors. The C<Unsafe> tag is a
- shortcut for unsafe EXIF tags in JPEG images which are not normally copied.
- See the L<tag name documentation|Image::ExifTool::TagNames> for more details
- about unsafe tags.
-
- =item exiftool -Tagsfromfile a.jpg out.xmp
-
- Copy meta information from C<a.jpg> to an XMP data file. If the XMP data
- file C<out.xmp> already exists, it will be updated with the new information.
- Otherwise the XMP data file will be created. Only XMP, ICC and MIE files
- may be created like this (other file types may be edited but not created).
- See L</WRITING EXAMPLES> above for another technique to generate XMP files.
-
- =item exiftool -tagsFromFile a.jpg -XMP:All= -ThumbnailImage= -m b.jpg
-
- Copy all meta information from C<a.jpg> to C<b.jpg>, deleting all XMP
- information and the thumbnail image from the destination.
-
- =item exiftool -TagsFromFile src.jpg -title -author=Phil dst.jpg
-
- Copy title from one image to another and set a new author name.
-
- =item exiftool -TagsFromFile a.jpg -ISO -TagsFromFile b.jpg -comment
- dst.jpg
-
- Copy ISO from one image and Comment from another image to a destination
- image.
-
- =item exiftool -tagsfromfile src.jpg -exif:all --subifd:all dst.jpg
-
- Copy only the EXIF information from one image to another, excluding SubIFD
- tags.
-
- =item exiftool '-DateTimeOriginal>FileModifyDate' dir
-
- Use the original date from the meta information to set the same file's
- filesystem modification date for all images in a directory. (Note that
- C<-TagsFromFile @> is assumed if no other B<-TagsFromFile> is specified when
- redirecting information as in this example.)
-
- =item exiftool -TagsFromFile src.jpg '-all>xmp:all' dst.jpg
-
- Copy all possible information from C<src.jpg> and write in XMP format to
- C<dst.jpg>.
-
- =item exiftool -@ iptc2xmp.args -iptc:all= a.jpg
-
- Translate IPTC information to XMP with appropriate tag name conversions, and
- delete the original IPTC information from an image. This example uses
- iptc2xmp.args, which is a file included with the ExifTool distribution that
- contains the required arguments to convert IPTC information to XMP format.
- Also included with the distribution is xmp2iptc.args, which performs the
- inverse conversion.
-
- =item exiftool -tagsfromfile %d%f.CRW -r -ext JPG dir
-
- Recursively rewrite all C<JPG> images in C<dir> with information copied from
- the corresponding C<CRW> images in the same directories.
-
- =item exiftool '-make+>keywords' image.jpg
-
- Add camera make to list of keywords.
-
- =item exiftool '-comment<ISO=$exif:iso Exposure=${shutterspeed}' dir
-
- Set the Comment tag of all images in C<dir> from the values of the EXIF:ISO
- and ShutterSpeed tags. The resulting comment will be in the form "ISO=100
- Exposure=1/60".
-
- =item exiftool -TagsFromFile src.jpg -icc_profile dst.jpg
-
- Copy ICC_Profile from one image to another.
-
- =item exiftool -TagsFromFile src.jpg -all:all dst.mie
-
- Copy all meta information in its original form from a JPEG image to a MIE
- file. The MIE file will be created if it doesn't exist. This technique can
- be used to store the metadata of an image so it can be inserted back into
- the image (with the inverse command) later in a workflow.
-
- =item exiftool -o dst.mie -all:all src.jpg
-
- This command performs exactly the same task as the command above, except
- that the C<-o> option will not write to an output file that already exists.
-
- =item exiftool -if '$jpgfromraw' -b -jpgfromraw -w %d%f_%ue.jpg -execute
- -if '$previewimage' -b -previewimage -w %d%f_%ue.jpg -execute
- -tagsfromfile @ -srcfile %d%f_%ue.jpg -overwrite_original
- -common_args --ext jpg DIR
-
- [Advanced] Extract JpgFromRaw or PreviewImage from all but JPG files in DIR,
- saving them with file names like C<image_EXT.jpg>, then add all meta
- information from the original files to the extracted images. Here, the
- command line is broken into three sections (separated by B<-execute>
- options), and each is executed as if it were a separate command. The
- B<-common_args> option causes the C<--ext jpg DIR> arguments to be applied
- to all three commands, and the B<-srcfile> option allows the extracted JPG
- image to be the source file for the third command (whereas the RAW files are
- the source files for the other two commands).
-
- =back
-
- =head1 RENAMING EXAMPLES
-
- By writing the C<FileName> and C<Directory> tags, files are renamed and/or
- moved to new directories. This can be particularly useful and powerful for
- organizing files by date when combined with the B<-d> option. New
- directories are created as necessary, but existing files will not be
- overwritten. The format codes %d, %f and %e may be used in the new file
- name to represent the directory, name and extension of the original file,
- and %c may be used to add a copy number if the file already exists (see the
- B<-w> option for details). Note that if used within a date format string,
- an extra '%' must be added to pass these codes through the date/time parser.
- (And further note that in a Windows batch file, all '%' characters must also
- be escaped, so in this extreme case '%%%%f' is necessary to pass a simple
- '%f' through the two levels of parsing.) See
- L<http://owl.phy.queensu.ca/~phil/exiftool/filename.html> for additional
- documentation and examples.
-
- =over 5
-
- =item exiftool -filename=new.jpg dir/old.jpg
-
- Rename C<old.jpg> to C<new.jpg> in directory C<dir>.
-
- =item exiftool -directory=%e dir
-
- Move all files from directory C<dir> into directories named by the original
- file extensions.
-
- =item exiftool '-Directory<DateTimeOriginal' -d %Y/%m/%d dir
-
- Move all files in C<dir> into a directory hierarchy based on year, month and
- day of C<DateTimeOriginal>. ie) This command would move the file
- C<dir/image.jpg> with a C<DateTimeOriginal> of C<2005:10:12 16:05:56> to
- C<2005/10/12/image.jpg>.
-
- =item exiftool -o . '-Directory<DateTimeOriginal' -d %Y/%m/%d dir
-
- Same effect as above except files are copied instead of moved.
-
- =item exiftool '-filename<%f_${focallength}.%e' dir
-
- Rename all files in C<dir> by adding FocalLength to the file name.
-
- =item exiftool '-FileName<CreateDate' -d %Y%m%d_%H%M%S%%-c.%%e dir
-
- Rename all images in C<dir> according to the C<CreateDate> date and time,
- adding a copy number with leading '-' if the file already exists (C<%-c>),
- and preserving the original file extension (C<%e>). Note the extra '%'
- necessary to escape the filename codes (C<%c> and C<%e>) in the date format
- string.
-
- =item exiftool -r '-FileName<CreateDate' -d %Y-%m-%d/%H%M_%%f.%%e dir
-
- Both the directory and the filename may be changed together via the
- C<FileName> tag if the new C<FileName> contains a '/'. The example above
- recursively renames all images in a directory by adding a C<CreateDate>
- timestamp to the start of the filename, then moves them into new directories
- named by date.
-
- =item exiftool '-FileName<${CreateDate}_$filenumber.jpg' -d %Y%m%d *.jpg
-
- Set the filename of all JPG images in the current directory from the
- CreateDate and FileNumber tags, in the form "20060507_118-1861.jpg".
-
- =back
-
- =head1 GEOTAGGING EXAMPLES
-
- ExifTool implements geotagging via 3 special tags: Geotag (which for
- convenience is also implemented as an exiftool option), Geosync and Geotime.
- The examples below highlight some geotagging features. See
- L<http://owl.phy.queensu.ca/~phil/exiftool/geotag.html> for additional
- documentation.
-
- =over 5
-
- =item exiftool -geotag track.log a.jpg
-
- Geotag an image (C<a.jpg>) from position information in a GPS track log
- (C<track.log>). Since the C<Geotime> tag is not specified, the value of
- DateTimeOriginal is used for geotagging. Local system time is assumed
- unless DateTimeOriginal contains a timezone.
-
- =item exiftool -geotag t.log -geotime='2009:04:02 13:41:12-05:00' a.jpg
-
- Geotag an image with the GPS position for a specific time. (Note that the
- C<Geotag> tag must be assigned before C<Geotime> for the GPS data to be
- available when C<Geotime> is set.)
-
- =item exiftool -geotag log.gpx '-xmp:geotimeE<lt>createdate' dir
-
- Geotag all images in directory C<dir> with XMP tags instead of EXIF tags,
- based on the image CreateDate. (In this case, the order of the arguments
- doesn't matter because tags with values copied from other tags are always
- set after constant values.)
-
- =item exiftool -geotag a.log -geosync=-20 dir
-
- Geotag images in directory C<dir>, accounting for image timestamps which
- were 20 seconds ahead of GPS.
-
- =item exiftool -geotag a.log -geosync=1.jpg -geosync=2.jpg dir
-
- Geotag images using time synchronization from two previously geotagged images
- (1.jpg and 2.jpg), synchronizing the image and GPS times using a linear time
- drift correction.
-
- =item exiftool -geotag a.log '-geotimeE<lt>${createdate}+01:00' dir
-
- Geotag images in C<dir> using CreateDate with the specified timezone. If
- CreateDate already contained a timezone, then the timezone specified on the
- command line is ignored.
-
- =item exiftool -geotag= a.jpg
-
- Delete GPS tags which may have been added by the geotag feature. Note that
- this does not remove all GPS tags -- to do this instead use C<-gps:all=>.
-
- =item exiftool -xmp:geotag= a.jpg
-
- Delete XMP GPS tags which were added by the geotag feature.
-
- =item exiftool -xmp:geotag=track.log a.jpg
-
- Geotag an image with XMP tags, using the time from DateTimeOriginal.
-
- =item exiftool -geotag a.log -geotag b.log -r dir
-
- Combine multiple track logs and geotag an entire directory tree of images.
-
- =item exiftool -geotag 'tracks/*.log' -r dir
-
- Read all track logs from the C<tracks> directory.
-
- =item exiftool -p gpx.fmt -d %Y-%m-%dT%H:%M:%SZ dir > out.gpx
-
- Generate a GPX track log from all images in directory C<dir>. This example
- uses the C<gpx.fmt> file included in the full ExifTool distribution package
- and assumes that the images in C<dir> have all been previously geotagged.
-
- =back
-
- =head1 PIPING EXAMPLES
-
- =over 5
-
- =item cat a.jpg | exiftool -
-
- Extract information from stdin.
-
- =item exiftool image.jpg -thumbnailimage -b | exiftool -
-
- Extract information from an embedded thumbnail image.
-
- =item cat a.jpg | exiftool -iptc:keywords+=fantastic - > b.jpg
-
- Add an IPTC keyword in a pipeline, saving output to a new file.
-
- =item wget -qO - http://a.domain.com/bigfile.jpg | exiftool -fast -
-
- Extract information from an image over the internet using the GNU wget
- utility. The B<-fast> option prevents exiftool from scanning for trailer
- information, so only the meta information header is transferred.
-
- =item exiftool a.jpg -thumbnailimage -b | exiftool -comment=wow - |
- exiftool a.jpg -thumbnailimage'<=-'
-
- Add a comment to an embedded thumbnail image. (Why anyone would want to do
- this I don't know, but I've included this as an example to illustrate the
- flexibility of ExifTool.)
-
- =back
-
- =head1 DIAGNOSTICS
-
- The exiftool application exits with a status of 0 on success, or 1 if an
- error occured or if all files failed the B<-if> condition.
-
- =head1 BUGS
-
- ExifTool does not handle information stored in the resource fork on
- Macintosh filesystems.
-
- =head1 AUTHOR
-
- Copyright 2003-2010, Phil Harvey
-
- This is free software; you can redistribute it and/or modify it under the
- same terms as Perl itself.
-
- =head1 SEE ALSO
-
- L<Image::ExifTool(3pm)|Image::ExifTool>,
- L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames>,
- L<Image::ExifTool::Shortcuts(3pm)|Image::ExifTool::Shortcuts>,
- L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl>
-
- =cut
-
- #------------------------------------------------------------------------------
- # end
-