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

Last change on this file since 10517 was 10517, checked in by grum, 13 years ago

fix bug:2222

  • Property svn:executable set to *
File size: 28.9 KB
Line 
1<?php
2/**
3 * --:: JPEG MetaDatas ::-------------------------------------------------------
4 *
5 * Version : 1.1.0
6 * Date    : 2010-07-29
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 * | 1.0.0   |            | * first public release
42 * |         |            |
43 * | 1.1.0   | 2010-07-29 | * mantis bug:1686
44 * |         |            |   . bug reported on IfdReader
45 * |         |            |     When sub IFD (0x8769) refers to a sub IFD with
46 * |         |            |     an offset lower than the current IFD, the reader
47 * |         |            |     loops on the current data block and terminate
48 * |         |            |     with an error 500 ; fix consist to ignore this
49 * |         |            |     kind of offset, but it's not the right solution
50 * |         |            |     (right solution: to be able to read negative
51 * |         |            |     offset)
52 * |         |            |
53 * |         |            | * mantis feature : 1719
54 * |         |            |   . Coding a DateTime class ; used only if there is
55 * |         |            |     no PHP built-in DateTime class
56 * |         |            |
57 * |         |            | * add the "schema" property to Tag class
58 * |         |            |
59 * |         |            | * fixed bug about filtering schema
60 * |         |            |   . when loading metadata, filter on schema are now
61 * |         |            |     applied ; 'magic' metadata are computed even if
62 * |         |            |     the other schema are filtered
63 * |         |            |
64 * |         |            | * add the "getTag" function
65 * |         |            |
66 * | 1.1.1   | 2010-09-13 | * mantis bug:1826
67 * |         |            |   . digiKam XMP tags are not recognized
68 * |         |            |
69 * |         |            | * mantis bug:1859
70 * |         |            |   . JpegMetadata class can't manage multiple IPTC
71 * |         |            |     keywords
72 * |         |            |
73 * |         |            | * mantis bug:1870
74 * |         |            |   . Xmp ISOSpeedRatings was not interpreted by
75 * |         |            |     'Magic' metadata
76 * |         |            |
77 * | 1.1.3   | 2010-09-30 | * mantis bug:1894
78 * |         |            |   . Error when filling the metadata repository
79 * |         |            |
80 * |         |            | * mantis bug:1898
81 * |         |            |   . Warning "division by zero" on Canon images
82 * |         |            |
83 * |         |            | * mantis bug:1911
84 * |         |            |   . Unable to read Jpeg file if there is extradata
85 * |         |            |     after the EOI marker
86 * |         |            |
87 * |         |            | * mantis bug:1863
88 * |         |            |   . Except "keywords" meta, all IPTC meta declared
89 * |         |            |     as "repeatable" are not managed
90 * |         |            |
91 * |         |            | * mantis bug:1955
92 * |         |            |   . Incorrect mapping for IPTC File format
93 * |         |            |
94 * |         |            | * mantis bug:1955
95 * |         |            |   . Incorrect mapping for IPTC File format
96 * |         |            |
97 * |         |            | * mantis bug:1956
98 * |         |            |   . IPTC "Subject Reference" is not implemented
99 * |         |            |
100 * |         |            | * mantis bug:1965
101 * |         |            |   . add XML <lr:hierarchicalSubject> markup
102 * |         |            |
103 * |         |            | * mantis bug:1975
104 * |         |            |   . Implement COM segment as a tag
105 * |         |            |
106 * |         |            | * mantis bug:1976
107 * |         |            |   . Implement keywords as magic tag
108 * |         |            |
109 * |         |            | * mantis bug:1978
110 * |         |            |   . Some meta names are not translated in french
111 * |         |            |
112 * | 1.1.4   | 2011-01-29 | * mantis bug:2141
113 * |         |            |   . MakerNotes on some Nikon file are not recognized
114 * |         |            |     and script is terminated with a memory allocation
115 * |         |            |     error
116 * |         |            |
117 * | 1.1.5   | 2011-04-20 | * mantis bug:2222
118 * |         |            |   . Division by zero with Exif ShutterSpeedValue
119 * |         |            |
120 * |         |            |
121 * |         |            |
122 * +---------+------------+-----------------------------------------------------
123 *
124 *
125 * -----------------------------------------------------------------------------
126 *
127 * References about definition & interpretation of metadata tags :
128 *  - EXIF 2.20 Specification    => http://www.exif.org/Exif2-2.PDF
129 *  - TIFF 6.0 Specification     => http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
130 *  - Exiftool by Phil Harvey    => http://www.sno.phy.queensu.ca/~phil/exiftool/
131 *                                  http://owl.phy.queensu.ca/~phil/exiftool/TagNames
132 *  - Exiv2 by Andreas Huggel    => http://www.exiv2.org/
133 *  - MetaData working group     => http://www.metadataworkinggroup.org/specs/
134 *  - Adobe XMP Developer Center => http://www.adobe.com/devnet/xmp/
135 *  - Gezuz                      => http://gezus.one.free.fr/?Plugin-EXIF-pour-Spip-1-9-2
136 *  - JPEG format                => http://crousseau.free.fr/imgfmt_jpeg.htm
137 *  - International Press Telecomunication Council specifications
138 *                               => http://www.iptc.org/
139 *  - IPTC headers structure     => http://www.codeproject.com/KB/graphics/iptc.aspx?msg=1014929
140 *  - CPAN                       => http://search.cpan.org/dist/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/Structures.pod
141 *                               => http://search.cpan.org/~bettelli/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/MakerNotes.pod
142 *
143 * -----------------------------------------------------------------------------
144 * To support internationalization the JpegMetaData package uses ".po" and ".mo"
145 * files, and use "php-gettext"
146 * ==> See files in External/php-gettext for more information about this project
147 * -----------------------------------------------------------------------------
148 *
149 * The JpegMetaData is the main class for reading metadata of a Jpeg file
150 *
151 * It provides two essentialy high level functions to read different kind of
152 * metadata (EXIF, IPTC, XMP) :
153 *  - (static) getTagList
154 *  - load
155 *  - getTags
156 *  - getTag
157 *
158 * -----------------------------------------------------------------------------
159 *
160 * .. Notes ..
161 *
162 * About tags and translation in local lang
163 * With the 'getTags()' method, the JpegMetaData returns an array of Tag objects
164 * found in the jpeg file.
165 *
166 * A Tag object have 2 properties that can be translated into a local language :
167 *  - the name, getted with 'getName()'
168 *  - the valueLabel, getted with 'getLabel()'
169 *
170 * Theses properties ARE NOT translated automatically.
171 *
172 * You can translate it with the Locale class, by using the static method 'get'
173 *
174 * Example :
175 *  Locale::get($myTag->getName()) will return the translated name of the Tag
176 *  Locale::get($myTag->getLabel()) will return the translated value of the Tag
177 *
178 * ===========> See Tag.class.php to know more about the Tag class <============
179 * ========> See Locale.class.php to know more about the Locale class <=========
180 *
181 *
182 * -----------------------------------------------------------------------------
183 */
184
185  define("JPEG_METADATA_DIR", dirname(__FILE__)."/");
186
187  require_once(JPEG_METADATA_DIR."Common/DateTime.class.php");
188  require_once(JPEG_METADATA_DIR."Readers/JpegReader.class.php");
189  require_once(JPEG_METADATA_DIR."TagDefinitions/MagicTags.class.php");
190
191  class JpegMetaData
192  {
193    const TAGFILTER_KNOWN       = 0x01;
194    const TAGFILTER_IMPLEMENTED = 0x02;
195    const TAGFILTER_ALL         = 0x03;
196
197    private $jpeg = null;
198    protected $tags = Array();
199    private $options = Array();
200
201    /**
202     * this static function returns an array of tags definitions
203     *
204     * the only parameter is an array to determine filter options
205     *
206     * ---------------------+---------------------------------------------------
207     * key                  | descriptions/values
208     * ---------------------+---------------------------------------------------
209     * filter               | Integer
210     *                      | This options is used to filter implemented tag
211     *                      |  JpegMetaData::TAGFILTER_ALL
212     *                      |  => returns all the tags
213     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
214     *                      |  => returns only the implemented tags
215     *                      |
216     * optimizeIptcDateTime | Boolean
217     *                      | IPTC Date/Time are separated into 2 tags
218     *                      | if this option is set to true, only dates tags are
219     *                      | returned (assuming this option is used when an
220     *                      | image file is loaded)
221     *                      |
222     * exif                 | Boolean
223     * iptc                 | If set to true, the function returns all the tags
224     *                      | known for the specified type tag
225     * xmp                  |
226     * maker                | maker => returns specifics tags from all the known
227     * magic                |          makers
228     *                      |
229     * ---------------------+---------------------------------------------------
230     *
231     * returned value is an array
232     * each keys is a tag name and the associated value is a 2-level array
233     *  'implemented' => Boolean, allowing to know if the tags is implemented or
234     *                   not
235     *  'translatable'=> Boolean, allowing to know if the tag value can be
236     *                   translated
237     *  'name'        => String, the tag name translated in locale language
238     *
239     * @Param Array $options  (optional)
240     * @return Array(keyName => Array('implemented' => Boolean, 'name' => String))
241     */
242    static public function getTagList($options=Array())
243    {
244      $default=Array(
245        'filter' => self::TAGFILTER_ALL,
246        'optimizeIptcDateTime' => false,
247        'exif'  => true,
248        'iptc'  => true,
249        'xmp'   => true,
250        'maker' => true,
251        'magic' => true,
252        'com'   => true
253      );
254
255      foreach($default as $key => $val)
256      {
257        if(array_key_exists($key, $options))
258          $default[$key]=$options[$key];
259      }
260
261      $list=Array();
262      $returned=Array();
263
264      if($default['exif'])
265      {
266        $list[]="exif";
267        $list[]="gps";
268      }
269
270      if($default['maker'])
271      {
272        $list[]=MAKER_PENTAX;
273        $list[]=MAKER_NIKON;
274        $list[]=MAKER_CANON;
275      }
276
277      if($default['iptc'])
278        $list[]="iptc";
279
280      if($default['xmp'])
281        $list[]="xmp";
282
283      if($default['magic'])
284        $list[]="magic";
285
286      if($default['com'])
287        $list[]="com";
288
289      foreach($list as $val)
290      {
291        unset($tmp);
292
293        switch($val)
294        {
295          case "exif":
296            $tmp=new IfdTags();
297            $schema=Schemas::EXIF;
298            break;
299          case "gps":
300            $tmp=new GpsTags();
301            $schema=Schemas::EXIF_GPS;
302            break;
303          case "iptc":
304            $tmp=new IptcTags();
305            $schema=Schemas::IPTC;
306            break;
307          case "xmp":
308            $tmp=new XmpTags();
309            $schema=Schemas::XMP;
310            break;
311          case "magic":
312            $tmp=new MagicTags();
313            $schema=Schemas::MAGIC;
314            break;
315          case "com":
316            $tmp=new ComTags();
317            $schema=Schemas::COM;
318            break;
319          case MAKER_PENTAX:
320            include_once(JPEG_METADATA_DIR."TagDefinitions/PentaxTags.class.php");
321            $tmp=new PentaxTags();
322            $schema=Schemas::EXIF_MAKER.'.'.MAKER_PENTAX;
323            break;
324          case MAKER_NIKON:
325            include_once(JPEG_METADATA_DIR."TagDefinitions/NikonTags.class.php");
326            $tmp=new NikonTags();
327            $schema=Schemas::EXIF_MAKER.'.'.MAKER_NIKON;
328            break;
329          case MAKER_CANON:
330            include_once(JPEG_METADATA_DIR."TagDefinitions/CanonTags.class.php");
331            $tmp=new CanonTags();
332            $schema=Schemas::EXIF_MAKER.'.'.MAKER_CANON;
333            break;
334          default:
335            $tmp=null;
336            $schema="?";
337            break;
338        }
339
340        if(!is_null($tmp))
341          foreach($tmp->getTags() as $key => $tag)
342          {
343            if(self::filter(true, $tag['implemented'], $default['filter']))
344            {
345              if(array_key_exists('tagName', $tag))
346                $name=$tag['tagName'];
347              else
348                $name=$key;
349
350              if(array_key_exists('schema', $tag) and $val==Schemas::EXIF)
351                $subSchema=".".$tag['schema'];
352              else
353                $subSchema="";
354
355              if($val==Schemas::XMP)
356                $keyName=$schema.$subSchema.".".$key;
357              else
358                $keyName=$schema.$subSchema.".".$name;
359              $returned[$keyName]=Array(
360                'implemented' => $tag['implemented'],
361                'translatable' => $tag['translatable'],
362                'name' => $name
363              );
364            }
365          }
366      }
367
368      ksort($returned);
369
370      return($returned);
371    }
372
373
374    /**
375     * the filter function is used by the classe to determine if a tag is
376     * filtered or not
377     *
378     * @Param Boolean $known
379     * @Param Boolean $implemented
380     * @Param Integer $filter
381     *
382     */
383    static public function filter($known, $implemented, $filter)
384    {
385      return(($known and (($filter & self::TAGFILTER_KNOWN) == self::TAGFILTER_KNOWN )) or
386                ($implemented and (($filter & self::TAGFILTER_IMPLEMENTED) == self::TAGFILTER_IMPLEMENTED )));
387    }
388
389    /**
390     * the constructor need an optional filename and options
391     *
392     * if no filename is given, you can use the "load" function after the object
393     * is instancied
394     *
395     * if no options are given, the class use the default values
396     *
397     * ---------------------+---------------------------------------------------
398     * key                  | descriptions/values
399     * ---------------------+---------------------------------------------------
400     * filter               | Integer
401     *                      | This options is used to filter implemented tag
402     *                      |  JpegMetaData::TAGFILTER_ALL
403     *                      |  => returns all the tags
404     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
405     *                      |  => returns only the implemented tags, not
406     *                      |     implemented tag are excluded
407     *                      |  JpegMetaData::TAGFILTER_KNOWN
408     *                      |  => returns only the known tags (implemented or
409     *                      |     not), unknown tag are excluded
410     *                      |
411     * optimizeIptcDateTime | Boolean
412     *                      | IPTC Date/Time are separated into 2 tags
413     *                      | if this option is set to true, only dates tags are
414     *                      | returned (in this case, time is included is the
415     *                      | date)
416     *                      |
417     * exif                 | Boolean
418     * iptc                 | If set to true, the function returns all the tags
419     * xmp                  | known for the specified type tag
420     * magic                | the exif parameter include the maker tags
421     *                      |
422     * ---------------------+---------------------------------------------------
423     *
424     * @Param String $file    (optional)
425     * @Param Array  $options (optional)
426     *
427     */
428    function __construct($file = "", $options = Array())
429    {
430      $this->load($file, $options);
431    }
432
433    function __destruct()
434    {
435      $this->unsetAll();
436    }
437
438    /**
439     * load a file
440     *
441     * options values are the same than the constructor's options
442     *
443     * @Param String $file
444     * @Param Array  $options (optional)
445     *
446     */
447    public function load($file, $options = Array())
448    {
449      $this->unsetAll();
450
451      $this->initializeOptions($options);
452      $this->tags = Array();
453      $this->jpeg = new JpegReader($file);
454
455      if($this->jpeg->isLoaded() and $this->jpeg->isValid())
456      {
457        foreach($this->jpeg->getAppMarkerSegments() as $key => $appMarkerSegment)
458        {
459          if($appMarkerSegment->dataLoaded())
460          {
461            $data=$appMarkerSegment->getData();
462
463            if($data instanceof TiffReader)
464            {
465              /*
466               * Load Exifs tags from Tiff block
467               */
468              if($data->getNbIFDs()>0 and
469                 ($this->options['magic'] or $this->options['exif']))
470              {
471                $this->loadIfdTags($data->getIFD(0), Schemas::EXIF_TIFF);
472              }
473            }
474            elseif($data instanceof XmpReader)
475            {
476              /*
477               * Load Xmp tags from Xmp block
478               */
479              if($this->options['magic'] or $this->options['xmp'])
480              {
481                $this->loadTags($data->getTags(), Schemas::XMP);
482              }
483            }
484            elseif($data instanceof IptcReader)
485            {
486              /*
487               * Load IPTC tags from IPTC block
488               */
489              if($this->options['optimizeIptcDateTime'])
490                $data->optimizeDateTime();
491
492              if($this->options['magic'] or $this->options['iptc'])
493              {
494                $this->loadTags($data->getTags(), Schemas::IPTC);
495              }
496            }
497            elseif($data instanceof ComReader)
498            {
499              $this->loadTags($data->getTags(), Schemas::COM);
500            }
501          }
502        }
503
504        if($this->options['magic'])
505        {
506          $this->processMagicTags();
507        }
508
509        // clean all unwanted metadata
510        foreach($this->tags as $key => $tag)
511        {
512          if(isset($this->options[$tag->getSchema()]) and !$this->options[$tag->getSchema()]) unset($this->tags[$key]);
513        }
514
515        ksort($this->tags);
516      }
517    }
518
519    /**
520     * This function returns an array of tags found in the loaded file
521     *
522     * It's possible to made a second selection to filter items
523     *
524     * ---------------------+---------------------------------------------------
525     * key                  | descriptions/values
526     * ---------------------+---------------------------------------------------
527     * tagFilter            | Integer
528     *                      | This options is used to filter implemented tag
529     *                      |  JpegMetaData::TAGFILTER_ALL
530     *                      |  => returns all the tags
531     *                      |  JpegMetaData::TAGFILTER_IMPLEMENTED
532     *                      |  => returns only the implemented tags, not
533     *                      |     implemented tag are excluded
534     *                      |  JpegMetaData::TAGFILTER_KNOWN
535     *                      |  => returns only the known tags (implemented or
536     *                      |     not), unknown tag are excluded
537     *                      |
538     * ---------------------+---------------------------------------------------
539     *
540     * Note, the filter is applied on loaded tags. If a filter was applied when
541     * the file was loaded, you cannot expand the tag list, only reduce
542     * example :
543     *  $jpegmd = new JpegMetadata($file, Array('filter' => JpegMetaData::TAGFILTER_IMPLEMENTED));
544     *     => the unknown tag are not loaded
545     *  $jpegmd->getTags(JpegMetaData::TAGFILTER_ALL)
546     *     => unknown tag will not be restitued because they are not loaded...
547     *
548     * the function returns an array of Tag.
549     *
550     *
551     * ===========> See the Tag.class.php to know all about a tag <=============
552     *
553     * @Param Integer $tagFilter (optional)
554     *
555     */
556    public function getTags($tagFilter = self::TAGFILTER_ALL)
557    {
558      $returned=Array();
559      foreach($this->tags as $key => $val)
560      {
561        if(self::filter($val->isKnown(), $val->isImplemented(), $tagFilter))
562        {
563          $returned[$key]=$val;
564        }
565      }
566      return($returned);
567    }
568
569
570    /**
571     * This function returns the tag object for the given tag name
572     *
573     * @param String $key : the tag name (ie: "exif.exif.ApertureValue")
574     * @return Tag : the tag object, null if the tag doesn't exist
575     */
576    public function getTag($key)
577    {
578      if(array_key_exists($key, $this->tags))
579      {
580        return($this->tags[$key]);
581      }
582      return(null);
583    }
584
585    /**
586     * initialize the options...
587     *
588     * @Param Array $options (optional)
589     *
590     */
591    private function initializeOptions($options=Array())
592    {
593      $this->options = Array(
594        'filter' => self::TAGFILTER_ALL,
595        'optimizeIptcDateTime' => false,
596        'exif'  => true,
597        'iptc'  => true,
598        'xmp'   => true,
599        'magic' => true,
600        'com'   => true
601      );
602
603      foreach($this->options as $key => $val)
604      {
605        if(array_key_exists($key, $options))
606          $this->options[$key]=$options[$key];
607      }
608    }
609
610    /**
611     * load tags from an IFD structure
612     *
613     * see Tiff.class.php and IfdReader.class.php for more informations
614     *
615     * @Param IfdReader $ifd
616     * @Param String    $exifKey
617     *
618     */
619    private function loadIfdTags($ifd, $exifKey)
620    {
621      foreach($ifd->getTags() as $key => $tag)
622      {
623        if((self::filter($tag->getTag()->isKnown(), $tag->getTag()->isImplemented(), $this->options['filter'])) or
624           ($tag->getTag()->getName()=='Exif IFD Pointer' or
625            $tag->getTag()->getName()=='MakerNote' or
626            $tag->getTag()->getName()=='GPS IFD Pointer'))
627        {
628          /*
629           * only tag responding to the filter are selected
630           * note the tags 'Exif IFD Pointer', 'MakerNote' & 'GPS IFD Pointer'
631           * are not declared as implemented (otherwise they are visible with
632           * the static 'getTagList' function) but must be selected even if
633           * filter says "implemented only"
634           */
635          if($tag->getTag()->getLabel() instanceof IfdReader)
636          {
637            switch($tag->getTag()->getName())
638            {
639              case 'Exif IFD Pointer':
640                $exifKey2=Schemas::EXIF_EXIF;
641                break;
642              case 'MakerNote':
643                $exifKey2=Schemas::EXIF_MAKER.".".$tag->getTag()->getLabel()->getMaker();
644                break;
645              case 'GPS IFD Pointer':
646                $exifKey2=Schemas::EXIF_GPS;
647                break;
648              default:
649                $exifKey2=$exifKey;
650                break;
651            }
652            $this->loadIfdTags($tag->getTag()->getLabel(), $exifKey2);
653          }
654          else
655          {
656            $this->tags[$exifKey.".".$tag->getTag()->getName()]=$tag->getTag();
657          }
658        }
659      }
660    }
661
662    /**
663     * Used to load tags from an IPTc or XMP structure
664     *
665     * see IptcReader.class.php and XmpReader.class.php
666     *
667     * @Param Tag[]  $ifd
668     * @Param String $tagKey
669     *
670     */
671    private function loadTags($tags, $tagKey)
672    {
673      foreach($tags as $key => $tag)
674      {
675        if(self::filter($tag->isKnown(), $tag->isImplemented(), $this->options['filter']))
676        {
677          $this->tags[$tagKey.".".$tag->getName()]=$tag;
678        }
679      }
680    }
681
682    /**
683     * MagicTags are built with this function
684     */
685    protected function processMagicTags()
686    {
687      $magicTags=new MagicTags();
688
689      foreach($magicTags->getTags() as $key => $val)
690      {
691        $tag=new Tag($key,0,$key);
692
693        for($i=0; $i<count($val['tagValues']); $i++)
694        {
695          $found=true;
696          preg_match_all('/{([a-z0-9:\.\s\/]*)(\[.*\])?}/i', $val['tagValues'][$i], $returned, PREG_PATTERN_ORDER);
697          foreach($returned[1] as $testKey)
698          {
699            $found=$found & (array_key_exists($testKey, $this->tags) and $this->tags[$testKey]->getValue()!='');
700          }
701          if(count($returned[1])==0) $found=false;
702
703          if($found)
704          {
705            $returned=trim(
706              preg_replace_callback(
707                '/{([a-z0-9:\.\s\/\[\]]*)}/i',
708                Array(&$this, "processMagicTagsCB"),
709                $val['tagValues'][$i]
710              )
711            );
712
713            $returned=$this->processSpecialMagicTag($key, $returned);
714
715            $tag->setValue($returned);
716            $tag->setLabel($returned);
717            $tag->setKnown(true);
718            $tag->setImplemented($val['implemented']);
719            $tag->setTranslatable($val['translatable']);
720            $tag->setSchema(Schemas::MAGIC);
721
722            $i=count($val['tagValues']);
723          }
724        }
725
726        if($tag->isImplemented() and $found)
727        {
728          $this->tags["magic.".$key]=$tag;
729        }
730
731        unset($tag);
732      }
733      unset($magicTags);
734    }
735
736    /**
737     * this function is called by the processMagicTags to replace tagId by the
738     * tag values
739     *
740     * @param Array $matches : array[1] = the tagId
741     * @return String : the tag value
742     */
743    protected function processMagicTagsCB($matches)
744    {
745      $label="";
746      preg_match_all('/([a-z0-9:\.\s\/]*)\[(.*)\]/i', $matches[1], $result, PREG_PATTERN_ORDER);
747      if(count($result[0])>0)
748      {
749
750        if(array_key_exists($result[1][0], $this->tags))
751        {
752          $tag=$this->tags[$result[1][0]]->getLabel();
753
754          preg_match_all('/([a-z0-9:\.\s\/]*)\[(.*)\]/i', $result[2][0], $result2, PREG_PATTERN_ORDER);
755
756          if(count($result2[0])>0)
757          {
758            if(array_key_exists($result2[2][0], $tag[$result2[1][0]] ))
759              $label=$tag[$result2[1][0]][$result2[2][0]];
760          }
761          else
762          {
763            if(array_key_exists($result[2][0], $tag))
764              $label=$tag[$result[2][0]];
765          }
766        }
767      }
768      else
769      {
770        if(array_key_exists($matches[1], $this->tags))
771        {
772          $label=$this->tags[$matches[1]]->getLabel();
773        }
774      }
775      if($label instanceof DateTime)
776        return($label->format("Y-m-d H:i:s"));
777
778      $label=XmpTags::getAltValue($label, L10n::getLanguage());
779
780      if(is_array($label))
781      {
782        return(implode(", ", $label));
783      }
784
785      return(trim($label));
786    }
787
788    /**
789     *
790     *
791     */
792    protected function processSpecialMagicTag($key, $value)
793    {
794      switch($key)
795      {
796        case "GPS.LatitudeNum":
797        case "GPS.LongitudeNum":
798          preg_match_all('/(\d{1,3})°\s*(\d{1,2})\'(?:\s*(\d+(?:\.\d+)?)")?[\s\|\.]*(N|S|E|W)/i', $value, $result);
799          $num=(float)$result[1][0] + ((float) $result[2][0])/60;
800          if($result[3][0]!="") $num+= ((float) $result[3][0])/3600;
801          if($result[4][0]=="W" or $result[4][0]=="S") $num=-$num;
802          return($num);
803        case "Author.Keywords":
804          $list=array_flip(array_flip(explode(',', str_replace(array('/', '|', ','), ',', $value))));
805          sort($list);
806          $tmp=array();
807          foreach($list as $key=>$val)
808          {
809            if(trim($val)!='') $tmp[]=trim($val);
810          }
811          return($tmp);
812          break;
813        default:
814          return($value);
815      }
816    }
817
818
819    /**
820     * used by the destructor to clean variables
821     */
822    private function unsetAll()
823    {
824      unset($this->tags);
825      unset($this->jpeg);
826      unset($this->options);
827    }
828
829
830  } // class JpegMetaData
831
832?>
Note: See TracBrowser for help on using the repository browser.