How to Setup a Webcam in Linux

At the beginning of this year I announced the launch of a webcam I had setup at my parents house in the Kootenays. In the announcement I talked briefly how the whole application worked, however wanted to get into more technical details here.


  • Mini Compaq computer with Ubuntu Server installed
  • Canon A310 digital camera
  • USB Cable
  • Dedicated Server Hosted Remotely


The mini Compaq computer is located at my parents place sitting in their sun room. It is connected to the Internet, as well via a USB cable it is connected to the Canon A310 camera, which is setup on a small tripod pointing out my parents window.

On the server a cron is setup to run a script every 5 minutes. The script will determine if there is daylight enough outside to take a photo, and there is it will execute a gphoto2 command that will trigger the camera to take a photo and download the image to the computer. Once the image is on the computer it is then FTP’ed to my dedicated server for processing.

A cron is setup on my dedicated server for a script that checks an incoming folder for newly uploaded images. When a new image is found, it will process the image, creating several different file sizes, upload each file to Amazon S3 for storage and distribution through Amazon Cloudfront, and store a record of the image in a MySQL database.

Finally I have a web front end that reads from the database to display the latest photo, along with an archive of all the older photos that had been taken. Lets de-construct each step in the process to document how it all works.

Taking the Photo

The wonderful open source library gphoto2 does most of the work here. Gphoto2 allows you to control a camera in Linux via the command line. There is a large list of cameras on the gphoto2 website that are supported. Since I didn’t want to spend a lot of money on this project I bought an old Canon camera that was noted to work well with gphoto2.

On the mini computer at my parents a cron executes a PHP script every 5 minutes. The PHP script first checks to see if there are any other instances of gphoto2 running, if there are it exits. Next it determines at what time sunset and sunrise is, and if the time of day is between those two times as we don’t want to take photos of the darkness of night. Next the script checks to see if it is in the first 15 of sunrise, or last 15 minutes of sunset, if so we want to use a higher ISO on the camera to take the photo. Finally the gphoto2 script is called to take the photo.

Here is our cron entry:

*/5 * * * * /usr/bin/php /PATH/TO/BIN/bin/call-shoot-camera.php

Here is what my script looks like:

 * Script to determine if it is light outside and whether to take a photo

// directory configuration
$base_dir = "/PATH/TO/HOME/FOLDER";
$archive_dir = $base_dir . "archive/".date("Y") . "/" . date("m") . "/" . date("d") . "/";

