macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Automated Web Photo Galleries with iPhoto and Perl
Pages: 1, 2

Perl

Perl is often called "the duct tape of the Internet," and this application certainly fits that description. Now is the time to open an editor and create some Perl code. This code needs to go in a particular location to allow the server to process it correctly. My iMac server is currently running MacOS X 10.2.8, which uses Sendmail. Accordingly, the Perl program needs to be located in the /usr/adm/sm.bin directory. You'll need to create any directories along the way, such as adm and sm.bin.

The gist of the program is that we are going to receive an email message, which will have MIME attachments. Those attachments will have to be extracted from the message, processed, and stored in a database. Once we're done processing the email, we'll send back a message indicating how many images were processed and the URLs to those images.


#!/usr/bin/perl -w
# load in the modules
use MIME::Parser;
use MIME::Entity;
use DBI;
use Image::Magick;
use Mail::Mailer;
use strict;

# get DBI vars and related info
my %dbHash = (
  "dbType"  => 'mysql',
  "dbName"  => 'domain',
  "dbHost"  => 'localhost',
  "dbUser"  => 'myUser',
  "dbPass"  => 'myPass'
);

# DBI handles
my ($sth, $sql, $rv, $dbh, $dbData);

# set up DB connection
DBConnect(%dbHash)
  or DBError("Died in Connect");

# MIME parsing vars
my ($i, $parser, $entity, $head, $preamble, 
  $epilogue, $num_parts, $part, $content_type, 
  $body, $tmp);

# extract the pieces out of the email message
$parser   = MIME::Parser->new();
$parser->output_dir("data");
$entity   = $parser->parse(\*STDIN);
$head     = $entity->head;
$preamble = $entity->preamble;
$epilogue = $entity->epilogue;

# get the subject and use it as 
# the category for all the photos
my $category = $head->get('Subject');
my $mailTo   = $head->get('To');
my $mailFrom = $head->get('From');
chomp($category);
chomp($mailTo);
chomp($mailFrom);
# get the domain name from the To mailing address
my $domain = (split('@', $mailTo))[1];

# MIME vars
my ($bh, $filename, %file, @data, $title, 
  $comments, @url, $id);
# Image::Magick vars
my ($img, $imageData, $thumbData, $err);

# loop through the file attachments
$num_parts = $entity->parts;
for ($i = 1; $i < $num_parts; $i++) {
  $part         = $entity->parts($i);
  $content_type = $part->mime_type unless 
    ($part->mime_type =~ 'text');
  $body         = $part->as_string;
  $bh           = $part->bodyhandle;
  $filename     = $bh->path;

  if (($i % 2) == 1) {
    # handle the image file
    $file{'image'} = $filename;
  }
  else {
    # handle the data file and populate 
    # the database
    $file{'data'} = $filename;

    # init the data array
    @data = ();

    # open the data file and load into 
    # data array
    open(\*FILE, "< $file{'data'}")
      or die "Error opening $file{'data'}: $!";
    while (<FILE>) {
      chomp;
      # ignore empty lines
      push @data, $_ if length > 0;
    }
    close(FILE)
      or warn "Error closing $file{'data'}: $!";

    # extract title and comments from data array
    $title = $data[0];
    shift @data;
    $comments = '';
    $comments = join ' ', @data if @data;

    # convert image to thumbnail
    $img = new Image::Magick;
    $err = $img->Read($file{'image'});
    die "Can't read image file: $err\n" if $err;
    $imageData = $img->ImageToBlob();
    $err = $img->Scale(geometry=>"200x200");
    die "Can not scale image file: $err" if $err;
    $thumbData = $img->ImageToBlob();
    
    # build, prepare and execute SQL command
    $sql = "REPLACE INTO Gallery 
      (Date, Image, Thumb, Type, Title, 
      Comments, Category)
      VALUES (NOW(), ?, ?, ?, ?, ?, ?)";
    $sth = $dbh->prepare($sql)
      or DBError("Died in prepare");
    $sth->execute($imageData, $thumbData, 
      $content_type, $title, $comments, 
      $category)
      or DBError("Died in execute");

    # get the last inserted ID to build a URL
    $sql = "SELECT ID FROM Gallery 
      WHERE ID=LAST_INSERT_ID()";
    $sth = $dbh->prepare($sql)
      or DBError("Died in prepare");
    $sth->execute()
      or DBError("Died in execute");
    $dbData = $sth->fetchrow_hashref();
    $sth->finish();
    $id = $dbData->{'ID'};

    push @url, "http://www.$domain/cgi-bin/" .
      "gallery.pl?id=$id";
  }
}

# disconnect from the database
DBDisconnect();

# remove files from data directory
$entity->purge;

# return an email message to the sender
my $mailer = Mail::Mailer->new("sendmail");
# get the number of images sent
my $count = @url;
# build the message body
my $text = "$count new image";
$text .= "s" if ($count > 1);
# build the header
$mailer->open(
  {
    Subject => "$text added to $domain gallery",
    From    => '<no-reply@' . $domain . '>',
    To      => $mailFrom
  }
);

# print the message body
$text = "The following new image";
if ($count > 1) {
  $text .= "s were";
}
else {
  $text .= " was";
}
print $mailer "$text added:\n\n";
print $mailer "$_\n" for (@url);
print $mailer "\n";

# close the message
$mailer->close();

