jhead

jhead is my tool of choice for command-line manipulation of images using tags on all platforms. Whoever is into Digital Photography in earnest will appreciate the ease with which it can perform batch operations with images (it also uses jpegtran to autorotate appropriately tagged images losslessly).

Recipes

So here are a few of my recipes for managing thousands of files using it (I keep my photos in a YYYY/MM hierarchy, with only a few sets imported into or similar apps).

Command-lines for canonical timestamps and scaling:

# basic rename

jhead -exonly -nf%Y%m%d%H%M%S *.jpg

# invoke jpegtran for lossless rotation (useful when your software doesn't understand EXIF orientation)

jhead -exonly -autorot -nf%Y%m%d%H%M%S *.jpg

# batch resize

jhead -cmd "mogrify -resize 800x800 -quality 100 &" *.jpg

Organizing Photos By Camera Attributes

I’ve recently needed to reorganize a set of folders based on camera model, and came up with this (simply tweak the metadata fields to your liking, and you can organize photos at will):

import os, sys, subprocess, shutil

photos = filter(lambda x: '.jpg' in x.lower(),os.listdir('.'))
for name in photos:
    pipe = subprocess.Popen("jhead %s" % name, shell=True, stdout=subprocess.PIPE).stdout
    meta = {k: v for k, v in map(lambda x: map(lambda x: x.strip(), x.split(':',1)),pipe.readlines())[:-1]}
    folder = meta['Camera model']
    if not os.path.exists(folder):
        os.mkdir(folder)
    shutil.move(meta['File name'], folder)

Generic Automation

I now use an workflow with the following bit inside:

from time import gmtime, strftime, localtime
import sys, os, re

pattern = re.compile(".+\.(jpg|mov|avi|mp4)$", re.IGNORECASE)
paths = []

def rename(f,p,c,e,alt=''):
  n = "%s/%s%s.%s" % (p,c,alt,e.lower())
  if f == n:
    print f
    return
  if os.path.exists(n):
    if alt is '':
      alt='a'
    else:
      alt=chr(ord(alt)+1)
    rename(f,p,c,e,alt)
  else:
    os.rename(f,n)
    print n

for f in sys.stdin:
  f = f.strip()
  matches = pattern.match(f)
  if matches:
    e = matches.group(1).lower()
    p = os.path.dirname(f)
    if p not in paths:
      paths.append(p)
    c = strftime("%Y%m%d%H%M%S",localtime(os.path.getmtime(f)))
    rename(f,p,c,e)
    

for p in paths:
  os.chdir(p)
  os.system('jhead -exonly -nf%Y%m%d%H%M%S *.jpg')

…that I save as a plugin for the built-in Image Capture app.

Perl snippet to tag Minolta (mis-stamped) images:

#!/bin/perl

opendir(DIR, ".") or die("directory open error: $!");

foreach(readdir(DIR)) {
  if( /PICT00([0-9]+)\.JPG/ ) {
    system( "jhead -ts2004:05:20-12:00:" . $1 . " " . $_ );
  }
}

Alternatives

There is also an XML-enhanced version and an alternative Perl utility called renrot available as an package that has the slight advantage of (for the particular purpose of managing timestamps) letting you rename any file according to timestamp (very useful for movies and suchlike).

It can be invoked in a very similar fashion:

renrot -n %Y%m%d%H%M%S *.*

…but I’m not crazy about it.

Downloads

Here are download links for:

This page is referenced in: