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

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

JpegMetaData class is updated

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