exiftool Examples

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.

Extracting EXIF Information

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.

Updating EXIF data

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)

Date/Time Adjustments

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.

Editing IPTC Metadata

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/ //g}' \
'-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 the keywords 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 read tag and apply expr regex substitutions in it. E.g. in the first one, I need to strip NIKON and strip away whitespaces from the model name so NIKON Z 6 would be tagged as Z6 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

A File Import Tool

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 informs exiftool that we would be using data from CreateDate 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 of YYYY/YY-MM are created as specified by the %Y/%Y-%m format string.
  • -r $SOURCE makes exiftool scan $SOURCE directory and its subdirectories for files to be copied. Please note that if any file doesn’t have the CreateDate 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

Searching by EXIF data

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 the FocalLengthIn35mmFormat 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 whether FocalLengthIn35mmFormat 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 called teles.lst This part is pure shell, nothing to do with exiftool.

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

Closing Thoughts & References

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.

Previous Post:
Using a QLC SSD for Backups. Am I Insane?

Next Post:
Fujifilm X-E4: An Engaging Experience I'd Rather Not Have

Articles

Tahir Hashmi