Rating images painlessly with exiftool feat. ranger & sxiv
Background
I was looking for a way to classifying images by rating them on the fly.
My first attempt was using darktable as suggested in a thread. Indeed, the
auto-advance rating mechanism was quite handy. But it is still
too heavy for this sole purpose. In darktable, user have to import images before
editing metadata. When tens of thousands images are involved, the process of
importing images can be quite time-consuming1
Dozen hours for 950.000 images.
as it creates for each image
an XMP file to store metadata. And this also applies to rating, even though I
configure it to improve performance (without OpenCL) the latency is counted by seconds.
Moreover, XMP are also created for symlink of image. This was not plausible
in my use-case2
Statistical classification of images. For each class, it creates a directory in which each symbolic link is associated to the actual image.
as it enforces me to keep multiple metadata files for the same image.
Lots of critics, but clearly darktable was not the right tool. It suits
better on raw photo post-production as intended. I will present in this post two
solutions to remediate the issues aforementioned.
Goals
After experiencing darktable, I know better what I am seeking:
- Edit metadata in the image file itself. This has two advantages:
- Keep metadata even if the filename is changed.
- Get rid of XMP files.
- Preview and select image without latency. Namely, preview images and rate them on the fly.
- Metadata editing should follow symlink. To centralize metadata in the same place.
- Batch rating. Rating a whole directory or multiple selected images at once.
Exiftool
ExifTool is a free and open-source software program for reading, writing, and manipulating image, audio, video, and PDF metadata.
Rating images with exiftool
Rating image with exiftool is very simple.
1exiftool -rating=5 -overwrite_original_in_place <files>
The option -overwrite_original_in_place overwrite directly the file(s) instead
of moving the original one to filename.ext_original. Use it wisely at your own risk.
To read back the rating:
1exiftool -rating <files>
… or format yourself the output:
1exiftool -p '$Rating $Filepath' -f <files>
And, of course the symbolic links are followed3
Beware, without -overwrite_original_in_place, symlink will be removed!
!
But with exiftool alone, one cannot watch and rate image at the same time.
This can be done by combine up exiftool with a file manager having preview ability
or an image viewer. Next, I will show how to integrate exiftool capability in
the file manager ranger and the image viewer sxiv.
File types supported by exiftool
In fact, the version 11.88 of exiftool already supports a large set of file types.
Thus, what has been and will be said is not limited to images and rating.
Table 1: File types supported by exiftool (v11.88) (r = read, w = write, c = create).
3FR (r) | DR4 (r/w/c) | ITC (r) | ODP (r) | RIFF (r) |
3G2 (r/w) | DSS (r) | J2C (r) | ODS (r) | RSRC (r) |
3GP (r/w) | DV (r) | JNG (r/w) | ODT (r) | RTF (r) |
A (r) | DVB (r/w) | JP2 (r/w) | OFR (r) | RW2 (r/w) |
AA (r) | DVR-MS (r) | JPEG (r/w) | OGG (r) | RWL (r/w) |
AAE (r) | DYLIB (r) | JSON (r) | OGV (r) | RWZ (r) |
AAX (r/w) | EIP (r) | K25 (r) | OPUS (r) | RM (r) |
ACR (r) | EPS (r/w) | KDC (r) | ORF (r/w) | SEQ (r) |
AFM (r) | EPUB (r) | KEY (r) | OTF (r) | SKETCH (r) |
AI (r/w) | ERF (r/w) | LA (r) | PAC (r) | SO (r) |
AIFF (r) | EXE (r) | LFP (r) | PAGES (r) | SR2 (r/w) |
APE (r) | EXIF (r/w/c) | LNK (r) | PBM (r/w) | SRF (r) |
ARQ (r/w) | EXR (r) | LRV (r/w) | PCD (r) | SRW (r/w) |
ARW (r/w) | EXV (r/w/c) | M2TS (r) | PCX (r) | SVG (r) |
ASF (r) | F4A, F4V (r/w) | M4A, M4V (r/w) | PDB (r) | SWF (r) |
AVI (r) | FFF (r/w) | MAX (r) | PDF (r/w) | THM (r/w) |
AVIF (r/w) | FITS (r) | MEF (r/w) | PEF (r/w) | TIFF (r/w) |
AZW (r) | FLA (r) | MIE (r/w/c) | PFA (r) | TORRENT (r) |
BMP (r) | FLAC (r) | MIFF (r) | PFB (r) | TTC (r) |
BPG (r) | FLIF (r/w) | MKA (r) | PFM (r) | TTF (r) |
BTF (r) | FLV (r) | MKS (r) | PGF (r) | TXT (r) |
CHM (r) | FPF (r) | MKV (r) | PGM (r/w) | VCF (r) |
COS (r) | FPX (r) | MNG (r/w) | PLIST (r) | VRD (r/w/c) |
CR2 (r/w) | GIF (r/w) | MOBI (r) | PICT (r) | VSD (r) |
CR3 (r/w) | GPR (r/w) | MODD (r) | PMP (r) | WAV (r) |
CRM (r/w) | GZ (r) | MOI (r) | PNG (r/w) | WDP (r/w) |
CRW (r/w) | HDP (r/w) | MOS (r/w) | PPM (r/w) | WEBP (r) |
CS1 (r/w) | HDR (r) | MOV (r/w) | PPT (r) | WEBM (r) |
CSV (r) | HEIC (r/w) | MP3 (r)4
You may notice that you can’t write MP3 metadata. ffmpeg should be used instead.
| PPTX (r) | WMA (r) |
DCM (r) | HEIF (r/w) | MP4 (r/w) | PS (r/w) | WMV (r) |
DCP (r/w) | HTML (r) | MPC (r) | PSB (r/w) | WTV (r) |
DCR (r) | ICC (r/w/c) | MPG (r) | PSD (r/w) | WV (r) |
DFONT (r) | ICS (r) | MPO (r/w) | PSP (r) | X3F (r/w) |
DIVX (r) | IDML (r) | MQV (r/w) | QTIF (r/w) | XCF (r) |
DJVU (r) | IIQ (r/w) | MRW (r/w) | R3D (r) | XLS (r) |
DLL (r) | IND (r/w) | MXF (r) | RA (r) | XLSX (r) |
DNG (r/w) | INSP (r/w) | NEF (r/w) | RAF (r/w) | XMP (r/w/c) |
DOC (r) | INSV (r) | NRW (r/w) | RAM (r) | ZIP (r) |
DOCX (r) | INX (r) | NUMBERS (r) | RAR (r) | |
DPX (r) | ISO (r) | O (r) | RAW (r/w) |
ranger
ranger is a free and open-source CLI files manager I’m using for years. It is very handy to select images and preview them5 As long as you use the right terminal emulator, e.g. iTerm, kitty, urxvt, xterm etc. From my past experience, Elementary OS’s pantheon-terminal doesn’t work. .
Append the following snippet in ~/.config/ranger/commands.py.
It will add the custom command rate_image <0-5> <files>.
1# ~/.config/ranger/commands.py2class rate_image(Command):3""":rate_image <0-5> <files>45Command for rating image with exiftools.6"""78def execute(self):9import subprocess10if self.arg(1):11rating_score = self.arg(1)12else:13self.fm.notify("rate_image: a rating score is required!", bad=True)14return15if self.arg(2):16files = self.arg(2)17else:18cwd = self.fm.thisdir19cf = self.fm.thisfile20if not cwd or not cf:21self.fm.notify("Error: no file selected for deletion!", bad=True)22return23if len(cwd.marked_items) > 1:24files = " ".join([f.shell_escaped_basename for f in cwd.marked_items])25self.fm.mark_files(all=True, val=False)26else:27files = cf.shell_escaped_basename28command = "exiftool -rating=" + rating_score + \29" -overwrite_original_in_place " + files30self.fm.notify("Run command: " + command)31result = self.fm.execute_command(command, stdout=subprocess.PIPE)32stdout, stderr = result.communicate()33if result.returncode == 0:34# This is a generic function to print text in ranger.35self.fm.notify("Succeeded to rate image " + files + \36" with score " + rating_score + ".")
It remains to define some key bindings to be granted the full power of ranger.
Append the following snippet to ~/.config/ranger/rc.conf.
1# ~/.config/ranger/rc.conf2map r1 rate_image 13map r2 rate_image 24map r3 rate_image 35map r4 rate_image 46map r5 rate_image 5
The resulting workflow is as follows:
- Select images with
SPC(single selection) orv(reverse selection). - Press
rand the rating1to5.

