Tech Stuff

Gallery2-to-Zenfolio, Continued

While the import process moved over all the images, there were still a few kinks. Because Zenfolio threw unidentified errors, metadata on more than a third of them wasn’t correctly updated.

But since we kept the log, there is a brute-force way of handling it. You can generate a CSV file and upload it into your root gallery. Based on the format of the log in the previous post, the problems we’re looking for went something like this:

Command was $f->UpdatePhoto(1245141149, a:3:{s:5:"Title";s:16:"Press Conference";s:7:"Caption";s:0:"";s:8:"Keywords";a:3:{i:1;s:9:"diplomacy";i:2;s:4:"2009";i:3;s:8:"December";}})
Error updating image /var/www/appdata/g2data/albums/shu_only/Diplomacy/Kofi A at the UN.jpg
Zenfolio API Error for method UpdatePhoto:  : An unexpected error has occurred. Please try again later. If this problem persists, contact Support. Error Code: -2

By reading the file line by line,

while (($line = fgets($logfile)) !== false) {

we can identify easily the lines that show problems:

if ( preg_match('@^Error updating image (.*)$@', $line, $catch ) ) {



The information we want to keep is in the line before that so the end of the loop will record it in a variable $lastline.

So from there you can parse out the ID number (in this instance 1245141149) and the serialized array of metadata:

preg_match('/Command was \$f->UpdatePhoto\((\d+), (.*)\)$/', $lastline, $matches);
$zenfolioID = $matches[1];
$zenfolioKeys = unserialize($matches[2]);

That gives us an array from which we can grab the values of the Title and Caption, plus another array with the keywords.

Some Quirks

For some reason, which I’m not going to sort out because the original program took hours to run, there were consecutive items with the same photo ID number. In the import process, these were throwing more “unknown errors.” At first I thought it might have been malformed data, but then I noticed that consecutive lines showed the same ID number at the end.

You’ll also want to sanity-check the set of keywords. If you have had people entering data who weren’t well trained, you’ll get bad spacing, duplication, mixed cases (“Green” and “green” need to be consolidated, for example). Sometimes even the filename showed up as a keyword.

The script generates a file that you can save as CSV and just import into Zenfolio. You can find the place in the Edit screen for the gallery you’re working in. Look for the “Bulk update metadata” link in the RH column.

The list you get doesn’t include images for which the metadata was successfully updated. Don’t worry, you won’t overwrite or delete anything. Entries look like this:

File Name,Title,Caption,Copyright,Alt attribute,Keywords,Zenfolio Photo ID [DO NOT EDIT]
shu_apr09_4414_001.jpg,"Female and Male Student Studying",,,"Female and Male Student Studying","Best,female,lounge,male,students",1245716070
green_027_001.jpg,"Green_027",,,"Green_027","campus outdoors,green,laptop,leisure,students",1245716200

You’ll want to include that first header line.

Here’s the code, commented for your edification:

//logfile is the file generated from the previous run. Read-only.
$logfile = fopen('logfile.txt','r');

//newcsv will hold the entries to directly upload to zenfolio.
$newcsv = fopen('import.csv', 'w');

//check that you have the right log file
if ( $logfile ) {

  //Then  read it in a line at a time
  while (($line = fgets($logfile)) !== false) {

    //If it's a line that indicates an error with the previous statement
    if ( preg_match('@^Error updating image (.*)$@', $line, $catch ) ) {

      //the filename will be in $catch[1]
      $filename = basename($catch[1]);

      //replace spaces with plus signs, zenfolio seems to like that.
      $filename = str_replace(' ','+',$filename);

      //Then grab the information we need from the previous line: Photo ID and the update array
      preg_match('/Command was \$f->UpdatePhoto\((\d+), (.*)\)$/', $lastline, $matches);
      $zenfolioID = $matches[1];

      //If there are duplicates, we'll only take the first one.
      if ( $zenfolioID == $lastID ) continue;

      //Turn the serialized data into an actual array
      $zenfolioKeys = unserialize($matches[2]);

      //Then assign the Title and Caption to variables
      $zenfolioTitle = $zenfolioKeys['Title'];
      $zenfolioCaption = $zenfolioKeys['Caption'];

      //If there's a value set, we'll enclose it in parens. Otherwise they would show up as lone quotation marks.
      if ( !empty($zenfolioTitle) ) $zenfolioTitle = '"' . $zenfolioTitle . '"';
      if ( !empty($zenfolioCaption) ) $zenfolioCaption = '"' . $zenfolioCaption . '"';

      //Establish the keywords as their own array
      $zenfolioKeywords = $zenfolioKeys['Keywords'];

      //Go through them and trim off leading and trailing whitespace
      array_walk($zenfolioKeywords, 'trim_value');
      //Sometimes the filename has been set as a keyword. Sigh. There are other problems, and you'll have your own
      //I suppose. So write your own tests.
      foreach($zenfolioKeywords as $key=>$val) {
        if ( preg_match('/\.jpg$/i',$val) ) unset ($zenfolioKeywords[$key]);

      //Get rid of obvious duplicates
      $zenfolioKeywords = array_unique($zenfolioKeywords);

      //And not-so-obvious ones.
      $zenfolioKeywords = array_unique_case($zenfolioKeywords);

      //Then unpack them into a string
      $zenkeys = '"' . implode(',',$zenfolioKeywords) . '"';

      //And write your complete line to the CSV file.
      fwrite($newcsv, $filename . ',' . $zenfolioTitle . ',' . $zenfolioCaption . ',,' . $zenfolioTitle . ',' . $zenkeys . ',' . $zenfolioID . "\n");

    //Set the last ID and the last line for testing on the next go-round
    $lastID = $zenfolioID;
    $lastline = $line;

  //And close up your files.
else echo "error opening file";

/* functions */
function trim_value(&$value) {

  //get rid of starting and ending whitespace
  $value = trim($value);   

//Helpfully picked up from the PHP site
//This takes an array that might have mixed-case values, such as "Green" and "green",
//And only keeps the capitalized version.
function array_unique_case($array) {
  $tmp = array();
  $callback = function ($a) use (&$tmp) {
    if (in_array(strtolower($a), $tmp))
      return false;
    $tmp[] = strtolower($a);
    return true;
    return array_filter($array, $callback);
Tom McGee has been building web sites since 1995, and blogging here since 2006. Currently a senior developer at Seton Hall University, he's also a freelance web programmer and musician. Contact him if you have the need for a blog, web site, redesign or custom programming!

Leave a Reply

Your email address will not be published. Required fields are marked *