CLI Tool to read and write image metadata for many kinds of images.

Tricks

Strip all tags

exiftool -all= filename.jpg

Show tags in a format that you can use to rewrite them

exiftool -S $filename

For example

$ exiftool -S "$filename" | grep Daniel
Artist: Daniel Austin Hoherd
Copyright: ┬ęDaniel Austin Hoherd
Creator: Daniel Austin Hoherd
Rights: ┬ęDaniel Austin Hoherd
$ exiftool -Rights='All rights reserved' "$filename"
    1 image files updated
$ exiftool -Rights "$filename"
Rights                          : All rights reserved

Expanded basic usage

This prints out a lot more information than normal usage, and indicates what type of metadata it is.

exiftool -a -u -G:1:2 "$filename"

Here is an example of each unique column 1 in a file

$ exiftool -a -u -G:1:2 "$filename" | sort -u -k1,1
[Adobe:Image]   DCT Encode Version              : 100
[Composite:Camera] Scale Factor To 35 mm Equivalent: 7.0
[Composite:Image] Aperture                      : 1.8
[Composite:Location] GPS Latitude               : 37 deg 15' 53.04" N
[Composite:Time] Date/Time Created              : 2019:01:08 15:59:06
[ExifIFD:Camera] Exposure Program               : Program AE
[ExifIFD:Image] Exposure Time                   : 1/120
[ExifIFD:Time]  Date/Time Original              : 2019:01:08 15:59:06
[ExifTool:ExifTool] ExifTool Version Number     : 11.11
[File:Image]    File Type                       : JPEG
[GPS:Location]  GPS Version ID                  : 2.2.0.0
[GPS:Time]      GPS Time Stamp                  : 23:59:06
[ICC-header:Image] Profile CMM Type             : Linotronic
[ICC-header:Time] Profile Date Time             : 1998:02:09 06:49:00
[ICC-meas:Image] Measurement Observer           : CIE 1931
[ICC-view:Image] Viewing Cond Illuminant        : 19.6445 20.3718 16.8089
[ICC_Profile:Camera] Device Mfg Desc            : IEC http://www.iec.ch
[ICC_Profile:Image] Profile Copyright           : Copyright (c) 1998 Hewlett-Packard Company
[IFD0:Author]   Artist                          : Daniel Austin Hoherd
[IFD0:Camera]   Make                            : Apple
[IFD0:Image]    X Resolution                    : 240
[IFD0:Time]     Modify Date                     : 2019:01:09 13:50:29
[IFD1:Image]    Compression                     : JPEG (old-style)
[IFD1:Preview]  Thumbnail Image                 : (Binary data 12008 bytes, use -b option to extract)
[IPTC:Author]   By-line                         : Daniel Austin Hoherd
[IPTC:Other]    Coded Character Set             : UTF8
[IPTC:Time]     Date Created                    : 2019:01:08
[Photoshop:Author] Copyright Flag               : True
[Photoshop:Image] X Resolution                  : 240
[Photoshop:Preview] Photoshop Thumbnail         : (Binary data 12008 bytes, use -b option to extract)
[System:Image]  File Name                       : 2019-01-08-15-59-06-46628465322_d1657e4c95_o.jpg
[System:Time]   File Modification Date/Time     : 2019:01:22 09:00:22-08:00
[XMP-aux:Camera] Distortion Correction Already Applied: True
[XMP-crs:Image] Already Applied                 : True
[XMP-dc:Author] Creator                         : Daniel Austin Hoherd
[XMP-dc:Image]  Format                          : image/jpeg
[XMP-photoshop:Image] Headline                  : ljwZuD
[XMP-photoshop:Time] Date Created               : 2019:01:08 15:59:06.448
[XMP-x:Document] XMP Toolkit                    : Image::ExifTool 11.11
[XMP-xmp:Image] Creator Tool                    : Adobe Photoshop Lightroom 6.14 (Macintosh)
[XMP-xmp:Time]  Create Date                     : 2019:01:08 15:59:06.448
[XMP-xmpMM:Other] Derived From Document ID      : 9880573B7AACBFC189C795E182E8A05D
[XMP-xmpMM:Time] History When                   : 2019:01:09 13:50:29-08:00
[XMP-xmpRights:Author] Marked                   : True

Add missing lens data on Rokinon 85mm

Rokinon 85mm is a mechanical lens with no electronics, so no data about photos taken with it are stored in the image. This adds some stock metadata describing characteristics of the lens that are always true, which helps these photos sorting accurately, etc..

exiftool \
  -overwrite_original \
  -LensModel='Rokinon 85mm f/1.4' \
  -FocalLength='85' \
  -LongFocal='85' \
  -ShortFocal='85' \
  filename.dng

