Flickerize My Photolog

Posted on May 6, 2005

What is This then?

This page contains a script for getting all the photos from a given flickr photoset and some HTMl and CSS to display them in a nice way.

Introduction and Motivation

This script was born out of my desire to use Flickr as a “storage engine” for my photolog.

I wanted to take a specific photoset from my Flickr account and display those photos as a photolog, on my site, with my own design.

The best way to do it (OK, it may or may not be the best, but it sure is the most fun) was via it’s API, specifically, the Flickr::API Perl’s API.

At the time of this writing the script goes out and fetches the information on the photos but doesn’t do anything intelligent with it. It is a work in progress and it is getting late…

Source Code

This version of the source code still has it’s own functions to parse the response from the Flickr::API::Response object. This job will be moved to a new set of modules and will be removed from here but until then here it is.

One more note: this script outputs something which is very specific to my own test site and it expects to have a combination of pages which include the files it outputs and also a CSS style-sheet to make it all look good.

In this page you will also find the companion files for my test setup.

#!/usr/bin/perl -w

use strict;
use Flickr::API;
use Data::Dumper;

my $DEBUG=1;
my $flickr_api_key='<YOUR API KEY>';
my $flickr_username='<YOUR FLICKR USERNAME>';
my $desired_photoset='<YOUR PHOTOSET NAME>';
my $api = new Flickr::API({'key' => $flickr_api_key});
my $local_photos_path='<LOCAL PATH FOR THE SITE>';
my $local_index_file=$local_photos_path.'/photolistbody.html';
my $local_individual_photos_path=$local_photos_path.'/photos';


# Get the nsid from the username
my $findUser = $api->execute_method('flickr.people.findByUsername',
	       {
		 'username' => $flickr_username
	       });
testError($findUser);

my $goodies = $findUser->{tree}{children}[1]{attributes};
my $nsid = $goodies->{nsid};
print STDERR "Got nsid: $nsid\n" if $DEBUG;

# Now get the required photoset's ID
my $getList = $api->execute_method('flickr.photosets.getList', {
	       'user_id' => $nsid
	       });
testError($getList);

my $photoset_id;
foreach $goodies (@{$getList->{tree}{children}[1]{children}}) {
  next unless (defined($goodies->{name})
		   and ($goodies->{name} eq 'photoset'));
  my $found_photoset=0;
  foreach my $this_photoset (@{$goodies->{children}}) {
    next unless (defined($this_photoset->{name})
		     and ($this_photoset->{name} eq 'title'));
    if ($this_photoset->{children}[0]{content} eq $desired_photoset)
    {
	$found_photoset = 1;
	last;
    }
  }
  if ($found_photoset) {
    $photoset_id = $goodies->{attributes}{id};
    last;
  }
}
print STDERR "Got the ID for the '$desired_photoset' photoset: $photoset_id\n"
  if $DEBUG;

# Let's finally get the pictures from the photoset
my $getPhotos = $api->execute_method('flickr.photosets.getPhotos', {
	       'photoset_id' => $photoset_id
	       });
testError($getPhotos);

my @photos;
foreach $goodies (@{$getPhotos->{tree}{children}[1]{children}}) {
  next unless (defined($goodies->{name}) and ($goodies->{name} eq 'photo'));
  unshift @photos, {title => $goodies->{attributes}{title},
		 flickrurl => photoFlickrURL($goodies->{attributes}),
		 localurl => photoLocalURL($goodies->{attributes}),
		 flickrthumb => photoFlickrURL($goodies->{attributes},'s'),
		 id => $goodies->{attributes}{id},
		 secret => $goodies->{attributes}{secret}};
}

foreach my $photo (@photos) {
  my $getInfo = $api->execute_method('flickr.photos.getInfo', {
		photo_id => $photo->{id},
		secret => $photo->{secret},
		});
  testError($getInfo);

  # Get all the relevant data for each photo
  my $info_data = readInfo($getInfo);
  foreach my $data_item (keys(%$info_data)) {
    $photo->{$data_item} = $info_data->{$data_item};
  } 
  
  my $getExif = $api->execute_method('flickr.photos.getExif', {
		photo_id => $photo->{id},
		secret => $photo->{secret},
		});
  testError($getExif);

  # Get all the EXIF data for each photo
  $photo->{exif} = readExif ($getExif);
}

