Following is a collection of real exiftool
commands that I've used, along with explanations of what each does. exiftool
is a command-line utility that provides very powerful EXIF reading, writing and searching capabilities.
I'm writing this down because I often spend a lot of time reading through exiftool documentation to find out how to get something done, just to forget it within hours. All of these examples work on a Unix shell environment like ZSH on MacOS or the various Linux shells.
What does EXIF data look like? Let's try the following on an image file, DSC09535.JPG
:
$ exiftool -all DSC09535.JPG
ExifTool Version Number : 11.59
File Name : DSC09535.JPG
Directory : .
File Size : 11 MB
File Modification Date/Time : 2020:02:02 14:38:34+07:00
File Access Date/Time : 2020:07:26 09:11:20+07:00
File Inode Change Date/Time : 2020:05:05 11:41:44+07:00
File Permissions : rwxrwxrwx
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
Exif Byte Order : Little-endian (Intel, II)
Image Description :
Make : SONY
Camera Model Name : DSC-RX10M3
... 165 lines snipped ...
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
Hyperfocal Distance : 1098.31 m
Light Value : 5.9
This simplest exiftool invocation that dumps all EXIF data for the file. The default output shows tag descriptions and their readably formatted values. In order to see the actual tag names, use the -s
option.
$ exiftool -s -all DSC09535.JPG
ExifToolVersion : 11.59
FileName : DSC09535.JPG
Directory : .
FileSize : 11 MB
FileModifyDate : 2020:02:02 14:38:34+07:00
FileAccessDate : 2020:07:26 09:11:20+07:00
FileInodeChangeDate : 2020:05:05 11:41:44+07:00
FilePermissions : rwxrwxrwx
FileType : JPEG
FileTypeExtension : jpg
MIMEType : image/jpeg
ExifByteOrder : Little-endian (Intel, II)
ImageDescription :
Make : SONY
Model : DSC-RX10M3
... 165 lines snipped ...
FocalLength35efl : 220.0 mm (35 mm equivalent: 600.0 mm)
HyperfocalDistance : 1098.31 m
LightValue : 5.9
Now it becomes easier to query for specific values in a whole collection of files. For example, here's how to list the 35mm equivalent focal length for all files in the current directory:
$ exiftool -FocalLength35efl .
======== ./DSC09535.JPG
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
======== ./DSC09568.JPG
Focal Length : 73.2 mm (35 mm equivalent: 200.0 mm)
======== ./DSC09540.JPG
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
======== ./DSC09556.JPG
Focal Length : 13.0 mm (35 mm equivalent: 35.0 mm)
======== ./DSC09550.JPG
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
======== ./DSC09574_DxO.jpg
Focal Length : 110.1 mm (35 mm equivalent: 300.0 mm)
======== ./7DX_0300_DxO.jpg
Focal Length : 200.0 mm (35 mm equivalent: 300.0 mm)
======== ./DSC09574.JPG
Focal Length : 110.1 mm (35 mm equivalent: 300.0 mm)
======== ./DSC09570.JPG
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
======== ./DSC09564.JPG
Focal Length : 110.1 mm (35 mm equivalent: 300.0 mm)
======== ./DSC09558.JPG
Focal Length : 146.8 mm (35 mm equivalent: 400.0 mm)
======== ./7DX_0300.JPG
Focal Length : 200.0 mm (35 mm equivalent: 300.0 mm)
======== ./DSC09539.JPG
Focal Length : 220.0 mm (35 mm equivalent: 600.0 mm)
1 directories scanned
13 image files read
I often like to perform some quantitative analysis on my photo metadata, so it is helpful to start with data that can be imported into a spreadsheet. Luckily, exiftool
can export the above data as CSV too. Example:
$ exiftool -csv -Model -Aperture -ShutterSpeed -ISO \
-FocalLengthIn35mmFormat . > stats.csv
1 directories scanned
13 image files read
$ cat stats.csv
SourceFile,Model,Aperture,ShutterSpeed,ISO,FocalLengthIn35mmFormat
./DSC09535.JPG,DSC-RX10M3,4.0,1/60,1600,600 mm
./DSC09568.JPG,DSC-RX10M3,4.0,1/30,64,200 mm
./DSC09540.JPG,DSC-RX10M3,4.0,1/250,400,600 mm
./DSC09556.JPG,DSC-RX10M3,4.0,1/25,64,35 mm
./DSC09550.JPG,DSC-RX10M3,5.6,1/250,64,600 mm
./DSC09574_DxO.jpg,DSC-RX10M3,4.0,1/1600,64,300 mm
./7DX_0300_DxO.jpg,NIKON D7500,5.6,1/800,100,300 mm
./DSC09574.JPG,DSC-RX10M3,4.0,1/1600,64,300 mm
./DSC09570.JPG,DSC-RX10M3,4.0,1/30,64,600 mm
./DSC09564.JPG,DSC-RX10M3,4.0,1/40,64,300 mm
./DSC09558.JPG,DSC-RX10M3,4.0,1/125,64,400 mm
./7DX_0300.JPG,NIKON D7500,5.6,1/800,100,300 mm
./DSC09539.JPG,DSC-RX10M3,4.0,1/250,400,600 mm
If you don't want the numeric values to be formatted, use the -n
option to extract raw numbers. Example:
$ exiftool -csv -n -Model -Aperture -ShutterSpeed -ISO \
-FocalLengthIn35mmFormat .
SourceFile,Model,Aperture,ShutterSpeed,ISO,FocalLengthIn35mmFormat
./DSC09535.JPG,DSC-RX10M3,4,0.01666666667,1600,600
./DSC09568.JPG,DSC-RX10M3,4,0.03333333333,64,200
./DSC09540.JPG,DSC-RX10M3,4,0.004,400,600
./DSC09556.JPG,DSC-RX10M3,4,0.04,64,35
./DSC09550.JPG,DSC-RX10M3,5.6,0.004,64,600
./DSC09574_DxO.jpg,DSC-RX10M3,4,0.000625,64,300
./7DX_0300_DxO.jpg,NIKON D7500,5.6,0.00125,100,300
./DSC09574.JPG,DSC-RX10M3,4,0.000625,64,300
./DSC09570.JPG,DSC-RX10M3,4,0.03333333333,64,600
./DSC09564.JPG,DSC-RX10M3,4,0.025,64,300
./DSC09558.JPG,DSC-RX10M3,4,0.008,64,400 ./7DX_0300.JPG,NIKON
D7500,5.6,0.00125,100,300 ./DSC09539.JPG,DSC-RX10M3,4,0.004,400,600
As you can see, the shutter speed has changed from a fraction, like 1/25
to a floating point value like 0.04
and the mm
unit has been dropped from the equivalent focal length field.
One of my frequent requirements is to ensure that post-processed files have the right EXIF data. This is often needed when stitching panoramas or image stacks from multiple source images. The easiest way is to make exiftool
copy the data from the corresponding raw file.
exiftool -tagsfromfile ../../DSC_2177.NEF superblend*.jpg
Here, the -tagsfromfile
option picks up all the EXIF data from the first file and overwrites it into the subsequent files.
Sometimes, a bad image editor might generate files with wrong EXIF data. The following example fixes such occurences by matching JPEG files against their corresponding raw (NEF) files and copying the data from the latter to the former.
for c in `ls -C1 *.jpg`; do
exiftool -P -overwrite_original -tagsfromfile ../${c/.jpg/.NEF} $c;
done;
Note the two new options here:
-P
is used to ensure that file modification date/time is not updated to the time when the command is executed-overwrite_original
is used to modify the original file and not save a backup of the original (refer to documentation for more details)
It's really convenient to have the timestamp of the processed photos match the time when they were originally shot. However, some image processors export files with the latest timestamps. One example is Nikon Studio NX, that not only doesn't adjust the modification time, but also adds a new EXIF tag for when the processed file was generated. Here's how I fix this:
exiftool -overwrite_original '-FileModifyDate<DateTimeOriginal' \
'-ModifyDate=' .
The above command operates on all files in the current directory (.
), deletes the ModifyDate
tag created by Studio NX and sets the actual file modification date (FileModifyDate
) to the time when the picture was taken (DateTimeOriginal
).
Another somewhat recurring requirement is to adjust all EXIF timestamps by some difference. This happens when I'm travelling with multiple cameras, whose clocks are not in sync, and sometimes I forget to match the timezone to that of the destination. Fortunately, it is really easy to make these adjustments with exiftool
.
For example, the following shots were taken at the same time but with different cameras. Unfortunately, the clock of one camera was way off.
$ exiftool -p '$filename $DateTimeOriginal $Model' DSC09574.JPG 7DX_0300.JPG
DSC09574.JPG 2020:05:03 13:31:50 DSC-RX10M3
7DX_0300.JPG 2020:05:03 15:08:22 NIKON D7500
By the way, I have used formatted printing capability of exiftool
here using the -p
option, but I'm not going to discuss it here.
To get the correct time, I need to add 1 hour 36 minutes and 31 seconds to all photos from the DSC-RX10M3
camera. Easy peasy. Here's how:
exiftool -alldates+="0:0:0 1:36:31" -if '$Model eq "DSC-RX10M3"' .
Here we use the alldates
tag, which is a shorthand for 3 different date fields, and shift it forward using +=
operator by a delta represented as Y:M:D h:m:s
for year, month, date, hour, minute, second respectively. To shift the date backward, we could use the -=
operator instead.
The -if
option is used to inspect tag values and only act on files where the values satisfy the condition in the expression. E.g. In the above example, the dates are adjusted only for files shot with "DSC-RX10M3" camera model.
Having been a Flickr user since 2004, I had been maintaining titles, descriptions, and keywords (Flickr "tags") very carefully. After 15 long years, I decided I needed to have that metadata in my photo files offline too, and not just on Flickr.
One of the biggest challenges there was to maintain a consistent keyword set for cameras and lenses. I tag each of my photos with the body and lens that was used to click the photo.
Thankfully, exiftool
comes in really handy for inspecting the camera and lens data from the original file and adding the appropriate keywords. Here's how:
exiftool -P -overwrite_original -addtagsfromfile @ \
'-keywords+<${model;s/NIKON //;s/ //}' \
'-keywords+<${lensid;s/(AF-.|[A-Za-z]{3,}|ED)//g;s/\s+/ /g;s/^ *//;s/ *$//}'
Let's break it down, option by option.
- The
-P
option ensures that the edited file has matching timestamps as the original - The
-overwrite_original
prevents creation of backup files - The
-addtagsfromfile
option is a variant of-tagsfromfile
that allows repeated copying of values into the same tag to be appended rather than overwritten. This is important because here we are adding multiple values into thekeywords
tag @
is a reference to the destination file as the source file too, since we are reading values from the same file and writing them back-keywords
indicates that we're editing the (IPTC)keywords
tag+
is to add to it and<
means to copy the value from the right hand side of the<
operator${<tag>;<expr>[;<expr>...]}
is a way to readtag
and applyexpr
regex substitutions in it. E.g. in the first one, I need to stripNIKON
and strip away whitespaces from the model name soNIKON Z 6
would be tagged asZ6
in the keywords.
The last line is a bit more complicated due to the rules I follow to generate the lens keywords. The result is Model, Lens pairs in the keywords like this:
D750, 70-200mm f/4G VR
D7500, 300mm f/4E PF VR
D7500, 500mm f/5.6E PF VR
Z6, 500mm f/5.6E PF VR
Z6, 70-200mm f/4G VR
Z6, Z 24-70mm f/4 S
One challenge with the above example for adding camera and lens tags was that as a stand-alone script, I had to remember to run it for every batch of files that I import. It would be a lot easier if the tags got applied automatically at the time of importing the photos in the first place. Thankfully, exiftool
also provides features for file movement and copying. All I had to do was add one line at the end of the previous example.
#!/bin/zsh
exiftool -P -addtagsfromfile @ \
'-keywords+<${model;s/NIKON //;s/ //g}' \
'-keywords+<${lensid;s/(AF-.|[A-Za-z]{3,}|ED)//g;s/\s+/ /g;s/^ *//;s/ *$//}' \
'-Directory<CreateDate' -o . -d $HOME/Pictures/%Y/%Y-%m -r $SOURCE
Let's examine the parts of the last line.
-Directory<CreateDate
informsexiftool
that we would be using data fromCreateDate
tag for synthesising the file destination path-o .
indicates that we need to copy the files, rather than move them (seems to be a special case for this usage)-d
provides the destination, in this case$HOME/Pictures/
being the base directory under which subdirectories in the format ofYYYY/YY-MM
are created as specified by the%Y/%Y-%m
format string.-r $SOURCE
makesexiftool
scan$SOURCE
directory and its subdirectories for files to be copied. Please note that if any file doesn't have theCreateDate
tag in it, that file would not be copied.
This command imports all new files into folders of the following structure, while also applying camera and lens tags and preserving the file timestamps as in the camera.
2021
├── 2021-01
├── 2021-02
├── 2021-03
├── 2021-04
└── 2021-05
One of the most powerful uses of exiftool
on a daily basis is being able to search through the images based on EXIF data. For example, I was interested in examining the general "look and feel" of photos shot in the medium telephoto field of view. One challenge was that the exact focal length for different format sensors would be different. Luckily, we have the FocalLengthIn35mmFormat
tag that shows a normalised value of focal length.
Next, all I had to do was to list out all the photos that fell within a range, which I chose to be 105mm to 200mm. Here's the command I used:
exiftool \
-if '$FocalLengthIn35mmFormat# >= 105' \
-if '$FocalLengthIn35mmFormat# <= 200' \
-p $PWD/'$directory/$filename' \
*/Finished > teles.lst
Let's go through this option by option.
- The first
-if
option checks whether theFocalLengthIn35mmFormat
is >= 105mm. We need a numeric comparison here so the tag value needs to be extracted as a number rather than a string. There are two ways to do this. One is to append a#
character at the end of the tag name, as we have done here. The other is to use the-n
option to apply to all the tags. - The second
-if
option is very similar and checks whetherFocalLengthIn35mmFormat
is <= 200mm. - The
-p
option is used to print the file names in a specific way -- the first part is the full path of the present working directory as captured by$PWD
followed by a trailing/
. This is then followed by the directory in which the matching file was found using the$directory
tag and lastly we print the$filename
with a/
between the two to make the complete path. - The search space is all the directories matching
*/Finished
path and the results are saved in a file calledteles.lst
This part is pure shell, nothing to do withexiftool
.
By the way, I used XnViewMP to open the above file list as if it were a "virtual directory" containing all of those files, which then, among other things, allowed me to view a fullscreen slide show of all matching photos.
Another search example is a riff on the above, except I'm more interested in photos with a 30mm FoV +/- 5mm, but shot with a specific lens, which we match by a regex on the LensId
tag.
exiftool \
-if '$FocalLengthIn35mmFormat# > 25' \
-if '$FocalLengthIn35mmFormat# < 35' \
-if '$LensId=~/24-70.*S/' \
-p $PWD/'$directory/$filename $LensId' 2020-* > zoom-30.lst
While I've verified all of the examples here to be working as per my usage, differences in our environments (most importantly, Operating Systems) might cause some of them to not work.
The explanations are my understanding of the official documentation and various forum posts. Please feel free to point out any inaccuracies that need correction.
For further information look at the exiftool
documentation or look through the forum for answers. There is also a vast collection of helpful resources.