Fast Gallery with Sidecar Files for EXIF Data... and more!

tstermitz - May 31, 2009 - 16:18
Project:Fast Gallery
Version:6.x-4.0-beta5
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:active
Description

The PHP function for reading EXIF information from an image is not very robust. I get errors reading EXIF data in Fast Gallery which remain when I directly use php:exif_read_data. EXIFTOOL is much more robust and offers a lot of options for formatting and output. The problem is you can't guarantee that your internet host can run exiftool. I edit my EXIF and IPTC tags in NIkon View NX, or Graphic Converter (which is great both for tag editing and bulk processing).

Solution: use EXIFTOOL on my local computer to create a JSON dump of my tags:

exiftool -json -exif:ALL -GPSLatitude -GPSLongitude -c "%.6f" -XMP-photoshop:ALL -iptc:ALL -nikon photo.jpg > photo.jpg.json

I script this into a loop so I do all the files at a throw. Then I upload the JSON sidecar files along with the rest of the gallery photos. PHP easily reads JSON into an array or object, including sub arrays like keywords list. I don't use the family:tag:data format. The different families (EXIF, IPTC, XMP, etc) reuse some tag names; doing the dump in one EXIFTOOL statement (as above) automatically creates uniqueness on the tags. One other glitch: Some IPTC tags have hyphens which can mess you up in PHP.

Here is the code to read a JSON Sidecar and over-write Fast Gallery tags if a sidecar file exists. It is a very simple modification to the updateExifDb function in the fast_gallery.class.php file. Doing a diff will show you the changes. (I can't guarantee there are no bugs).

  /**
   * Write the image's EXIF information to the database.
   *
   * @param file
   *   Path of the image file.
   * @param gid
   *   ID of the picture assigned by the database.
   */
public function updateExifDb($file, $gid) {
    // Get the file extension
    $tmp = explode('.', $file);
    $fileType = strtolower(end($tmp));

    if ($fileType != 'jpg' && $fileType != 'jpeg') {
      // Construct the custom thumbnail path
      $tmp_ar = explode('.', $file);
      array_pop($tmp_ar);
      $tmp = implode('.', $tmp_ar);
      $file = $tmp . '.screenshot.jpg';
    }
    // Grab the EXIF data that we're interested in

// NEW SIDECAR CODE:
    // Sidecar JSON created from exiftool:
    // exiftool -json -exif:ALL -GPSLatitude -GPSLongitude -c "%.6f" -XMP-photoshop:ALL  -iptc:ALL -nikon photo.jpg > photo.jpg.json
    // First look for sidecar; if no sidecar, continue with original EXIF data:
    $file_json = $file . $file_json . ".json";
    if (file_exists($file_json)) {
        $fh = fopen($file_json, 'r');
         $json = fread($fh, filesize($file_json));
        fclose($fh);

      //remove curly brackets to beware from regex errors
      // strips outer json package brackets to return only the inside array.

        $json = substr($json, strpos($json,'{')+1, strlen($json));
        $json = substr($json, 0, strrpos($json,'}'));
        $json = preg_replace('/(^|,)([\\s\\t]*)([^:]*) (([\\s\\t]*)):(([\\s\\t]*))/s', '$1"$3"$4:', trim($json));
     
        // array for php to work with
        $ar_json = json_decode('{'.$json.'}', true);
        // var_dump($ar_json);

        // If you prefer to work with the object:
        $obj_json = (object) $ar_json;
   
        // THE FOLLOWING code keys deals with hyphens so PHP can work with it.
        // (hyphens (-) in IPCT codes crashes the php; similar problem with colons (:) ):
        $byline = "By-line";
        $bylinetitle = "By-lineTitle";
        $captionabstract = "Caption-Abstract";
        $provincestate = "Province-State";
        $countryprimarylocationname = "Country-PrimaryLocationName";
        $sublocation = "Sub-location";
   
        // Ternary makes Latitude/Longitude negative if Southern/Western
        $pieces = explode (" ", $obj_json->GPSLatitude );
        $json_lat = ($pieces[1] == "S") ? -$pieces[0] : $pieces[0];
        $pieces = explode (" ", $obj_json->GPSLongitude );
        $json_long = ($pieces[1] == "W") ? -$pieces[0] : $pieces[0];
   
        // Keywords list goes into $keywords; People list goes into $contact
        $json_keywords = implode (", ", $obj_json->Keywords );
        $json_contacts = implode (", ", $obj_json->Contact );
       
        // Over-write normal fast_callery from sidecar:
        $param['model'] = $obj_json->Model;
        $param['exposureTime'] = $obj_json->ExposureTime;
        $param['dateTaken'] = $obj_json->DateTimeOriginal;
        $param['ISOSpeedRatings'] = $obj_json->ISO;
        $param['description'] = $obj_json->$captionabstract;
        $param['comment'] = $obj_json->Headline;
        $param['fileCreated'] = $obj_json->DateCreated;
        $param['copyright'] = $obj_json->CopyrightNotice;
        $param['artist'] = $obj_json->$byline;
   
        // Windows XP files data
        $param['xptitle'] = $obj_json->ObjectName;
        $param['xpcomments'] = $obj_json->$sublocation;
        $param['xpkeywords'] = $json_keywords;
   
        // END OF NEW SIDECAR CODE
    } else {
     
      // ORIGINAL EXIF_READ CODE:

      if (file_exists(utf8_decode($file)))
        $exif = exif_read_data(utf8_decode($file), 0, true);
      $param['model'] = $exif['IFD0']['Model'];
      $param['exposureTime'] = $exif['EXIF']['ExposureTime'];
      $param['dateTaken'] = $exif['EXIF']['DateTimeOriginal'];
      $param['ISOSpeedRatings'] = $exif['EXIF']['ISOSpeedRatings'];
      $param['description'] = utf8_encode($exif['IFD0']['ImageDescription']);
      $param['comment'] = utf8_encode($exif['COMPUTED']['UserComment']);
      if (file_exists($file)) { //else will return error on videos
        $param['fileCreated'] = filemtime(utf8_decode($file));
      }
      $param['copyright'] = utf8_encode($exif['COMPUTED']['Copyright']);
      $param['artist'] = utf8_encode($exif['IFD0']['Artist']);

      // Windows XP files data
      $param['xptitle'] = utf8_encode($exif['WINXP']['Title']);
      $param['xpcomments'] = utf8_encode($exif['WINXP']['Comments']);
      $param['xpkeywords'] = utf8_encode($exif['WINXP']['Keywords']);

    // End of original, non sidecar code

    }
   

    // Check for previous entries
    $sql = "SELECT * FROM {fast_gallery_exif} WHERE gid='%s'";
    $result = db_query($sql, $gid);
    $row = db_fetch_object($result);

    // Update entries if they exist
    if ($row != NULL) {
      $existing_record[$row->field] = $row->value;
      while ($row = db_fetch_object($result))
        $existing_record[$row->field] = $row->value;

      if ($existing_record != NULL) {
        foreach ($existing_record as $key => $value) {
          // Make changes to database only if there was a change
          if ($param[$key] != $value) {
            $sql = "UPDATE {fast_gallery_exif} SET value='%s' WHERE gid='%s' AND field='%s'";
            db_query($sql, $param[$key], $gid, $key);
          }
        }
      }
    }
    // Or else write a new record to the database
    else {
      foreach ($param as $key => $value) {
        $dbObject['field'] = $key;
        $dbObject['value'] = $value;
        $dbObject['gid'] = $gid;
        drupal_write_record('fast_gallery_exif', $dbObject);
      }
    }
  }

 
 

Drupal is a registered trademark of Dries Buytaert.