Digital camera access is simple enough on GNU/Linux, but with a couple of tweaks here and there, it can be made even simpler.
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