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

Last change on this file since 17554 was 17554, checked in by grum, 12 years ago

feature:2701
bug:2702
bug:2720
bug:2722

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