2008-04-14

Simplifying Digital Camera Access on GNU/Linux

Digital camera access is simple enough on GNU/Linux, but with a couple of tweaks here and there, it can be made even simpler.

Summary: I keep my photos in directories named "yyyy-mm-dd" by the date taken. When I plug in the camera, photos are automatically downloaded and sent to correct directories. If you like to know how I did it, please read on!

Accessing digital cameras has always been simple on GNU/Linux. A large number of digital cameras are supported out of the box. When using the shell, arguably gphoto is the most convenient. Running gphoto with the "P" option autodetects the camera and downloads all the photos in it.

% gphoto2 -P

I keep all my photos in a "photos" directory with subdirectories in the "yyyy-dd-mm" format indicating the date taken.

First step of simplification is to automatically put each image into the correct location. Digital cameras put a lot of Exif information into each image, so extracting the date taken is quite straightforward. I use a simple tool called exif to do this.

Both gphoto2 and exif are available on Debian.

# apt-get install gphoto2 exif

After some trial and error, I figured that the images taken with my Canon PowerShot S3 IS have a tag 0x132 indicating the date each photo was taken.

% exif -t 0x132 IMG_0416.JPG 
EXIF entry 'Date and Time' (0x132, 'Date and Time')...
Tag: 0x132 ('DateTime')
  Format: 2 ('Ascii')
  Components: 20
  Size: 20
  Value: 2007:10:22 06:05:49

What we want is in the "Value:" line. After filtering that line with grep, and using sed a couple of times, we can get the date in yyyy-dd-mm format.

% exif -t 0x132 IMG_0416.JPG | \
    grep 'Value: ' | \                           # Filter the line with "Value:"
    sed 's/.*Value: \(....:..:..\) .*/\1/' | \   # Get the yyyy:mm:dd part of the value line
    sed 's/:/-/g'                                # convert ":" to "-"
2007-10-22

If you want to understand exactly what each step is doing, try the above pipeline by adding one filter at a time.

Then I put together a small script to move each image in the current directory to ~/pictures/yyyy-mm-dd/ subdirectories where I want them.

gphoto2 -P

for i in *.JPG
do
    date=$(exif -t 0x132 $i | \
        grep 'Value: ' | \
        sed 's/.*Value: \(....:..:..\) .*/\1/' | \
        sed 's/:/-/g')
    dir="/home/anuradha/pictures/test/$date"
    mkdir -p "$dir"
    mv -f "$i" "$dir"
done

Notice that I use a test directory. I saved this in ~/bin/, and made it executable.

Now comes the fun part. After connecting the camera to the computer, I used "lsusb" to find out its vendor ID and product ID are 04a9:311a. The following udev rule in /etc/udev/rules.d/010_local.rules invokes the above script whenever this camera is plugged in.

ACTION=="add", BUS=="usb", \
    SYSFS{idVendor}=="04a9", SYSFS{idProduct}=="311a", \
    RUN+="/home/anuradha/bin/pictures.sh"

Well, matters are a little more complicated. Udev seems to invoke the script multiple times. So I added two extra "features" to stop that.

  • Adding a lock file to prevent multiple simultaneous running of the script.
  • Use a "timestamp" file at the end of the script, and not run again "too soon" (60 seconds turned out to be ok).

These made sure that the script is run only once when the camera is plugged in.

I used the number of seconds since the Unix Epoch given by the stat and date commands. If the timestamp file was created less than 60 seconds ago, the script aborts.

So here is the complete script:

#!/bin/bash

set -e

user=anuradha
group=users
pictures="/home/$user/Pictures"
download="$pictures/.download"
logdir="$pictures/log"
log=$(date +"$logdir/%Y-%m-%d");
cooldown=60

lock=/tmp/.pictures.download
lasttime=$lock.time

# Avoid multiple simultaneous runs
ln -s $lock $lock || exit 0

# Abort if we had run less than $cooldown seconds ago
if [ -f "$lasttime" ]
then
    t1=$(stat -c '%Z' $lasttime)
    t2=$(date +'%s');
    dt=$((t2 - t1))
    if [ $dt -lt $cooldown ]
    then
        rm -f $lock
        exit 0
    fi
fi

# Take it slowly ;-)
sleep 3

mkdir -p $download
mkdir -p $logdir
rm -f $download/*

# Get the photos, all of them
cd $download
gphoto2 -P

for i in *.JPG
do
    date=$(exif -t 0x132 $i | \
        grep 'Value: ' | \
        sed 's/.*Value: \(....:..:..\) .*/\1/' | \
        sed 's/:/-/g')
    dir="$pictures/$date"
    if [ ! -f "$dir/$i" ]
    then
        [ -d $dir ] || mkdir -p $dir
        chown $user:$group $i
        chmod 644 $i
        chown $user:$group $dir
        mv -f $i $dir
        echo "$date/$i" >> $log
    fi
done

cd
rmdir $download

# Add a timestamp
touch $lasttime

rm -f $lock
exit 0

3 comments:

Dilantha said...

Nice. I was using f-spot on Ubuntu. It puts files in yyyy/mm/dd anyway this is good to know. Will try out gphoto.

Also this udev bit might help with a backup script that I wanted triggered when my external usb drive was connected.

Thanks.

Anuradha Ratnaweera said...

Hi Dilantha,

Gphoto2 is one of the several programs that use the gphoto library. Digikam, gphotofs and f-spot all use gphoto library.

Be extra careful about multiple and simultaneous invocations of the script. As I have indicated in the post, a lock file and a timestamp will be good enough.

Dilantha said...

Ya I think I'll stick to my f-spot setup. I don't mind importing the photos manually once in a while.

But definitely going to look at the udev thing when I get some time.