loadFile($f); return $o->getMeta(); } /** * Get metadata * * Returns all image metadata in an array as defined in {@link $properties}. * Should call {@link loadFile()} before. * * @return array */ public function getMeta() { foreach ($this->properties as $k => $v) { if (!empty($this->xmp[$k])) { $this->properties[$k] = $this->xmp[$k]; } elseif (!empty($this->iptc[$k])) { $this->properties[$k] = $this->iptc[$k]; } elseif (!empty($this->exif[$k])) { $this->properties[$k] = $this->exif[$k]; } } # Fix date format $this->properties['DateTimeOriginal'] = preg_replace( '/^(\d{4}):(\d{2}):(\d{2})/', '$1-$2-$3', $this->properties['DateTimeOriginal'] ); return $this->properties; } /** * Load file * * Loads a file and read its metadata. * * @param string $f Image file path */ public function loadFile($f) { if (!is_file($f) || !is_readable($f)) { throw new Exception('Unable to read file'); } $this->readXMP($f); $this->readIPTC($f); $this->readExif($f); } /** * Read XMP * * Reads XML metadata and assigns values to {@link $xmp}. * * @param string $f Image file path */ protected function readXMP($f) { if (($fp = @fopen($f, 'rb')) === false) { throw new Exception('Unable to open image file'); } $inside = false; $done = false; $xmp = null; while (!feof($fp)) { $buffer = fgets($fp, 4096); $xmp_start = strpos($buffer, ''); if ($xmp_end !== false) { $buffer = substr($buffer, $xmp_end, 12); $inside = false; $done = true; } $xmp .= $buffer; } if ($done) { break; } } fclose($fp); if (!$xmp) { return; } foreach ($this->xmp_reg as $code => $patterns) { foreach ($patterns as $p) { if (preg_match($p, $xmp, $m)) { $this->xmp[$code] = $m[1]; break; } } } if (preg_match('%\s*(.+?)(.+?)%msu', $m[1], $m)) { $this->xmp['Keywords'] = implode(',', $m[1]); } foreach ($this->xmp as $k => $v) { $this->xmp[$k] = html::decodeEntities(text::toUTF8($v)); } } /** * Read IPTC * * Reads IPTC metadata and assigns values to {@link $iptc}. * * @param string $f Image file path */ protected function readIPTC($f) { if (!function_exists('iptcparse')) { return; } $imageinfo = null; @getimagesize($f, $imageinfo); if (!is_array($imageinfo) || !isset($imageinfo['APP13'])) { return; } $iptc = @iptcparse($imageinfo['APP13']); if (!is_array($iptc)) { return; } foreach ($this->iptc_ref as $k => $v) { if (isset($iptc[$k]) && isset($this->iptc_to_property[$v])) { $this->iptc[$this->iptc_to_property[$v]] = text::toUTF8(trim(implode(',', $iptc[$k]))); } } } /** * Read EXIF * * Reads EXIF metadata and assigns values to {@link $exif}. * * @param string $f Image file path */ protected function readEXIF($f) { if (!function_exists('exif_read_data')) { return; } $d = @exif_read_data($f, 'ANY_TAG'); if (!is_array($d)) { return; } foreach ($this->exif_to_property as $k => $v) { if (isset($d[$k])) { if (is_array($d[$k])) { foreach ($d[$k] as $kk => $vv) { $this->exif[$v . '.' . $kk] = text::toUTF8($vv); } } else { $this->exif[$v] = text::toUTF8($d[$k]); } } } } /** * array $properties Final properties array */ protected $properties = [ 'Title' => null, 'Description' => null, 'Creator' => null, 'Rights' => null, 'Make' => null, 'Model' => null, 'Exposure' => null, 'FNumber' => null, 'MaxApertureValue' => null, 'ExposureProgram' => null, 'ISOSpeedRatings' => null, 'DateTimeOriginal' => null, 'ExposureBiasValue' => null, 'MeteringMode' => null, 'FocalLength' => null, 'Lens' => null, 'CountryCode' => null, 'Country' => null, 'State' => null, 'City' => null, 'Keywords' => null ]; # XMP protected $xmp_reg = [ 'Title' => [ '%\s*\s*(.+?)%msu' ], 'Description' => [ '%\s*\s*(.+?)%msu' ], 'Creator' => [ '%\s*\s*(.+?)%msu' ], 'Rights' => [ '%\s*\s*(.+?)%msu' ], 'Make' => [ '%(.+?)%msu', '%tiff:Make="(.+?)"%msu' ], 'Model' => [ '%(.+?)%msu', '%tiff:Model="(.+?)"%msu' ], 'Exposure' => [ '%(.+?)%msu', '%exif:ExposureTime="(.+?)"%msu' ], 'FNumber' => [ '%(.+?)%msu', '%exif:FNumber="(.+?)"%msu' ], 'MaxApertureValue' => [ '%(.+?)%msu', '%exif:MaxApertureValue="(.+?)"%msu' ], 'ExposureProgram' => [ '%(.+?)%msu', '%exif:ExposureProgram="(.+?)"%msu' ], 'ISOSpeedRatings' => [ '%\s*\s*(.+?)%msu' ], 'DateTimeOriginal' => [ '%(.+?)%msu', '%exif:DateTimeOriginal="(.+?)"%msu' ], 'ExposureBiasValue' => [ '%(.+?)%msu', '%exif:ExposureBiasValue="(.+?)"%msu' ], 'MeteringMode' => [ '%(.+?)%msu', '%exif:MeteringMode="(.+?)"%msu' ], 'FocalLength' => [ '%(.+?)%msu', '%exif:FocalLength="(.+?)"%msu' ], 'Lens' => [ '%(.+?)%msu', '%aux:Lens="(.+?)"%msu' ], 'CountryCode' => [ '%(.+?)%msu', '%Iptc4xmpCore:CountryCode="(.+?)"%msu' ], 'Country' => [ '%(.+?)%msu', '%photoshop:Country="(.+?)"%msu' ], 'State' => [ '%(.+?)%msu', '%photoshop:State="(.+?)"%msu' ], 'City' => [ '%(.+?)%msu', '%photoshop:City="(.+?)"%msu' ] ]; # IPTC protected $iptc_ref = [ '1#090' => 'Iptc.Envelope.CharacterSet', // Character Set used (32 chars max) '2#005' => 'Iptc.ObjectName', // Title (64 chars max) '2#015' => 'Iptc.Category', // (3 chars max) '2#020' => 'Iptc.Supplementals', // Supplementals categories (32 chars max) '2#025' => 'Iptc.Keywords', // (64 chars max) '2#040' => 'Iptc.SpecialsInstructions', // (256 chars max) '2#055' => 'Iptc.DateCreated', // YYYYMMDD (8 num chars max) '2#060' => 'Iptc.TimeCreated', // HHMMSS+/-HHMM (11 chars max) '2#062' => 'Iptc.DigitalCreationDate', // YYYYMMDD (8 num chars max) '2#063' => 'Iptc.DigitalCreationTime', // HHMMSS+/-HHMM (11 chars max) '2#080' => 'Iptc.ByLine', // Author (32 chars max) '2#085' => 'Iptc.ByLineTitle', // Author position (32 chars max) '2#090' => 'Iptc.City', // (32 chars max) '2#092' => 'Iptc.Sublocation', // (32 chars max) '2#095' => 'Iptc.ProvinceState', // (32 chars max) '2#100' => 'Iptc.CountryCode', // (32 alpha chars max) '2#101' => 'Iptc.CountryName', // (64 chars max) '2#105' => 'Iptc.Headline', // (256 chars max) '2#110' => 'Iptc.Credits', // (32 chars max) '2#115' => 'Iptc.Source', // (32 chars max) '2#116' => 'Iptc.Copyright', // Copyright Notice (128 chars max) '2#118' => 'Iptc.Contact', // (128 chars max) '2#120' => 'Iptc.Caption', // Caption/Abstract (2000 chars max) '2#122' => 'Iptc.CaptionWriter' // Caption Writer/Editor (32 chars max) ]; protected $iptc_to_property = [ 'Iptc.ObjectName' => 'Title', 'Iptc.Caption' => 'Description', 'Iptc.ByLine' => 'Creator', 'Iptc.Copyright' => 'Rights', 'Iptc.CountryCode' => 'CountryCode', 'Iptc.CountryName' => 'Country', 'Iptc.ProvinceState' => 'State', 'Iptc.City' => 'City', 'Iptc.Keywords' => 'Keywords' ]; # EXIF protected $exif_to_property = [ //'' => 'Title', 'ImageDescription' => 'Description', 'Artist' => 'Creator', 'Copyright' => 'Rights', 'Make' => 'Make', 'Model' => 'Model', 'ExposureTime' => 'Exposure', 'FNumber' => 'FNumber', 'MaxApertureValue' => 'MaxApertureValue', 'ExposureProgram' => 'ExposureProgram', 'ISOSpeedRatings' => 'ISOSpeedRatings', 'DateTimeOriginal' => 'DateTimeOriginal', 'ExposureBiasValue' => 'ExposureBiasValue', 'MeteringMode' => 'MeteringMode', 'FocalLength' => 'FocalLength' //'' => 'Lens', //'' => 'CountryCode', //'' => 'Country', //'' => 'State', //'' => 'City', //'' => 'Keywords' ]; }