Current oav website
This commit is contained in:
387
dotclear._no/inc/libs/clearbricks/image/class.image.meta.php
Normal file
387
dotclear._no/inc/libs/clearbricks/image/class.image.meta.php
Normal file
@ -0,0 +1,387 @@
|
||||
<?php
|
||||
/**
|
||||
* @class imageMeta
|
||||
* @brief Image metadata
|
||||
*
|
||||
* This class reads EXIF, IPTC and XMP metadata from a JPEG file.
|
||||
*
|
||||
* - Contributor: Mathieu Lecarme.
|
||||
*
|
||||
* @package Clearbricks
|
||||
* @subpackage Images
|
||||
*
|
||||
* @copyright Olivier Meunier & Association Dotclear
|
||||
* @copyright GPL-2.0-only
|
||||
*/
|
||||
|
||||
class imageMeta
|
||||
{
|
||||
protected $xmp = []; ///< array: Internal XMP array
|
||||
protected $iptc = []; ///< array: Internal IPTC array
|
||||
protected $exif = []; ///< array: Internal EXIF array
|
||||
|
||||
/**
|
||||
* Read metadata
|
||||
*
|
||||
* Returns all image metadata in an array as defined in {@link $properties}.
|
||||
*
|
||||
* @param string $f Image file path
|
||||
* @return array
|
||||
*/
|
||||
public static function readMeta($f)
|
||||
{
|
||||
$o = new self;
|
||||
$o->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, '<x:xmpmeta');
|
||||
|
||||
if ($xmp_start !== false) {
|
||||
$buffer = substr($buffer, $xmp_start);
|
||||
$inside = true;
|
||||
}
|
||||
|
||||
if ($inside) {
|
||||
$xmp_end = strpos($buffer, '</x:xmpmeta>');
|
||||
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('%<dc:subject>\s*<rdf:Bag>(.+?)</rdf:Bag%msu', $xmp, $m)
|
||||
&& preg_match_all('%<rdf:li>(.+?)</rdf:li>%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' => [
|
||||
'%<dc:title>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
|
||||
],
|
||||
'Description' => [
|
||||
'%<dc:description>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
|
||||
],
|
||||
'Creator' => [
|
||||
'%<dc:creator>\s*<rdf:Seq>\s*<rdf:li>(.+?)</rdf:li>%msu'
|
||||
],
|
||||
'Rights' => [
|
||||
'%<dc:rights>\s*<rdf:Alt>\s*<rdf:li.*?>(.+?)</rdf:li>%msu'
|
||||
],
|
||||
'Make' => [
|
||||
'%<tiff:Make>(.+?)</tiff:Make>%msu',
|
||||
'%tiff:Make="(.+?)"%msu'
|
||||
],
|
||||
'Model' => [
|
||||
'%<tiff:Model>(.+?)</tiff:Model>%msu',
|
||||
'%tiff:Model="(.+?)"%msu'
|
||||
],
|
||||
'Exposure' => [
|
||||
'%<exif:ExposureTime>(.+?)</exif:ExposureTime>%msu',
|
||||
'%exif:ExposureTime="(.+?)"%msu'
|
||||
],
|
||||
'FNumber' => [
|
||||
'%<exif:FNumber>(.+?)</exif:FNumber>%msu',
|
||||
'%exif:FNumber="(.+?)"%msu'
|
||||
],
|
||||
'MaxApertureValue' => [
|
||||
'%<exif:MaxApertureValue>(.+?)</exif:MaxApertureValue>%msu',
|
||||
'%exif:MaxApertureValue="(.+?)"%msu'
|
||||
],
|
||||
'ExposureProgram' => [
|
||||
'%<exif:ExposureProgram>(.+?)</exif:ExposureProgram>%msu',
|
||||
'%exif:ExposureProgram="(.+?)"%msu'
|
||||
],
|
||||
'ISOSpeedRatings' => [
|
||||
'%<exif:ISOSpeedRatings>\s*<rdf:Seq>\s*<rdf:li>(.+?)</rdf:li>%msu'
|
||||
],
|
||||
'DateTimeOriginal' => [
|
||||
'%<exif:DateTimeOriginal>(.+?)</exif:DateTimeOriginal>%msu',
|
||||
'%exif:DateTimeOriginal="(.+?)"%msu'
|
||||
],
|
||||
'ExposureBiasValue' => [
|
||||
'%<exif:ExposureBiasValue>(.+?)</exif:ExposureBiasValue>%msu',
|
||||
'%exif:ExposureBiasValue="(.+?)"%msu'
|
||||
],
|
||||
'MeteringMode' => [
|
||||
'%<exif:MeteringMode>(.+?)</exif:MeteringMode>%msu',
|
||||
'%exif:MeteringMode="(.+?)"%msu'
|
||||
],
|
||||
'FocalLength' => [
|
||||
'%<exif:FocalLength>(.+?)</exif:FocalLength>%msu',
|
||||
'%exif:FocalLength="(.+?)"%msu'
|
||||
],
|
||||
'Lens' => [
|
||||
'%<aux:Lens>(.+?)</aux:Lens>%msu',
|
||||
'%aux:Lens="(.+?)"%msu'
|
||||
],
|
||||
'CountryCode' => [
|
||||
'%<Iptc4xmpCore:CountryCode>(.+?)</Iptc4xmpCore:CountryCode>%msu',
|
||||
'%Iptc4xmpCore:CountryCode="(.+?)"%msu'
|
||||
],
|
||||
'Country' => [
|
||||
'%<photoshop:Country>(.+?)</photoshop:Country>%msu',
|
||||
'%photoshop:Country="(.+?)"%msu'
|
||||
],
|
||||
'State' => [
|
||||
'%<photoshop:State>(.+?)</photoshop:State>%msu',
|
||||
'%photoshop:State="(.+?)"%msu'
|
||||
],
|
||||
'City' => [
|
||||
'%<photoshop:City>(.+?)</photoshop: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'
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user