]> git.mjollnir.org Git - s9y.git/commitdiff
Parse, Display, Retrieve and Store EXIF, IPTC and XMP metadata. Phew.
authorgarvinhicking <garvinhicking>
Wed, 12 Apr 2006 14:41:19 +0000 (14:41 +0000)
committergarvinhicking <garvinhicking>
Wed, 12 Apr 2006 14:41:19 +0000 (14:41 +0000)
docs/NEWS
include/functions_images.inc.php
templates/default/admin/media_items.tpl

index f1514ffcd1833d846c24c3115f2417f92485e2f4..53632ce2dd11559d2027d9d3d39e6014ce2a599e 100644 (file)
--- a/docs/NEWS
+++ b/docs/NEWS
@@ -10,8 +10,17 @@ Version 1.1-alpha4()
 
    * Make media manager able to store media properties (garvinhicking)
      TODO:
-        - Read/Parse EXIF metadata, ask which to import [config option?]
-        - Search/Filter for specific properties/keywords
+        - Video properties: Run length
+        - All properties: Creation date
+        - Prefill values from EXIF/IPTC/XMP metadata for Title, DPI, etc.
+        - Search/Filter for specific properties/keywords:
+            Sort by: Title, Filename, Creation date, Upload date, file type, author
+            Search by: Title, Creation date (From-To), Filetype, Comment(s), Keyword, Author
+        - Optionally increase filename (1, 2, 4, 5, ...) when file already exists and is not identical
+        - Store "real" filename when filename has been automatically inreased
+        - New option for image_Selector to save a specific sized version (?fid=XXX&targetSize=XX)
+        - Track referrers by image selector and store as property
+        - Restrict file upload by size/dimensions
         - Move/rename images/directories (browse serendipity_entries to fix up image paths [using <img src="..." />]. Also move ALL images of a directory, like when moving s9y installations. Put this into "Sync Thumbs" or "Manage Directories" panel.
         ? Create Smarty functions to access media properties by ID/filename
 
index b4eae5fc039fc9daae17eea400f3397735efe178..2419195f83c5b76e6d4bb7b49ced133a55044e50 100644 (file)
@@ -1822,7 +1822,7 @@ function serendipity_getThumbNailPath($sRelativePath, $sName, $sExtension, $sThu
  * @return array    Data about image
  *
  */
-function serendipity_getImageData($sRelativePath) {
+function &serendipity_getImageData($sRelativePath) {
     global $serendipity;
 
     // First, peel off the file name from the path
@@ -1898,7 +1898,13 @@ function serendipity_showPropertyForm(&$new_media, $keywordsPerBlock = 3, $is_ed
 
         $show[$idx] =& $media['internal'];
         $show[$idx]['image_id'] = $media['image_id'];
+
         serendipity_prepareMedia($show[$idx]);
+        if (!is_array($props['base_metadata'])) {
+            $show[$idx]['metadata'] =& serendipity_getMetaData($show[$idx]['realfile'], $show[$idx]['header']);
+        } else {
+            $show[$idx]['metadata'] = $props['base_metadata'];
+        }
 
         foreach($dprops AS $prop) {
             $type = 'input';
@@ -1986,6 +1992,26 @@ function serendipity_parsePropertyForm() {
                          serendipity_db_escape_string($val));
             serendipity_db_query($q);
         }
+
+        $s9y_img = $media['internal'];
+        $s9y_img['image_id'] = $media['image_id'];
+        serendipity_prepareMedia($s9y_img);
+        $s9y_img['metadata'] =& serendipity_getMetaData($s9y_img['realfile'], $s9y_img['header']);
+        serendipity_db_query("DELETE FROM {$serendipity['dbPrefix']}mediaproperties
+                                    WHERE mediaid = " . (int)$media['image_id'] . "
+                                          property_group = 'base_metadata'");
+        foreach($s9y_img['metadata'] AS $maingroup => $items) {
+            foreach($items AS $key => $val) {
+                $q = sprintf("INSERT INTO {$serendipity['dbPrefix']}mediaproperties
+                                          (mediaid, property_group, property_subgroup, property, value)
+                                   VALUES (%d, 'base_metadata', '%s', '%s', '%s')",
+                             $media['image_id'],
+                             $maingroup,
+                             serendipity_db_escape_string($key),
+                             serendipity_db_escape_string($val));
+                serendipity_db_query($q);
+            }
+        }
     }
 
     foreach($serendipity['POST']['mediaKeywords'] AS $id => $keywords) {
@@ -2008,7 +2034,6 @@ function serendipity_parsePropertyForm() {
         'image_id'          => $serendipity['POST']['mediaProperties'][0]['image_id'],
     );
 
-
     return $array;
 }
 
@@ -2031,6 +2056,8 @@ function &serendipity_fetchMediaProperties($id) {
         foreach($rows AS $row) {
             if (empty($row['property_subgroup'])) {
                 $props[$row['property_group']][$row['property']] = $row['value'];
+            } else {
+                $props[$row['property_group']][$row['property_subgroup']][$row['property']] = $row['value'];
             }
         }
     }
@@ -2089,9 +2116,10 @@ function serendipity_prepareMedia(&$file, $url = '') {
        if (!isset($file['imgsrc'])) {
        $file['imgsrc'] = $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . (!empty($file['thumbnail_name']) ? '.' . $file['thumbnail_name'] : '') . '.' . $file['extension'];
        }
-       $file['dim']       = @getimagesize($img);
+       $file['dim']       = @getimagesize($img, $file['header']);
     $file['is_image']  = serendipity_isImage($file);
     $file['full_file'] = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $file['path'] . $file['name'] . '.'. $file['extension'];
+    $file['realfile']  = $serendipity['serendipityPath'] . $serendipity['uploadPath'] . $file['path'] . $file['name'] . '.'. $file['extension'];
 
     if ($full_perm || $serendipity['authorid'] == $file['authorid'] || $file['authorid'] == '0') {
         $file['is_editable'] = true;
@@ -2142,6 +2170,7 @@ function serendipity_prepareMedia(&$file, $url = '') {
  * @param  boolean  Whether to show maintenance task items
  * @param  int      how many media items to display per row
  * @param  boolean  Enclose within a table cell?
+ * @param  array    Additional Smarty variables
  * @return boolean
  *
  */
@@ -2197,4 +2226,279 @@ function serendipity_showMedia(&$file, &$paths, $url = '', $manage = false, $lin
     }
 
     return true;
-}
\ No newline at end of file
+}
+
+/**
+ * Convert a IPTC/EXIF/XMP item
+ *
+ * @param  string   The content
+ * @param  string   The type of the content
+ * @return string   The converted content
+ *
+ */
+function serendipity_metaFieldConvert(&$item, $type) {
+    switch($type) {
+        case 'math':
+            $parts = explode('/', $item);
+            return ($parts[0] / $parts[1]);
+            break;
+
+        case 'or':
+            if ($item == '1') {
+                return 'Landscape';
+            } else {
+                return 'Portrait';
+            }
+
+        case 'date':
+            return strtotime($item);
+            break;
+
+        case 'date2':
+            $parts = explode(':', $item);
+            return mktime($parts[3], $parts[4], $parts[5], $parts[1], $parts[2], $parts[0]);
+            break;
+
+        case 'rdf':
+            if (preg_match('@<rdf:li[^>]*>(.*)</rdf:li>@i', $item, $ret)) {
+                return $ret[1];
+            }
+            break;
+
+        case 'text':
+        default:
+            return trim($item);
+            break;
+    }
+
+    return '';
+}
+
+/**
+ * Get the RAW media header data (XMP)
+ *
+ * @param  string   Filename
+ * @return array    The raw media header data
+ *
+ * Inspired, but rewritten,  by "PHP JPEG Metadata Toolkit" from http://electronics.ozhiker.com.
+ * Code is GPL so sadly we couldn't bundle that GREAT library.
+ */
+function serendipity_getMediaRaw($filename) {
+    $abort = false;
+
+    $f = @fopen($filename, 'rb');
+    $ret = array();
+    if (!$f) {
+        return $ret;
+    }
+
+    $filedata = fread($f, 2);
+
+    if ($filedata != "\xFF\xD8") {
+        fclose($f);
+        return $ret;
+    }
+
+    $filedata = fread($f, 2);
+
+    if ($filedata{0} != "\xFF") {
+        fclose($f);
+        return $ret;
+    }
+
+    while (!$abort && !feof($f) && $filedata{1} != "\xD9") {
+        if ((ord($filedata{1}) < 0xD0) || (ord($filedata{1}) > 0xD7)) {
+            $ordret   = fread($f, 2);
+            $ordstart = ftell($f);
+            $int      = unpack('nsize', $ordret);
+
+            if (ord($filedata{1}) == 225) {
+                $content  = fread($f, $int['size'] - 2);
+
+                if (substr($content, 0, 24) == 'http://ns.adobe.com/xap/') {
+                    $ret[] = array(
+                        'ord'      => ord($filedata{1}),
+                        'ordstart' => $ordstart,
+                        'int'      => $int,
+                        'content'  => $content
+                    );
+                }
+            } else {
+                fseek($f, $int['size'] - 2, SEEK_CUR);
+            }
+        }
+
+        if ($filedata{1} == "\xDA") {
+            $abort = true;
+        } else {
+            $filedata = fread($f, 2);
+            if ($filedata{0} != "\xFF") {
+                fclose($f);
+                return $ret;
+            }
+        }
+    }
+
+    fclose($f);
+
+    return $ret;
+}
+
+/**
+ * Get the IPTC/EXIF/XMP media metadata
+ *
+ * @param  string   Filename
+ * @return array    The raw media header data
+ *
+ */
+function &serendipity_getMetaData($file, &$info) {
+    # Fields taken from: http://demo.imagefolio.com/demo/ImageFolio31_files/skins/cool_blue/images/iptc.html
+    static $IPTC_Fields = array(
+    '2#005' => 'ObjectName',
+    '2#025' => 'Keywords',
+    '2#026' => 'LocationCode',
+    '2#027' => 'LocationName',
+    '2#030' => 'ReleaseDate',
+    '2#035' => 'ReleaseTime',
+    '2#037' => 'ExpirationDate',
+    '2#038' => 'ExpirationTime',
+    '2#055' => 'DateCreated',
+    '2#060' => 'TimeCreated',
+    '2#062' => 'DigitalDateCreated',
+    '2#063' => 'DigitalTimeCreated',
+    '2#065' => 'Software',
+    '2#070' => 'SoftwareVersion',
+    '2#080' => 'Photographer',
+    '2#085' => 'Photographer Name',
+    '2#090' => 'PhotoLocation',
+    '2#092' => 'PhotoLocation2',
+    '2#095' => 'PhotoState',
+    '2#100' => 'PhotoCountryCode',
+    '2#101' => 'PhotoCountry',
+    '2#105' => 'Title',
+    '2#110' => 'Credits',
+    '2#115' => 'Source',
+    '2#116' => 'Copyright',
+    '2#118' => 'Contact',
+    '2#120' => 'Description',
+    '2#131' => 'Orientation',
+    '2#150' => 'AudioType',
+    '2#151' => 'AudioSamplingRate',
+    '2#152' => 'AudioSamplingResolution',
+    '2#153' => 'AudioDuration'
+    );
+
+    static $ExifFields = array(
+        'IFD0' => array(
+            'Make'          => array('type' => 'text',  'name' => 'CameraMaker'),
+            'Model'         => array('type' => 'text',  'name' => 'CameraModel'),
+            'Orientation'   => array('type' => 'or',    'name' => 'Orientation'),
+            'XResolution'   => array('type' => 'math',  'name' => 'XResolution'),
+            'YResolution'   => array('type' => 'math',  'name' => 'YResolution'),
+            'Software'      => array('type' => 'text',  'name' => 'Software'),
+            'DateTime'      => array('type' => 'date2', 'name' => 'DateCreated'),
+            'Artist'        => array('type' => 'text',  'name' => 'Creator'),
+        ),
+
+        'EXIF' => array(
+            'ExposureTime'          => array('type' => 'math',  'name' => 'ExposureTime'),
+            'ApertureValue'         => array('type' => 'math',  'name' => 'ApertureValue'),
+            'MaxApertureValue'      => array('type' => 'math',  'name' => 'MaxApertureValue'),
+            'ISOSpeedRatings'       => array('type' => 'text',  'name' => 'ISOSpeedRatings'),
+            'DateTimeOriginal'      => array('type' => 'date2', 'name' => 'DateCreated'),
+            'MeteringMode'          => array('type' => 'text',  'name' => 'MeteringMode'),
+            'FNumber'               => array('type' => 'math',  'name' => 'FNumber'),
+            'ExposureProgram'       => array('type' => 'text',  'name' => 'ExposureProgram'),
+            'FocalLength'           => array('type' => 'math',  'name' => 'FocalLength'),
+            'WhiteBalance'          => array('type' => 'text',  'name' => 'WhiteBalance'),
+            'DigitalZoomRatio'      => array('type' => 'math',  'name' => 'DigitalZoomRatio'),
+            'FocalLengthIn35mmFilm' => array('type' => 'text',  'name' => 'FocalLengthIn35mmFilm'),
+            'Flash'                 => array('type' => 'text',  'name' => 'Flash'),
+            'Fired'                 => array('type' => 'text',  'name' => 'FlashFired'),
+            'RedEyeMode'            => array('type' => 'text',  'name' => 'RedEyeMode'),
+        )
+    );
+
+    static $xmpPatterns = array(
+    'tiff:Orientation'              => array('type' => 'or',   'name' => 'Orientation'),
+    'tiff:XResolution'              => array('type' => 'math', 'name' => 'XResolution'),
+    'tiff:YResolution'              => array('type' => 'math', 'name' => 'YResolution'),
+    'tiff:Make'                     => array('type' => 'text', 'name' => 'CameraMaker'),
+    'tiff:Model'                    => array('type' => 'text', 'name' => 'CameraModel'),
+    'xap:ModifyDate'                => array('type' => 'date', 'name' => 'DateModified'),
+    'xap:CreatorTool'               => array('type' => 'text', 'name' => 'Software'),
+    'xap:CreateDate'                => array('type' => 'date', 'name' => 'DateCreated'),
+    'xap:MetadataDate'              => array('type' => 'date', 'name' => 'DateMetadata'),
+
+    'exif:ExposureTime'             => array('type' => 'math',  'name' => 'ExposureTime'),
+    'exif:ApertureValue'            => array('type' => 'math',  'name' => 'ApertureValue'),
+    'exif:MaxApertureValue'         => array('type' => 'math',  'name' => 'MaxApertureValue'),
+    'exif:ISOSpeedRatings'          => array('type' => 'text',  'name' => 'ISOSpeedRatings'),
+    'exif:DateTimeOriginal'         => array('type' => 'date',  'name' => 'DateCreated'),
+    'exif:MeteringMode'             => array('type' => 'text',  'name' => 'MeteringMode'),
+    'exif:FNumber'                  => array('type' => 'math',  'name' => 'FNumber'),
+    'exif:ExposureProgram'          => array('type' => 'text',  'name' => 'ExposureProgram'),
+    'exif:FocalLength'              => array('type' => 'math',  'name' => 'FocalLength'),
+    'exif:WhiteBalance'             => array('type' => 'text',  'name' => 'WhiteBalance'),
+    'exif:DigitalZoomRatio'         => array('type' => 'math',  'name' => 'DigitalZoomRatio'),
+    'exif:FocalLengthIn35mmFilm'    => array('type' => 'text',  'name' => 'FocalLengthIn35mmFilm'),
+    'exif:Fired'                    => array('type' => 'text',  'name' => 'FlashFired'),
+    'exif:RedEyeMode'               => array('type' => 'text',  'name' => 'RedEyeMode'),
+
+    'dc:title'                      => array('type' => 'rdf',   'name' => 'Title'),
+    'dc:creator'                    => array('type' => 'rdf',   'name' => 'Creator'),
+    );
+
+    $ret = array();
+
+    if (!file_exists($file)) {
+        return $ret;
+    }
+
+    if (function_exists('iptcparse') && is_array($info) && isset($info['APP13'])) {
+        $iptc = iptcparse($info['APP13']);
+        foreach($IPTC_Fields AS $field => $desc) {
+            if ($iptc[$field]) {
+                if (is_array($iptc[$field])) {
+                    $ret['IPTC'][$desc] = trim(implode(';', $iptc[$field]));
+                } else {
+                    $ret['IPTC'][$desc] = trim($iptc[$field]);
+                }
+            }
+        }
+    }
+
+    if (function_exists('exif_read_data') && is_array($info)) {
+        $exif = @exif_read_data($file, 'FILE,COMPUTED,ANY_TAG,IFD0,COMMENT,EXIF', true, false);
+        if (is_array($exif)) {
+            foreach($ExifFields AS $Exifgroup => $ExifField) {
+                foreach($ExifField AS $ExifName => $ExifItem) {
+                    if (!isset($exif[$Exifgroup][$ExifName])) {
+                        continue;
+                    }
+                    $ret['EXIF'][$ExifItem['name']] = serendipity_metaFieldConvert($exif[$Exifgroup][$ExifName], $ExifItem['type']);
+                    if ($ret['EXIF'][$item['name']] == $ret['IPTC'][$item['name']]) {
+                        unset($ret['IPTC'][$item['name']]);
+                    }
+                }
+            }
+        }
+    }
+
+    $xmp = serendipity_getMediaRaw($file);
+    foreach($xmp AS $xmp_data) {
+        if (empty($xmp_data['content'])) {
+            continue;
+        }
+        foreach($xmpPatterns AS $lookup => $item) {
+            if (preg_match('@<' . $lookup . '>(.*)</' . $lookup . '>@', $xmp_data['content'], $match)) {
+                $ret['XMP'][$item['name']] = serendipity_metaFieldConvert($match[1], $item['type']);
+                if ($ret['EXIF'][$item['name']] == $ret['XMP'][$item['name']]) {
+                    unset($ret['EXIF'][$item['name']]);
+                }
+            }
+        }
+    }
+
+    return $ret;
+}
index f4f91627c64df8b85e7fb5ec830b32850b733fb7..2c0ed33ab04a71131d91857be944f5b685a60837 100644 (file)
             {/foreach}
             </table>
         </div>
+
+        <h3>EXIF/IPTC/XMP</h3>
+        <div>
+        <dl>
+        {foreach from=$file.metadata key="meta_type" item="meta_data"}
+            <dt><h4>{$meta_type}</h4></dt>
+            <dd><table>
+            {foreach from=$meta_data key="meta_name" item="meta_value"}
+                <tr>
+                    <td><em>{$meta_name}</em></th>
+                    <td>{$meta_value}</td>
+                </tr>
+            {/foreach}
+            </table></dd>
+        {/foreach}
+        </dl>
+        </div>
     {/if}
 
     {if $media.enclose AND (($smarty.foreach.mediafiles.iteration % $media.lineBreak) == 0)}