Le weblog entièrement nu

Roland, entièrement nu... de temps en temps.

Synchronisation scripts for digital cameras

Here's a more detailed explanation of my digital camera setup, described in this blog post.

I've rewritten my scripts to be a bit cleaner and properly configurable; but since I started by updating only the PTP half, cameras working in USB mass storage mode are supported through a kind of hack (although the recent addition of pmount support makes it much more bearable).

Rationale

It all started a bit before my trip around the world. I had planned to bring a digital camera and a small laptop computer with me, but since the laptop wasn't exactly convenient to work with (old battery, slow computer, broken pointing device), I wanted to be able to download pictures from the camera to the computer with as little manual intervention as possible. I think I managed to do that: everything happens automatically as soon as I plug the camera into the USB port, I don't have to be logged in, much less to be running any kind of graphical interface.

Overview

First things first: this is all about a Debian GNU/Linux system. It may work with other operating systems as well, but the udev part probably requires Linux (as opposed to *BSD or Hurd).

When the camera is plugged, udev runs a script that first downloads the pictures to a directory, then registers them into the F-Spot database, then ensures there's some free space on the memory card.

One problem is that some cameras work as "USB mass storage", which means you can mount them like any filesystem, while others only work in "PTP mode". In the first case, one can use rsync, df, ls, rm and friends; in the second case, one needs to use gphoto2. As mentioned earlier, USB mass storage was handled by the first implementation (see the other page), but the second one is currently very centered around gphoto2 (especially since I've discovered that it can still work on mass storage cameras, as long as they're mounted).

The whole thing is done by a Perl script; it does the insertion into the F-Spot database by directly accessing the database via SQLite. It's not perfect: there's no locking, and the thumbnails aren't generated on the fly.

A few satellite shell scripts help make the whole thing smooth.

Implementation details

First, get the camsync script. You'll notice it uses a few Perl modules, make sure you have them installed (proper Debian packaging with dependencies and all will probably happen someday). This script uses a configuration file in $HOME/.config/camsync.conf. Here's mine:

[default]
tag_name = "À trier"
default_camera = EOS350D

[EOS350D]
camera_pattern = "Canon EOS 350D *usb: *\$"
camera_id = "Canon EOS 350D"
destination_dir = /home/roland/images/photos/.canon-spool
max_remaining = 50
dev_file = /dev/canon-eos350d

[CONTAXU4R]
camera_mode = pmount
pmount_label = CONTAXU4R
destination_dir = /home/roland/images/photos/.contax-spool
dev_file = /dev/contax-u4r
max_remaining = 100

[CONTAXU4ROLD]
camera_mode = automount
automount_point = /mnt/auto/removable/contax-u4r
destination_dir = /home/roland/images/photos/.contax-spool
dev_file = /dev/contax-u4r
max_remaining = 100

"À trier" is French for "Unsorted". The camera_pattern and camera_id are used for gphoto2; destination_dir is probably explicit, and max_remaining is the maximum number of pictures that should remain on the camera's memory card after the script has run. There are a few other configuration options, see the source for details. camera_mode can be set to pmount, in which case you'll need to have the appropriate permissions to be able to invoke pmount and pumount; on Debian systems, that requires your user to be a member of the plugdev group. The old way of supporting mass storage cameras was automount; it's still there, but it requires you have configured autofs. In both cases, the actual downloading of the pictures is still done through the gphoto2 program, which is able to work on mass storage cameras, but apparently it needs the camera to be mounted. The upside of this is that the whole script uses the same API.

I also use a shell wrapper around this script, which is stored as $HOME/bin/camsync-wrapper-canon in my setup:

#! /bin/sh

/home/roland/bin/maybe-notify.sh "EOS 350D" "Début de synchronisation" -i emblem-camera &> /dev/null || true

sleep 2
camera_owner=$(gphoto2 --get-config=owner \
    | grep '^Current:' \
    | cut -c10-)
if [ "$camera_owner" != "Roland Mas" ] ; then
    # Same model, but not mine
    exit 0
fi