# Finaly write the index file and the individual photo files
open INDEX, ">$local_index_file" or die "Could not open index file for writing: $!";
print INDEX "<ul id=\"thumbList\">\n";
my $index=0;
foreach my $photo (@photos) {
  open PHOTOFILE, ">$local_individual_photos_path/".$photo->{id}.".html" or die "Could not open photo file for writing: $!";
  print INDEX "<li><a href=\"".$photo->{localurl}."\"><span class=\"thumbNail\"><img src=\"".$photo->{flickrthumb}."\" alt=\"".$photo->{title}."\" /></span><span class=\"thumbNailTitle\">".$photo->{title}."</span></a></li>\n";
  print PHOTOFILE "<ul class=\"photoNav\">\n";
  if (exists $photos[$index+1]) {
    print PHOTOFILE "<li><a href=\"".$photos[$index+1]->{localurl}."\"><--</a></li>\n";
  } else {
    print PHOTOFILE "<li>|--</li>\n";
  }
  print PHOTOFILE "<li><a href=\"ispy.html\">index</a></li>\n";
  if ($index) {
    print PHOTOFILE "<li><a href=\"".$photos[$index-1]->{localurl}."\">--></a></li>\n";
  } else {
    print PHOTOFILE "<li>--|</a></li>\n";
  }
  print PHOTOFILE "</ul>\n";
  print PHOTOFILE "<h1 id=\"photoTitle\">".$photo->{title}."</h1>\n";
  print PHOTOFILE "<div id=\"photoBig\"><img src=\"".$photo->{flickrurl}."\" alt=\"".$photo->{title}."\" /></div>\n";
  print PHOTOFILE "<p id=\"photoDescription\">".$photo->{description}."</p>\n"
    if defined $photo->{description};
  print PHOTOFILE "<div id=\"photoData\">\n";
  print PHOTOFILE "<p id=\"photoDate\">Photographed on: ".$photo->{date_taken}."</p>\n"
    if defined $photo->{date_taken};
  if (exists $photo->{exif}) {
    print PHOTOFILE "<ul id=\"EXIFData\">\n";
    foreach my $exifItemName (keys(%{$photo->{exif}})) {
      if ($exifItemName eq 'cameramodel') {
	print PHOTOFILE "<li>Camera model: ".$photo->{exif}{$exifItemName}."</li>\n";
      } elsif ($exifItemName eq 'shutterspeed') {
	print PHOTOFILE "<li>Shutter speed: 1/".eval($photo->{exif}{$exifItemName})."s</li>\n";
      } elsif ($exifItemName eq 'aperture') {
	print PHOTOFILE "<li>Lens aperture: f/".eval($photo->{exif}{$exifItemName})."</li>\n";
      } elsif ($exifItemName eq 'ISOspeed') {
	print PHOTOFILE "<li>ISO speed: ".$photo->{exif}{$exifItemName}."</li>\n";
      } elsif ($exifItemName eq 'focallength') {
	print PHOTOFILE "<li>Focal length: ".eval($photo->{exif}{$exifItemName})." mm (35mm equivalent: ".eval($photo->{exif}{$exifItemName})*1.6."mm)</li>\n";
      } elsif ($exifItemName eq 'exposurebias') {
	print PHOTOFILE "<li>Exposure bias: ".eval($photo->{exif}{$exifItemName})."EV</li>\n";
      }
    }
    print PHOTOFILE "</ul>\n";
  }
  print PHOTOFILE "</div>\n";
  close PHOTOFILE;
  $index++;
}
print INDEX "</ul>\n";
close INDEX;



# print STDERR Dumper(\@photos) if $DEBUG;

print STDERR "Got ".scalar(@photos)." photos\n" if $DEBUG;





sub testError {
  my $response = shift;

  die "Error getting data from flickr: got error ".
    $response->{error_code}.
    " (".
    $response->{error_message}
    .")" if (!$response->{success});
}





sub photoFlickrURL {
  my $attributes = shift;
  my $size = shift || 'o';

  # Check out http://www.flickr.com/services/api/misc.urls.html for
  # the URL translation algorithm. At this time it takes this form:
  # http://photos{server-id}.flickr.com/{id}_{secret}_[mstb].jpg
  # 
  die "Error getting photo attributes" unless $attributes;
  my $photo_id;
  if (defined($attributes->{id})) {
    $photo_id .= $attributes->{id};
  } else {
    die "Didn't get a photo ID at all!";
  }
  
  my $server_id;
  if (defined($attributes->{server})) {
    $server_id .= $attributes->{server};
  } else {
    die "Didn't get the server_id from the photo with ID $photo_id";
  } 

  my $secret;
  if (defined($attributes->{secret})) {
    $secret .= $attributes->{secret};
  } else {
    die "Didn't get the secret from the photo with ID $photo_id";
  } 

  my $url = "http://photos$server_id.flickr.com/$photo_id\_$secret\_$size.jpg";
  return $url;
}





