root/extensions/AMetaData/JpegMetaData/JpegMetaData.class.php @ 7399

Revision 7399, 27.0 KB (checked in by grum, 3 years ago)

Fix many bugs
bug:1894, bug:1898, bug:1911, bug:1863, bug:1955, bug:1956, bug:1925

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