sxiv
sxiv is a free, open-source, lightweight and scriptable image viewer.
Add the following entries in ~/.config/sxiv/exec/key-handler6
If this file doesn’t exist, you can copy the sample one: mkdir -p ~/.config/sxiv/exec/ && cp /usr/share/doc/sxiv/examples/key-handler .
1# ~/.config/sxiv/exec/key-handler2case "$1" in3# ...4"C-1") tr '\n' '\0' | xargs -0 -I {} exiftool -rating=1 -overwrite_original_in_place "{}" ;;5"C-2") tr '\n' '\0' | xargs -0 -I {} exiftool -rating=2 -overwrite_original_in_place "{}" ;;6"C-3") tr '\n' '\0' | xargs -0 -I {} exiftool -rating=3 -overwrite_original_in_place "{}" ;;7"C-4") tr '\n' '\0' | xargs -0 -I {} exiftool -rating=4 -overwrite_original_in_place "{}" ;;8"C-5") tr '\n' '\0' | xargs -0 -I {} exiftool -rating=5 -overwrite_original_in_place "{}" ;;9esac
The resulting workflow is as follows:
- Open images with
sxiv. - Press
C-x C-<1..5>to rate the current image. - Or mark images with
m7 See sxiv/config.def.h for more keybindings to mark images. , toggle thumbnails mode withRETand pressC-x C-<1..5>to rate selected images.
Bonus
Some interesting tips are presented here.
The main dependencies are zsh and GNU parallel, adapt it
to fit your need.
Rate or view images of specific rating
The long command below search all JPG files (potentially symlink) of rating 3, 4
or 5 and view them with sxiv.
1# 1. Find recursively all JPG under the current directory2# 2. Use GNU parallel, for each JPG3# (a) noglob (zsh specific): disable zsh globing to prevent "No such file" error4# (b) Build the string "$Rating $Filepath"5# (c) Remove the last line of exiftool output which count the amounts of the files.6# (d) Keep entry with $Rating in {3, 4, 5}. Remove the first space.7# 3. Use GNU parallel: view at most 250 found images with sxiv at a time.8find -L . -type f -regex ".*\.jpg" |9parallel -L 5000 \10'noglob exiftool -p '\''$Rating $Filepath'\'' -f -q -q {} | \11head --lines=-1 | \12awk '\''$1 ~ /[345]/ {$1=""; print substr($0, 2)}'\'' ' |13parallel -L 250 -q sxiv "{}"
Some remarks:
- To see unrated images, use
-as rating, then you can rate them withsxivas above. - This one-shot command is fast enough for hundreds of images. Above this amount, you may want to take some time to dump the result in a file as follows.
1find -L -type f -regex ".*\.jpg$" |2parallel -L 5000 \3'noglob exiftool -p '\''$Rating $Filename'\'' -f -q -q {} | \4head --lines=-1 | \5awk '\''$1 ~ /[^0-]/ {print $0}'\'' ' >> ratingdb.txt
And view images with specific rating with this command:
1awk '$1 ~ /345/ {$1=""; print substr($0, 2)}' ratingdb.txt |2parallel -L 250 -q sxiv "{}"
- The best would be writing a python script to maintain an SQLite database, and adapt the script of ranger and sxiv above to update the database each time a file rating changed.
shufmay be used afterawkto shuffle images before viewing them.- Actually, no one will type again and again those lengthy commands. I either
use
C-Rinzshwith fzf for casual ones or add them as entries in pet, a manager of parametrizable snippet.
Migrate XMP rating into image metadata
If you migrate from darktable or have XMP files with rating, you can try the following commands.
1zmodload zsh/mapfile2# 1. Find recursively all XMP under the current directory3# 2. Use GNU parallel, for each XMP4# (a) Extract the rating with grep and awk; put it in $RATE.5# (b) Append the image path to .xmp-$RATE-rating.db6find . -type f -regex ".*\.xmp" -print0 |7parallel -0 'TMP=$(grep -o "Rating=\"[012345]\"" {} | \8awk '\''{print gensub(/Rating="([0-5])"/, "\1", "g", $1)}'\''); \9echo {} >> xmp-$TMP-rating.db'10# 3. For each $RATE:11for i in {1..5}; do12FNAME="xmp-$i-rating.db"13# For each file in .xmp-$RATE-rating.db14for f in "${(f)mapfile[$FNAME]}"; do15# Use exiftool to rate it with the corresponding $RATE16exiftool -rating=$i -overwrite_original_in_place "${f%.*}"17done18done
Footnotes
-
Dozen hours for 950.000 images. ↩
-
Statistical classification of images. For each class, it creates a directory in which each symbolic link is associated to the actual image. ↩
-
Beware, without
-overwrite_original_in_place, symlink will be removed! ↩ -
You may notice that you can’t write MP3 metadata.
ffmpegshould be used instead. ↩ -
As long as you use the right terminal emulator, e.g. iTerm, kitty, urxvt, xterm etc. From my past experience, Elementary OS’s pantheon-terminal doesn’t work. ↩
-
If this file doesn’t exist, you can copy the sample one:
mkdir -p ~/.config/sxiv/exec/ && cp /usr/share/doc/sxiv/examples/key-handler .↩ -
See sxiv/config.def.h for more keybindings to mark images. ↩