290 lines
9.1 KiB
PHP
290 lines
9.1 KiB
PHP
|
<?php
|
||
|
/**
|
||
|
* PHP Exif Native Mapper
|
||
|
*
|
||
|
* @link http://github.com/miljar/PHPExif for the canonical source repository
|
||
|
* @copyright Copyright (c) 2015 Tom Van Herreweghe <tom@theanalogguy.be>
|
||
|
* @license http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License
|
||
|
* @category PHPExif
|
||
|
* @package Mapper
|
||
|
*/
|
||
|
|
||
|
namespace PHPExif\Mapper;
|
||
|
|
||
|
use PHPExif\Exif;
|
||
|
use DateTime;
|
||
|
use Exception;
|
||
|
|
||
|
/**
|
||
|
* PHP Exif Native Mapper
|
||
|
*
|
||
|
* Maps native raw data to valid data for the \PHPExif\Exif class
|
||
|
*
|
||
|
* @category PHPExif
|
||
|
* @package Mapper
|
||
|
*/
|
||
|
class Native implements MapperInterface
|
||
|
{
|
||
|
const APERTUREFNUMBER = 'ApertureFNumber';
|
||
|
const ARTIST = 'Artist';
|
||
|
const CAPTION = 'caption';
|
||
|
const COLORSPACE = 'ColorSpace';
|
||
|
const COPYRIGHT = 'copyright';
|
||
|
const DATETIMEORIGINAL = 'DateTimeOriginal';
|
||
|
const CREDIT = 'credit';
|
||
|
const EXPOSURETIME = 'ExposureTime';
|
||
|
const FILESIZE = 'FileSize';
|
||
|
const FOCALLENGTH = 'FocalLength';
|
||
|
const FOCUSDISTANCE = 'FocusDistance';
|
||
|
const HEADLINE = 'headline';
|
||
|
const HEIGHT = 'Height';
|
||
|
const ISOSPEEDRATINGS = 'ISOSpeedRatings';
|
||
|
const JOBTITLE = 'jobtitle';
|
||
|
const KEYWORDS = 'keywords';
|
||
|
const MIMETYPE = 'MimeType';
|
||
|
const MODEL = 'Model';
|
||
|
const ORIENTATION = 'Orientation';
|
||
|
const SOFTWARE = 'Software';
|
||
|
const SOURCE = 'source';
|
||
|
const TITLE = 'title';
|
||
|
const WIDTH = 'Width';
|
||
|
const XRESOLUTION = 'XResolution';
|
||
|
const YRESOLUTION = 'YResolution';
|
||
|
const GPSLATITUDE = 'GPSLatitude';
|
||
|
const GPSLONGITUDE = 'GPSLongitude';
|
||
|
|
||
|
const SECTION_FILE = 'FILE';
|
||
|
const SECTION_COMPUTED = 'COMPUTED';
|
||
|
const SECTION_IFD0 = 'IFD0';
|
||
|
const SECTION_THUMBNAIL = 'THUMBNAIL';
|
||
|
const SECTION_COMMENT = 'COMMENT';
|
||
|
const SECTION_EXIF = 'EXIF';
|
||
|
const SECTION_ALL = 'ANY_TAG';
|
||
|
const SECTION_IPTC = 'IPTC';
|
||
|
|
||
|
/**
|
||
|
* A list of section names
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $sections = array(
|
||
|
self::SECTION_FILE,
|
||
|
self::SECTION_COMPUTED,
|
||
|
self::SECTION_IFD0,
|
||
|
self::SECTION_THUMBNAIL,
|
||
|
self::SECTION_COMMENT,
|
||
|
self::SECTION_EXIF,
|
||
|
self::SECTION_ALL,
|
||
|
self::SECTION_IPTC,
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Maps the ExifTool fields to the fields of
|
||
|
* the \PHPExif\Exif class
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $map = array(
|
||
|
self::APERTUREFNUMBER => Exif::APERTURE,
|
||
|
self::FOCUSDISTANCE => Exif::FOCAL_DISTANCE,
|
||
|
self::HEIGHT => Exif::HEIGHT,
|
||
|
self::WIDTH => Exif::WIDTH,
|
||
|
self::CAPTION => Exif::CAPTION,
|
||
|
self::COPYRIGHT => Exif::COPYRIGHT,
|
||
|
self::CREDIT => Exif::CREDIT,
|
||
|
self::HEADLINE => Exif::HEADLINE,
|
||
|
self::JOBTITLE => Exif::JOB_TITLE,
|
||
|
self::KEYWORDS => Exif::KEYWORDS,
|
||
|
self::SOURCE => Exif::SOURCE,
|
||
|
self::TITLE => Exif::TITLE,
|
||
|
self::ARTIST => Exif::AUTHOR,
|
||
|
self::MODEL => Exif::CAMERA,
|
||
|
self::COLORSPACE => Exif::COLORSPACE,
|
||
|
self::DATETIMEORIGINAL => Exif::CREATION_DATE,
|
||
|
self::EXPOSURETIME => Exif::EXPOSURE,
|
||
|
self::FILESIZE => Exif::FILESIZE,
|
||
|
self::FOCALLENGTH => Exif::FOCAL_LENGTH,
|
||
|
self::ISOSPEEDRATINGS => Exif::ISO,
|
||
|
self::MIMETYPE => Exif::MIMETYPE,
|
||
|
self::ORIENTATION => Exif::ORIENTATION,
|
||
|
self::SOFTWARE => Exif::SOFTWARE,
|
||
|
self::XRESOLUTION => Exif::HORIZONTAL_RESOLUTION,
|
||
|
self::YRESOLUTION => Exif::VERTICAL_RESOLUTION,
|
||
|
self::GPSLATITUDE => Exif::GPS,
|
||
|
self::GPSLONGITUDE => Exif::GPS,
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Maps the array of raw source data to the correct
|
||
|
* fields for the \PHPExif\Exif class
|
||
|
*
|
||
|
* @param array $data
|
||
|
* @return array
|
||
|
*/
|
||
|
public function mapRawData(array $data)
|
||
|
{
|
||
|
$mappedData = array();
|
||
|
$gpsData = array();
|
||
|
foreach ($data as $field => $value) {
|
||
|
if ($this->isSection($field) && is_array($value)) {
|
||
|
$subData = $this->mapRawData($value);
|
||
|
|
||
|
$mappedData = array_merge($mappedData, $subData);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!$this->isFieldKnown($field)) {
|
||
|
// silently ignore unknown fields
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
$key = $this->map[$field];
|
||
|
|
||
|
// manipulate the value if necessary
|
||
|
switch ($field) {
|
||
|
case self::DATETIMEORIGINAL:
|
||
|
try {
|
||
|
$value = new DateTime($value);
|
||
|
} catch (Exception $exception) {
|
||
|
continue 2;
|
||
|
}
|
||
|
break;
|
||
|
case self::EXPOSURETIME:
|
||
|
if (!is_float($value)) {
|
||
|
$value = $this->normalizeComponent($value);
|
||
|
}
|
||
|
|
||
|
// Based on the source code of Exiftool (PrintExposureTime subroutine):
|
||
|
// http://cpansearch.perl.org/src/EXIFTOOL/Image-ExifTool-9.90/lib/Image/ExifTool/Exif.pm
|
||
|
if ($value < 0.25001 && $value > 0) {
|
||
|
$value = sprintf('1/%d', intval(0.5 + 1 / $value));
|
||
|
} else {
|
||
|
$value = sprintf('%.1f', $value);
|
||
|
$value = preg_replace('/.0$/', '', $value);
|
||
|
}
|
||
|
break;
|
||
|
case self::FOCALLENGTH:
|
||
|
$parts = explode('/', $value);
|
||
|
// Avoid division by zero if focal length is invalid
|
||
|
if (end($parts) == '0') {
|
||
|
$value = 0;
|
||
|
} else {
|
||
|
$value = (int) reset($parts) / (int) end($parts);
|
||
|
}
|
||
|
break;
|
||
|
case self::XRESOLUTION:
|
||
|
case self::YRESOLUTION:
|
||
|
$resolutionParts = explode('/', $value);
|
||
|
$value = (int) reset($resolutionParts);
|
||
|
break;
|
||
|
case self::GPSLATITUDE:
|
||
|
$gpsData['lat'] = $this->extractGPSCoordinate($value);
|
||
|
break;
|
||
|
case self::GPSLONGITUDE:
|
||
|
$gpsData['lon'] = $this->extractGPSCoordinate($value);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// set end result
|
||
|
$mappedData[$key] = $value;
|
||
|
}
|
||
|
|
||
|
// add GPS coordinates, if available
|
||
|
if (count($gpsData) === 2) {
|
||
|
$latitudeRef = empty($data['GPSLatitudeRef'][0]) ? 'N' : $data['GPSLatitudeRef'][0];
|
||
|
$longitudeRef = empty($data['GPSLongitudeRef'][0]) ? 'E' : $data['GPSLongitudeRef'][0];
|
||
|
|
||
|
$gpsLocation = sprintf(
|
||
|
'%s,%s',
|
||
|
(strtoupper($latitudeRef) === 'S' ? -1 : 1) * $gpsData['lat'],
|
||
|
(strtoupper($longitudeRef) === 'W' ? -1 : 1) * $gpsData['lon']
|
||
|
);
|
||
|
|
||
|
$mappedData[Exif::GPS] = $gpsLocation;
|
||
|
} else {
|
||
|
unset($mappedData[Exif::GPS]);
|
||
|
}
|
||
|
|
||
|
return $mappedData;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if given field is a section
|
||
|
*
|
||
|
* @param string $field
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function isSection($field)
|
||
|
{
|
||
|
return (in_array($field, $this->sections));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines if the given field is known,
|
||
|
* in a case insensitive way for its first letter.
|
||
|
* Also update $field to keep it valid against the known fields.
|
||
|
*
|
||
|
* @param string &$field
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function isFieldKnown(&$field)
|
||
|
{
|
||
|
$lcfField = lcfirst($field);
|
||
|
if (array_key_exists($lcfField, $this->map)) {
|
||
|
$field = $lcfField;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
$ucfField = ucfirst($field);
|
||
|
if (array_key_exists($ucfField, $this->map)) {
|
||
|
$field = $ucfField;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract GPS coordinates from components array
|
||
|
*
|
||
|
* @param array|string $components
|
||
|
* @return float
|
||
|
*/
|
||
|
protected function extractGPSCoordinate($components)
|
||
|
{
|
||
|
if (!is_array($components)) {
|
||
|
$components = array($components);
|
||
|
}
|
||
|
$components = array_map(array($this, 'normalizeComponent'), $components);
|
||
|
|
||
|
if (count($components) > 2) {
|
||
|
return floatval($components[0]) + (floatval($components[1]) / 60) + (floatval($components[2]) / 3600);
|
||
|
}
|
||
|
|
||
|
return reset($components);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Normalize component
|
||
|
*
|
||
|
* @param mixed $component
|
||
|
* @return int|float
|
||
|
*/
|
||
|
protected function normalizeComponent($component)
|
||
|
{
|
||
|
$parts = explode('/', $component);
|
||
|
|
||
|
if (count($parts) > 1) {
|
||
|
if ($parts[1]) {
|
||
|
return intval($parts[0]) / intval($parts[1]);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return floatval(reset($parts));
|
||
|
}
|
||
|
}
|