How to sort 400 wedding photos using the command line

I have recently got married and the wedding day was beautiful. Everything went perfectly smooth, we met all our friends and family, there was a lot of dancing and a lot of fun. And also we got a lot of photos. So yesterday I sat down with determination to sort all those photos so that our guests get some pictures with themselves as a small gift from us. My idea was to have a separate folder for each guest (or a couple of guests) and copy photos from the main folder to those folders. And I knew that I definitely don’t want to do that the “normal way”. That is: open a file manager, select a photo, open it in image viewer, copy, create a folder for the given guest (or multiple), paste the photo there, go back to the main folder, select the next photo, repeat. Oh, Gosh, how tedious is that? So I decided utilize the power of the command line to make this task much less manual and also give myself a nice brain exercise.

Viewing and sorting photos

First, a short demo of the final result:

Demo

Note: for the demonstration purposes I used stock photos from pixabay.com

For this task I needed two things:

  1. An image viewer that is extensible enough to allow running custom actions on an image being viewed;
  2. Some way to quickly and efficiently create folders and copy stuff there.

After some research I found a program that can fulfill the first criterion. It’s feh - lightweight and configurable command line based image viewer. Feh has a really minimalistic interface and can be controlled either by keyboard or mouse. It also has a number of modes it can work in and a lot of command line options, including custom actions. -A option allows to specify a shell command which will be executed upon hitting Return key on the keyboard. There are also options --action1 .. --action9 to bind more actions to the corresponding number keys. So with this command

feh -F -A "cp %F Guests" *.jpg

we run feh in fullscreen mode (option -F), assigning the cp command to the Return key. %F is a placeholder for the path to the current image, so the command basically means Copy the current image to the Guests directory. Feh will show all the images in the current directory, as a slideshow - the default mode in feh.

Great, we’re half-way there! Now we need to build a command that will allow us to copy the current image to the directory of the given guest. Ideally the command should also create the directory for us if it doesn’t exist yet.

The program which I chose to use for this is Rofi. Rofi’s main use-case is to be an application launcher - similarly to Spotlight on MacOS. Unlike Spotlight though, Rofi can also be used in the command line and act as a filter. Which means that it takes a list of items and lets the user to select one of those items with fuzzy search. It might not sound to exciting, but this is exactly what I needed for the task. Here is the script that I wrote to be called from feh.

#!/bin/sh

dir=${GUESTS_DIR:-Guests}

[ ! -d $dir ] && echo "Guests dir is not found" && exit

selected_dir=$(ls $dir | rofi -dmenu -i)

[ ! "$selected_dir" ] && echo "No directory is selected" && exit

output_dir=$dir/$selected_dir

[ ! -d $output_dir ] && mkdir $output_dir

cp $1 $output_dir/$1

The first few lines just check if the output directory exists. The 7-th line is the most important here: it gets the directory name that we want to put our image into. We get a list of directories which are already present in the output directory with ls $dir and those will be the options that rofi presents for us. Then we can type a few letters and it will filter the options that contain those letters. As soon as we press enter, rofi will print the selected option to the standard output and it will be assigned to $selected_dir variable. Note that it also allows to type an option that is not currently present on a list.

I also specify the -dmenu option for rofi, since enables it to run as a filter. Dmenu, by the way, is another program itself, which is in some sense similar to rofi, but is considered more minimal. -i flag is there to make the fuzzy-search case-insensitive.

Note: if you get an error in rofi saying that it failed to open theme, you will need to edit the config. Go to ~/.config/rofi/config file and find the line that starts with rofi.theme. Make sure this is actually correct absolute path. In my case it had a value of .config/rofi/rofi.rasi, which is a relative path and therefore rofi failed to load from any directory that is not my home directory. Changing the path to ~/.config/rofi/rofi.rasi solved the issue.

The rest is straightforward: we check if any option was selected, then we combine the directory names to get the final path, we create this directory if it doesn’t exist and we copy an image there. Now what’s left is to save this script in a file named sort.sh, make it executable with chmod +x sort.sh and call it from feh:

feh -F -A "./sort.sh %F" *.jpg

And that’s it. Very efficient workflow, where copying one image takes less than 2 seconds. It allows going through the entire process without lifting your hands off the keyboard, to always have the image viewer open full-screen, and not to get distracted by switching windows all the time. You can grab the demo project and try it for yourself from my GitHub repo.

As a bonus though, here are two more things I’ve done within the same task:

Bonus 1: view all photos of the specific guest

Now that we have all photos sorted, would be nice to also have a way to quickly view photos of different guests. For this task I wrote another script:

#!/bin/sh

dir=${GUESTS_DIR:-Guests}

[ ! -d $dir ] && echo "Guests dir is not found" && exit

selected_dir=$(ls $dir | fzf)

[ ! "$selected_dir" ] && echo "No directory is selected" && exit

feh -F $dir/$selected_dir

This should already look familiar, except for the fact that we use fzf instead of rofi here. fzf is also fuzzy-finder, but for the command line, not GUI like rofi. I use these two programs depending on the context. The previous script wasn’t called directly from the command line, so the graphical promt was needed (rofi). And this one is supposed to be run directly from terminal, hence I use fzf.

Demo

Bonus 2: count how many photos each guest has

Another thing that I thought will be useful to know is how many photos did each guest get. For this I decided to have each guest name along with a number of photos in some file arranged in columns. This way I can further process this data with tools such as awk or sort to get any kind of information I want. Here is the script:

#!/bin/sh

dir=${GUESTS_DIR:-Guests}

[ ! -d $dir ] && echo "Guests dir is not found" && exit

echo "" > people.txt

for d in $(ls $dir)
do
    echo "$(basename $d) $(ls $d | wc -l)" >> people.txt
done

Quick overview: after checking the output dir as usual we:

  1. Clear the file people.txt from any data that was there before. It will be created if it doesn’t exist.
  2. List all directories in the output directory and loop over them
  3. For each directory we append the directory name and the number of files inside to the people.txt

So now we have an overview of all guests and how many photos they have. The following command will show them sorted from the most photographed person to the least:

sort -nr -k2 people.txt

And this one will show only those who has less than 5 photos:

awk '$2 < 5 {print $0}' people.txt

I hope this inspired someone to go and experiment with command line to optimize some of their day-to-day computing tasks. Stay curious and happy hacking!