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

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

JpegMetadata => Implement multiple IPTC keywords managements & manage ISO-8859-1 to UTF8 charset
bug:1859, bug:1861

  • Property svn:executable set to *
File size: 12.4 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       * build a special 'keywords' tag made as an array from all iptc 'keywords' (0x0219) tags found
220       */
221      $keywordsTag=null;
222      foreach($this->entries as $key => $tag)
223      {
224        $this->setTagProperties($tag);
225        if($tag->getId()==0x0219)
226        {
227          if(is_null($keywordsTag))
228          {
229            $keywordsTag=new Tag(
230              0x0219,
231              array($tag->getValue()),
232              $tag->getName(),
233              array($tag->getLabel()),
234              "",
235              $tag->isKnown(),
236              $tag->isImplemented(),
237              $tag->isTranslatable(),
238              $tag->getSchema()
239            );
240          }
241          else
242          {
243            $keywordsTag->setValue(array_merge($keywordsTag->getValue(), array($tag->getValue())));
244            $keywordsTag->setLabel(array_merge($keywordsTag->getLabel(), array($tag->getLabel())));
245          }
246        }
247      }
248      if(!is_null($keywordsTag))
249      {
250        /*
251         * IPTC 'keywords' is stored like XMP 'xmp.dc:subject' (as a 'seq')
252         */
253        $keywordsTag->setValue(
254          array(
255            'type' => 'seq',
256            'values' => $keywordsTag->getValue()
257          )
258        );
259
260        $keywordsTag->setLabel(
261          array(
262            'type' => 'seq',
263            'values' => $keywordsTag->getLabel()
264          )
265        );
266        $this->entries[]=$keywordsTag;
267        unset($keywordsTag);
268      }
269    }
270
271    /**
272     * Interprets the tag values into 'human readable values'
273     *
274     * @param Tag $entry
275     */
276    private function setTagProperties($tag)
277    {
278      /*
279       * if the given tag id is defined, analyzing its values
280       */
281      if($this->tagDef->tagIdExists($tag->getId()))
282      {
283        $tagProperties=$this->tagDef->getTagById($tag->getId());
284
285        $tag->setKnown(true);
286        $tag->setName($tagProperties['tagName']);
287        $tag->setImplemented($tagProperties['implemented']);
288        $tag->setTranslatable($tagProperties['translatable']);
289        $tag->setSchema($this->schema);
290
291        /*
292         * if there is values defined for the tag, analyze it
293         */
294        if(array_key_exists('tagValues', $tagProperties))
295        {
296          if(array_key_exists($tag->getValue(), $tagProperties['tagValues']))
297          {
298            $tag->setLabel($tagProperties['tagValues'][$tag->getValue()]);
299          }
300          else
301          {
302            $tag->setLabel("[unknow value 0x".sprintf("%04x", $tag->getValue())."]");
303          }
304        }
305        else
306        {
307          /*
308           * there is no values defined for the tag, analyzing it with dedicated
309           * function
310           */
311          $tag->setLabel($this->processSpecialTag($tag->getId(), $tag->getValue(), 0, 0));
312        }
313      }
314    }
315
316    /**
317     * this function can be overrided to process special tags
318     */
319    protected function processSpecialTag($tagId, $values, $type, $valuesOffset=0)
320    {
321      switch($tagId)
322      {
323        /*
324         * Tags managed
325         */
326        case 0x0105: // 2:05  - Destination
327        case 0x011E: // 1:30  - Service Identifier
328        case 0x0128: // 1:40  - Envelope Number
329        case 0x0132: // 1:50  - Product I.D.
330        case 0x0205: // 2:05  - Title
331        case 0x0207: // 2:07  - Edit Status
332        case 0x020F: // 2:15  - Category
333        case 0x0214: // 2:20  - Supplemental Category
334        case 0x0216: // 2:22  - Fixture Identifier
335        case 0x0219: // 2:25  - Keywords
336        case 0x021A: // 2:25  - Content Location Code
337        case 0x021B: // 2:25  - Content Location Name
338        case 0x0228: // 2:40  - Special Instructions
339        case 0x0241: // 2:65  - Originating Program
340        case 0x0246: // 2:70  - Program Version
341        case 0x0250: // 2:80  - By-line
342        case 0x0255: // 2:80  - By-line Title
343        case 0x025A: // 2:90  - City
344        case 0x025C: // 2:92  - Sublocation
345        case 0x025F: // 2:95  - Province/State
346        case 0x0264: // 2:100 - Country Code
347        case 0x0265: // 2:101 - Country
348        case 0x0267: // 2:103 - Original Transmission Reference
349        case 0x0269: // 2:105 - Headline
350        case 0x026E: // 2:110 - credit
351        case 0x0273: // 2:115 - source
352        case 0x0274: // 2:116 - Copyright Notice
353        case 0x0276: // 2:118 - Contact
354        case 0x0278: // 2:120 - Description
355        case 0x027A: // 2:122 - Writer/Editor
356        case 0x0287: // 2:150 - Language Identifier
357          $returned=utf8_encode($values);
358          break;
359        case 0x0114: // 1:20  - File Format
360          $tag=$this->tagDef->getTagById(0x0114);
361          $returned=$tag['tagValues.special'][ConvertData::toUShort($values, BYTE_ORDER_BIG_ENDIAN)];
362          unset($tag);
363          break;
364        case 0x0203: // 2:03  - Object Type Reference
365        case 0x0204: // 2:04  - Intellectual Genre
366          $returned=explode(":", $values);
367          break;
368        case 0x0146: // 1:70  - Date Sent
369        case 0x021E: // 2:30  - Release Date
370        case 0x0225: // 2:37  - Expiration Date
371        case 0x0237: // 2:55  - Date Created
372        case 0x023E: // 2:62  - Digital Creation Date
373          $returned=ConvertData::toDateTime($values);
374          break;
375        case 0x0150: // 1:80  - Time Sent
376        case 0x0223: // 2:35  - Release Time
377        case 0x0226: // 2:38  - Expiration Time
378        case 0x023C: // 2:60  - Time Created
379        case 0x023F: // 2:63  - Digital Creation Time
380          $returned=ConvertData::toDateTime("00010101T".$values);
381          break;
382        /*
383         * Tags not managed
384         */
385        default:
386          $returned="Not yet implemented;".ConvertData::toHexDump($tagId, ByteType::USHORT)." => ".ConvertData::toHexDump($values, $type, 64)." [$values]";
387          break;
388      }
389      return($returned);
390    }
391  }
392
393
394
395
396?>
Note: See TracBrowser for help on using the repository browser.