3 namespace Drupal\file_mdm_exif\Plugin\FileMetadata;
5 use Drupal\Core\Cache\CacheBackendInterface;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\file_mdm\Plugin\FileMetadata\FileMetadataPluginBase;
8 use Drupal\file_mdm_exif\ExifTagMapperInterface;
9 use Symfony\Component\DependencyInjection\ContainerInterface;
10 use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
11 use lsolesen\pel\PelEntry;
12 use lsolesen\pel\PelExif;
13 use lsolesen\pel\PelIfd;
14 use lsolesen\pel\PelJpeg;
15 use lsolesen\pel\PelTiff;
18 * FileMetadata plugin for EXIF.
22 * title = @Translation("EXIF"),
23 * help = @Translation("File metadata plugin for EXIF image information, using the PHP Exif Library (PEL)."),
26 class Exif extends FileMetadataPluginBase {
29 * The MIME type guessing service.
31 * @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
33 protected $mimeTypeGuesser;
36 * The EXIF tag mapping service.
38 * @var \Drupal\file_mdm_exif\ExifTagMapperInterface
43 * The PEL file object being processed.
45 * @var \lsolesen\pel\PelJpeg|\lsolesen\pel\PelTiff
50 * Constructs an Exif file metadata plugin.
52 * @param array $configuration
53 * A configuration array containing information about the plugin instance.
54 * @param string $plugin_id
55 * The plugin_id for the plugin instance.
56 * @param array $plugin_definition
57 * The plugin implementation definition.
58 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_service
60 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
62 * @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser
63 * The MIME type mapping service.
64 * @param \Drupal\file_mdm_exif\ExifTagMapperInterface $tag_mapper
65 * The EXIF tag mapping service.
67 public function __construct(array $configuration, $plugin_id, array $plugin_definition, CacheBackendInterface $cache_service, ConfigFactoryInterface $config_factory, MimeTypeGuesserInterface $mime_type_guesser, ExifTagMapperInterface $tag_mapper) {
68 parent::__construct($configuration, $plugin_id, $plugin_definition, $cache_service, $config_factory);
69 $this->mimeTypeGuesser = $mime_type_guesser;
70 $this->tagMapper = $tag_mapper;
76 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
81 $container->get('cache.file_mdm'),
82 $container->get('config.factory'),
83 $container->get('file.mime_type.guesser'),
84 $container->get('file_mdm_exif.tag_mapper')
91 public function getSupportedKeys($options = NULL) {
92 return $this->tagMapper->getSupportedKeys($options);
96 * Returns the PEL file object for the image file.
98 * @return \lsolesen\pel\PelJpeg|\lsolesen\pel\PelTiff
101 protected function getFile() {
102 if ($this->pelFile !== NULL) {
103 return $this->pelFile;
106 switch ($this->mimeTypeGuesser->guess($this->getUri())) {
108 $this->pelFile = new PelJpeg($this->getLocalTempPath());
109 return $this->pelFile !== NULL ? $this->pelFile : FALSE;
112 $this->pelFile = new PelTiff($this->getLocalTempPath());
113 return $this->pelFile !== NULL ? $this->pelFile : FALSE;
125 protected function doGetMetadataFromFile() {
126 // Get the file as a PelJpeg or PelTiff object.
127 $file = $this->getFile();
132 // Get the TIFF section if existing, or return if not.
133 if ($file instanceof PelJpeg) {
134 $exif = $file->getExif();
135 if ($exif === NULL) {
138 $tiff = $exif->getTiff();
139 if ($tiff === NULL) {
143 elseif ($file instanceof PelTiff) {
147 // Scans metadata for entries of supported tags.
149 $keys = $this->tagMapper->getSupportedKeys();
150 foreach ($keys as $key) {
151 $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
152 if ($entry = $this->getEntry($tiff, $ifd_tag['ifd'], $ifd_tag['tag'])) {
153 $metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = $entry;
160 * Returns a PelEntry.
162 * @param \lsolesen\pel\PelTiff $tiff
164 * @param int $ifd_tag
165 * The IFD EXIF integer identifier.
166 * @param int $key_tag
167 * The TAG EXIF integer identifier.
169 * @return \lsolesen\pel\PelEntry
170 * The PelEntry for the specified IFD and TAG.
172 protected function getEntry(PelTiff $tiff, $ifd_tag, $key_tag) {
173 $ifd = $tiff->getIfd();
176 return $ifd->getEntry($key_tag);
179 $ifd1 = $ifd->getNextIfd();
183 return $ifd1->getEntry($key_tag);
186 $exif = $ifd->getSubIfd(PelIfd::EXIF);
190 return $exif->getEntry($key_tag);
192 case PelIfd::INTEROPERABILITY:
193 $exif = $ifd->getSubIfd(PelIfd::EXIF);
197 $interop = $exif->getSubIfd(PelIfd::INTEROPERABILITY);
201 return $interop->getEntry($key_tag);
204 $gps = $ifd->getSubIfd(PelIfd::GPS);
208 return $gps->getEntry($key_tag);
216 public function isSaveToFileSupported() {
223 protected function doSaveMetadataToFile() {
224 // Get the file as a PelJpeg or PelTiff object.
225 $file = $this->getFile();
230 // Get the TIFF section if existing, or create one if not.
231 if ($file instanceof PelJpeg) {
232 $exif = $file->getExif();
233 if ($exif === NULL) {
234 // If EXIF section is missing we simply create a new APP1 section
235 // (a PelExif object) and add it to the PelJpeg object.
236 $exif = new PelExif();
237 $file->setExif($exif);
239 $tiff = $exif->getTiff();
240 if ($tiff === NULL) {
241 // Same for TIFF section.
242 $tiff = new PelTiff();
243 $exif->setTiff($tiff);
246 elseif ($file instanceof PelTiff) {
250 // Get IFD0 if existing, or create it if not.
251 $ifd0 = $tiff->getIfd();
252 if ($ifd0 === NULL) {
253 // No IFD in the TIFF data, we just create and insert an empty PelIfd
255 $ifd0 = new PelIfd(PelIfd::IFD0);
256 $tiff->setIfd($ifd0);
259 // Loops through in-memory metadata and update tag entries accordingly.
260 foreach ($this->metadata as $ifd_id => $entries) {
263 $this->setIfdEntries($ifd0, $entries);
267 $ifd1 = $ifd0->getNextIfd();
268 if ($ifd1 === NULL) {
269 $ifd1 = new PelIfd(PelIfd::IFD1);
270 $ifd0->setNextIfd($ifd1);
272 $this->setIfdEntries($ifd1, $entries);
276 $exif = $ifd0->getSubIfd(PelIfd::EXIF);
277 if ($exif === NULL) {
278 $exif = new PelIfd(PelIfd::EXIF);
279 $ifd0->addSubIfd($exif);
281 $this->setIfdEntries($exif, $entries);
284 case PelIfd::INTEROPERABILITY:
285 $exif = $ifd0->getSubIfd(PelIfd::EXIF);
286 if ($exif === NULL) {
287 $exif = new PelIfd(PelIfd::EXIF);
288 $ifd0->addSubIfd($exif);
290 $interop = $exif->getSubIfd(PelIfd::INTEROPERABILITY);
291 if ($interop === NULL) {
292 $interop = new PelIfd(PelIfd::INTEROPERABILITY);
293 $exif->addSubIfd($interop);
295 $this->setIfdEntries($interop, $entries);
299 $gps = $ifd0->getSubIfd(PelIfd::GPS);
301 $gps = new PelIfd(PelIfd::GPS);
302 $ifd0->addSubIfd($gps);
304 $this->setIfdEntries($gps, $entries);
310 return $file->saveFile($this->getLocalTempPath()) === FALSE ? FALSE : TRUE;
314 * Adds or changes entries for an IFD.
316 * @param lsolesen\pel\PelIfd $ifd
318 * @param lsolesen\pel\PelEntry[] $entries
319 * An array of PelEntry objects.
322 * TRUE if entries were added/changed successfully, FALSE otherwise.
324 protected function setIfdEntries(PelIfd $ifd, array $entries) {
325 foreach ($entries as $tag => $input_entry) {
326 if ($c = $ifd->getEntry($tag)) {
327 if ($input_entry === 'deleted') {
331 $c->setValue($input_entry->getValue());
335 if ($input_entry !== 'deleted') {
336 $ifd->addEntry($input_entry);
346 protected function doGetMetadata($key = NULL) {
347 if (!$this->metadata) {
351 return $this->metadata;
354 $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
355 if (!isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']]) || $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] === 'deleted') {
358 $entry = $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']];
360 'value' => $entry->getValue(),
361 'text' => $entry->getText(),
369 protected function doSetMetadata($key, $value) {
370 $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
371 if ($value instanceof PelEntry) {
372 $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = $value;
375 elseif (isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']])) {
376 $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']]->setValue($value);
385 protected function doRemoveMetadata($key) {
386 if (!$this->metadata || !$key) {
390 $ifd_tag = $this->tagMapper->resolveKeyToIfdAndTag($key);
391 if (isset($this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']])) {
392 $this->metadata[$ifd_tag['ifd']][$ifd_tag['tag']] = 'deleted';