sub photoLocalURL {
  my $attributes = shift;

  die "Error getting photo attributes" unless $attributes;

  my $photo_id;
  if (defined($attributes->{id})) {
    $photo_id .= $attributes->{id};
  } else {
    die "Didn't get a photo ID at all!";
  }
  
  my $photo_title;
  if (defined($attributes->{title})) {
    $photo_title .= $attributes->{title};
  }
  
  my $url = "photo.html?phototitle=$photo_title&photoid=$photo_id";
  return $url;
}





sub readInfo {
  my $infoData = shift;

  die "Didn't get the photo info!" unless $infoData;

  my $photo_info;
  foreach my $photo_data_item (@{$infoData->{tree}{children}[1]{children}}) {
    my $a = $photo_data_item->{name};
    if ((defined $a) and ($a)) {
      if ($a eq 'description') {
	$photo_info->{description} = $photo_data_item->{children}[0]{content};
      }
      if ($a eq 'visibility') {
	$photo_info->{public} = $photo_data_item->{attributes}{ispublic};
      }
      if ($a eq 'dates') {
	$photo_info->{date_taken} = $photo_data_item->{attributes}{taken};
	$photo_info->{date_posted} = $photo_data_item->{attributes}{posted};
      }

    }
  }

  return $photo_info;
}





sub readExif {
  my $exifData = shift;

  die "Didn't get the EXIF data!" unless $exifData;
  
  my $photo_exif;
  foreach my $photo_exif_item (@{$exifData->{tree}{children}[1]{children}}) {
    my $a = $photo_exif_item->{name};
    if ((defined $a) and ($a eq 'exif')) {
      my $label = $photo_exif_item->{attributes}{label};
      my $content = $photo_exif_item->{children}[1]{children}[0]{content};
      if ($label eq 'Model') {
	$photo_exif->{cameramodel} = $content;
      } elsif ($label eq 'ISO Speed') {
	$photo_exif->{ISOspeed} = $content;
      } elsif ($label eq 'Shutter Speed') {
	$photo_exif->{shutterspeed} = $content;
      } elsif ($label eq 'Aperture') {
	$photo_exif->{aperture} = $content;
      } elsif ($label eq 'Exposure Bias') {
	$photo_exif->{exposurebias} = $content;
      } elsif ($label eq 'Focal Length') {
	$photo_exif->{focallength} = $content;
      } 
    }
  }

  return $photo_exif;
}

The main index file

This file is called ispy.html and it is the main entrance page.
Mostly it just includes the main page generated by the script.

And a quick note: Yes, I do use PHP for this. It is just so right for this task… ;-)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>iSpy</title>
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
<? include "photolistbody.html" ?>
</body>
</html>

The individual photo page

This is the file which includes each individual photo (whose content is generated by the script).

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<?
  $phototitle = $_GET[phototitle];
  $photoid = $_GET[photoid];
?>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>iSpy - <? print $phototitle ?></title>
<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body id="photoDisplay">
<? include "photos/$photoid.html" ?>
</body>
</html>

The Style-sheet

This is the style.css file. I’m no CSS wizard, so this is essentially a test of my own.

#thumbList {
  list-style-type: none;
  width: 100px;
  /*margin: 0 auto 0 auto;*/
}

#thumbList li {
  margin-top: 20px;
}

#thumbList li a {
  text-decoration: none;
}

#thumbList li a:hover {
  text-decoration: underline;
  color: #ab5518;
}

.thumbNail {
  display: block;
  text-align: center;
}

.thumbNail img {
  border: none;
}

.thumbNailTitle {
  font-family: verdana, arial; 
  font-size: 80%;
  display: block;
  text-align: center;
}

#photoDisplay {
  width: 800px;
  margin: 0 auto 0 auto;
  font-family: verdana, arial;
  color: #999999;
  background-color: #333333;
}

#photoDisplay h1 {
  text-align: center;
}

.photoNav {
  list-style-type: none;
  text-align: center;
  margin: 20px auto 0 auto;
  /*border: 1px solid red;*/
}

.photoNav li {
  display: inline;
  /*border: 1px solid yellow;*/
}

.photoNav li a {
  text-decoration: none;
  color: #999999;
}

.photoNav li a:hover {
  text-decoration: underline;
  color: #ab5518;
}

#photoBig {
  margin: 0 auto 0 auto;
  text-align: center;
}

#photoBig img {
  border: 35px solid #222222;
}

#photoDescription {
  width: 600px;
  margin: 20px auto 0 auto;
  padding: 15px;
  border: 2px solid #222222;
  background-color: #2f2f2f;
  font-family: verdana, arial;
  text-align: left;
  font-size: 100%;
}

#photoData {
  width: 600px;
  margin: 20px auto 50px auto;
  padding: 5px;
  border: 2px solid #222222;
  background-color: #2f2f2f;
  text-align: left;
  font-size: 75%;
  font-family: verdana, arial;
}

#EXIFData {
  list-style-type: none;
}