source: extensions/AMetaData/JpegMetaData/JpegMetaData.class.php @ 6729

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

feature:1777

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