source: extensions/AMetaData/JpegMetaData/Readers/XmpReader.class.php @ 4698

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

[Plugin:AMetaData] Finished to comment the JpegMetaData classes and rename some methods

  • Property svn:executable set to *
File size: 22.0 KB
Line 
1<?php
2/*
3 * --:: JPEG MetaDatas ::-------------------------------------------------------
4 *
5 *  Author    : Grum
6 *   email    : grum at piwigo.org
7 *   website  : http://photos.grum.fr
8 *
9 *   << May the Little SpaceFrog be with you ! >>
10 *
11 *
12 * +-----------------------------------------------------------------------+
13 * | JpegMetaData - a PHP based Jpeg Metadata manager                      |
14 * +-----------------------------------------------------------------------+
15 * | Copyright(C) 2010  Grum - http://www.grum.fr                          |
16 * +-----------------------------------------------------------------------+
17 * | This program is free software; you can redistribute it and/or modify  |
18 * | it under the terms of the GNU General Public License as published by  |
19 * | the Free Software Foundation                                          |
20 * |                                                                       |
21 * | This program is distributed in the hope that it will be useful, but   |
22 * | WITHOUT ANY WARRANTY; without even the implied warranty of            |
23 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
24 * | General Public License for more details.                              |
25 * |                                                                       |
26 * | You should have received a copy of the GNU General Public License     |
27 * | along with this program; if not, write to the Free Software           |
28 * | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
29 * | USA.                                                                  |
30 * +-----------------------------------------------------------------------+
31 *
32 *
33 * -----------------------------------------------------------------------------
34 *
35 * The XmpReader class is the dedicated class to read XMP from the APP1 segment
36 *
37 * -----------------------------------------------------------------------------
38 *
39 * .. Notes ..
40 *
41 * The XmpReader class is derived from the GenericReader class.
42 *
43 * ======> See GenericReader.class.php to know more about common methods <======
44 *
45 * -----------------------------------------------------------------------------
46 */
47
48  require_once(JPEG_METADATA_DIR."Common/Data.class.php");
49  require_once(JPEG_METADATA_DIR."Common/XmlData.class.php");
50  require_once(JPEG_METADATA_DIR."Readers/GenericReader.class.php");
51  require_once(JPEG_METADATA_DIR."TagDefinitions/XmpTags.class.php");
52
53  class XmpReader extends GenericReader
54  {
55    private $xmlData = NULL;
56
57    private $xmpTag2Exif = NULL;
58
59    /**
60     * The constructor needs, like the ancestor, the datas to be parsed
61     *
62     * @param String $data
63     */
64    function __construct($data)
65    {
66      /**
67       * XML data are given from an APP1 segement.
68       *
69       * The XMP header ends with a \x00 char, so XML data can be extracted
70       * after the \x00 char
71       *
72       */
73      $this->data=explode("\x00", $data);
74      if(count($this->data)>1)
75      {
76        $this->data=$this->data[1];
77      }
78      else
79      {
80        $this->data="";
81      }
82
83      parent::__construct($this->data);
84
85      $this->xmpTag2Exif = new XmpTag2Exif("", 0, 0);
86
87      $this->xmlData = new XmlData($this->data->readASCII(-1,0));
88      $this->tagDef = new XmpTags();
89
90      $this->loadTags();
91
92      $this->isValid = $this->xmlData->isValid();
93      $this->isLoaded = $this->xmlData->isLoaded();
94
95    }
96
97    function __destruct()
98    {
99      unset($this->xmpTag2Exif);
100      parent::__destruct();
101    }
102
103    public function toString()
104    {
105      $returned=$this->data->readASCII(-1,0);
106      return($returned);
107    }
108
109    /**
110     * This function load tags from the xml tree
111     */
112    private function loadTags()
113    {
114      if($this->xmlData->hasNodes())
115      {
116        $this->processNode($this->xmlData->getFirstNode());
117      }
118    }
119
120    /**
121     * this function analyze the node properties
122     *  - attributes are converted in Tag through a call to the addTag function
123     *  - childs are processed by a specific call to the processChildNode
124     *    function
125     *  - next node is processed by a recursive call
126     *
127     * @param XmlNode $node
128     */
129    private function processNode($node)
130    {
131      if(!is_null($node))
132      {
133        $node->setName($this->processNodeName($node->getName()));
134
135        foreach($node->getAttributes() as $key => $val)
136        {
137          $val['name']=$this->processNodeName($val['name']);
138          $this->addTag($val['name'], $val['value']);
139        }
140
141        if(!is_null($node->getValue()))
142        {
143          $this->addTag($node->getName(), $node->getValue());
144        }
145
146        if($node->hasChilds())
147        {
148          $this->processChildNode($node);
149        }
150
151        $this->processNode($node->getNextNode());
152      }
153    }
154
155
156    /**
157     * 'xap' schemas have to be interpreted as a 'xmp' schemas
158     * so, this function rename all 'xap' nodes in 'xmp' nodes
159     *
160     * @param String $nodeName
161     */
162    private function processNodeName($nodeName)
163    {
164      $name=preg_replace(Array("/^(xap:)/", "/^(xapMM:)/"), Array("xmp:", "xmpMM:"), $nodeName);
165
166      return($name);
167    }
168
169
170    /**
171     * childs node are 'seq', 'alt' or 'bag' type and needs a specific
172     * interpretation
173     *
174     * this function process this kind of data, and add it in the Tag entries
175     *
176     * if child node is not a special a specific node, process it as a normal
177     * node
178     *
179     * @param XmlNode $node
180     */
181    private function processChildNode($node)
182    {
183      switch($node->getName())
184      {
185        /*
186         * child must be 'rdf:Seq', 'rdf:Bag' or 'rdf:Alt'
187         */
188        case "dc:contributor" : // bag
189        case "dc:creator" : // seq
190        case "dc:date" : // seq
191        case "dc:description" : // alt
192        case "dc:language" : // bag
193        case "dc:publisher" : // bag
194        case "dc:relation" : // bag
195        case "dc:rights" : // alt
196        case "dc:subject" : // bag
197        case "dc:title" : // alt
198        case "dc:Type" : // bag
199        case "xmp:Advisory" : // bag
200        case "xmp:Identifier" : // bag
201        case "xmp:Thumbnails" : // alt
202        case "xmpRights:Owner" : // bag
203        case "xmpRights:UsageTerms" : // alt
204        case "xmpMM:History" : // seq
205        case "xmpMM:Versions" : // seq
206        case "xmpBJ:JobRef" : // bag
207        case "xmpTPg:Fonts" : // bag
208        case "xmpTPg:Colorants" : // seq
209        case "xmpTPg:PlateNames" : // seq
210        case "photoshop:SupplementalCategories" : // bag
211        case "crs:ToneCurve" : // seq
212        case "tiff:BitsPerSample" : // seq
213        case "tiff:YCbCrSubSampling" : // seq
214        case "tiff:TransferFunction" : // seq
215        case "tiff:WhitePoint" : // seq
216        case "tiff:PrimaryChromaticities" : // seq
217        case "tiff:YCbCrCoefficients" : // seq
218        case "tiff:ReferenceBlackWhite" : // seq
219        case "tiff:ImageDescription" : // alt
220        case "tiff:Copyright" : // alt
221        case "exif:ComponentsConfiguration" : // seq
222        case "exif:UserComment" : // alt
223        case "exif:ISOSpeedRatings" : // seq
224        case "exif:SubjectArea" : // seq
225        case "exif:SubjectLocation" : // seq
226        case "Iptc4xmpCore:Scene": //bag
227        case "Iptc4xmpCore:SubjectCode": //bag
228          $child=$node->getFirstChild();
229          switch($child->getName())
230          {
231            case "rdf:Seq":
232              $type="seq";
233              break;
234            case "rdf:Bag":
235              $type="bag";
236              break;
237            case "rdf:Alt":
238              $type="alt";
239              break;
240            default:
241              $type="n/a";
242              break;
243          }
244          if($type=="seq" or $type=="bag" or $type="alt")
245          {
246            $value=Array('type' => $type, 'values' => Array());
247            $childNode=$child->getFirstChild();
248            while(!is_null($childNode))
249            {
250              if($childNode->getName() == "rdf:li")
251              {
252                if($type=="alt")
253                {
254                  $attributes=$childNode->getAttributes();
255                  if(count($attributes)>0)
256                  {
257                    $attributes=$attributes[0];
258                  }
259                  else
260                  {
261                    $attributes="n/a";
262                  }
263                  $value['values'][]=Array('type' => $attributes, 'value' => $childNode->getValue());
264                }
265                else
266                {
267                  $value['values'][]=$childNode->getValue();
268                }
269              }
270              $childNode=$childNode->getNextNode();
271            }
272            $this->addTag($node->getName(), $value);
273          }
274          break;
275        default:
276          $this->processNode($node->getFirstChild());
277          break;
278      }
279    }
280
281    /**
282     * add a Tag to the entries.
283     * name and value are needed, the function made the rest (interpret the
284     * value into a 'human readable' value)
285     *
286     * @param String $name : the name of the tag
287     * @param $value       : can be of any type
288     */
289    private function addTag($name, $value)
290    {
291      $tag=new Tag($name, $value, $name);
292
293      if($this->tagDef->tagIdExists($name))
294      {
295        $tagProperties=$this->tagDef->getTagById($name);
296
297        $tag->setKnown(true);
298        $tag->setImplemented($tagProperties['implemented']);
299        $tag->setTranslatable($tagProperties['translatable']);
300
301
302        if(array_key_exists('name', $tagProperties))
303        {
304          $tag->setName($tagProperties['name']);
305        }
306
307        /*
308         * if there is values defined for the tag, analyze it
309         */
310        if(array_key_exists('tagValues', $tagProperties))
311        {
312          if(array_key_exists($value, $tagProperties['tagValues']))
313          {
314            $tag->setLabel($tagProperties['tagValues'][$value]);
315          }
316          else
317          {
318            $tag->setLabel("[unknow value '".$value."']");
319          }
320        }
321        else
322        {
323          /*
324           * there is no values defined for the tag, analyzing it with dedicated
325           * function
326           */
327          if(array_key_exists('exifTag', $tagProperties))
328          {
329            $tag->setLabel($this->xmp2Exif($name, $tagProperties['exifTag'], $value));
330          }
331          else
332          {
333            $tag->setLabel($this->processSpecialTag($name, $value));
334          }
335
336        }
337      }
338
339      $this->entries[]=$tag;
340    }
341
342    /**
343     * this function do the interpretation of specials tags
344     *
345     * the function return the interpreted value for the tag
346     *
347     * @param $name              : the name of the tag
348     * @param $value             : 'raw' value to be interpreted
349     */
350    private function processSpecialTag($name, $value)
351    {
352      /*
353       * Note : exif & tiff values are not processed in this function
354       */
355      switch($name)
356      {
357        case "x:xmptk":
358        case "dc:coverage":
359        case "dc:format":
360        case "dc:identifier":
361        case "dc:relation":
362        case "dc:source":
363        case "dc:title":
364        case "xmp:BaseURL":
365        case "xmp:CreatorTool":
366        case "xmp:Label":
367        case "xmp:Nickname":
368        case "xmp:Rating:":
369        case "xmpRights:Certificate":
370        case "xmpRights:Marked":
371        case "xmpRights:UsageTerms":
372        case "xmpRights:WebStatement":
373        case "xmpMM:DocumentID":
374        case "xmpMM:InstanceID":
375        case "xmpMM:Manager":
376        case "xmpMM:ManageTo":
377        case "xmpMM:ManageUI":
378        case "xmpMM:ManagerVariant":
379        case "xmpMM:RenditionParams":
380        case "xmpMM:VersionID":
381        case "xmpMM:LastURL":
382        case "photoshop:AuthorsPosition":
383        case "photoshop:CaptionWriter":
384        case "photoshop:Category":
385        case "photoshop:City":
386        case "photoshop:Country":
387        case "photoshop:Credit":
388        case "photoshop:Headline":
389        case "photoshop:Instructions":
390        case "photoshop:Source":
391        case "photoshop:State":
392        case "photoshop:TransmissionReference":
393        case "crs:AutoBrightness":
394        case "crs:AutoContrast":
395        case "crs:AutoExposure":
396        case "crs:AutoShadows":
397        case "crs:BlueHue":
398        case "crs:BlueSaturation":
399        case "crs:Brightness":
400        case "crs:CameraProfile":
401        case "crs:ChromaticAberrationB":
402        case "crs:ChromaticAberrationR":
403        case "crs:ColorNoiseReduction":
404        case "crs:Contrast":
405        case "crs:CropTop":
406        case "crs:CropLeft":
407        case "crs:CropBottom":
408        case "crs:CropRight":
409        case "crs:CropAngle":
410        case "crs:CropWidth":
411        case "crs:CropHeight":
412        case "crs:Exposure":
413        case "crs:GreenHue":
414        case "crs:GreenSaturation":
415        case "crs:HasCrop":
416        case "crs:HasSettings":
417        case "crs:LuminanceSmoothing":
418        case "crs:RawFileName":
419        case "crs:RedHue":
420        case "crs:RedSaturation":
421        case "crs:Saturation":
422        case "crs:Shadows":
423        case "crs:ShadowTint":
424        case "crs:Sharpness":
425        case "crs:Temperature":
426        case "crs:Tint":
427        case "crs:Version":
428        case "crs:VignetteAmount":
429        case "crs:VignetteMidpoint":
430        case "Iptc4xmpCore:CountryCode":
431        case "Iptc4xmpCore:Location":
432        case "Iptc4xmpCore:CiEmailWork":
433        case "Iptc4xmpCore:CiUrlWork":
434        case "Iptc4xmpCore:CiTelWork":
435        case "Iptc4xmpCore:CiAdrExtadr":
436        case "Iptc4xmpCore:CiAdrPcode":
437        case "Iptc4xmpCore:CiAdrCity":
438        case "Iptc4xmpCore:CiAdrCtry":
439          $returned=$value;
440          break;
441        case "Iptc4xmpCore:IntellectualGenre":
442          $returned=explode(":", $value);
443          break;
444        case "xmp:CreateDate":
445        case "xmp:ModifyDate":
446        case "xmp:MetadataDate":
447        case "tiff:DateTime":
448        case "photoshop:DateCreated":
449        case "Iptc4xmpCore:DateCreated":
450          $returned=ConvertData::toDateTime($value);
451          break;
452        case "dc:contributor" : // bag
453        case "dc:creator" : // seq
454        case "dc:date" : // seq
455        case "dc:description" : // alt
456        case "dc:language" : // bag
457        case "dc:publisher" : // bag
458        case "dc:relation" : // bag
459        case "dc:rights" : // alt
460        case "dc:subject" : // bag
461        case "dc:title" : // alt
462        case "dc:Type" : // bag
463        case "xmp:Advisory" : // bag
464        case "xmp:Identifier" : // bag
465        case "xmp:Thumbnails" : // alt
466        case "xmpRights:Owner" : // bag
467        case "xmpRights:UsageTerms" : // alt
468        case "xmpMM:History" : // seq
469        case "xmpMM:Versions" : // seq
470        case "xmpBJ:JobRef" : // bag
471        case "xmpTPg:Fonts" : // bag
472        case "xmpTPg:Colorants" : // seq
473        case "xmpTPg:PlateNames" : // seq
474        case "photoshop:SupplementalCategories" : // bag
475        case "crs:ToneCurve" : // seq
476        case "tiff:BitsPerSample" : // seq
477        case "tiff:YCbCrSubSampling" : // seq
478        case "tiff:TransferFunction" : // seq
479        case "tiff:WhitePoint" : // seq
480        case "tiff:PrimaryChromaticities" : // seq
481        case "tiff:YCbCrCoefficients" : // seq
482        case "tiff:ReferenceBlackWhite" : // seq
483        case "tiff:ImageDescription" : // alt
484        case "tiff:Copyright" : // alt
485        case "exif:ComponentsConfiguration" : // seq
486        case "exif:UserComment" : // alt
487        case "exif:ISOSpeedRatings" : // seq
488        case "exif:SubjectArea" : // seq
489        case "exif:SubjectLocation" : // seq
490          $returned=$value;
491          break;
492        case "Iptc4xmpCore:Scene": //bag
493          $tag=$this->tagDef->getTagById('Iptc4xmpCore:Scene');
494          $returned=$value;
495          foreach($returned['values'] as $key=>$val)
496          {
497            if(array_key_exists($val, $tag['tagValues.special']))
498              $returned['values'][$key]=$tag['tagValues.special'][$val];
499          }
500          break;
501        case "Iptc4xmpCore:SubjectCode": //bag
502          $returned=$value;
503          foreach($returned['values'] as $key=>$val)
504          {
505            $tmp=explode(":", $val);
506
507            $returned['values'][$key]=Array();
508
509            if(count($tmp)>=1)
510              $returned['values'][$key]['IPR']=$tmp[0];
511
512            if(count($tmp)>=2)
513              $returned['values'][$key]['subjectCode']=$tmp[1];
514
515            if(count($tmp)>=3)
516              $returned['values'][$key]['subjectName']=$tmp[2];
517
518            if(count($tmp)>=4)
519              $returned['values'][$key]['subjectMatterName']=$tmp[3];
520
521            if(count($tmp)>=5)
522              $returned['values'][$key]['subjectDetailName']=$tmp[4];
523          }
524          break;
525        default:
526          $returned="Not yet implemented; $name";
527          break;
528      }
529      return($returned);
530    }
531
532    /**
533     * this function convert the value from XMP 'exif' or XMP 'tiff' data by
534     * using an instance of a XmpTag2Exif object (using exif tag object allows
535     * not coding the same things twice)
536     */
537    private function xmp2Exif($tagName, $exifTag, $xmpValue)
538    {
539      switch($tagName)
540      {
541        /* integers */
542        case "tiff:ImageWidth":
543        case "tiff:ImageLength":
544        case "tiff:PhotometricInterpretation":
545        case "tiff:Orientation":
546        case "tiff:SamplesPerPixel":
547        case "tiff:PlanarConfiguration":
548        case "tiff:YCbCrSubSampling":
549        case "tiff:YCbCrPositioning":
550        case "tiff:ResolutionUnit":
551        case "exif:ColorSpace":
552        case "exif:PixelXDimension":
553        case "exif:PixelYDimension":
554        case "exif:MeteringMode":
555        case "exif:LightSource":
556        case "exif:FlashEnergy":
557        case "exif:FocalPlaneResolutionUnit":
558        case "exif:SensingMethod":
559        case "exif:FileSource":
560        case "exif:SceneType":
561        case "exif:CustomRendered":
562        case "exif:ExposureMode":
563        case "exif:Balance":
564        case "exif:FocalLengthIn35mmFilm":
565        case "exif:SceneCaptureType":
566        case "exif:GainControl":
567        case "exif:Contrast":
568        case "exif:Saturation":
569        case "exif:Sharpness":
570        case "exif:SubjectDistanceRange":
571        case "exif:GPSAltitudeRef":
572        case "exif:GPSDifferential":
573          $returned=(int)$xmpValue;
574          $type=ByteType::ULONG;
575          break;
576        /* specials */
577        case "tiff:BitsPerSample":
578        case "tiff:Compression":
579        case "tiff:TransferFunction":
580        case "tiff:WhitePoint":
581        case "tiff:PrimaryChromaticities":
582        case "tiff:YCbCrCoefficients":
583        case "tiff:ReferenceBlackWhite":
584        case "exif:ComponentsConfiguration":
585        case "exif:ExposureProgram":
586        case "exif:ISOSpeedRatings":
587        case "exif:OECF":
588        case "exif:Flash":
589        case "exif:SubjectArea":
590        case "exif:SpatialFrequencyResponse":
591        case "exif:SubjectLocation":
592        case "exif:CFAPattern":
593        case "exif:DeviceSettingDescription":
594        case "exif:GPSLatitude":
595        case "exif:GPSLongitude":
596        case "exif:GPSDestLatitude":
597        case "exif:GPSDestLongitude":
598          $returned=$xmpValue;
599          $type=ByteType::UNDEFINED;
600          break;
601        /* rational */
602        case "tiff:XResolution":
603        case "tiff:YResolution":
604        case "exif:CompressedBitsPerPixel":
605        case "exif:ExposureTime":
606        case "exif:FNumber":
607        case "exif:ShutterSpeedValue":
608        case "exif:ApertureValue":
609        case "exif:BrightnessValue":
610        case "exif:ExposureBiasValue":
611        case "exif:MaxApertureValue":
612        case "exif:SubjectDistance":
613        case "exif:FocalLength":
614        case "exif:FocalPlaneXResolution":
615        case "exif:FocalPlaneYResolution":
616        case "exif:ExposureIndex":
617        case "exif:DigitalZoomRatio":
618        case "exif:GPSAltitude":
619        case "exif:GPSDOP":
620        case "exif:GPSSpeed":
621        case "exif:GPSTrack":
622        case "exif:GPSImgDirection":
623        case "exif:GPSDestBearing":
624        case "exif:GPSDestDistance":
625          $computed=explode("/", $xmpValue);
626          $returned=Array((int)$computed[0], (int)$computed[1]);
627          $type=ByteType::URATIONAL;
628          break;
629        /* dates & texts */
630        case "tiff:DateTime":
631        case "tiff:ImageDescription":
632        case "tiff:Make":
633        case "tiff:Model":
634        case "tiff:Software":
635        case "tiff:Artist":
636        case "tiff:Copyright":
637        case "exif:ExifVersion":
638        case "exif:FlashpixVersion":
639        case "exif:UserComment":
640        case "exif:RelatedSoundFile":
641        case "exif:DateTimeOriginal":
642        case "exif:DateTimeDigitized":
643        case "exif:SpectralSensitivity":
644        case "exif:ImageUniqueID":
645        case "exif:GPSVersionID":
646        case "exif:GPSTimeStamp":
647        case "exif:GPSSatellites":
648        case "exif:GPSStatus":
649        case "exif:GPSMeasureMode":
650        case "exif:GPSSpeedRef":
651        case "exif:GPSTrackRef":
652        case "exif:GPSImgDirectionRef":
653        case "exif:GPSMapDatum":
654        case "exif:GPSDestBearingRef":
655        case "exif:GPSDestDistanceRef":
656        case "exif:GPSProcessingMethod":
657        case "exif:GPSAreaInformation":
658          $returned=$xmpValue;
659          $type=ByteType::ASCII;
660          break;
661        default:
662          $returned="Unknown tag: $tagName => $xmpValue";
663          break;
664      }
665      if(is_array($returned))
666      {
667        if(array_key_exists('type', $returned))
668        {
669          if($returned['type']=='alt')
670          {
671          }
672          else
673          {
674            foreach($returned['values'] as $key => $val)
675            {
676              $returned['values'][$key]['value']=$this->xmpTag2Exif->convert($exifTag, $val['value'], $type);
677            }
678          }
679        }
680        else
681        {
682          return($this->xmpTag2Exif->convert($exifTag, $returned, $type));
683        }
684      }
685      return($returned);
686    }
687
688  }
689
690  /**
691   * The XmpTag2Exif is derived from the IfdReader class
692   *
693   * This function provides the public function 'convert', allowing to convert
694   * 'exif' datas in 'human readable' values
695   *
696   */
697  class XmpTag2Exif extends IfdReader
698  {
699    public function convert($exifTag, $value, $type)
700    {
701      return($this->processSpecialTag($exifTag, $value, $type));
702    }
703  }
704
705?>
706
Note: See TracBrowser for help on using the repository browser.