Correct EXIF time, for instance to sync with GPS time

The following example increases all metadata dates by 1 minute and 56 seconds.

# exiftool -AllDates-='Y:M:D H:M:S'
exiftool -AllDates+='0:0:0 0:1:56'

Set all dates to something obviously wrong

This is useful when scanning or photographing film or prints where you do not want the current date associated with the image.

exiftool -alldates='1900:01:01 01:01:01' *

Rename GPX files based on the capture time

You will end up with a filename like 2013-09-30-23-35-40.gpx based off of the first trkpt timestamp.

exiftool -d '%Y%m%d-%H-%M-%S' '-FileName<${GpxTrkTrksegTrkptTime;tr/ /-/;tr/:/-/;tr(/Z/)()d;}%-c.gpx' *.gpx

Rename files to their original date and time using a lower case file extension

# %le = lowercase extension
# %-c = unique filenames when the timestamp is exactly the same. EG: filename-1.jpg
exiftool "-FileName<CreateDate" -d "%Y%m%d-%H-%M-%S%%-c.%%le" *.jpg

Rename files using a combination of tags

Using the name of the tag as output by exiftool -S, you can create complicated filenames by combining tags:

exiftool -d '%Y%m%d-%H-%M-%S' '-FileName<${CreateDate;}_${Headline;}%-c.%e'

Set file modify time to image capture time

Useful when you want to sort in your file browser by modification time and get a chronological order of files.

exiftool "-FileModifyDate<DateTimeOriginal" *.jpg

Generate a table of Filename, Camera Model and File Size in bytes, sorted by bytes

$ find /src_dir/ -iname '*.dng' |
  xargs  exiftool -p '$filename,$Model,$FileSize#' 2>/dev/null |
  sort   -t, -k3 -n |
  column -s, -t
2012-01-26-23-19-54-6795223065_2e771d1012_o.jpg   iPhone 4S             1242739
2013-02-03-10-01-56-8441346635_df4404a1f6_o.jpg   NIKON D5200           1646481
2012-01-22-15-16-38-6746574603_d52311264f_o.jpg   Canon EOS REBEL T3i   1671734
2011-01-22-23-44-31-6271225963_f9b95b2d7a_o.jpg   NIKON D3S             1773081
2010-01-27-13-07-00-4313649499_835a6649c2_o.jpg   NIKON D300            1829578
2016-02-03-07-26-32-24522158414_4aaf116d2a_o.jpg  iPhone 6              2319061
2018-10-24-13-39-09-44676649345_1de0f581cd_o.jpg  iPhone XS Max         2971254
2015-02-02-19-17-09-24587486051_3032823e4e_o.jpg  NIKON D800            3309696
2014-01-27-13-52-41-12951707465_79a8dd3827_o.jpg  iPhone 5              3401479
2017-01-22-18-33-28-31693592473_40478df088_o.jpg  ILCE-7                4230661
2018-12-23-22-33-40-45536007225_8fdd50691a_o.jpg  NIKON D850            4924617
2017-02-06-08-04-18-44658317900_98e04997fb_o.jpg  iPhone 6s             8712631
2018-12-28-16-56-42-39713091073_c57ec1a8a8_o.jpg  Canon EOS 5D Mark II  8741601
2019-01-08-16-11-49-39716361093_479e6a2323_o.jpg  iPhone 8 Plus         12041600

Generate rsync commands for files matching a string

Useful for reviewing commands before running them, the following example generates a command for every file, then uses awk to do a numeric comparison on the last field to sort out images under a certain ImageHeight. These rsync commands can be pasted into a terminal to run. (Generating a list of files for use with rsync --files-from would be a better option for this specific use case, but this illustration could be adapted for commands that do not have such an option.)

$ exiftool -d "%s" -p 'rsync -aP $filename otherhost:~/Pictures/ # $ImageHeight' * 2>/dev/null | awk '$NF >= 2800 {print}'
rsync -aP 2017-02-06-08-04-18-44658317900_98e04997fb_o.jpg otherhost:~/Pictures/ # 2869
rsync -aP 2018-02-06-09-50-04-31514483967_a422a3e3aa_o.jpg otherhost:~/Pictures/ # 2880
rsync -aP 2018-02-06-15-04-43-45541501845_8dbdc3b208_o.jpg otherhost:~/Pictures/ # 2880
rsync -aP 2018-02-06-15-05-43-31514485997_e2551fdbbc_o.jpg otherhost:~/Pictures/ # 2880
rsync -aP 2018-12-19-10-53-27-45663859984_0f93ac24ec_o.jpg otherhost:~/Pictures/ # 2880

