/* $Id$ */
/* Much of the code in this module was borrowed from the public domain
- jhead.c package with the author's consent. The main changes have been
- to eliminate all the global variables to make it thread safe and to wrap
- it in the PHP 4 API.
-
- Aug.3 2001 - Added support for multiple M_COM entries - Rasmus
-
- Additional changes and fixes mainly to expand functionality for image
- galleries.
-
- Feb.26 2002 - Marcus
-
- Added UNICODE support for Comments
- Added Description,Artist
- Added missing memory deallocation
- Corrected error with multiple comments
- Corrected handling of ExifVersion, Tag has 4 ASCII
- characters *WITHOUT* NUL
- Corrected handling of Thumbnailsize if current source
- detects size < 0
- Changed all fields to char* that do not have a maximum
- length in EXIF standard
- Undocumented second Parameter ReadAll frees memory to
- early -> moved to third position default changed to
- false -> faster
- New second Parameter [true|false] to specify whether or
- not to to read thumbnails -> reading is timeconsumpting
- suppose default should be false -> done so
-
- ToDos - Copyright tag stores "<Photographer> '\0' <Editor> '\0'" but we
- stop at first '\0'
- JIS encoding for comments
- See if example images from http://www.exif.org have illegal
- thumbnail sizes or if code is corrupt.
-
- The original header from the jhead.c file was:
+ jhead.c package with the author's consent. The main changes have been
+ to eliminate all the global variables to make it thread safe and to wrap
+ it in the PHP 4 API.
+
+ Aug.3 2001 - Added support for multiple M_COM entries - Rasmus
+
+ Feb.26 2002 - Marcus
+
+ Additional changes and fixes mainly to expand functionality for image
+ galleries.
+
+ Added UNICODE support for Comments
+ Added Description,Artist
+ Added missing memory deallocation
+ Corrected error with multiple comments
+ Corrected handling of ExifVersion, Tag has 4 ASCII
+ characters *WITHOUT* NUL
+ Corrected handling of Thumbnailsize if current source
+ detects size < 0
+ Changed all fields to char* that do not have a maximum
+ length in EXIF standard
+ Undocumented second Parameter ReadAll frees memory to
+ early -> moved to third position default changed to
+ false -> faster
+ New second Parameter [true|false] to specify whether or
+ not to to read thumbnails -> reading is timeconsumpting
+ suppose default should be false -> done so
+
+ Feb.28 2002 - Marcus
+
+ Support for Photographer/Editor Copyright as associative
+ array as this is a new feature the change (optionally
+ being an array) has to be mentioned in documentation.
+ New function exif_headername can be used to read the internal Tag
+ namelist (was mainly created for debugging purpose but maybe
+ somone writes code to create/update exif headers here).
+ A testpage is supplied test.txt describes how the test works.
+
+ ToDos - JIS encoding for comments
+ See if example images from http://www.exif.org have illegal
+ thumbnail sizes or if code is corrupt.
+ Create/Update exif headers.
+ Create/Remove/Update image thumbnails.
+
+ The original header from the jhead.c file was:
--------------------------------------------------------------------------
Program to pull the information out of various types of EFIF digital
Matthias Wandel, Dec 1999 - April 2000
--------------------------------------------------------------------------
- */
+*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define EXIF_MAX_COMMENTS 12
+/* EXIF standard defines Copyright as "<Photographer> [ '\0' <Editor> ] ['\0']" */
+#define EXIF_MAX_COPYRIGHT 2
+
/* {{{ structs
This structure stores Exif header image elements in a simple manner
Used to store camera data as extracted from the various ways that it can be
stored in a nexif header
*/
typedef struct {
- char FileName [120];
+ char FileName[120];
time_t FileDateTime;
unsigned FileSize;
char *CameraMake;
char *CameraModel;
- char DateTime [20];
+ char DateTime[20];
int Height, Width;
int IsColor;
int FlashUsed;
char GPSinfo[48];
int ISOspeed;
char ExifVersion[8];
- char *Copyright; /* maybe two strings see ToDos */
+ char *RawCopyright;
+ int lenRawCopyright;
+ char *Copyright[EXIF_MAX_COPYRIGHT];
+ int numCopyrights;
char *Artist;
char *Software;
char *Thumbnail;
*/
function_entry exif_functions[] = {
PHP_FE(read_exif_data, NULL)
+ PHP_FALIAS(exif_read_data,read_exif_data, NULL)
+ PHP_FE(exif_headername, NULL)
{NULL, NULL, NULL}
};
/* }}} */
+#define EXIF_VERSION "2.0a $Revision$"
+
PHP_MINFO_FUNCTION(exif);
/* {{{ exif_module_entry
NULL, NULL,
NULL, NULL,
PHP_MINFO(exif),
- NO_VERSION_YET,
+ EXIF_VERSION,
STANDARD_MODULE_PROPERTIES
};
/* }}} */
{
php_info_print_table_start();
php_info_print_table_row(2, "EXIF Support", "enabled" );
+ php_info_print_table_row(2, "EXIF Version", EXIF_VERSION );
+ php_info_print_table_row(2, "Supported EXIF Version", "02100");
php_info_print_table_end();
}
/* }}} */
a = ImageInfo->numComments;
(ImageInfo->Comments)[a] = emalloc(nch+1);
- strcpy(ImageInfo->Comments[a], Comment);
+ strcpy(ImageInfo->Comments[a], Comment);
(ImageInfo->numComments)++;
}
/* }}} */
{ 0xA217, "SensingMethod"}, /* 0x9217 - - */
{ 0xA300, "FileSource"},
{ 0xA301, "SceneType"},
- { 0, NULL}
+ { 0, NULL} /* Important for get_exif_headername() */
} ;
/* }}} */
+/* {{{ get_exif_headername
+ Get headername for tag_num or NULL if not defined */
+char * get_exif_headername(int tag_num)
+{
+ int i,t;
+
+ for (i=0;;i++) {
+ if ( (t=TagTable[i].Tag) == tag_num || !t) return TagTable[i].Desc;
+ }
+ return NULL;
+}
+/* }}} */
+
+/* {{{ proto string|false exif_headername(index)
+ Get headername for index or false if not defined */
+PHP_FUNCTION(exif_headername)
+{
+ pval **p_num;
+ int ac = ZEND_NUM_ARGS();
+ char *szTemp;
+
+ if ((ac < 1 || ac > 1) || zend_get_parameters_ex(ac, &p_num) == FAILURE) {
+ WRONG_PARAM_COUNT;
+ }
+
+ convert_to_long_ex(p_num);
+ if ( (szTemp = get_exif_headername(Z_LVAL_PP(p_num))) == NULL) {
+ RETURN_BOOL(FALSE);
+ } else {
+ RETURN_STRING(szTemp, 1)
+ }
+}
+/* }}} */
+
/* {{{ Get16u
* Convert a 16 bit unsigned value from file's native byte order */
static int Get16u(void *Short, int MotorolaOrder)
}
/* }}} */
+/* {{{ CopyExifStringRaw
+ * Copy a string in Exif header to a character string returns length of allocated buffer if any. */
+static int CopyExifStringRaw(char **pszInfoPtr,char *szValuePtr,int ByteCount) {
+ /* we cannot use strlcpy - here the problem is that we have to copy NUL
+ * chars up to ByteCount, we also have to add a single NUL character to
+ * force end of string.
+ */
+ if (ByteCount) {
+ (*pszInfoPtr) = emalloc(ByteCount+1);
+ memcpy(*pszInfoPtr, szValuePtr, ByteCount);
+ (*pszInfoPtr)[ByteCount] = '\0';
+ return ByteCount+1;
+ }
+ return 0;
+}
+/* }}} */
+
/* {{{ CopyExifString
- * Copy a string in Exif header to a character string. */
+ * Copy a string in Exif header to a character string returns length of allocated buffer if any. */
static int CopyExifString(char **pszInfoPtr,char *szValuePtr,int ByteCount) {
int l;
- l = strlen(szValuePtr)+1;
- if (ByteCount<l) l = ByteCount;
- if (l>1) {
- (*pszInfoPtr) = emalloc(l);
- strlcpy(*pszInfoPtr, szValuePtr, l);
- return 1;
+ /* we cannot use strlcpy - here the problem is that we cannot use strlen to
+ * determin length of string and we cannot use strlcpy with len=ByteCount+1
+ * because then we might get into an EXCEPTION if we exceed an aallocate
+ * memory page...
+ */
+ if (ByteCount && CopyExifStringRaw(pszInfoPtr,szValuePtr,ByteCount)) {
+ if ( (l = strlen(*pszInfoPtr)) < ByteCount) {
+ (*pszInfoPtr)[l] = '\0';
+ }
+ return l+1;
}
return 0;
}
static void ProcessExifDir(ImageInfoType *ImageInfo, char *DirStart, char *OffsetBase, unsigned ExifLength, char *LastExifRefd, int ReadThumbnail)
{
int de;
- int a;
+ int a,l;
int NumDirEntries;
int NextDirOffset;
}
/* Extract useful components of tag */
+ /* php_error(E_NOTICE,"Scan tag: %s", get_exif_headername(Tag)); */
switch(Tag) {
case TAG_MAKE:
break;
case TAG_COPYRIGHT:
- CopyExifString(&ImageInfo->Copyright,ValuePtr,ByteCount);
+ if (CopyExifStringRaw(&ImageInfo->RawCopyright,ValuePtr,ByteCount)) {
+ l = strlen(ImageInfo->RawCopyright);
+ } else {
+ l = 0;
+ }
+ ImageInfo->lenRawCopyright = ByteCount;
+ if ( ImageInfo->numCopyrights==0 && ByteCount>1 && l<ByteCount-1) {
+ CopyExifString(&((ImageInfo->Copyright)[ImageInfo->numCopyrights++]), ValuePtr, l);
+ CopyExifString(&((ImageInfo->Copyright)[ImageInfo->numCopyrights++]), ValuePtr+l+1, ByteCount-l-1);
+ }
break;
case TAG_ARTIST:
}
/* CurrentFile = FileName; */
-/* php_error(E_NOTICE,"EXIF: Process %s%s: %s", ReadThumbnail?"thumbs ":"", ReadAll?"All ":"", FileName); */
+/* php_error(E_WARNING,"EXIF: Process %s%s: %s", ReadThumbnail?"thumbs ":"", ReadAll?"All ":"", FileName); */
/* Start with an empty image information structure. */
memset(ImageInfo, 0, sizeof(*ImageInfo));
memset(Sections, 0, sizeof(*Sections));
Reads the EXIF header data from the JPEG identified by filename and optionally reads the internal thumbnails */
PHP_FUNCTION(read_exif_data)
{
- pval **p_name, **p_readthumbnail, **p_readall, *tmpi;
+ pval **p_name, **p_readthumbnail, **p_readall;
int i, ac = ZEND_NUM_ARGS(), ret, readthumbnail=0, readall=0;
ImageInfoType ImageInfo;
char tmp[64];
if (ImageInfo.ExifVersion[0]) {
add_assoc_string(return_value, "ExifVersion", ImageInfo.ExifVersion, 1);
}
- if (ImageInfo.Copyright) {
- if (ImageInfo.Copyright[0]) {
- add_assoc_string(return_value, "Copyright", ImageInfo.Copyright, 1);
+ if (ImageInfo.RawCopyright || ImageInfo.numCopyrights) {
+ if (ImageInfo.RawCopyright && !ImageInfo.numCopyrights && ImageInfo.lenRawCopyright) {
+ add_assoc_stringl(return_value, "Copyright", ImageInfo.RawCopyright, ImageInfo.lenRawCopyright, 1);
+ }else{
+ pval *tmpi;
+
+ MAKE_STD_ZVAL(tmpi);
+ array_init(tmpi);
+ /*for(i=0; i<ImageInfo.numCopyrights; i++) {
+ * add_index_string(tmpi, i, (ImageInfo.Copyright)[i], 0);
+ *}
+ */
+ add_assoc_string(tmpi, "Photographer", ImageInfo.Copyright[0], 1);
+ add_assoc_string(tmpi, "Editor", ImageInfo.Copyright[1], 1);
+ add_assoc_zval(return_value, "Copyright", tmpi);
+ }
+ if (ImageInfo.RawCopyright) {
+ efree(ImageInfo.RawCopyright);
+ }
+ for(i=0; i<ImageInfo.numCopyrights; i++) {
+ efree((ImageInfo.Copyright)[i]);
}
- efree(ImageInfo.Copyright);
}
if (ImageInfo.Software) {
if (ImageInfo.Software[0]) {
if(ImageInfo.numComments==1) {
add_assoc_string(return_value, "Comments", (ImageInfo.Comments)[0], 1);
} else {
+ pval *tmpi;
+
MAKE_STD_ZVAL(tmpi);
array_init(tmpi);
for(i=0; i<ImageInfo.numComments; i++) {
add_index_string(tmpi, i, (ImageInfo.Comments)[i], 0);
}
- zend_hash_update(return_value->value.ht, "Comments", 9, &tmpi, sizeof(zval *), NULL);
+ add_assoc_zval(return_value, "Comments", tmpi);
+ /* zend_hash_add(return_value->value.ht, "Comments", 9, &tmpi, sizeof(zval *), NULL); */
}
for(i=0; i<ImageInfo.numComments; i++) {
efree((ImageInfo.Comments)[i]);
- (ImageInfo.Comments)[i] = NULL;
}
}
if (ImageInfo.Description) {
--- /dev/null
+<?php
+
+/* Test script for PHP module ext/exif
+ *
+ * (c) Marcus Boerger, 2002
+ *
+ * Rename the file to test.php and read the instructions. If the
+ * script cannot be executed or does not generate any output check
+ * you error log. In most cases this would mean you found an error
+ * if the rest of your php environment works fine.
+ *
+ * The original version of module exif has many errors and mostly
+ * fails on executing this script.
+ */
+
+$possible = array();
+
+/****************************************************************************/
+// message function is used for debugging purpose: just to se what happens
+function message($msg) {
+ error_log($msg,0);
+ echo "$msg\n";
+}
+
+function error_msg() {
+ if (array_key_exists('php_errormsg',$GLOBALS)) return $GLOBALS['php_errormsg'];
+ return 'php.ini: track_errors is off';
+}
+
+/****************************************************************************/
+// private to function search_file()
+function _search_file($root,&$possible,$path='') {
+ $sub = array();
+
+ //error_log("search_file($root,$path)",0);
+ if ($dir = @opendir($root.$path.'/')) {
+ while (($found = @readdir($dir)) !== false) {
+ $type = @filetype($root.$path.'/'.$found);
+ //error_log("search_file($root$path):$type=$found",0);
+ switch( $type) {
+ case 'file':
+ $pos = strpos($found,'.');
+ if ( $pos !== false && strtolower(substr($found,$pos+1))=='jpg') {
+ $possible[] = $root.$path.'/'.$found;
+ //error_log("search_file($root$path) add:$path/$found",0);
+ }
+ break;
+ case 'dir':
+ if ( $found!='.' && $found!='..') {
+ $sub[count($sub)] = $found;
+ }
+ break;
+ }
+ }
+ @closedir($dir);
+ foreach( $sub as $idx => $found) {
+ _search_file($root,$possible,$path.'/'.$found);
+ }
+ }
+}
+
+/****************************************************************************/
+// function: search_file($file,$ext)
+//
+// Searches for $file in document tree. The path is ignored.
+//
+function search_file() {
+ global $argv;
+ $possible = array();
+
+ if ( array_key_exists('SCRIPT_FILENAME',$_SERVER)) {
+ $path = $_SERVER['SCRIPT_FILENAME'];
+ } else {
+ $path = $argv[0];
+ }
+ if ( ($p=strpos($path,'?')) !== false) $path = substr($path,0,$p);
+ if ( ($p=strrpos($path,'/')) < strlen($path)-1) $path = substr($path,0,$p);
+ _search_file($path,$possible);
+ return $possible;
+}
+
+/****************************************************************************/
+// function: search_file($file,$ext)
+//
+// Searches for $file in document tree. The path is ignored.
+//
+function AddInfo($Name,$Value) {
+ $Value = nl2br($Value);
+ return "<tr><td>$Name</td><td>$Value </td></tr>\n";
+}
+
+$possible = search_file();
+
+?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional">
+<html>
+<head>
+<title>PHP moduls exif test page</title>
+<style type="text/css">
+body {
+ font-size: 12pt;
+}
+h1 {
+ font-size: 20pt;
+ font-weight:bold;
+}
+h2 {
+ font-size: 16pt;
+ font-weight:bold;
+}
+th {
+ text-align: left;
+}
+</style>
+</head>
+<body>
+<h1>Test script for PHP module ext/exif</h1>
+<p>
+(c) Marcus Boerger, 2002
+</p>
+<p>
+Images taken from <a href="http://www.exif.org">www.exif.org</a>,
+<a href="http://marcus-boerger.de">marcus-boerger.de</a>
+all rights reserved by their authors and artists, see exif headers.
+The files can be downloaded <a href="http://marcus-boerger.de/php/ext/exif/test/">here</a>.
+To start the test you simple have to put all images into the same directory as this script.
+The test will work with all files in that directory and all subdirectories. To test private
+images just put them into that directory.
+</p>
+<p>
+Youmay take a look at the test <a href="http://marcus-boerger.de/php/ext/exif/test.txt">source here</a>.
+</p>
+<p>
+This test just prooves that some exif headers can be scanned.
+If all files produce a header in output the module might be o.k.
+</p>
+<p>
+What to look for in detail:
+</p>
+<ul>
+<li>kodak-dc4800-plus-acdsee.jpg
+ <ul>
+ <li>should provide a long comment 'by marcus börger<%04i>'*n</li>
+ </ul>
+</li>
+<li>hp-photosmart
+ <ul>
+ <li>should provide a ***two line*** copyright notice</li>
+ </ul>
+</li>
+</ul>
+<h2>function exif_headername</h2>
+<table border='1' cellspacing='0' cellpadding='3' summary="EXIF headernames">
+<?php
+if (function_exists('exif_headername')) {
+?>
+<tr><td>ImageWidth</td><td><?=@exif_headername(0x0100)?></td><td><?=error_msg()?></td></tr>
+<tr><td>JPEGProc</td><td><?=@exif_headername(0x0200)?></td><td><?=error_msg()?></td></tr>
+<tr><td>SceneType</td><td><?=@exif_headername(0xA301)?></td><td><?=error_msg()?></td></tr>
+<tr><td>false</td><td><?=@exif_headername(0x0000)===false?'false':'value'?></td><td><?=error_msg()?></td></tr>
+<?php
+} else {
+ echo "<tr><td>function exif_headername is not supported</td></tr>\n";
+}
+?>
+</table>
+<br clear="all">
+<h2>function read_exif_data for <?=count($possible)?> images</h2>
+<table border='1' cellspacing='0' cellpadding='3' summary="EXIF information">
+<?php
+if (function_exists('read_exif_data')) {
+ $num = 0;
+ foreach($possible as $idx => $file) {
+ $num++;
+ $res = '';
+ $len = 2;
+ $image = @read_exif_data($file,false);
+ $error = error_msg();
+ //error_log("read_exif_data($file)",0);
+ foreach($image as $Name => $Value) {
+ if ( $Name!='Thumbnail') {
+ if ( is_array($Value)) {
+ $len++;
+ $res .= AddInfo($Name,'Array('.count($Value).')');
+ foreach( $Value as $idx => $Entry) {
+ $len++;
+ $res .= AddInfo($Name.':'.$idx,$Entry);
+ }
+ } else {
+ $len++;
+ $res .= AddInfo($Name,$Value);
+ }
+ }
+ }
+ echo "<tr><td rowspan='$len' valign='top'>$num</td></tr><tr><th>$file</th><td>$error</td></tr>\n$res";
+ }
+} else {
+ echo "<tr><td>function read_exif_data is not supported</td></tr>\n";
+}
+?>
+</table>
+</body>
+</html>
\ No newline at end of file