PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/libs/clearbricks/image/class.image.meta.php

https://bitbucket.org/dotclear/dotclear/
PHP | 406 lines | 284 code | 36 blank | 86 comment | 22 complexity | dea8c1d155c6d3cf6071bd34d105c47b MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. # -- BEGIN LICENSE BLOCK ---------------------------------------
  3. #
  4. # This file is part of Clearbricks.
  5. #
  6. # Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
  7. # Licensed under the GPL version 2.0 license.
  8. # See LICENSE file or
  9. # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  10. #
  11. # -- END LICENSE BLOCK -----------------------------------------
  12. /**
  13. * Image metadata
  14. *
  15. * This class reads EXIF, IPTC and XMP metadata from a JPEG file.
  16. *
  17. * - Contributor: Mathieu Lecarme.
  18. *
  19. * @package Clearbricks
  20. * @subpackage Images
  21. */
  22. class imageMeta
  23. {
  24. /** @var array Internal XMP array */
  25. protected $xmp = array();
  26. /** @var array Internal IPTC array */
  27. protected $iptc = array();
  28. /** @var array Internal EXIF array */
  29. protected $exif = array();
  30. /**
  31. * Read metadata
  32. *
  33. * Returns all image metadata in an array as defined in {@link $properties}.
  34. *
  35. * @param string $f Image file path
  36. * @return array
  37. */
  38. public static function readMeta($f)
  39. {
  40. $o = new self;
  41. $o->loadFile($f);
  42. return $o->getMeta();
  43. }
  44. /**
  45. * Get metadata
  46. *
  47. * Returns all image metadata in an array as defined in {@link $properties}.
  48. * Should call {@link loadFile()} before.
  49. *
  50. * @param string $f Image file path
  51. * @return array
  52. */
  53. public function getMeta()
  54. {
  55. foreach ($this->properties as $k => $v)
  56. {
  57. if (!empty($this->xmp[$k])) {
  58. $this->properties[$k] = $this->xmp[$k];
  59. } elseif (!empty($this->iptc[$k])) {
  60. $this->properties[$k] = $this->iptc[$k];
  61. } elseif (!empty($this->exif[$k])) {
  62. $this->properties[$k] = $this->exif[$k];
  63. }
  64. }
  65. # Fix date format
  66. $this->properties['DateTimeOriginal'] = preg_replace(
  67. '/^(\d{4}):(\d{2}):(\d{2})/','$1-$2-$3',
  68. $this->properties['DateTimeOriginal']
  69. );
  70. return $this->properties;
  71. }
  72. /**
  73. * Load file
  74. *
  75. * Loads a file and read its metadata.
  76. *
  77. * @param string $f Image file path
  78. */
  79. public function loadFile($f)
  80. {
  81. if (!is_file($f) || !is_readable($f)) {
  82. throw new Exception('Unable to read file');
  83. }
  84. $this->readXMP($f);
  85. $this->readIPTC($f);
  86. $this->readExif($f);
  87. }
  88. /**
  89. * Read XMP
  90. *
  91. * Reads XML metadata and assigns values to {@link $xmp}.
  92. *
  93. * @param string $f Image file path
  94. */
  95. protected function readXMP($f)
  96. {
  97. if (($fp = @fopen($f,'rb')) === false) {
  98. throw new Exception('Unable to open image file');
  99. }
  100. $inside = false;
  101. $done = false;
  102. $xmp = null;
  103. while (!feof($fp))
  104. {
  105. $buffer = fgets($fp,4096);
  106. $xmp_start = strpos($buffer,'<x:xmpmeta');
  107. if ($xmp_start !== false) {
  108. $buffer = substr($buffer,$xmp_start);
  109. $inside = true;
  110. }
  111. if ($inside)
  112. {
  113. $xmp_end = strpos($buffer,'</x:xmpmeta>');
  114. if ($xmp_end !== false) {
  115. $buffer = substr($buffer,$xmp_end,12);
  116. $inside = false;
  117. $done = true;
  118. }
  119. $xmp .= $buffer;
  120. }
  121. if ($done) {
  122. break;
  123. }
  124. }
  125. fclose($fp);
  126. if (!$xmp) {
  127. return;
  128. }
  129. foreach ($this->xmp_reg as $code => $patterns)
  130. {
  131. foreach ($patterns as $p)
  132. {
  133. if (preg_match($p,$xmp,$m)) {
  134. $this->xmp[$code] = $m[1];
  135. break;
  136. }
  137. }
  138. }
  139. if (preg_match('%<dc:subject>\s*<rdf:Bag>(.+?)</rdf:Bag%msu',$xmp,$m)
  140. && preg_match_all('%<rdf:li>(.+?)</rdf:li>%msu',$m[1],$m))
  141. {
  142. $this->xmp['Keywords'] = implode(',',$m[1]);
  143. }
  144. foreach ($this->xmp as $k => $v) {
  145. $this->xmp[$k] = html::decodeEntities(text::toUTF8($v));
  146. }
  147. }
  148. /**
  149. * Read IPTC
  150. *
  151. * Reads IPTC metadata and assigns values to {@link $iptc}.
  152. *
  153. * @param string $f Image file path
  154. */
  155. protected function readIPTC($f)
  156. {
  157. if (!function_exists('iptcparse')) {
  158. return;
  159. }
  160. $imageinfo = null;
  161. @getimagesize($f,$imageinfo);
  162. if (!is_array($imageinfo) || !isset($imageinfo['APP13'])) {
  163. return;
  164. }
  165. $iptc = @iptcparse($imageinfo['APP13']);
  166. if (!is_array($iptc)) {
  167. return;
  168. }
  169. foreach ($this->iptc_ref as $k => $v)
  170. {
  171. if (isset($iptc[$k]) && isset($this->iptc_to_property[$v])) {
  172. $this->iptc[$this->iptc_to_property[$v]] = text::toUTF8(trim(implode(',',$iptc[$k])));
  173. }
  174. }
  175. }
  176. /**
  177. * Read EXIF
  178. *
  179. * Reads EXIF metadata and assigns values to {@link $exif}.
  180. *
  181. * @param string $f Image file path
  182. */
  183. protected function readEXIF($f)
  184. {
  185. if (!function_exists('exif_read_data')) {
  186. return;
  187. }
  188. $d = @exif_read_data($f,'ANY_TAG');
  189. if (!is_array($d)) {
  190. return;
  191. }
  192. foreach ($this->exif_to_property as $k => $v)
  193. {
  194. if (isset($d[$k])) {
  195. $this->exif[$v] = text::toUTF8($d[$k]);
  196. }
  197. }
  198. }
  199. /* Properties
  200. ------------------------------------------------------- */
  201. /** @var array Final properties array */
  202. protected $properties = array(
  203. 'Title' => null,
  204. 'Description' => null,
  205. 'Creator' => null,
  206. 'Rights' => null,
  207. 'Make' => null,
  208. 'Model' => null,
  209. 'Exposure' => null,
  210. 'FNumber' => null,
  211. 'MaxApertureValue' => null,
  212. 'ExposureProgram' => null,
  213. 'ISOSpeedRatings' => null,
  214. 'DateTimeOriginal' => null,
  215. 'ExposureBiasValue' => null,
  216. 'MeteringMode' => null,
  217. 'FocalLength' => null,
  218. 'Lens' => null,
  219. 'CountryCode' => null,
  220. 'Country' => null,
  221. 'State' => null,
  222. 'City' => null,
  223. 'Keywords' => null
  224. );
  225. # XMP
  226. /** @ignore */
  227. protected $xmp_reg = array(
  228. 'Title' => array(
  229. '%<dc:title>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
  230. ),
  231. 'Description' => array(
  232. '%<dc:description>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
  233. ),
  234. 'Creator' => array(
  235. '%<dc:creator>\s*<rdf:Seq>\s*<rdf:li>(.+?)</rdf:li>%msu'
  236. ),
  237. 'Rights' => array(
  238. '%<dc:rights>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
  239. ),
  240. 'Make' => array(
  241. '%<tiff:Make>(.+?)</tiff:Make>%msu',
  242. '%tiff:Make="(.+?)"%msu'
  243. ),
  244. 'Model' => array(
  245. '%<tiff:Model>(.+?)</tiff:Model>%msu',
  246. '%tiff:Model="(.+?)"%msu'
  247. ),
  248. 'Exposure' => array(
  249. '%<exif:ExposureTime>(.+?)</exif:ExposureTime>%msu',
  250. '%exif:ExposureTime="(.+?)"%msu'
  251. ),
  252. 'FNumber' => array(
  253. '%<exif:FNumber>(.+?)</exif:FNumber>%msu',
  254. '%exif:FNumber="(.+?)"%msu'
  255. ),
  256. 'MaxApertureValue' => array(
  257. '%<exif:MaxApertureValue>(.+?)</exif:MaxApertureValue>%msu',
  258. '%exif:MaxApertureValue="(.+?)"%msu'
  259. ),
  260. 'ExposureProgram' => array(
  261. '%<exif:ExposureProgram>(.+?)</exif:ExposureProgram>%msu',
  262. '%exif:ExposureProgram="(.+?)"%msu'
  263. ),
  264. 'ISOSpeedRatings' => array(
  265. '%<exif:ISOSpeedRatings>\s*<rdf:Seq>\s*<rdf:li>(.+?)</rdf:li>%msu'
  266. ),
  267. 'DateTimeOriginal' => array(
  268. '%<exif:DateTimeOriginal>(.+?)</exif:DateTimeOriginal>%msu',
  269. '%exif:DateTimeOriginal="(.+?)"%msu'
  270. ),
  271. 'ExposureBiasValue' => array(
  272. '%<exif:ExposureBiasValue>(.+?)</exif:ExposureBiasValue>%msu',
  273. '%exif:ExposureBiasValue="(.+?)"%msu'
  274. ),
  275. 'MeteringMode' => array(
  276. '%<exif:MeteringMode>(.+?)</exif:MeteringMode>%msu',
  277. '%exif:MeteringMode="(.+?)"%msu'
  278. ),
  279. 'FocalLength' => array(
  280. '%<exif:FocalLength>(.+?)</exif:FocalLength>%msu',
  281. '%exif:FocalLength="(.+?)"%msu'
  282. ),
  283. 'Lens' => array(
  284. '%<aux:Lens>(.+?)</aux:Lens>%msu',
  285. '%aux:Lens="(.+?)"%msu'
  286. ),
  287. 'CountryCode' => array(
  288. '%<Iptc4xmpCore:CountryCode>(.+?)</Iptc4xmpCore:CountryCode>%msu',
  289. '%Iptc4xmpCore:CountryCode="(.+?)"%msu'
  290. ),
  291. 'Country' => array(
  292. '%<photoshop:Country>(.+?)</photoshop:Country>%msu',
  293. '%photoshop:Country="(.+?)"%msu'
  294. ),
  295. 'State' => array(
  296. '%<photoshop:State>(.+?)</photoshop:State>%msu',
  297. '%photoshop:State="(.+?)"%msu'
  298. ),
  299. 'City' => array(
  300. '%<photoshop:City>(.+?)</photoshop:City>%msu',
  301. '%photoshop:City="(.+?)"%msu'
  302. )
  303. );
  304. # IPTC
  305. /** @ignore */
  306. protected $iptc_ref = array(
  307. '1#090' => 'Iptc.Envelope.CharacterSet',// Character Set used (32 chars max)
  308. '2#005' => 'Iptc.ObjectName', // Title (64 chars max)
  309. '2#015' => 'Iptc.Category', // (3 chars max)
  310. '2#020' => 'Iptc.Supplementals', // Supplementals categories (32 chars max)
  311. '2#025' => 'Iptc.Keywords', // (64 chars max)
  312. '2#040' => 'Iptc.SpecialsInstructions', // (256 chars max)
  313. '2#055' => 'Iptc.DateCreated', // YYYYMMDD (8 num chars max)
  314. '2#060' => 'Iptc.TimeCreated', // HHMMSS+/-HHMM (11 chars max)
  315. '2#062' => 'Iptc.DigitalCreationDate', // YYYYMMDD (8 num chars max)
  316. '2#063' => 'Iptc.DigitalCreationTime', // HHMMSS+/-HHMM (11 chars max)
  317. '2#080' => 'Iptc.ByLine', // Author (32 chars max)
  318. '2#085' => 'Iptc.ByLineTitle', // Author position (32 chars max)
  319. '2#090' => 'Iptc.City', // (32 chars max)
  320. '2#092' => 'Iptc.Sublocation', // (32 chars max)
  321. '2#095' => 'Iptc.ProvinceState', // (32 chars max)
  322. '2#100' => 'Iptc.CountryCode', // (32 alpha chars max)
  323. '2#101' => 'Iptc.CountryName', // (64 chars max)
  324. '2#105' => 'Iptc.Headline', // (256 chars max)
  325. '2#110' => 'Iptc.Credits', // (32 chars max)
  326. '2#115' => 'Iptc.Source', // (32 chars max)
  327. '2#116' => 'Iptc.Copyright', // Copyright Notice (128 chars max)
  328. '2#118' => 'Iptc.Contact', // (128 chars max)
  329. '2#120' => 'Iptc.Caption', // Caption/Abstract (2000 chars max)
  330. '2#122' => 'Iptc.CaptionWriter' // Caption Writer/Editor (32 chars max)
  331. );
  332. /** @ignore */
  333. protected $iptc_to_property = array(
  334. 'Iptc.ObjectName' => 'Title',
  335. 'Iptc.Caption' => 'Description',
  336. 'Iptc.ByLine' => 'Creator',
  337. 'Iptc.Copyright' =>'Rights',
  338. 'Iptc.CountryCode' => 'CountryCode',
  339. 'Iptc.CountryName' => 'Country',
  340. 'Iptc.ProvinceState' => 'State',
  341. 'Iptc.City' => 'City',
  342. 'Iptc.Keywords' => 'Keywords'
  343. );
  344. # EXIF
  345. /** @ignore */
  346. protected $exif_to_property = array(
  347. //'' => 'Title',
  348. 'ImageDescription' => 'Description',
  349. 'Artist' => 'Creator',
  350. 'Copyright' => 'Rights',
  351. 'Make' => 'Make',
  352. 'Model' => 'Model',
  353. 'ExposureTime' => 'Exposure',
  354. 'FNumber' => 'FNumber',
  355. 'MaxApertureValue' => 'MaxApertureValue',
  356. 'ExposureProgram' => 'ExposureProgram',
  357. 'ISOSpeedRatings' => 'ISOSpeedRatings',
  358. 'DateTimeOriginal' => 'DateTimeOriginal',
  359. 'ExposureBiasValue' => 'ExposureBiasValue',
  360. 'MeteringMode' => 'MeteringMode',
  361. 'FocalLength' => 'FocalLength'
  362. //'' => 'Lens',
  363. //'' => 'CountryCode',
  364. //'' => 'Country',
  365. //'' => 'State',
  366. //'' => 'City',
  367. //'' => 'Keywords'
  368. );
  369. }
  370. ?>