source: extensions/AMetaData/JpegMetaData/Readers/CanonReader.class.php @ 13677

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

fix bug:1992 - On Canon file, debug informations are displayed

  • Property svn:executable set to *
File size: 23.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 * +-----------------------------------------------------------------------+
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 CanonReader class is the dedicated to read the specific Canon tags
36 *
37 * ====> See MakerNotesReader.class.php to know more about the structure <======
38 *
39 * -----------------------------------------------------------------------------
40 *
41 * .. Notes ..
42 *
43 *
44 * The CanonReader class is derived from the MakerNotesReader class.
45 *
46 * =====> See MakerNotesReader.class.php to know more about common methods <====
47 *
48 * -----------------------------------------------------------------------------
49 */
50
51
52
53  require_once(JPEG_METADATA_DIR."TagDefinitions/CanonTags.class.php");
54  require_once(JPEG_METADATA_DIR."Readers/MakerNotesReader.class.php");
55
56  class CanonReader extends MakerNotesReader
57  {
58    protected $schema = Schemas::EXIF;
59
60    /**
61     * The constructor needs, like the ancestor, the datas to be parsed
62     *
63     * Some datas are offset on extra data, and this offset can be (some time)
64     * absolute inside the IFD, or relative. So, the offset of the IFD structure
65     * is needed
66     *
67     * The byte order can be different from the TIFF byte order !
68     *
69     * The constructor need the maker signature (see the MakerNotesSignatures
70     * class for a list of known signatures)
71     *
72     * @param String $data
73     * @param ULong $offset : offset of IFD block in the jpeg file
74     * @param String $byteOrder
75     * @param String $makerSignature :
76     */
77    function __construct($data, $offset, $byteOrder, $makerSignature)
78    {
79      /*
80       * Canon don't have signatures in his maker note, starting directly with
81       * the number of entries
82       */
83      $this->maker = MAKER_CANON;
84      $this->header = "";
85      $this->headerSize = 0;
86
87      parent::__construct($data, $offset, $byteOrder);
88    }
89
90    function __destruct()
91    {
92      parent::__destruct();
93    }
94
95
96    /**
97     * initialize the definition for Pentax exif tags
98     */
99    protected function initializeTagDef()
100    {
101      $this->tagDef = new CanonTags();
102    }
103
104    /**
105     * skip the IFD header
106     */
107    protected function skipHeader($headerSize=0)
108    {
109      parent::skipHeader($headerSize);
110    }
111
112    /**
113     * this function do the interpretation of specials tags
114     *
115     * the function return the interpreted value for the tag
116     *
117     * @param $tagId             : the id of the tag
118     * @param $values            : 'raw' value to be interpreted
119     * @param UByte $type        : if needed (for IFD structure) the type of data
120     * @param ULong $valueOffset : if needed, the offset of data in the jpeg file
121     * @return String or Array or DateTime or Integer or Float...
122     */
123    protected function processSpecialTag($tagId, $values, $type, $valuesOffset=0)
124    {
125      switch($tagId)
126      {
127        case 0x0001: // "CanonImageType"
128          $this->processSubTag0x0001($values);
129          $returned=$values;
130          break;
131        case 0x0004: // "CanonShotInfo"
132          $this->processSubTag0x0004($values);
133          $returned=$values;
134          break;
135        case 0x0006: // "CanonImageType"
136        case 0x0007: // "CanonFirmwareVersion"
137        case 0x0009: // "OwnerName"
138        case 0x0095: // "LensModel"
139        case 0x0096: // "InternalSerialNumber"
140          /*
141           * null terminated strings
142           */
143          $returned=ConvertData::toStrings($values);
144          break;
145        case 0x000c: // "SerialNumber"
146          $returned=$values;
147          break;
148        case 0x000d: // "CanonCameraInfo"
149          $returned=$this->processSubTag0x000d($values);
150          break;
151        case 0x0010: // "CanonModelID"
152          $tag=$this->tagDef->getTagById(0x0010);
153          $returned=$tag['tagValues.special'][sprintf("0x%08x", $values)];
154          unset($tag);
155          break;
156        case 0x0015: // "SerialNumberFormat"
157          $tag=$this->tagDef->getTagById(0x0015);
158          $returned=$tag['tagValues.special'][sprintf("0x%08x", $values)];
159          unset($tag);
160          break;
161        default:
162          $returned="Not yet implemented;".ConvertData::toHexDump($tagId, ByteType::USHORT)." => ".ConvertData::toHexDump($values, $type);
163          break;
164      }
165      return($returned);
166    }
167
168    /**
169     * this function process the subtag of the 0x0001 "CanonCameraSettings" tag
170     *
171     * @param Boolean $add : if set to false, the function return the tag value
172     *                       otherwise the function adds the tag
173     */
174    protected function processSubTag0x0001($values, $add=true)
175    {
176      foreach($values as $key => $val)
177      {
178        $tagDef=$this->tagDef->getTagById("0x0001.$key");
179
180        if(is_array($tagDef))
181        {
182          // make a fake IFDEntry
183          $entry=new IfdEntryReader("\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\x01\x00".chr($key), $this->byteOrder, "", 0, null);
184
185          $entry->getTag()->setId("0x0001.$key");
186          $entry->getTag()->setName($tagDef['tagName']);
187          $entry->getTag()->setValue($val);
188          $entry->getTag()->setKnown(true);
189          $entry->getTag()->setImplemented($tagDef['implemented']);
190          $entry->getTag()->setTranslatable($tagDef['translatable']);
191          $entry->getTag()->setSchema($this->schema);
192
193          if(array_key_exists('tagValues', $tagDef))
194          {
195            if(array_key_exists($val, $tagDef['tagValues']))
196            {
197              $returned=$tagDef['tagValues'][$val];
198            }
199            else
200            {
201              $returned="unknown (".$val.")";
202            }
203          }
204          else
205          {
206            switch($key)
207            {
208              case 2: // SelfTimer
209                if($val==0)
210                {
211                  $returned=Array("Off");
212                }
213                else
214                {
215                  $returned=Array((($val & 0xfff) / 10).' s');
216                  if($val & 0x4000)
217                    $returned[]="Custom";
218                }
219                break;
220              case 22: // LensType
221                /* in most case, with one Id we have one lens
222                 * in some case, with one Id we can have more than one lens
223                 * in this case, we made a $focal var like :
224                 *             "90 mm"
225                 *             "28-300mm"
226                 * and try to find a lens with this properties
227                 */
228                if(array_key_exists($val, $tagDef['tagValues.special']))
229                {
230                  $lens=$tagDef['tagValues.special'][$val];
231
232                  if(is_array($lens))
233                  {
234                    $focalUnit=(array_key_exists(25, $values))?$values[25]:1;
235                    $FocalShort=(array_key_exists(24, $values) && ($focalUnit!=0))?$values[24]/$focalUnit:0;
236                    $FocalLong=(array_key_exists(23, $values) && ($focalUnit!=0))?$values[23]/$focalUnit:0;
237                    $focal=(($FocalShort==$FocalLong or $FocalLong==0)?$FocalShort:$FocalShort."-".$FocalLong)."mm";
238
239                    foreach($lens as $name)
240                    {
241                      if(preg_match("/.*".$focal.".*/i", $name))
242                        $returned=$name;
243                    }
244                  }
245                  else
246                  {
247                    $returned=$lens;
248                  }
249                }
250                else
251                {
252                  $returned=$tagDef['tagValues.special'][0xffff];
253                }
254
255                break;
256              case 23: // LongFocal
257              case 24: // ShortFocal
258                /* note : the values seems to be divided by the FocalUnit
259                 * (subTag #25)
260                 */
261                if(array_key_exists(25, $values))
262                {
263                  $focalUnit=$values[25];
264                }
265                else
266                {
267                  $focalUnit=1;
268                }
269                if($focalUnit==0) $focalUnit=1;
270
271                $returned=ConvertData::toFocalLength($val/$focalUnit);
272                break;
273              case 25: // FocalUnit
274                $returned=$val."/mm";
275                break;
276              case 26: // MaxAperture
277              case 27: // MinAperture
278                $returned=ConvertData::toFNumber(round(exp($this->canonEv($val)*log(2)/2),1));
279                break;
280              case 29: // FlashBits
281                $returned=Array();
282                foreach($tagDef['tagValues.special'] as $key => $name)
283                {
284                  if(($key & $val) == $key)
285                  {
286                    $returned[]=$name;
287                  }
288                }
289                break;
290              default:
291                $returned="not yet implemented";
292                break;
293            }
294          }
295
296          if($add)
297          {
298            $entry->getTag()->setLabel($returned);
299            $this->entries[]=$entry;
300          }
301          else
302          {
303            // only return the value for the asked tag
304            unset($entry);
305            unset($tagDef);
306            return($returned);
307          }
308
309          unset($entry);
310        }
311        unset($tagDef);
312      }
313    }
314
315    /**
316     * this function process the subtag of the 0x0004 "CanonShotInfo" tag
317     *
318     * @param Boolean $add : if set to false, the function return the tag value
319     *                       otherwise the function adds the tag
320     */
321    protected function processSubTag0x0004($values, $add=true)
322    {
323      foreach($values as $key => $val)
324      {
325        $tagDef=$this->tagDef->getTagById("0x0004.$key");
326
327        if(is_array($tagDef))
328        {
329          // make a fake IFDEntry
330          $entry=new IfdEntryReader("\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\x04\x00".chr($key), $this->byteOrder, "", 0, null);
331
332          $entry->getTag()->setId("0x0004.$key");
333          $entry->getTag()->setName($tagDef['tagName']);
334          $entry->getTag()->setValue($val);
335          $entry->getTag()->setKnown(true);
336          $entry->getTag()->setImplemented($tagDef['implemented']);
337          $entry->getTag()->setTranslatable($tagDef['translatable']);
338          $entry->getTag()->setSchema($this->schema);
339
340          if(array_key_exists('tagValues', $tagDef))
341          {
342            if(array_key_exists($val, $tagDef['tagValues']))
343            {
344              $returned=$tagDef['tagValues'][$val];
345            }
346            else
347            {
348              $returned="unknown (".$val.")";
349            }
350          }
351          else
352          {
353            switch($key)
354            {
355              case 1: // AutoISO
356                $returned=round(exp($val/32*log(2))*100,0);
357                break;
358              case 2: // BaseISO
359                $returned=exp($this->canonEv($val) * log(2)) * 100 / 32;
360                break;
361              case 4: // TargetAperture
362              case 21: // FNumber
363                $returned=ConvertData::toFNumber(round(exp($this->canonEv($val)*log(2)/2), 1));
364                break;
365              case 5: // TargetExposureTime
366                $returned=ConvertData::toExposureTime(exp(-$this->CanonEv($val)*log(2)));
367                break;
368              case 6: // ExposureCompensation
369              case 15: // FlashExposureComp
370              case 17: // AEBBracketValue
371                $returned=ConvertData::$this->CanonEv($val);
372                break;
373              case 9: // SlowShutter
374                $returned=$val;
375                break;
376              case 13: // FlashGuideNumber
377                $returned=$val/32;
378                break;
379              case 23: // MeasuredEV2
380                $returned=$val/8-6;
381                break;
382              case 24: // BulbDuration
383              case 29: // SelfTimer2
384                $returned=$val/10;
385                break;
386              default:
387                $returned="not yet implemented";
388                break;
389            }
390          }
391
392          if($add)
393          {
394            $entry->getTag()->setLabel($returned);
395            $this->entries[]=$entry;
396          }
397          else
398          {
399            // only return the value for the asked tag
400            unset($entry);
401            unset($tagDef);
402            return($returned);
403          }
404
405          unset($entry);
406        }
407        unset($tagDef);
408      }
409    }
410
411    /**
412     * this function process the subtag of the 0x000d "CanonCameraInfo" tag
413     *
414     * @param Boolean $add : if set to false, the function return the tag value
415     *                       otherwise the function adds the tag
416     */
417    protected function processSubTag0x000d($values, $add=true)
418    {
419      $name=GlobalTags::getExifMaker();
420
421      if(preg_match("/\b1DS?$/", $name))
422      {
423
424      }/*
425      elseif(preg_match("/\b1Ds? Mark II$/", $name))
426      {
427
428      }
429      elseif(preg_match("/\b1Ds? Mark II N$/", $name))
430      {
431
432      }
433      elseif(preg_match("/\b1Ds? Mark III$/", $name))
434      {
435
436      }
437      elseif(preg_match("/EOS 5D$/", $name))
438      {
439
440      }
441      elseif(preg_match("/EOS 5D Mark II$/", $name))
442      {
443
444      }
445      elseif(preg_match("/EOS 7D$/", $name))
446      {
447
448      }*/
449      elseif(preg_match("/.*\b1D Mark IV/i", $name))
450      {
451        $returned=$this->processSubTag0x000d_1DMarkIV($values, $add);
452      }
453      elseif(preg_match("/.*EOS 40D/i", $name))
454      {
455        $returned=$this->processSubTag0x000d_40D($values, $add);
456      }/*
457      elseif(preg_match("/EOS 50D$/", $name))
458      {
459
460      }
461      elseif(preg_match("/\b(450D|REBEL XSi|Kiss X2)\b/", $name))
462      {
463
464      }
465      elseif(preg_match("/\b(500D|REBEL T1i|Kiss X3)\b/", $name))
466      {
467
468      }
469      elseif(preg_match("/\b(1000D|REBEL XS|Kiss F)\b/", $name))
470      {
471
472      }
473      elseif(preg_match("/\b1DS?/", $name))
474      {
475
476      }*/
477      else
478      {
479        /*
480         * note : powershot are not implemented, in exiftool condition to
481         * determine the model are not very understandable
482         */
483        $returned="$name is not yet implemented => ".ConvertData::toHexDump($values, ByteType::ASCII);
484      }
485      return($returned);
486    }
487
488    /**
489     * this function process the subtag of the 0x000d "CanonCameraInfo" tag
490     * for the EOS 40D camera
491     */
492    protected function processSubTag0x000d_40D($values)
493    {
494      $tagDef=$this->tagDef->getTagById(0x000d);
495      $list=$tagDef['tagValues.special']['40D'];
496      $data=new Data($values);
497
498      foreach($list as $tagIndex)
499      {
500        $subTagDef=$this->tagDef->getTagById("0x000d.40D.$tagIndex");
501
502        if(is_array($subTagDef))
503        {
504          $val=$this->readDataFromSubTag($data, $subTagDef);
505
506          // make a fake IFDEntry
507          $entry=new IfdEntryReader("\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\x0d\x00\x00", $this->byteOrder, "", 0, null);
508
509          $entry->getTag()->setId("0x000d.40D.$tagIndex");
510          $entry->getTag()->setName($subTagDef['tagName']);
511          $entry->getTag()->setValue($val);
512          $entry->getTag()->setKnown(true);
513          $entry->getTag()->setImplemented($subTagDef['implemented']);
514          $entry->getTag()->setTranslatable($subTagDef['translatable']);
515          $entry->getTag()->setSchema($this->schema);
516
517          if(array_key_exists('tagValues', $subTagDef))
518          {
519            if(array_key_exists($val, $subTagDef['tagValues']))
520            {
521              $returned=$subTagDef['tagValues'][$val];
522            }
523            else
524            {
525              $returned="unknown (".$val.")";
526            }
527          }
528          else
529          {
530            switch($tagIndex)
531            {
532              case 24: // CameraTemperature
533                $returned=($val-128)."°C";
534                break;
535              case 29: // FocalLength
536                $returned=ConvertData::toFocalLength($val);
537                break;
538              case 111: // WhiteBalance
539                // same method than the ShotInfo.WhiteBalance tag
540                $returned=$this->processSubTag0x0004(Array(7 => $val), false);
541                break;
542              case 214: // LensType
543                // same method than the CanonCameraSettings.LensType tag
544                $FocalShort=$this->readDataFromSubTag($data, $this->tagDef->getTagById("0x000d.40D.216"));
545                $FocalLong=$this->readDataFromSubTag($data, $this->tagDef->getTagById("0x000d.40D.218"));
546
547                $returned=$this->processSubTag0x0001(Array(22 => $val, 23 => $FocalLong, 24 => $FocalShort), false);
548                break;
549              case 216: // ShortFocal
550              case 218: // LongFocal
551                $returned=ConvertData::toFocalLength($val);
552                break;
553              case 255: // FirmwareVersion
554              case 2347: // LensModel
555                $returned=ConvertData::toStrings($val);
556                break;
557              default:
558                $returned="not yet implemented ($tagIndex)";
559                break;
560            }
561          }
562
563          $entry->getTag()->setLabel($returned);
564          $this->entries[]=$entry;
565
566          unset($entry);
567        }
568        unset($subTagDef);
569      }
570      unset($tagDef);
571      unset($list);
572      unset($data);
573
574      return("[EOS 40D]");
575    }
576
577    /**
578     * this function process the subtag of the 0x000d "CanonCameraInfo" tag
579     * for the EOS 1D Mark IV camera
580     *
581     */
582    protected function processSubTag0x000d_1DMarkIV($values)
583    {
584      $tagDef=$this->tagDef->getTagById(0x000d);
585      $list=$tagDef['tagValues.special']['1DMarkIV'];
586      $data=new Data($values);
587
588      foreach($list as $tagIndex)
589      {
590        $subTagDef=$this->tagDef->getTagById("0x000d.1DMarkIV.$tagIndex");
591
592        if(is_array($subTagDef))
593        {
594          $val=$this->readDataFromSubTag($data, $subTagDef);
595
596          // make a fake IFDEntry
597          $entry=new IfdEntryReader("\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\x0d\x00\x00", $this->byteOrder, "", 0, null);
598
599          $entry->getTag()->setId("0x000d.1DMarkIV.$tagIndex");
600          $entry->getTag()->setName($subTagDef['tagName']);
601          $entry->getTag()->setValue($val);
602          $entry->getTag()->setKnown(true);
603          $entry->getTag()->setImplemented($subTagDef['implemented']);
604          $entry->getTag()->setTranslatable($subTagDef['translatable']);
605          $entry->getTag()->setSchema($this->schema);
606
607          if(array_key_exists('tagValues', $subTagDef))
608          {
609            if(array_key_exists($val, $subTagDef['tagValues']))
610            {
611              $returned=$subTagDef['tagValues'][$val];
612            }
613            else
614            {
615              $returned="unknown (".$val.")";
616            }
617          }
618          else
619          {
620            switch($tagIndex)
621            {
622              case 25: // CameraTemperature
623                $returned=($val-128)."°C";
624                break;
625              case 30: // FocalLength
626                $returned=ConvertData::toFocalLength($val);
627                break;
628              default:
629                $returned="not yet implemented ($tagIndex)";
630                break;
631            }
632          }
633
634          $entry->getTag()->setLabel($returned);
635          $this->entries[]=$entry;
636
637          unset($entry);
638        }
639        unset($subTagDef);
640      }
641      unset($tagDef);
642      unset($list);
643      unset($data);
644
645      return("[EOS 1D Mark IV]");
646    }
647
648
649
650    /**
651     * read datas from a $data object, according to the tag definition
652     *
653     * @param Data $data : a Data object
654     * @param Array $tagDef : the tag definition
655     */
656    protected function readDataFromSubTag($data, $tagDef)
657    {
658      if(!array_key_exists('pos', $tagDef))
659        return("Invalid tagDef (no 'pos')");
660
661      $pos=$tagDef['pos'];
662
663      if(array_key_exists('byteOrder', $tagDef))
664      {
665        $data->setByteOrder($tagDef['byteOrder']);
666      }
667      else
668      {
669        $data->setByteOrder(BYTE_ORDER_LITTLE_ENDIAN);
670      }
671
672      $returned="";
673      switch($tagDef['type'])
674      {
675        case ByteType::UNDEFINED:
676        case ByteType::UNKNOWN :
677        case ByteType::ASCII:
678          $nbCar=(array_key_exists('length', $tagDef))?$tagDef['length']:1;
679          $returned=$data->readASCII($nbCar, $pos);
680          break;
681        case ByteType::UBYTE:
682          $returned=$data->readUByte($pos);
683          break;
684        case ByteType::USHORT:
685          $returned=$data->readUShort($pos);
686          break;
687        case ByteType::ULONG:
688          $returned=$data->readULong($pos);
689          break;
690        case ByteType::URATIONAL:
691          $returned=$data->readURational($pos);
692          break;
693        case ByteType::SBYTE:
694          $returned=$data->readSByte($pos);
695          break;
696        case ByteType::SSHORT:
697          $returned=$data->readSShort($pos);
698          break;
699        case ByteType::SLONG:
700          $returned=$data->readSLong($pos);
701          break;
702        case ByteType::SRATIONAL:
703          $returned=$data->readSRational($pos);
704          break;
705        case ByteType::FLOAT:
706          $returned="";
707          break;
708        case ByteType::DOUBLE:
709          $returned="";
710          break;
711      }
712      return($returned);
713    }
714
715
716    /**
717     * Convert Canon hex-based EV (modulo 0x20) to real number
718     *     0x00 -> 0
719     *     0x0c -> 0.33333
720     *     0x10 -> 0.5
721     *     0x14 -> 0.66666
722     *     0x20 -> 1   ...  etc
723     *
724     * function from exiftool
725     *
726     * @param Integer $value : the canon EV value
727     * @return Float : the converted value
728     */
729    protected function canonEv($val)
730    {
731      //$val=95;
732
733      if($val<0)
734      {
735        $val=-$val;
736        $sign=-1;
737      }
738      else
739      {
740        $sign=1;
741      }
742      $frac = (float) ($val & 0x1f); //$frac = $val % 0x20;
743      $val -= $frac;  // remove fraction
744      // Convert 1/3 and 2/3 codes
745      if ($frac == 0x0c)
746      {
747        $frac = 0x20 / 3;
748      }
749      elseif($frac==0x14)
750      {
751        $frac = 0x40 / 3;
752      }
753      return($sign * ($val + $frac) / 0x20);
754    }
755  }
756
757?>
Note: See TracBrowser for help on using the repository browser.