// check to see if gphoto2 is already running
$ps = `ps ua`;
if (strstr($ps, 'gphoto2')) {
    return 0; 

// fifteen minutes in seconds
$fifteen_min = 900;

// setup directory for todays date
if (!is_file($archive_dir)) {
    `/bin/mkdir -p $archive_dir`;

// sunrise/sunset config
$long = -118.00737;
$lat = 49.8672;

$now = time();

$sunrise = date_sunrise($now, SUNFUNCS_RET_TIMESTAMP, $lat, $long, $zenith, -8);
$sunset = date_sunset($now, SUNFUNCS_RET_TIMESTAMP, $lat, $long, $zenith, -8);

$first_fifteen = $sunrise + $fifteen_min;
$second_fifteen = $sunrise + $fifteen_min + $fifteen_min;

$last_fifteen = $sunset - $fifteen_min;
$second_last_fifteen = $sunset - $fifteen_min - $fifteen_min;

// set the ISO higher if we are in the first 15 minutes of sunrise or last 15 minutes of sunset
if (($now >= $sunrise && $now <= $first_fifteen) || ($now >= $last_fifteen && $now <= $sunset)) {
    $iso = 2;
else {
    $iso = 1;

// take a photo if the time of day is between sunrise and sunset
if ($now >= $sunrise && $now <= $sunset) {
    `/usr/bin/gphoto2 --set-config capturetarget=0 --set-config capture=1 
--set-config imagesize="medium 1" --set-config aperture=4 --set-config iso=$iso 
--set-config photoeffect=1 --capture-image-and-download 
--filename "/PATH/TO/CAPTURE/capture/archive/%Y-%m-%d_%H:%M:%S.jpg" 

return 0;

I will leave you to discover what all the flags do when calling the gphoto2 command, however would like to point out the –hook-script flag. What this flag does is call a script after the newly taken image has been downloaded from the camera to the computer. Thus we are able to call a second script that will upload our new photo to our server. This script is extremely basic and uses the standard linux FTP command.


if (getenv('ACTION') == 'download') {
    $file = getenv('ARGUMENT');
    $array = preg_split("/\//", $file);

    $final_file = array_pop($array);

    `/usr/bin/ftp -n -i -p HOSTNAME_OF_SERVER <<EOF
put $file $final_file`;

As you can see, a really basic script that simply checks to see if the file was downloaded and if so to upload via ftp to our dedicated server.

I wanted to point out one final flag of gphoto2 before moving on to the dedicated server processing. An extremely handy flag when using gphoto2 with a new camera is the –list-config flag. This will tell you all the different config items of the camera you can set when using gphoto2.

Processing the Photo

On the dedicated server images are being uploaded to an incoming folder to be processed by another script. The processing script will scan the incoming folder looking for new images. Before processing an image, the script will check to make sure the image has not been written to within 20 seconds of the current time to make sure that the photo currently not being uploaded.

Once it is determined that the photo is not being uploaded we can process the image. The processing is done using the GD PHP extension, then uploaded to Amazon S3 using undesigned’s fantastic S3 PHP library. Finally the image information is stored in a MySQL database table.

Here is how the script looks:


// include our config file (contains db passwords and aws keys)

// check if the incoming folder is already being processed
if (file_exists($cam_config['tmp_dir'] . '')) {
else {
    file_put_contents($cam_config['tmp_dir'] . '', getmypid());

// libraries needed for s3 uploads and MySQL access
include_once($cam_config['base_dir'] . 'classes/s3-php5-curl/S3.php');
include_once($cam_config['base_dir'] . 'classes/db.cls');

$db = new db($cam_config['db_server'], $cam_config['db_user'], $cam_config['db_password'], $cam_config['db']);
$s3 = new S3($cam_config['aws_access_key'], $cam_config['aws_secret_key']);

// read all new files
$file_array = array();
if ($handle = opendir($cam_config['incoming_dir'])) {
    while (false !== ($file = readdir($handle))) {
        if ($file != "." &#038;&#038; $file != "..") {
            $file_array[] = $file;

// get some exif info
foreach ($file_array as $file) {
    $file_full_path = $cam_config['incoming_dir'].$file;
    $file_base = basename($file, '.jpg');

    $filemtime = filemtime($file_full_path);

    $diff = time() - $filemtime;

    // skip any file that is still being written to
    if ($diff <= 20) {

    $exif = exif_read_data($file_full_path, 'File');

    $timestamp = $exif['FileDateTime'];

    $archive_dir = $cam_config['archive_dir'] . date("Y/m/d", $timestamp);

    if (!file_exists($archive_dir)) {
        `mkdir -p $archive_dir`;

    $height = $exif['COMPUTED']['Height'];
    $width = $exif['COMPUTED']['Width'];
    // loop the different file sizes we want to create
    foreach ($cam_config['dir_array'] as $dir=>$new_width) {

        if ($dir == 'full') {
            // upload fullsize image to s3
            $s3->putObjectFile($file_full_path, $cam_config['bucket'], baseName($file_full_path), S3::ACL_PUBLIC_READ);
            `cp $file_full_path $archive_dir`;
        else {
            // resize the image
            $new_height = ceil($new_width/$width * $height);
            $src = @imagecreatefromjpeg($file_full_path);
            $dst = @imagecreatetruecolor($new_width, $new_height);
            @imagecopyresampled($dst, $src, 0, 0, 0, 0, $new_width, $new_height, $width, $height);

            $new_file = $cam_config['tmp_dir'] . $file_base . '-' . $dir . '.jpg';
            @imagejpeg($dst, $new_file, 90);

            // upload resized image to s3
            $s3->putObjectFile($new_file, $cam_config['bucket'], baseName($new_file), S3::ACL_PUBLIC_READ);
            `cp $new_file $archive_dir`;

        // insert the photo into the database
        $picture_row = array(

        $result = $db->createRow("picture", $picture_row);

    `rm -f $file_full_path`;
    `rm -f {$cam_config['tmp_dir']}*.jpg`;

// remove the pid
unlink($cam_config['tmp_dir'] . '');

With this script running on a cron we can now process any new file that gets uploaded to our server, upload it to Amazon S3, and log it in our database. With the photos now stored in the database we can easily setup a simple homepage to display the latest photo, and produce an archive of photos taken over time.


With the help of a few powerful open source programs and libraries and a small bit of hardware we are able to easily setup a camera that takes a photo every 5 minutes, and posts it to the Internet. You can view the webcam I setup at In the future I will extend the processing further such that after every day, week, or month a time lapse is generated using mencoder.