source: extensions/AMetaData/JpegMetaData/JpegMetaData.class.php @ 4935

Last change on this file since 4935 was 4935, checked in by grum, 14 years ago

Start to implement Canon camera's maker note

  • Property svn:executable set to *
File size: 19.6 KB
Line 
1<?php
2/**
3 * --:: JPEG MetaDatas ::-------------------------------------------------------
4 *
5 * Version : 1.0.0
6 * Date    : 2009/12/26
7 *
8 *  Author    : Grum
9 *   email    : grum at piwigo.org
10 *   website  : http://photos.grum.fr
11 *
12 *   << May the Little SpaceFrog be with you ! >>
13 *
14 * +-----------------------------------------------------------------------+
15 * | JpegMetaData - a PHP based Jpeg Metadata manager                      |
16 * +-----------------------------------------------------------------------+
17 * | Copyright(C) 2010  Grum - http://www.grum.fr                          |
18 * +-----------------------------------------------------------------------+
19 * | This program is free software; you can redistribute it and/or modify  |
20 * | it under the terms of the GNU General Public License as published by  |
21 * | the Free Software Foundation                                          |
22 * |                                                                       |
23 * | This program is distributed in the hope that it will be useful, but   |
24 * | WITHOUT ANY WARRANTY; without even the implied warranty of            |
25 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
26 * | General Public License for more details.                              |
27 * |                                                                       |
28 * | You should have received a copy of the GNU General Public License     |
29 * | along with this program; if not, write to the Free Software           |
30 * | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
31 * | USA.                                                                  |
32 * +-----------------------------------------------------------------------+
33 *
34 *
35 * +-:: HISTORY ::--------+-----------------------------------------------------
36 * |         |            |
37 * | Release | Date       |
38 * +---------+------------+-----------------------------------------------------
39 * | 0.1.0a  | 2009-12-26 |
40 * |         |            |
41 * |         |            |
42 * |         |            |
43 * |         |            |
44 * |         |            |
45 * |         |            |
46 * |         |            |
47 * |         |            |
48 * |         |            |
49 * |         |            |
50 * +---------+------------+-----------------------------------------------------
51 *
52 *
53 * -----------------------------------------------------------------------------
54 *
55 * References about definition & interpretation of metadata tags :
56 *  - EXIF 2.20 Specification    => http://www.exif.org/Exif2-2.PDF
57 *  - TIFF 6.0 Specification     => http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
58 *  - Exiftool by Phil Harvey    => http://www.sno.phy.queensu.ca/~phil/exiftool/
59 *                                  http://owl.phy.queensu.ca/~phil/exiftool/TagNames
60 *  - Exiv2 by Andreas Huggel    => http://www.exiv2.org/
61 *  - MetaData working group     => http://www.metadataworkinggroup.org/specs/
62 *  - Adobe XMP Developer Center => http://www.adobe.com/devnet/xmp/
63 *  - Gezuz                      => http://gezus.one.free.fr/?Plugin-EXIF-pour-Spip-1-9-2
64 *  - JPEG format                => http://crousseau.free.fr/imgfmt_jpeg.htm
65 *  - International Press Telecomunication Council specifications
66 *                               => http://www.iptc.org/
67 *  - IPTC headers structure     => http://www.codeproject.com/KB/graphics/iptc.aspx?msg=1014929
68 *  - CPAN                       => http://search.cpan.org/dist/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/Structures.pod
69 *                               => http://search.cpan.org/~bettelli/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/MakerNotes.pod
70 *
71 * -----------------------------------------------------------------------------
72 * To support internationalization the JpegMetaData package uses ".po" and ".mo"
73 * files, and use "php-gettext"
74 * ==> See files in External/php-gettext for more information about this project
75 * -----------------------------------------------------------------------------
76 *
77 * The JpegMetaData is the main class for reading metadata of a Jpeg file
78 *
79 * It provides two essentialy high level functions to read different kind of
80 * metadata (EXIF, IPTC, XMP) :
81 *  - (static) getTagList
82 *  - load
83 *  - getTags
84 *
85 * -----------------------------------------------------------------------------
86 *
87 * .. Notes ..
88 *
89 * About tags and translation in local lang
90 * With the 'getTags()' method, the JpegMetaData returns an array of Tag objects
91 * found in the jpeg file.
92 *
93 * A Tag object have 2 properties that can be translated into a local language :
94 *  - the name, getted with 'getName()'
95 *  - the valueLabel, getted with 'getLabel()'
96 *
97 * Theses properties ARE NOT translated automatically.
98 *
99 * You can translate it with the Locale class, by using the static method 'get'
100 *
101 * Example :
102 *  Locale::get($myTag->getName()) will return the translated name of the Tag
103 *  Locale::get($myTag->getLabel()) will return the translated value of the Tag
104 *
105 * ===========> See Tag.class.php to know more about the Tag class <============
106 * ========> See Locale.class.php to know more about the Locale class <=========
107 *
108 *
109 * -----------------------------------------------------------------------------
110 */
111
112  define("JPEG_METADATA_DIR", dirname(__FILE__)."/");
113
114  require_once(JPEG_METADATA_DIR."Readers/JpegReader.class.php");
115
116  class JpegMetaData
117  {
118    const TAGFILTER_KNOWN       = 0x01;
119    const TAGFILTER_IMPLEMENTED = 0x02;
120    const TAGFILTER_ALL         = 0x03;
121
122    const KEY_EXIF_TIFF = "exif.tiff";
123    const KEY_EXIF_EXIF = "exif.exif";
124    const KEY_EXIF_GPS  = "exif.gps";
125    const KEY_EXIF = "exif";
126    const KEY_IPTC = "iptc";
127    const KEY_XMP  = "xmp";
128
129    private $jpeg = null;
130    private $tags = Array();
131    private $options = Array();
132
133    /**
134     * this static function returns an array of tags definitions
135     *
136     * the only parameter is an array to determine filter options
137     *
138     * ---------------------+---------------------------------------------------
139     * key                  | descriptions/values
140     * ---------------------+---------------------------------------------------
141     * filter               | Integer
142     *                      | This options is used to filter implemented tag
143     *                      |  JpegMetaData::TAGFILTER_ALL
144     *                      |  => returns all the tags
145     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
146     *                      |  => returns only the implemented tags
147     *                      |
148     * optimizeIptcDateTime | Boolean
149     *                      | IPTC Date/Time are separated into 2 tags
150     *                      | if this option is set to true, only dates tags are
151     *                      | returned (assuming this option is used when an
152     *                      | image file is loaded)
153     *                      |
154     * exif                 | Boolean
155     * iptc                 | If set to true, the function returns all the tags
156     *                      | known for the specified type tag
157     * xmp                  |
158     * maker                | maker => returns specifics tags from all the known
159     *                      |          makers
160     *                      |
161     * ---------------------+---------------------------------------------------
162     *
163     * returned value is an array
164     * each keys is a tag name and the associated value is a 2-level array
165     *  'implemented' => Boolean, allowing to know if the tags is implemented or
166     *                   not
167     *  'translatable'=> Boolean, allowing to know if the tag value can be
168     *                   translated
169     *  'name'        => String, the tag name translated in locale language
170     *
171     * @Param Array $options  (optional)
172     * @return Array(keyName => Array('implemented' => Boolean, 'name' => String))
173     */
174    static public function getTagList($options=Array())
175    {
176      $default=Array(
177        'filter' => self::TAGFILTER_ALL,
178        'optimizeIptcDateTime' => false,
179        'exif'  => true,
180        'iptc'  => true,
181        'xmp'   => true,
182        'maker' => true
183      );
184
185      foreach($default as $key => $val)
186      {
187        if(array_key_exists($key, $options))
188          $default[$key]=$options[$key];
189      }
190
191      $list=Array();
192      $returned=Array();
193
194      if($default['exif'])
195      {
196        $list[]="exif";
197        $list[]="gps";
198      }
199
200      if($default['maker'])
201      {
202        $list[]=MAKER_PENTAX;
203        $list[]=MAKER_NIKON;
204        $list[]=MAKER_CANON;
205      }
206
207      if($default['iptc'])
208        $list[]="iptc";
209
210      if($default['xmp'])
211        $list[]="xmp";
212
213      foreach($list as $val)
214      {
215        unset($tmp);
216
217        switch($val)
218        {
219          case "exif":
220            $tmp=new IfdTags();
221            $schema="exif";
222            break;
223          case "gps":
224            $tmp=new GpsTags();
225            $schema="exif.gps";
226            break;
227          case "iptc":
228            $tmp=new IptcTags();
229            $schema="iptc";
230            break;
231          case "xmp":
232            $tmp=new XmpTags();
233            $schema="xmp";
234            break;
235          case MAKER_PENTAX:
236            include_once(JPEG_METADATA_DIR."TagDefinitions/PentaxTags.class.php");
237            $tmp=new PentaxTags();
238            $schema="exif.".MAKER_PENTAX;
239            break;
240          case MAKER_NIKON:
241            include_once(JPEG_METADATA_DIR."TagDefinitions/NikonTags.class.php");
242            $tmp=new NikonTags();
243            $schema="exif.".MAKER_NIKON;
244            break;
245          case MAKER_CANON:
246            include_once(JPEG_METADATA_DIR."TagDefinitions/CanonTags.class.php");
247            $tmp=new CanonTags();
248            $schema="exif.".MAKER_CANON;
249            break;
250          default:
251            $tmp=null;
252            $schema="?";
253            break;
254        }
255
256        if(!is_null($tmp))
257          foreach($tmp->getTags() as $key => $tag)
258          {
259            if(self::filter(true, $tag['implemented'], $default['filter']))
260            {
261              if(array_key_exists('tagName', $tag))
262                $name=$tag['tagName'];
263              else
264                $name=$key;
265
266              if(array_key_exists('schema', $tag) and $val=="exif")
267                $subSchema=".".$tag['schema'];
268              else
269                $subSchema="";
270
271              if($val=='xmp')
272                $keyName=$schema.$subSchema.".".$key;
273              else
274                $keyName=$schema.$subSchema.".".$name;
275              $returned[$keyName]=Array(
276                'implemented' => $tag['implemented'],
277                'translatable' => $tag['translatable'],
278                'name' => $name
279              );
280            }
281          }
282      }
283
284      return($returned);
285    }
286
287
288    /**
289     * the filter function is used by the classe to determine if a tag is
290     * filtered or not
291     *
292     * @Param Boolean $known
293     * @Param Boolean $implemented
294     * @Param Integer $filter
295     *
296     */
297    static public function filter($known, $implemented, $filter)
298    {
299      return(($known and (($filter & self::TAGFILTER_KNOWN) == self::TAGFILTER_KNOWN )) or
300                ($implemented and (($filter & self::TAGFILTER_IMPLEMENTED) == self::TAGFILTER_IMPLEMENTED )));
301    }
302
303    /**
304     * the constructor need an optional filename and options
305     *
306     * if no filename is given, you can use the "load" function after the object
307     * is instancied
308     *
309     * if no options are given, the class use the default values
310     *
311     * ---------------------+---------------------------------------------------
312     * key                  | descriptions/values
313     * ---------------------+---------------------------------------------------
314     * filter               | Integer
315     *                      | This options is used to filter implemented tag
316     *                      |  JpegMetaData::TAGFILTER_ALL
317     *                      |  => returns all the tags
318     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
319     *                      |  => returns only the implemented tags, not
320     *                      |     implemented tag are excluded
321     *                      |  JpegMetaData::TAGFILTER_KNOWN
322     *                      |  => returns only the known tags (implemented or
323     *                      |     not), unknown tag are excluded
324     *                      |
325     * optimizeIptcDateTime | Boolean
326     *                      | IPTC Date/Time are separated into 2 tags
327     *                      | if this option is set to true, only dates tags are
328     *                      | returned (in this case, time is included is the
329     *                      | date)
330     *                      |
331     * exif                 | Boolean
332     * iptc                 | If set to true, the function returns all the tags
333     * xmp                  | known for the specified type tag
334     *                      | the exif parameter include the maker tags
335     *                      |
336     * ---------------------+---------------------------------------------------
337     *
338     * @Param String $file    (optional)
339     * @Param Array  $options (optional)
340     *
341     */
342    function __construct($file = "", $options = Array())
343    {
344      $this->load($file, $options);
345    }
346
347    function __destruct()
348    {
349      $this->unsetAll();
350    }
351
352    /**
353     * load a file
354     *
355     * options values are the same than the constructor's options
356     *
357     * @Param String $file
358     * @Param Array  $options (optional)
359     *
360     */
361    public function load($file, $options = Array())
362    {
363      $this->unsetAll();
364
365      $this->initializeOptions($options);
366      $this->tags = Array();
367      $this->jpeg = new JpegReader($file);
368
369      if($this->jpeg->isLoaded() and $this->jpeg->isValid())
370      {
371        foreach($this->jpeg->getAppMarkerSegments() as $key => $appMarkerSegment)
372        {
373          if($appMarkerSegment->dataLoaded())
374          {
375            $data=$appMarkerSegment->getData();
376
377            if($data instanceof TiffReader)
378            {
379              /*
380               * Load Exifs tags from Tiff block
381               */
382              if($data->getNbIFDs()>0)
383              {
384                $this->loadIfdTags($data->getIFD(0), self::KEY_EXIF_TIFF);
385              }
386            }
387            elseif($data instanceof XmpReader)
388            {
389              /*
390               * Load Xmp tags from Xmp block
391               */
392              $this->loadTags($data->getTags(), self::KEY_XMP);
393            }
394            elseif($data instanceof IptcReader)
395            {
396              /*
397               * Load IPTC tags from IPTC block
398               */
399              if($this->options['optimizeIptcDateTime'])
400                $data->optimizeDateTime();
401
402              $this->loadTags($data->getTags(), self::KEY_IPTC);
403            }
404          }
405        }
406      }
407    }
408
409    /**
410     * This function returns an array of tags found in the loaded file
411     *
412     * It's possible to made a second selection to filter items
413     *
414     * ---------------------+---------------------------------------------------
415     * key                  | descriptions/values
416     * ---------------------+---------------------------------------------------
417     * tagFilter            | Integer
418     *                      | This options is used to filter implemented tag
419     *                      |  JpegMetaData::TAGFILTER_ALL
420     *                      |  => returns all the tags
421     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
422     *                      |  => returns only the implemented tags, not
423     *                      |     implemented tag are excluded
424     *                      |  JpegMetaData::TAGFILTER_KNOWN
425     *                      |  => returns only the known tags (implemented or
426     *                      |     not), unknown tag are excluded
427     *                      |
428     * ---------------------+---------------------------------------------------
429     *
430     * Note, the filter is applied on loaded tags. If a filter was applied when
431     * the file was loaded, you cannot expand the tag list, only reduce
432     * example :
433     *  $jpegmd = new JpegMetadata($file, Array('filter' => JpegMetaData::TAGFILTER_IMPLEMENTED));
434     *     => the unknown tag are not loaded
435     *  $jpegmd->getTags(JpegMetaData::TAGFILTER_ALL)
436     *     => unknown tag will not be restitued because they are not loaded...
437     *
438     * the function returns an array of Tag.
439     *
440     *
441     * ===========> See the Tag.class.php to know all about a tag <=============
442     *
443     * @Param Integer $tagFilter (optional)
444     *
445     */
446    public function getTags($tagFilter = self::TAGFILTER_ALL)
447    {
448      $returned=Array();
449      foreach($this->tags as $key => $val)
450      {
451        if(self::filter($val->isKnown(), $val->isImplemented(), $tagFilter))
452        {
453          $returned[$key]=$val;
454        }
455      }
456      return($returned);
457    }
458
459    /**
460     * initialize the options...
461     *
462     * @Param Array $options (optional)
463     *
464     */
465    private function initializeOptions($options=Array())
466    {
467      $this->options = Array(
468        'filter' => self::TAGFILTER_ALL,
469        'optimizeIptcDateTime' => false,
470        'exif' => true,
471        'iptc' => true,
472        'xmp'  => true
473      );
474
475      foreach($this->options as $key => $val)
476      {
477        if(array_key_exists($key, $options))
478          $this->options[$key]=$options[$key];
479      }
480    }
481
482    /**
483     * load tags from an IFD structure
484     *
485     * see Tiff.class.php and IfdReader.class.php for more informations
486     *
487     * @Param IfdReader $ifd
488     * @Param String    $exifKey
489     *
490     */
491    private function loadIfdTags($ifd, $exifKey)
492    {
493      foreach($ifd->getTags() as $key => $tag)
494      {
495        if((self::filter($tag->getTag()->isKnown(), $tag->getTag()->isImplemented(), $this->options['filter'])) or
496           ($tag->getTag()->getName()=='Exif IFD Pointer' or
497            $tag->getTag()->getName()=='MakerNote' or
498            $tag->getTag()->getName()=='GPS IFD Pointer'))
499        {
500          /*
501           * only tag responding to the filter are selected
502           * note the tags 'Exif IFD Pointer', 'MakerNote' & 'GPS IFD Pointer'
503           * are not declared as implemented (otherwise they are visible with
504           * the static 'getTagList' function) but must be selected even if
505           * filter says "implemented only"
506           */
507          if($tag->getTag()->getLabel() instanceof IfdReader)
508          {
509            switch($tag->getTag()->getName())
510            {
511              case 'Exif IFD Pointer':
512                $exifKey2=self::KEY_EXIF_EXIF;
513                break;
514              case 'MakerNote':
515                $exifKey2=self::KEY_EXIF.".".$tag->getTag()->getLabel()->getMaker();
516                break;
517              case 'GPS IFD Pointer':
518                $exifKey2=self::KEY_EXIF_GPS;
519                break;
520              default:
521                $exifKey2=$exifKey;
522                break;
523            }
524            $this->loadIfdTags($tag->getTag()->getLabel(), $exifKey2);
525          }
526          else
527          {
528            $this->tags[$exifKey.".".$tag->getTag()->getName()]=$tag->getTag();
529          }
530        }
531      }
532    }
533
534    /**
535     * Used to load tags from an IPTc or XMP structure
536     *
537     * see IptcReader.class.php and XmpReader.class.php
538     *
539     * @Param Tag[]  $ifd
540     * @Param String $tagKey
541     *
542     */
543    private function loadTags($tags, $tagKey)
544    {
545      foreach($tags as $key => $tag)
546      {
547        if(self::filter($tag->isKnown(), $tag->isImplemented(), $this->options['filter']))
548        {
549          $this->tags[$tagKey.".".$tag->getName()]=$tag;
550        }
551      }
552    }
553
554    private function unsetAll()
555    {
556      unset($this->tags);
557      unset($this->jpeg);
558      unset($this->options);
559    }
560
561
562  } // class JpegMetaData
563
564?>
Note: See TracBrowser for help on using the repository browser.