Exiftool.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. /**
  3. * PHP Exif Exiftool Reader Adapter
  4. *
  5. * @link http://github.com/miljar/PHPExif for the canonical source repository
  6. * @copyright Copyright (c) 2013 Tom Van Herreweghe <tom@theanalogguy.be>
  7. * @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License
  8. * @category PHPExif
  9. * @package Reader
  10. */
  11. namespace PHPExif\Adapter;
  12. use PHPExif\Exif;
  13. use InvalidArgumentException;
  14. use RuntimeException;
  15. /**
  16. * PHP Exif Exiftool Reader Adapter
  17. *
  18. * Uses native PHP functionality to read data from a file
  19. *
  20. * @category PHPExif
  21. * @package Reader
  22. */
  23. class Exiftool extends AdapterAbstract
  24. {
  25. const TOOL_NAME = 'exiftool';
  26. /**
  27. * Path to the exiftool binary
  28. *
  29. * @var string
  30. */
  31. protected $toolPath;
  32. /**
  33. * @var boolean
  34. */
  35. protected $numeric = true;
  36. /**
  37. * @var array
  38. */
  39. protected $encoding = array();
  40. /**
  41. * @var string
  42. */
  43. protected $mapperClass = '\\PHPExif\\Mapper\\Exiftool';
  44. /**
  45. * Setter for the exiftool binary path
  46. *
  47. * @param string $path The path to the exiftool binary
  48. * @return \PHPExif\Adapter\Exiftool Current instance
  49. * @throws \InvalidArgumentException When path is invalid
  50. */
  51. public function setToolPath($path)
  52. {
  53. if (!file_exists($path)) {
  54. throw new InvalidArgumentException(
  55. sprintf(
  56. 'Given path (%1$s) to the exiftool binary is invalid',
  57. $path
  58. )
  59. );
  60. }
  61. $this->toolPath = $path;
  62. return $this;
  63. }
  64. /**
  65. * @param boolean $numeric
  66. */
  67. public function setNumeric($numeric)
  68. {
  69. $this->numeric = $numeric;
  70. }
  71. /**
  72. * @see http://www.sno.phy.queensu.ca/~phil/exiftool/faq.html#Q10
  73. * @param array $encoding encoding parameters in an array eg. ["exif" => "UTF-8"]
  74. */
  75. public function setEncoding($encoding)
  76. {
  77. $possible_keys = array("exif", "iptc", "id3", "photoshop", "quicktime",);
  78. $possible_values = array("UTF8", "cp65001", "UTF-8", "Thai", "cp874", "Latin", "cp1252",
  79. "Latin1", "MacRoman", "cp10000", "Mac", "Roman", "Latin2", "cp1250", "MacLatin2",
  80. "cp10029", "Cyrillic", "cp1251", "Russian", "MacCyrillic", "cp10007", "Greek",
  81. "cp1253", "MacGreek", "cp10006", "Turkish", "cp1254", "MacTurkish", "cp10081",
  82. "Hebrew", "cp1255", "MacRomanian", "cp10010", "Arabic", "cp1256", "MacIceland",
  83. "cp10079", "Baltic", "cp1257", "MacCroatian", "cp10082", "Vietnam", "cp1258",);
  84. foreach ($encoding as $type => $encoding) {
  85. if (in_array($type, $possible_keys) && in_array($encoding, $possible_values)) {
  86. $this->encoding[$type] = $encoding;
  87. }
  88. }
  89. }
  90. /**
  91. * Getter for the exiftool binary path
  92. * Lazy loads the "default" path
  93. *
  94. * @return string
  95. */
  96. public function getToolPath()
  97. {
  98. if (empty($this->toolPath)) {
  99. $path = exec('which ' . self::TOOL_NAME);
  100. $this->setToolPath($path);
  101. }
  102. return $this->toolPath;
  103. }
  104. /**
  105. * Reads & parses the EXIF data from given file
  106. *
  107. * @param string $file
  108. * @return \PHPExif\Exif Instance of Exif object with data
  109. * @throws \RuntimeException If the EXIF data could not be read
  110. */
  111. public function getExifFromFile($file)
  112. {
  113. $encoding = '';
  114. if (!empty($this->encoding)) {
  115. $encoding = '-charset ';
  116. foreach ($this->encoding as $key => $value) {
  117. $encoding .= escapeshellarg($key).'='.escapeshellarg($value);
  118. }
  119. }
  120. $result = $this->getCliOutput(
  121. sprintf(
  122. '%1$s%3$s -j -a -G1 %5$s -c %4$s %2$s',
  123. $this->getToolPath(),
  124. escapeshellarg($file),
  125. $this->numeric ? ' -n' : '',
  126. escapeshellarg('%d deg %d\' %.4f"'),
  127. $encoding
  128. )
  129. );
  130. if (!mb_check_encoding($result, "utf-8")) {
  131. $result = utf8_encode($result);
  132. }
  133. $data = json_decode($result, true);
  134. if (!is_array($data)) {
  135. throw new RuntimeException(
  136. 'Could not decode exiftool output'
  137. );
  138. }
  139. // map the data:
  140. $mapper = $this->getMapper();
  141. $mapper->setNumeric($this->numeric);
  142. $mappedData = $mapper->mapRawData(reset($data));
  143. // hydrate a new Exif object
  144. $exif = new Exif();
  145. $hydrator = $this->getHydrator();
  146. $hydrator->hydrate($exif, $mappedData);
  147. $exif->setRawData(reset($data));
  148. return $exif;
  149. }
  150. /**
  151. * Returns the output from given cli command
  152. *
  153. * @param string $command
  154. * @return mixed
  155. * @throws RuntimeException If the command can't be executed
  156. */
  157. protected function getCliOutput($command)
  158. {
  159. $descriptorspec = array(
  160. 0 => array('pipe', 'r'),
  161. 1 => array('pipe', 'w'),
  162. 2 => array('pipe', 'a')
  163. );
  164. $process = proc_open($command, $descriptorspec, $pipes);
  165. if (!is_resource($process)) {
  166. throw new RuntimeException(
  167. 'Could not open a resource to the exiftool binary'
  168. );
  169. }
  170. $result = stream_get_contents($pipes[1]);
  171. fclose($pipes[0]);
  172. fclose($pipes[1]);
  173. fclose($pipes[2]);
  174. proc_close($process);
  175. return $result;
  176. }
  177. }