source: extensions/AMetaData/JpegMetaData/Readers/JpegReader.class.php @ 15247

Last change on this file since 15247 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: 10.1 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 * | JpegMetaData - a PHP based Jpeg Metadata manager                      |
13 * +-----------------------------------------------------------------------+
14 * | Copyright(C) 2010  Grum - http://www.grum.fr                          |
15 * +-----------------------------------------------------------------------+
16 * | This program is free software; you can redistribute it and/or modify  |
17 * | it under the terms of the GNU General Public License as published by  |
18 * | the Free Software Foundation                                          |
19 * |                                                                       |
20 * | This program is distributed in the hope that it will be useful, but   |
21 * | WITHOUT ANY WARRANTY; without even the implied warranty of            |
22 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
23 * | General Public License for more details.                              |
24 * |                                                                       |
25 * | You should have received a copy of the GNU General Public License     |
26 * | along with this program; if not, write to the Free Software           |
27 * | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
28 * | USA.                                                                  |
29 * +-----------------------------------------------------------------------+
30 *
31 *
32 * -----------------------------------------------------------------------------
33 *
34 * The JpegReader is the class for reading a Jpeg file
35 *
36 * Datas provided by this class are less friendly than data provided by the
37 * JpegMetaData class, because here you have to know the Jpeg format to
38 * understand how to exploit datas
39 *
40 * -----------------------------------------------------------------------------
41 *
42 * .. Notes ..
43 *
44 * The JpegReader class use a dedicated AppMarkerSegmentReader class rather than
45 * the php "getimagesize()" function because the php function returns an
46 * associative array of markers like ("APP1" => "......", "APPx" => "......")
47 *
48 * Typically a jpeg file contains XMP data inside an APP1 marker, like the exif
49 * codes. So, when using the php function, APP1 with XMP values isn't present
50 * (only the first APP1 segment found and used for EXIF is present)
51 *
52 *
53 * A Jpeg file normally begins with the 2 bytes FFD8 (the SOI marker) and ends
54 * with the 2 bytes FFD9 (the EOI marker)
55 * Some file have extra data after the EOI marker ; for now, the class can't
56 * manage this kind of file.
57 *
58 * The class use the AppMarkerSegmentReader class to get the content of :
59 *  - APP1 marker  => Exif, XMP
60 *  - APP13 marker => Iptc
61 *
62 * This class provides theses public functions :
63 *  - load
64 *  - getFileName
65 *  - isLoaded
66 *  - isValid
67 *  - countAppMarkerSegments
68 *  - getAppMarkerSegments
69 *
70 *  Read the code for help
71 *
72 * -----------------------------------------------------------------------------
73 */
74
75  require_once(JPEG_METADATA_DIR."Readers/AppMarkerSegmentReader.class.php");
76
77  class JpegReader
78  {
79    /**
80     * start of image file tag
81     */
82    const JPEG_SOI = 0xFFD8;
83    /**
84     * end of image file tag
85     */
86    const JPEG_EOI = 0xFFD9;
87    /**
88     * start of scan tag
89     */
90    const JPEG_SOS = 0xFFDA;
91
92
93    private $fileName = "";
94    private $isLoaded = false;
95    private $isValid = false;
96    private $appMarkerSegmentReader = Array();
97
98    private $fileHandler = false;
99
100    /**
101     * the constructor need an optional filename
102     *
103     * if no filename is given, you can use the "load" function after the object
104     * is instancied
105     *
106     * @Param String $fileName (optional)
107     *
108     */
109    function __construct($fileName = "")
110    {
111      if($fileName!="") $this->load($fileName);
112    }
113
114    function __destruct()
115    {
116      unset($this->appMarkerSegmentReader);
117      unset($this->fileHandler);
118      unset($this->fileName);
119    }
120
121    /**
122     * load a file
123     *
124     * @Param String $fileName
125     *
126     */
127    public function load($fileName)
128    {
129      /*
130       * initialize values
131       *
132       * if the file isn't a valid file, theses properties will not be in an
133       * uncertain state
134       */
135      $this->isLoaded=false;
136      $this->fileName="";
137      $this->isValid = false;
138      $this->appMarkerSegmentReader=Array();
139
140      /*
141       * if the file exist, try to open it
142       * determine if the file is a valid JPEG file
143       *
144       * if the file is valid, read the markers and close the file
145       */
146      if(file_exists($fileName))
147      {
148        $this->fileName=$fileName;
149
150        $this->fileHandler = fopen($fileName, "r");
151        if($this->fileHandler)
152        {
153          $this->isValid = $this->isAValidFile();
154
155          if($this->isValid)
156          {
157            $this->readAppMarkerSegments();
158            $this->processSpecialAppMarkerSegments();
159            $this->isLoaded=true;
160          }
161
162          fclose($this->fileHandler);
163          return(true);
164        }
165        return(false);
166      }
167      return(false);
168    }
169
170    /**
171     *
172     * this function try to see if the given file is a valid jpg file (the file
173     * must begins with the SOI and ends with the EOI)
174     *
175     */
176    protected function isAValidFile()
177    {
178      fseek($this->fileHandler, -2, SEEK_END);
179      $header=fread($this->fileHandler, 2);
180      $haveEOI=(ConvertData::toUShort($header, BYTE_ORDER_BIG_ENDIAN) == self::JPEG_EOI);
181
182      // look if 2 first bytes of file are SOI
183      fseek($this->fileHandler, 0);
184      $header=fread($this->fileHandler, 2);
185      if(ConvertData::toUShort($header, BYTE_ORDER_BIG_ENDIAN) == self::JPEG_SOI)
186      {
187        //if file have EOI, it seems to be a valid JPEG file
188        if($haveEOI) return(true);
189
190        // otherwise, try to find SOS
191        while(!feof($this->fileHandler))
192        {
193          $header=ConvertData::toUShort(fread($this->fileHandler, 2), BYTE_ORDER_BIG_ENDIAN);
194
195          if($header==self::JPEG_EOI or $header==self::JPEG_SOS )
196          {
197            //seems to be a valid JPEG file
198            return(true);
199          }
200          elseif($header>>8==0xFF)
201          {
202            //seems to be a valid marker, jump to next marker...
203            $sizeBlock=ConvertData::toUShort(fread($this->fileHandler, 2), BYTE_ORDER_BIG_ENDIAN);
204            fseek($this->fileHandler, $sizeBlock-2, SEEK_CUR);
205          }
206          else
207          {
208            // not a marker, not e JPEG file
209            return(false);
210          }
211        }
212      }
213      return(false);
214    }
215
216    /**
217     *
218     * This function reads all the segment of the jpeg file
219     *
220     */
221    private function readAppMarkerSegments()
222    {
223      /*
224       * loop to read all markers
225       */
226      $offset=2;
227      while(true)
228      {
229        $marker=AppMarkerSegmentReader::read($this->fileHandler, $offset);
230        if($marker)
231        {
232          /*
233           * if there is a marker returned, push it on the markers array
234           */
235          $this->appMarkerSegmentReader[]=$marker;
236          $offset+=$marker->getLength();
237          unset($marker);
238        }
239        else
240        {
241          /*
242           * if there is no marker returned (end of file ?) stop markers loading
243           */
244          return(false);
245        }
246      }
247
248    }
249
250
251    /**
252     *
253     * This function process special APP marker segment
254     *
255     * At now, only the extendedXmp segment are managed
256     *
257     */
258    protected function processSpecialAppMarkerSegments()
259    {
260      /*
261       * process APP extendedXmp segment
262       *
263       * 1/ read all APP segment and find APP1/EXTENDEDXMP segments
264       * 2/ sort APP1/EXTENDEDXMP segments by UID+OFFSET
265       * 3/ merge segment data
266       * 4/ build a new AppMarkerSegmentReader (as an APP1_XMP segement)
267       */
268      $extendedXmp=Array();
269      foreach($this->appMarkerSegmentReader as $marker)
270      {
271        if($marker->getHeader()==AppMarkerSegmentReader::SEGMENT_APP1 and
272           $marker->getSubType()==AppMarkerSegmentReader::APP1_EXTENDEDXMP)
273        {
274          $extendedXmp[]=$marker->getData();
275        }
276      }
277      usort($extendedXmp, Array(&$this, "sortExtendedXmp"));
278
279      $xmp="";
280      foreach($extendedXmp as $marker)
281      {
282        $xmp.=$marker['data'];
283      }
284
285      $marker=new AppMarkerSegmentReader(
286        AppMarkerSegmentReader::SEGMENT_APP1,
287        0,
288        strlen($xmp),
289        AppMarkerSegmentReader::APP1_XMP,
290        "http://ns.adobe.com/xap/1.0/\x00".$xmp
291      );
292
293      if($marker)
294      {
295        /*
296         * if there is a marker returned, push it on the markers array
297         */
298        $this->appMarkerSegmentReader[]=$marker;
299        unset($marker);
300      }
301      unset($extendedXmp);
302      unset($xmp);
303    }
304
305    /**
306     * This function is used to sort the extendedXmp segments
307     */
308    protected function sortExtendedXmp($a, $b)
309    {
310      if (($a['uid'].$a['offset']) == ($b['uid'].$b['offset']))
311      {
312        return(0);
313      }
314      return((($a['uid'].$a['offset']) < ($b['uid'].$b['offset'])) ? -1 : 1);
315    }
316
317
318    /**
319     *
320     * returns the filename given to the object
321     *
322     */
323    public function getFileName()
324    {
325      return($this->fileName);
326    }
327
328    /**
329     *
330     * returns true if the given jpeg file has been succesfully loaded
331     *
332     */
333    public function isLoaded()
334    {
335      return($this->isLoaded);
336    }
337
338    /**
339     *
340     * returns true if the given jpeg file is a valid file
341     *
342     */
343    public function isValid()
344    {
345      return($this->isValid);
346    }
347
348    /**
349     *
350     * returns the number od segments loaded
351     *
352     */
353    public function countAppMarkerSegments()
354    {
355      return(count($this->appMarkerSegmentReader));
356    }
357
358    /**
359     *
360     * returns an array of AppMarkerSegments
361     *
362     * see the AppMarkerSegmentsReader.class.php for more informations
363     *
364     */
365    public function getAppMarkerSegments()
366    {
367      return($this->appMarkerSegmentReader);
368    }
369
370  }
371
372
373 ?>
Note: See TracBrowser for help on using the repository browser.