Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris

iPhoto CGI Thumbnail gallery

by tfrayner (Curate)
on Nov 13, 2003 at 14:21 UTC ( #306796=sourcecode: print w/replies, xml ) Need Help??
Category: CGI Programming
Author/Contact Info Tim Rayner
Description: If, like me, you're a complete control freak who doesn't like the way Apple's iPhoto application exports web page thumbnail galleries, this script could be for you. It can be dropped into existing iPhoto web galleries where it parses out information from the relevant pages and builds thumbnail galleries with full navigation links between photos. Instead of having to edit the page for every photo to change the overall look of a gallery, now everything is in one place. Maintaining a consistent 'look' across your photo gallery web site now becomes trivial. The script generates XHTML-compliant pages (thanks to which can be arranged using CSS. Personally I like to use this script with the CSS scheme described here:

Tableless layout HOWTO

The DIV tags are set up with appropriate ID and CLASS attributes to allow this layout to be used.

N.B. This script has currently only been tested with iPhoto 2 exported galleries (the current version at the time of posting). Also note that the script has been designed to work with the pages that iPhoto generates and so you shouldn't delete them.

Comments, particularly regarding the security or otherwise of this script, are always welcomed.

Updated: Minor amendment to the documentation.

Update 2: Minor bug fix.

#!/usr/bin/perl -Tw

use strict;
use warnings;

use CGI qw( :standard );
use POSIX;
require HTML::TokeParser;

# set some vars to help defend against DoS attacks
$CGI::POST_MAX=1024 * 10;   # max 10K posts
$CGI::DISABLE_UPLOADS = 1;  # no uploads

# Copyright Tim F. Rayner, 2003
# This is a CGI script to convert Apple iPhoto-exported
# thumbnail galleries into a slicker-looking XHTML/CSS based 
# layout. Minor editing by the user is required: The DATA
# section at the bottom of this script holds values for each
# gallery in a |-delimited table format. Required parameters 
# include: (1) a keyword identifier, matching the name of 
# the directory which holds the three "-Pages", 
# "-Thumbnails" and "-Images" directories produced by 
# iPhoto; (2) the actual title of the gallery to be used in 
# the generated HTML; (3) the total number of photos in the 
# gallery (this seems safer than automatically detecting the 
# number of images available); (4) the background colour
# to use (I prefer a centralised location for such things) 
# in the standard #nnnnnn hexadecimal form (other forms 
# usable in CSS should also work, but are untested); and
# (5) a 'subdirectory' term, for when the individual gallery 
# page is held in a subdirectory of the top-level directory. 
# An example of the directory organisation:
# /var/www/htdocs/photos/spain/madrid
# /var/www/htdocs/photos/spain/barcelona
# /var/www/htdocs/photos/wedding
# /var/www/htdocs/photos/vacation
# /var/www/htdocs/styles/photos.css
# You should also set the variables $docroot, $galleryroot 
# and $stylesheet.
# For the example above:
# my $docroot = "/var/www/htdocs";
# my $galleryroot = "/photos";
# my $stylesheet = "/styles/photos.css";
# In this case, the wedding and vacation galleries are in 
# the top level and so do not require subdirectory 
# information; however, the various spain galleries are in
# the spain subdirectory and therefore require entries in 
# the DATA section such as:
# madrid|Photos of Madrid|24|#fdffbc|spain
# Finally, the script is called as:
# This assumes you call the script and put it in 
# the /photos directory; you can call it anything you want 
# and put it anywhere you like, however.
# (From there, the rest should be obvious - I hope!)

my $docroot = "/var/www/htdocs";       # Path to webpages on the serve
my $galleryroot = "/photos";           # Path from $docroot to where t
+he galleries are
my $stylesheet = "/styles/photos.css"; # Path from $docroot to where t
+he stylesheet is

my $rowno = 5;       # Number of rows in the thumbnail galleries
my $colno = 3;       # Number of columns in the thumbnail galleries

### END USER CONFIG ### - But also see DATA section at end of script!

sub parsedata{

  # Get the relevant <DATA>
  # Takes: $cgi->param("page"), as passed to it
  # Gives: hashref containing all the data parsed from <DATA> for the 
+given page

    my $target=shift;
    while (my $line=<DATA>){
        chomp $line;
        my %data;
+ir})=split /\|/,$line;
        return(\%data) if ($target eq $data{page});

sub drawindex{

    # Draw an index page

    # Takes a CGI object, a data hashref (originally from &parsedata),
    # the number of columns and rows desired in the index table (this 
    # trucated as necessary to match the total number of pictures in
    # $data{number}.

    my ($cgi,$dataref,$colno,$rowno)=splice(@_,0,4);

    my %data    = %{$dataref};
    my $idxno   = POSIX::ceil($data{number}/($colno * $rowno));
    my $index   = $cgi->param("index");
    my $prev    = $index-1;
    my $next    = $index+1;
    my $pageroot = "$galleryroot/$data{subdir}/$data{page}"; # Where t
+he thumbnail, page and image dirs are
    my $url     = $cgi->url();

  # Check our index is in range

    unless ($index > 0 && $index <= $idxno){
        print $cgi->h4("Error in CGI input: index out of range.");
        return 0;

  # Get the thumbnail titles

    my @alt;

    for (my $i=1; $i <= $idxno+1 ; $i++){

        my $tmpidx = $i - 1;
        my $indexfile = ($i == 1) ? "$data{page}.html" : "Page$tmpidx.

        open (PAGE, "<$docroot$pageroot/$indexfile") or die("$!\n");

        my $p = HTML::TokeParser->new(\*PAGE) or die ("Can't open: $!\

        while (my $token = $p->get_token) {
            if ((${$token}[0] eq "S") && (${$token}[1] eq "img")){
                push @alt, ${$token}[2]->{alt};
        close (PAGE) or die ("$!\n");
        last if ($#alt >= ($data{number}-1));

  # Start the page content

    print $cgi->h1($data{title});         # level 1 header

  # Navigation bar
  # (new version using $" trick to insert spacer characters)
    my @navbar;
    for (my $i=1; $i <= $idxno; $i++){
        push @navbar, a({href => "$url?page=$data{page}&amp;index=".$i
+}, "Page ".$i)
        local($") = ' | ';
        print $cgi->h3(@navbar),hr;

  # Build the table

    my @table;
    for (my $r=0; $r < $rowno; $r++){
        my @row;
        for (my $c=0; $c < $colno; $c++){
            my $item = (($index - 1) * $colno * $rowno) + ($colno * $r
+) + $c;
            last if ($item >= $data{number});
            push @row, h5(a({ href => "$url?page=$data{page}&amp;image
+=".$item } ,
                         img({ src    => "$pageroot/$data{page}-Thumbn
                               alt    => "$alt[$item]",
                               } ),br,"$alt[$item]"
        push @table, td(\@row);

  # Print the table

    print $cgi->table( { -width => "100%",
                         -align => "center",
                       Tr( { -align  => "center",
                             -valign => "bottom",

  # Finish off nicely

    print $cgi->hr,h5(a({href => "$galleryroot/"},"Back to Galleries p


sub drawimage{

    # Draw an image page

    # Takes a CGI object, a data hashref (originally from &parsedata),
    # the number of columns and rows desired in the index table (this 
    # trucated as necessary to match the total number of pictures in
    # $data{number}.

    my ($cgi,$dataref,$colno,$rowno)=splice(@_,0,4);

    my %data = %{$dataref};
    my $pageroot = "$galleryroot/$data{subdir}/$data{page}";
    my $url = $cgi->url();
    my $image = $cgi->param("image");
    my $prev = $image-1;
    my $next = $image+1;

  # Check our image is in range

    unless ($image >= 0 && $image < $data{number}){
        print $cgi->h4("Error in CGI input: image number out of range.
        return 0;

  # Get the relevant photo title, from the iPhoto HTML files
    my $imgtitle;
    open (PAGE, "<$docroot$pageroot/$data{page}-Pages/Image$image.html
+") or die("$!\n");

    my $p = HTML::TokeParser->new(\*PAGE) or die ("Can't open: $!\n");

    while (my $token = $p->get_token) {
        if ((${$token}[0] eq "S") && (${$token}[1] eq "title")){  # fi
+nd the <title> tag
            $token = $p->get_token;                               # ge
+t the next token (containing the title text)
            if (${$token}[0] eq "T"){                             # ch
+eck it's the right one
                $imgtitle = ${$token}[1];
                last;                                             # fo
+r what little efficiency it affords us
    close (PAGE) or die ("$!\n");

    # Start the page content

    print h2($data{title});         # level 2 header

    if ($prev >= 0){
        print $cgi->div({id    => "list1",
                         class => "link-list"
                        h5(a({-href => "$url?page=$data{page}&amp;imag
                             img({src    => "$pageroot/$data{page}-Thu
                                  alt    => "Previous image",
                                  align  => "left",
                                  width  => "100%",

    if ($next < $data{number}){
        print $cgi->div({id    => "list2",
                         class => "link-list"
                        h5(a({-href => "$url?page=$data{page}&amp;imag
                             img({src    => "$pageroot/$data{page}-Thu
                                  alt    => "Next image",
                                  align  => "right",
                                  width  => "100%",

    if ($image >= $data{number}){
        print $cgi->h4("Error: image number out of range.");
        print $cgi->div({id    => "main",
                         align => "center"
                        img({src   => "$pageroot/$data{page}-Images/$i
                             alt   => "$imgtitle",

    my $index = POSIX::floor($image/($colno * $rowno))+1;
    print $cgi->h5(a({-href=>"$url?page=$data{page}&amp;index=$index"}
+,"Back to index"));


### MAIN ###

my $cgi = new CGI;

# create the HTTP header
print $cgi->header;

# Check that the script was passed a page keyword parameter
unless (defined $cgi->param("page")){
    print $cgi->h4("Error in CGI input: \"page\" parameter undefined.\

# Get the relevant data from <DATA>, check that there's a match
my $dataref=&parsedata($cgi->param("page"));
unless ($dataref){
    print $cgi->h4("Error in CGI input: requested page not found in in

my %data = %{$dataref};

# Start the HTML
print $cgi->start_html(-title   => $data{title},
                       -style   => {'src'  => "$stylesheet",
                                    'code' => "html,body {background-c
+olor: $data{bkgd}}",

if (defined $cgi->param("index")){         # Draw an index page, if re


}elsif (defined $cgi->param("image")){     # Draw an image page, if re



    print $cgi->h4("Error in CGI input: Neither index nor image page r


# end the HTML
print $cgi->end_html;

# DATA is in the form:
# page|title|total no. of photos|background colour|subdirectory
# Note: page param and $page should be identical to each 
# other and the the name of the relevant subdirectory
wedding|Wedding Photos|30|#daffcc|
madrid|Photos of Madrid|30|#fdffbc|spain
barcelona|Photos of Barcelona|34|#fdffbc|spain
vacation|Holiday Pics|28|#fdffbc|

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://306796]
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others musing on the Monastery: (6)
As of 2021-10-27 13:54 GMT
Find Nodes?
    Voting Booth?
    My first memorable Perl project was:

    Results (93 votes). Check out past polls.