Rename files to their ShutterCount

Filenames will not be changed if ShutterCount field is not populated.

exiftool -P '-filename<${ShutterCount;}.%e' *.dng

Rename files based on a set of possible names

Exiftool will use the last parameter where all variables are present.

exiftool -P -d '%F-%H-%M-%S' \
  '-filename<${DateTimeOriginal} - ${Make;}.%e' \
  '-filename<${CreateDate} - ${Make;}.%e' \
  '-filename<${DateTimeOriginal} - ${Make;} - ${Model;}.%e' \
  '-filename<${CreateDate} - ${Make;} - ${Model;}.%e' \
  '-filename<${DateTimeOriginal} - ${Make;} - ${Model;} - ${ShutterCount}.%e' \
  '-filename<${CreateDate} - ${Make;} - ${Model;} - ${ShutterCount}.%e' \
  *.dng

Use TestName tag target to test what files would be renamed to

This block builds an array of possible tags to use as a filename, creates an exiftool argument string from that array, then tests what files would be named to. This is useful when dealing with files from various sources that don't all use the same tag to store the original media creation time. By using TestName instead of FileName as the target, we observe what would occur, essentially a dry-run, instead of actually renaming the files.

There is a funky behavior of %-c when you operate on a file that should ideally not be renamed. Exiftool will toggle back and forth each run appending and removing -1.

This assumes GNU xargs for the -r flag.

#!/usr/bin/env bash
set -x

# The last valid variable from this list is used as the filename source
create_date_sources=(
  TrackCreateDate
  RIFF:DateTimeOriginal
  MediaCreateDate
  FileModifyDate
  DateTimeOriginal
  CreateDate
)

for opt in "${create_date_sources[@]}" ; do
  args+=( "-TestName<${opt}" ) ;
done ;

args+=( '-d' './%Y/%m/%Y%m%d-%H-%M-%S%%-c.%%le' )

find . -maxdepth 1 -type f ! -name '*.sh' -print0 | xargs -0 -r exiftool "${args[@]}"

This example prints all filenames for jpg files that do not have GPS Coordinates

exiftool -p '$Filename' -if 'not defined $GPSPosition' *.jpg

Rename music files in a directory

There is a big gotcha here, which is that slashes will create directories where they appear, which can cause serious problems. The ${Tag;s/\//_/} syntax replaces / with _, but there may be other characters that can cause unexpected results. This is a great place to use -TestName to inspect what would change before using -FileName to make the changes.

exiftool \
  '-FileName<${Artist;s/\//_/} - ${Title;s/\//_/}.%e' \
  '-FileName<${Artist;s/\//_/} - ${Album;s/\//_/} - ${Title;s/\//_/}.%e' \
  *.mp3 *.m4a

Move short videos to one dir, long videos to another dir

In iOS, if you have Live Photo enabled it creates little movies each time you take a photo. While these can be very interesting context around photos, they can be quite irritating if you're playing through a collection of videos where these are mixed with videos of more moderate duration. The following code snip separates videos with a duration of more than 10 seconds from those with equal or lesser duration.

# -TestName is used here so it does not destroy data. Replace this with FileName to make this actually work.
# $Duration# has the # sign appended to make this tag machine readable so it can accurately be compared.
# We must use perl's numeric comparisons (>, <=), not string comparisons (gt, le)
# exiftool does not support if else syntax, so for the else condition you must run a second command.

long_args=(  "-TestName<${opt}" '-d' "${working_path}/long/%Y/%m/%Y%m%d-%H-%M-%S%%-c.%%le"  '-if' '${Duration#} >  10' )
short_args=( "-TestName<${opt}" '-d' "${working_path}/short/%Y/%m/%Y%m%d-%H-%M-%S%%-c.%%le" '-if' '${Duration#} <= 10' )

find "${PWD}" -maxdepth 1 -type f -print0 | xargs -0 -r exiftool "${long_args[@]}"
find "${PWD}" -maxdepth 1 -type f -print0 | xargs -0 -r exiftool "${short_args[@]}"

Add missing date metadata to Nintendo Switch screenshots

Nintendo Switch screenshots are named with the date, but do not contain this information in the EXIF, which makes this data fragile.

# Filename like: 2020041909511400-87C68A817A974473877AC288310226F6.jpg
for X in *.jpg ; do
  echo "${X}" |
  sed -E 's/^((....)(..)(..)(..)(..)(..).*)/\2 \3 \4 \5 \6 \7 \1/'
done | while read -r Y M D h m s f ; do
  exifftool -alldates="$Y:$M:$D $h:$m:$s" "$f"
done

See Also