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).