source: extensions/AMetaData/JpegMetaData/Readers/IptcReader.class.php @ 11607

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

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

  • Property svn:executable set to *
File size: 14.8 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 IptcReader class is the dedicated class to read IPTC from the APP13
36 * segment
37 *
38 * The APP13 segment (the 'photoshop' segment) contain 8BIM blocks.
39 * The IPTC are stored inside the specific 0x0404 block. If there is more than
40 * one 8BIM 0x0404 block, the IptcReader reads all blocks and merge IPTC tags.
41 *
42 * =======> See HeightBIMReader.class.php to know more about 8BIM blocks <======
43 *
44 * -----------------------------------------------------------------------------
45 *
46 * .. Notes ..
47 *
48 * The IptcReader class is derived from the GenericReader class.
49 *
50 * ======> See GenericReader.class.php to know more about common methods <======
51 *
52 * This class provides theses public functions :
53 *  - optimizeDateTime
54 *
55 * -----------------------------------------------------------------------------
56 */
57
58  require_once(JPEG_METADATA_DIR."Common/Data.class.php");
59  require_once(JPEG_METADATA_DIR."Readers/HeightBIMReader.class.php");
60  require_once(JPEG_METADATA_DIR."TagDefinitions/IptcTags.class.php");
61
62
63  class IptcReader extends GenericReader
64  {
65    const HEADER_1 = "Photoshop 3.0\x00";
66    const HEADER_2 = "Adobe_Photoshop2.5:\x00";
67    protected $schema = Schemas::IPTC;
68
69    private $header = "";
70
71    /**
72     * The constructor needs, like the ancestor, the datas to be parsed
73     *
74     * @param String $data
75     */
76    function __construct($data)
77    {
78      parent::__construct($data);
79
80      /*
81       * read the header
82       * if header is a valid header, read entries
83       */
84      if($this->readHeader())
85      {
86        $this->initializeEntries();
87      }
88    }
89
90    function __destruct()
91    {
92      parent::__destruct();
93    }
94
95
96    public function toString()
97    {
98      $returned="IPTC ; NbEntries: ".sprintf("%02d", $this->nbEntries);
99      return($returned);
100    }
101
102    /**
103     * All IPTC Date & Time are separated into distinct tags
104     * this function complete "date" tags with the associated "time" tags, and
105     * delete the "time" tags
106     *
107     * Example :
108     *  0x146 "Date Sent" = 2009/12/24 => tag value : 2009/12/24 00:00:00
109     *  0x150 "Time Sent" = 19:43:28   => tag value : 0001/01/01 19:43:28
110     *
111     * Optimize Date merge date & time => tag value : 2009/12/24 19:43:28
112     *
113     */
114    public function optimizeDateTime()
115    {
116      $assoc=Array(
117        Array(0x0146, 0x0150),
118        Array(0x021E, 0x0223),
119        Array(0x0225, 0x0226),
120        Array(0x0237, 0x023C),
121        Array(0x023E, 0x023F),
122      );
123
124      foreach($assoc as $val)
125      {
126        $tagD=$this->getTagIndexById($val[0]);
127        $tagT=$this->getTagIndexById($val[1]);
128
129        if($tagD>-1 and $tagT>-1)
130        {
131          /*
132           * can't use the timestamp function because not compatible with php < 5.3
133           */
134          if($this->entries[$tagD]->getLabel() instanceof DateTime and
135             $this->entries[$tagT]->getLabel() instanceof DateTime)
136          {
137            $this->entries[$tagD]->getLabel()->setTime(
138              (int)$this->entries[$tagT]->getLabel()->format("H"),
139              (int)$this->entries[$tagT]->getLabel()->format("i"),
140              (int)$this->entries[$tagT]->getLabel()->format("s")
141            );
142          }
143          array_splice($this->entries, $tagT, 1);
144        }
145        unset($tagD);
146        unset($tagT);
147      }
148    }
149
150
151    /**
152     * initialize the definition for IPTC tags
153     */
154    protected function initializeTagDef()
155    {
156      $this->tagDef = new IptcTags();
157    }
158
159    /**
160     * read the header of the APP13 segment, and try to determinate wich kind of
161     * data are stored
162     *
163     * at now, only "Photoshop 3.0" data structure is known
164     * the "Adobe_Photoshop2.5" data structure is not recognized yet
165     *
166     * @return Boolean : true if the header is known
167     */
168    private function readHeader()
169    {
170      $this->data->seek();
171      $header=$this->data->readASCII(strlen(self::HEADER_1));
172      if($header==self::HEADER_1)
173      {
174        $this->header=$header;
175        return(true);
176      }
177
178      $this->data->seek();
179      $header=$this->data->readASCII(strlen(self::HEADER_2));
180      if($header==self::HEADER_2)
181      {
182        $this->header=$header;
183        /*
184         * structure from an HEADER_2 is not known....
185         */
186        return(false);
187      }
188
189      return(false);
190    }
191
192    /**
193     * reads all the 8BIM blocks of the segment. If the 8BIM block is an IPTC
194     * block, read all the IPTC entries and set the Tag properties
195     *
196     * An entry is a Tag object
197     *
198     * Add the entry to the entries array
199     *
200     */
201    protected function initializeEntries()
202    {
203      $blocks=explode("8BIM", $this->data->readASCII());
204      foreach($blocks as $key=> $val)
205      {
206        $block=new HeightBIMReader("8BIM".$val);
207        if($block->isValid())
208        {
209          /* merge entries from all 8BIM blocks */
210          $this->entries=array_merge($this->entries, $block->getTags());
211        }
212        unset($block);
213      }
214      unset($blocks);
215
216
217      /* for each entries, convert value to human readable tag value
218       *
219       * repeatable values are stored in arrays
220       *
221       * for Subject Reference tags (0x020C), made derived tags (0x020Cnn)
222       */
223      $repeatableTags=array();
224      foreach($this->entries as $key => $tag)
225      {
226        $this->setTagProperties($tag);
227
228        $list=array();
229
230        if($tag->getId()==0x020C)
231        {
232          $tmpValues=explode(':', $tag->getValue());
233          $tmpLabels=explode(':', $tag->getLabel());
234
235          $list=array(
236            array(
237              'id' => 0x020C,
238              'value' => $tag->getValue(),
239              'label' => $tag->getLabel(),
240            ),
241            array(
242              'id' => 0x020C00,
243              'value' => isset($tmpValues[0])?$tmpValues[0]:'',
244              'label' => isset($tmpLabels[0])?$tmpLabels[0]:'',
245            ),
246            array(
247              'id' => 0x020C01,
248              'value' => isset($tmpValues[1])?$tmpValues[1]:'',
249              'label' => isset($tmpLabels[1])?$tmpLabels[1]:'',
250            ),
251            array(
252              'id' => 0x020C02,
253              'value' => isset($tmpValues[2])?$tmpValues[2]:'',
254              'label' => isset($tmpLabels[2])?$tmpLabels[2]:'',
255            ),
256            array(
257              'id' => 0x020C03,
258              'value' => isset($tmpValues[3])?$tmpValues[3]:'',
259              'label' => isset($tmpLabels[3])?$tmpLabels[3]:'',
260            ),
261            array(
262              'id' => 0x020C04,
263              'value' => isset($tmpValues[4])?$tmpValues[4]:'',
264              'label' => isset($tmpLabels[4])?$tmpLabels[4]:'',
265            )
266          );
267        }
268        else
269        {
270          $list=array(
271            array(
272              'id' => $tag->getId(),
273              'value' => $tag->getValue(),
274              'label' => $tag->getLabel(),
275            )
276          );
277        }
278
279
280        foreach($list as $tagItem)
281        {
282          $tagDef=$this->tagDef->getTagById($tagItem['id']);
283
284          if($tagDef['repeatable'])
285          {
286            if(!array_key_exists($tagItem['id'], $repeatableTags))
287            {
288              $repeatableTags[$tagItem['id']]=new Tag(
289                $tagItem['id'],
290                array($tagItem['value']),
291                $tagDef['tagName'],
292                array($tagItem['label']),
293                "",
294                $tag->isKnown(),
295                $tagDef['implemented'],
296                $tagDef['translatable'],
297                $tag->getSchema()
298              );
299            }
300            else
301            {
302              $repeatableTags[$tagItem['id']]->setValue(array_merge($repeatableTags[$tagItem['id']]->getValue(), array($tagItem['value'])));
303              $repeatableTags[$tagItem['id']]->setLabel(array_merge($repeatableTags[$tagItem['id']]->getLabel(), array($tagItem['label'])));
304            }
305          }
306          unset($tagDef);
307        }
308        unset($tagId);
309      }
310      foreach($repeatableTags as $key => $tag)
311      {
312        /*
313         * IPTC 'keywords' is stored like XMP 'xmp.dc:subject' (as a 'seq')
314         */
315        $repeatableTags[$key]->setValue(
316          array(
317            'type' => 'seq',
318            'values' => $repeatableTags[$key]->getValue()
319          )
320        );
321
322        $repeatableTags[$key]->setLabel(
323          array(
324            'type' => 'seq',
325            'values' => $repeatableTags[$key]->getLabel()
326          )
327        );
328        $this->entries[]=$repeatableTags[$key];
329        unset($repeatableTags[$key]);
330      }
331      unset($repeatableTags);
332    }
333
334    /**
335     * Interprets the tag values into 'human readable values'
336     *
337     * @param Tag $entry
338     */
339    private function setTagProperties($tag)
340    {
341      /*
342       * if the given tag id is defined, analyzing its values
343       */
344      if($this->tagDef->tagIdExists($tag->getId()))
345      {
346        $tagProperties=$this->tagDef->getTagById($tag->getId());
347
348        $tag->setKnown(true);
349        $tag->setName($tagProperties['tagName']);
350        $tag->setImplemented($tagProperties['implemented']);
351        $tag->setTranslatable($tagProperties['translatable']);
352        $tag->setSchema($this->schema);
353
354        /*
355         * if there is values defined for the tag, analyze it
356         */
357        if(array_key_exists('tagValues', $tagProperties))
358        {
359          if(array_key_exists($tag->getValue(), $tagProperties['tagValues']))
360          {
361            $tag->setLabel($tagProperties['tagValues'][$tag->getValue()]);
362          }
363          else
364          {
365            $tag->setLabel("[unknow value 0x".sprintf("%04x", $tag->getValue())."]");
366          }
367        }
368        else
369        {
370          /*
371           * there is no values defined for the tag, analyzing it with dedicated
372           * function
373           */
374          $tag->setLabel($this->processSpecialTag($tag->getId(), $tag->getValue(), 0, 0));
375        }
376      }
377    }
378
379    /**
380     * this function can be overrided to process special tags
381     */
382    protected function processSpecialTag($tagId, $values, $type, $valuesOffset=0)
383    {
384      switch($tagId)
385      {
386        /*
387         * Tags managed
388         */
389        case 0x0105: // 2:05  - Destination
390        case 0x011E: // 1:30  - Service Identifier
391        case 0x0128: // 1:40  - Envelope Number
392        case 0x0132: // 1:50  - Product I.D.
393        case 0x0205: // 2:05  - Title
394        case 0x0207: // 2:07  - Edit Status
395        case 0x020C: // 2:12 - Subject Reference
396        case 0x020F: // 2:15  - Category
397        case 0x0214: // 2:20  - Supplemental Category
398        case 0x0216: // 2:22  - Fixture Identifier
399        case 0x0219: // 2:25  - Keywords
400        case 0x021A: // 2:25  - Content Location Code
401        case 0x021B: // 2:25  - Content Location Name
402        case 0x0228: // 2:40  - Special Instructions
403        case 0x0241: // 2:65  - Originating Program
404        case 0x0246: // 2:70  - Program Version
405        case 0x0250: // 2:80  - By-line
406        case 0x0255: // 2:80  - By-line Title
407        case 0x025A: // 2:90  - City
408        case 0x025C: // 2:92  - Sublocation
409        case 0x025F: // 2:95  - Province/State
410        case 0x0264: // 2:100 - Country Code
411        case 0x0265: // 2:101 - Country
412        case 0x0267: // 2:103 - Original Transmission Reference
413        case 0x0269: // 2:105 - Headline
414        case 0x026E: // 2:110 - credit
415        case 0x0273: // 2:115 - source
416        case 0x0274: // 2:116 - Copyright Notice
417        case 0x0276: // 2:118 - Contact
418        case 0x0278: // 2:120 - Description
419        case 0x027A: // 2:122 - Writer/Editor
420        case 0x0287: // 2:150 - Language Identifier
421          $returned=utf8_encode($values);
422          break;
423        case 0x0114: // 1:20  - File Format
424          $tag=$this->tagDef->getTagById(0x0114);
425          $tmpValue=ConvertData::toUShort($values, BYTE_ORDER_BIG_ENDIAN);
426          if(array_key_exists($tmpValue, $tag['tagValues.special']))
427          {
428            $returned=$tag['tagValues.special'][$tmpValue];
429          }
430          else
431          {
432            $returned='Unknown file format : '.ConvertData::toHexDump($tmpValue, ByteType::USHORT);
433          }
434          unset($tag);
435          break;
436        case 0x0203: // 2:03  - Object Type Reference
437        case 0x0204: // 2:04  - Intellectual Genre
438          $returned=explode(":", $values);
439          break;
440        case 0x0146: // 1:70  - Date Sent
441        case 0x021E: // 2:30  - Release Date
442        case 0x0225: // 2:37  - Expiration Date
443        case 0x0237: // 2:55  - Date Created
444        case 0x023E: // 2:62  - Digital Creation Date
445          $returned=ConvertData::toDateTime($values);
446          break;
447        case 0x0150: // 1:80  - Time Sent
448        case 0x0223: // 2:35  - Release Time
449        case 0x0226: // 2:38  - Expiration Time
450        case 0x023C: // 2:60  - Time Created
451        case 0x023F: // 2:63  - Digital Creation Time
452          $returned=ConvertData::toDateTime("00010101T".$values);
453          break;
454        /*
455         * Tags not managed
456         */
457        default:
458          $returned="Not yet implemented;".ConvertData::toHexDump($tagId, ByteType::USHORT)." => ".ConvertData::toHexDump($values, $type, 64)." [$values]";
459          break;
460      }
461      return($returned);
462    }
463  }
464
465
466
467
468?>
Note: See TracBrowser for help on using the repository browser.