# DB connection subroutine
sub DBConnect {
  # convert the list to a hash
  my %dbHash = @_;
  # data source name
  my $dsn = "DBI:$dbHash{'dbType'}:" .
    "$dbHash{'dbName'}:$dbHash{'dbHost'}";

  # attributes
  my %attr = (
    PrintError => 0,
    RaiseError => 1
  );
  # connection command
  $dbh = DBI->connect($dsn, $dbHash{'dbUser'}, " .
    $dbHash{'dbPass'}, \%attr) or
    DBError("Cannot connect to $dsn");
}

# DB disconnect subroutine
sub DBDisconnect {
  $dbh->disconnect()
    or DBError("Cannot disconnect from DB");
}

# DB error subroutine
sub DBError {
  my $message = shift;
  # display a message
  warn "$message\nError $DBI::err " .
    "($DBI::errstr)\n";
}

Now we've accomplished getting the images into the database. That's all well and good, but the other half of the task is displaying them on a web site. Thankfully, we just got through the longer part. There are several gallery applications available that work with databases of images. This section handles extracting three thumbnail images to be used each day on a rotating basis. Each thumbnail is linked to its full-size image, which will be displayed in a new window when the thumbnail is selected. The HTML output is presented after the Perl code.


#!/usr/bin/perl -w
use CGI;
use DBI;
use HTML::Entities;
use strict;
$|++;

# local vars
my ($id, $comments, $file, $url);

# set file name
$file = '/Library/WebServer/WebSites/' .
  'www.domain.com/include/gallery.shtml';

# get a CGI object
my $q = new CGI;

# DBI handles
my ($sth, $sql, $rv, $dbh, $dbData);

# DB connection values
my %dbHash = (
  "dbType"  => 'mysql',
  "dbName"  => 'domain',
  "dbHost"  => 'localhost',
  "dbUser"  => 'myUser',
  "dbPass"  => 'myPass',
  "dbTable" => 'Gallery'
);

# connect to the database
DBConnect(%dbHash);

# build the sql command
$sql = "SELECT ID, Comments FROM 
  $dbHash{'dbTable'} ORDER BY RAND() 
  LIMIT 3";
# prepare and execute the statement
$sth = $dbh->prepare($sql)
  or DBError("Died in prepare");
$sth->execute()
  or DBError("Died in execute");

open(\*FILE, "> $file")
  or die "Unable to open $file: $!";

while ($dbData = $sth->fetchrow_hashref) {
  $id = $dbData->{'ID'};
  $comments = $dbData->{'Comments'};
  # set HTML nbsp if no comments
  $comments = ' ' unless 
    (length($comments) > 0);
  $url = "/cgi-bin/gallery.pl?id=$id";
  # send the results to the output file
  print FILE 
    $q->td(
      {
        -align=>'center'
      },
      $q->a(
        {
          -href=>$url,
          -target=>'gallery'
        },
        $q->img(
          {
            -src=>"/cgi-bin/gallery.pl?" .
              "id=$id;thumb=1",
            -border=>0,
            -alt=>$comments
          }
        )
      ),
      $q->br(),
      $comments
    ),
    "\n";
}
$sth->finish();

close(FILE)
  or warn "Unable to close $file: $!";

# DB connection subroutine
sub DBConnect {
  # convert the list to a hash
  my %dbHash = @_;
  # data source name
  my $dsn = "DBI:$dbHash{'dbType'}:" .
    "$dbHash{'dbName'}:$dbHash{'dbHost'}";

  # attributes
  my %attr = (
    PrintError => 0,
    RaiseError => 1
  );
  # connection command
  $dbh = DBI->connect($dsn, $dbHash{'dbUser'}, " .
    $dbHash{'dbPass'}, \%attr) or
    DBError("Cannot connect to $dsn");
}

# DB disconnect subroutine
sub DBDisconnect {
  $dbh->disconnect()
    or DBError("Cannot disconnect from DB");
}

# DB error subroutine
sub DBError {
  my $message = shift;
  # display a message
  warn "$message\nError $DBI::err " .
    "($DBI::errstr)\n";
}

Here is some sample output from the above code. The output has been prettied up, but renders the same in HTML.


<td align="center">
  <a target="gallery" 
  href="/cgi-bin/gallery.pl?id=28">
    <img alt="Georgetown Railroad" border="0"
    src="/cgi-bin/gallery.pl?id=28;thumb=1" />
  </a>
  <br />
  Georgetown Railroad
</td>

<td align="center">
  <a target="gallery" 
    href="/cgi-bin/gallery.pl?id=85">
    <img alt="Lakeside morning" border="0"
    src="/cgi-bin/gallery.pl?id=85;thumb=1" />
  </a>
  <br />
  Lakeside morning
</td>

<td align="center">
  <a target="gallery" 
    href="/cgi-bin/gallery.pl?id=95">
    <img alt="Elk in stream" border="0"
    src="/cgi-bin/gallery.pl?id=95;thumb=1" />
  </a>
  <br />
  Elk in stream
</td>

The actual output looks more like this:

Georgetown Railroad
Georgetown Railroad
Lakeside morning
Lakeside morning
Elk in stream
Elk in stream

Final Thoughts

While this application is still under development, the techniques are used to show a daily sample of pictures, eCards, and photos by category. There is also a database editor application that can be used to change images from one category to another, or to change comments for an image.

Mike Schienle has been using Macs since the day they took the Lisa computer out of his office in 1984. After 10 years of kicking shell scripts around, Perl became his language of choice, although he knows his way around several others. Mike runs Custom Visuals, LLC, a small company that dabbles in internet applications, website design/hosting and database integration.


Return to the Mac DevCenter