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

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

fix bug:1992 - On Canon file, debug informations are displayed

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