(echo get-config time ; echo set-config time=$(date +%s)) \
    | gphoto2 --shell &> /dev/null

/home/roland/bin/camsync $*

/home/roland/bin/maybe-notify.sh "EOS 350D" "Synchronisation terminée" -i emblem-camera &> /dev/null || true

This script basically checks that the camera currently plugged in is actually mine, and not another one of a similar model, since I know a few people who own an EOS 350D, and they might be tempted to plug them into my computer. If it is mine, then the camera's internal clock is updated. (I have another script, $HOME/bin/camsync-wrapper-contax, which is quite similar although it doesn't do the owner trick; nor does it update the camera's clock, since I don't think you can do that over mass storage). Then there's some magic to display a pop-up if I happen to be running a desktop session (which is the only reason I have two such wrappers). I used to have a hackish script running at each session start to get the DBUS session address, but that was a hack, and I've been told of a better way. Here's my /home/roland/bin/maybe-notify.sh:

#!/bin/sh

[ "$USER" ] || USER=$LOGNAME
[ "$USER" ] || USER=$(whoami)

session_pids=$(pgrep -u "$USER" "gnome-session|xfce4-session|kwrapper")

export DBUS_SESSION_BUS_ADDRESS

for pid in $session_pids; do
  DBUS_SESSION_BUS_ADDRESS=$(grep -z '^DBUS_SESSION_BUS_ADDRESS=' \
    /proc/$pid/environ | sed -e 's/^DBUS_SESSION_BUS_ADDRESS=//' || true)
  [ "$DBUS_SESSION_BUS_ADDRESS" ] || continue
  notify-send -u low "$@"
done

The two wrapper scripts can be avoided if you don't care about pop-up notifications, multiple cameras of the same model, or clock updating.

The whole thing is started automatically when I plug the camera into the computer, by way of the following lines in my /etc/udev/rules.d/local.rules:

SUBSYSTEM=="usb_device", SYSFS{idVendor}=="04a9", SYSFS{idProduct}=="30ef", SYMLINK+="canon-eos350d", RUN+="/usr/local/sbin/canon-eos350d-trigger.sh"
KERNEL=="sd[a-z][0-9]*", SYSFS{vendor}=="Kyocera", SYSFS{model}=="CONTAX U4R", SYMLINK+="contax-u4r", RUN+="/usr/local/sbin/contax-u4r-trigger.sh"

And for completeness's sake, here's /usr/local/sbin/canon-eos350d-trigger.sh (I'm omitting contax-u4r-trigger.sh, which is quite similar):

#! /bin/sh

if [ "$ACTION" = add ] ; then
  start-stop-daemon --start --background \
    --oknodo \
    --chuid roland --user roland \
    --exec /home/roland/bin/camsync-wrapper-canon -- --camera=EOS350D
fi

TODO and warnings

This setup works for me, and I'm quite content with it. Be careful and use at your own risk, though: things might change (especially in the F-Spot database schema). I'm aware that there are a few hardcoded paths and file names and a mix of French and English and whatnot, though, so these scripts are not as generic as they could/should be. Also, the integration with F-Spot should be made optional (maybe even modular).

It would be useful if there existed a good generic program to download pictures off a digital camera without user interaction, after maybe an initial configuration (which should be automated as much as possible). I hope this set of scripts can be taken as a starting point, although there remains work to be done. If anyone wants to start such a project, it could be a good way of diving into scripting languages and it would fill a gap. If someone wants to take that idea and turn it into real code, I'd be happy to provide help in the form of testing, bug reports, guidance and maybe even patches... Oh, and a link from here, of course. Also, feel free to take this code and make it better. Unless I change my mind, the scripts evoked here are licensed under the GNU GPL, version 2.

Thanks

Thanks to Marco d'Itri, for sending in a proper udev rule spec for the PTP cameras (one that's better supported by old and possibly even future kernels). He also gave me the bulk of the maybe-notify.sh script, which I only adapted slightly (in order to make it desktop-agnostic).

Creative Commons License Sauf indication contraire, le contenu de ce site est mis à disposition sous un contrat Creative Commons.