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

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