Skip to content

Commit

Permalink
Merge pull request #1570 from Nazar65/ASI-IPTC-ABJUSTMENTS
Browse files Browse the repository at this point in the history
Add support for writing new IPTC segments for jpeg format and new XMP segments for PNG and GIF images formats
  • Loading branch information
sivaschenko authored Jul 22, 2020
2 parents b2e96b4 + e21e98e commit eed0caa
Show file tree
Hide file tree
Showing 15 changed files with 758 additions and 79 deletions.
140 changes: 114 additions & 26 deletions MediaGalleryMetadata/Model/AddIptcMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface;
use Magento\MediaGalleryMetadataApi\Model\FileInterface;
use Magento\MediaGalleryMetadataApi\Model\SegmentInterface;
use Magento\MediaGalleryMetadata\Model\Jpeg\FileReader;
use Magento\Framework\Filesystem\DriverInterface;
use Magento\MediaGalleryMetadataApi\Model\FileInterfaceFactory;
use Magento\Framework\Exception\LocalizedException;

/**
* Add metadata to the IPTC data
Expand All @@ -20,57 +24,141 @@ class AddIptcMetadata
private const IPTC_DESCRIPTION_SEGMENT = '2#120';
private const IPTC_KEYWORDS_SEGMENT = '2#025';

/**
* @var DriverInterface
*/
private $driver;

/**
* @var FileReader
*/
private $fileReader;

/**
* @var FileInterfaceFactory
*/
private $fileFactory;

/**
* @param FileInterfaceFactory $fileFactory
* @param DriverInterface $driver
* @param FileReader $fileReader
*/
public function __construct(
FileInterfaceFactory $fileFactory,
DriverInterface $driver,
FileReader $fileReader
) {
$this->fileFactory = $fileFactory;
$this->driver = $driver;
$this->fileReader = $fileReader;
}

/**
* Write metadata
*
* @param FileInterface $file
* @param MetadataInterface $metadata
* @param SegmentInterface $segment
* @return string
* @param null|SegmentInterface $segment
*/
public function execute(FileInterface $file, MetadataInterface $metadata, SegmentInterface $segment): string
public function execute(FileInterface $file, MetadataInterface $metadata, ?SegmentInterface $segment): FileInterface
{
if (is_callable('iptcembed')) {
$iptcData = iptcparse($segment->getData());
if (!empty($metadata->getTitle())) {
$iptcData[self::IPTC_TITLE_SEGMENT][0] = $metadata->getTitle();
}
if (!is_callable('iptcembed') && !is_callable('iptcparse')) {
throw new LocalizedException(__('iptcembed() && iptcparse() must be enabled in php configuration'));
}

$iptcData = $segment ? iptcparse($segment->getData()) : [];

if (!empty($metadata->getDescription())) {
$iptcData[self::IPTC_DESCRIPTION_SEGMENT][0] = $metadata->getDescription();
}
if (!empty($metadata->getTitle())) {
$iptcData[self::IPTC_TITLE_SEGMENT][0] = $metadata->getTitle();
}

if (!empty($metadata->getKeywords())) {
foreach ($metadata->getKeywords() as $key => $keyword) {
$iptcData[self::IPTC_KEYWORDS_SEGMENT][$key] = $keyword;
}
}
if (!empty($metadata->getDescription())) {
$iptcData[self::IPTC_DESCRIPTION_SEGMENT][0] = $metadata->getDescription();
}

if (!empty($metadata->getKeywords())) {
$iptcData = $this->writeKeywords($metadata->getKeywords(), $iptcData);
}

$newData = '';
$newData = '';

foreach ($iptcData as $tag => $values) {
foreach ($values as $value) {
$newData .= $this->iptcMaketag(2, substr($tag, 2), $value);
}
foreach ($iptcData as $tag => $values) {
foreach ($values as $value) {
$newData .= $this->iptcMaketag(2, (int) substr($tag, 2), $value);
}
$content = iptcembed($newData, $file->getPath());
}

$this->writeFile($file->getPath(), iptcembed($newData, $file->getPath()));

$fileWithIptc = $this->fileReader->execute($file->getPath());

return $this->fileFactory->create([
'path' => $fileWithIptc->getPath(),
'segments' => $this->getSegmentsWithIptc($fileWithIptc, $file)
]);
}

/**
* Return iptc segment from file.
*
* @param FileInterface $fileWithIptc
* @param FileInterface $originFile
*/
private function getSegmentsWithIptc(FileInterface $fileWithIptc, $originFile): array
{
$segments = $fileWithIptc->getSegments();
$originFileSegments = $originFile->getSegments();

return $content;
foreach ($segments as $key => $segment) {
if ($segment->getName() === 'APP13') {
$originFileSegments[$key] = $segments[$key];
return $originFileSegments;
}
}
return $originFileSegments;
}

/**
* Write keywords field to the iptc segment.
*
* @param array $keywords
* @param array $iptcData
*/
private function writeKeywords(array $keywords, array $iptcData): array
{
foreach ($keywords as $key => $keyword) {
$iptcData[self::IPTC_KEYWORDS_SEGMENT][$key] = $keyword;
}
return $iptcData;
}

/**
* Write iptc data to the image directly to the file.
*
* @param string $filePath
* @param string $content
*/
private function writeFile(string $filePath, string $content): void
{
$resource = $this->driver->fileOpen($filePath, 'wb');

$this->driver->fileWrite($resource, $content);
$this->driver->fileClose($resource);
}

/**
* Create new iptc tag text
*
* @param int $rec
* @param string $tag
* @param int $tag
* @param string $value
*/
private function iptcMaketag($rec, $tag, $value)
private function iptcMaketag(int $rec, int $tag, string $value)
{
//phpcs:disable Magento2.Functions.DiscouragedFunction
$length = strlen($value);
$retval = chr(0x1C) . chr($rec) . chr((int)$tag);
$retval = chr(0x1C) . chr($rec) . chr($tag);

if ($length < 0x8000) {
$retval .= chr($length >> 8) . chr($length & 0xFF);
Expand Down
27 changes: 12 additions & 15 deletions MediaGalleryMetadata/Model/Gif/FileReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private function getSegments($resource): array
if ($separator == $gifFrameSeparator) {
$segments[] = $this->segmentFactory->create([
'name' => 'frame',
'data' => $this->readFrame($resource)
'data' => $gifFrameSeparator . $this->readFrame($resource)
]);
continue;
}
Expand All @@ -154,7 +154,6 @@ private function getSegments($resource): array
}

$segments[] = $this->getExtensionSegment($resource);

} while (!$this->driver->endOfFile($resource));

return $segments;
Expand All @@ -169,28 +168,29 @@ private function getSegments($resource): array
*/
private function getExtensionSegment($resource): SegmentInterface
{
$gifExtensionSeparator = pack("C", ord("!"));
$extensionCodeBinary = $this->read($resource, 1);
//phpcs:ignore Magento2.Functions.DiscouragedFunction
$extensionCode = unpack('C', $extensionCodeBinary)[1];

if ($extensionCode == 0xF9) {
return $this->segmentFactory->create([
'name' => 'Graphics Control Extension',
'data' => $extensionCodeBinary . $this->readBlock($resource)
'data' => $gifExtensionSeparator . $extensionCodeBinary . $this->readBlock($resource)
]);
}

if ($extensionCode == 0xFE) {
return $this->segmentFactory->create([
'name' => 'comment',
'data' => $extensionCodeBinary . $this->readBlock($resource)
'data' => $gifExtensionSeparator . $extensionCodeBinary . $this->readBlock($resource)
]);
}

if ($extensionCode != 0xFF) {
return $this->segmentFactory->create([
'name' => 'unknown',
'data' => $extensionCodeBinary . $this->readBlock($resource)
'name' => 'Programm extension',
'data' => $gifExtensionSeparator . $extensionCodeBinary . $this->readBlock($resource)
]);
}

Expand All @@ -206,14 +206,15 @@ private function getExtensionSegment($resource): SegmentInterface
if ($name == 'XMP DataXMP') {
return $this->segmentFactory->create([
'name' => $name,
'data' => $extensionCodeBinary . $blockLengthBinary
'data' => $gifExtensionSeparator . $extensionCodeBinary . $blockLengthBinary
. $name . $this->readBlockWithSubblocks($resource)
]);
}

return $this->segmentFactory->create([
'name' => $name,
'data' => $extensionCodeBinary . $blockLengthBinary . $name . $this->readBlock($resource)
'data' => $gifExtensionSeparator . $extensionCodeBinary . $blockLengthBinary
. $name . $this->readBlock($resource)
]);
}

Expand Down Expand Up @@ -300,17 +301,13 @@ private function readBlockWithSubblocks($resource): string
{
$data = '';
$subLength = $this->read($resource, 1);
$blocks = 0;

while ($subLength !== "\0") {
$blocks++;
$data .= $subLength;

$data .= $this->read($resource, ord($subLength));
$data .= $subLength . $this->read($resource, ord($subLength));
$subLength = $this->read($resource, 1);
}

return $data;
return $data . $subLength;
}

/**
Expand All @@ -327,6 +324,6 @@ private function readBlock($resource): string
if ($blockLength == 0) {
return '';
}
return $blockLength . $this->read($resource, $blockLength) . $this->read($resource, 1);
return $blockLengthBinary . $this->read($resource, $blockLength) . $this->read($resource, 1);
}
}
76 changes: 76 additions & 0 deletions MediaGalleryMetadata/Model/Gif/FileWriter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\MediaGalleryMetadata\Model\Gif;

use Magento\Framework\Exception\FileSystemException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem\DriverInterface;
use Magento\MediaGalleryMetadataApi\Model\FileInterface;
use Magento\MediaGalleryMetadataApi\Model\FileWriterInterface;
use Magento\MediaGalleryMetadataApi\Model\SegmentInterface;
use Magento\MediaGalleryMetadata\Model\SegmentNames;

/**
* File segments writer
*/
class FileWriter implements FileWriterInterface
{
/**
* @var DriverInterface
*/
private $driver;

/**
* @var SegmentNames
*/
private $segmentNames;

/**
* @param DriverInterface $driver
* @param SegmentNames $segmentNames
*/
public function __construct(
DriverInterface $driver,
SegmentNames $segmentNames
) {
$this->driver = $driver;
$this->segmentNames = $segmentNames;
}

/**
* Write file object to the filesystem
*
* @param FileInterface $file
* @throws LocalizedException
* @throws FileSystemException
*/
public function execute(FileInterface $file): void
{
$resource = $this->driver->fileOpen($file->getPath(), 'wb');

$this->writeSegments($resource, $file->getSegments());
$this->driver->fileClose($resource);
}

/**
* Write gif segment
*
* @param resource $resource
* @param SegmentInterface[] $segments
*/
private function writeSegments($resource, array $segments): void
{
foreach ($segments as $segment) {
$this->driver->fileWrite(
$resource,
$segment->getData()
);
}
$this->driver->fileWrite($resource, pack("C", ord(";")));
}
}
8 changes: 4 additions & 4 deletions MediaGalleryMetadata/Model/Gif/Segment/XmpReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ class XmpReader implements MetadataReaderInterface
/**
* see XMP Specification Part 3, 1.1.2 GIF
*/
private const MAGIC_TRAILER_LENGTH = 257;
private const MAGIC_TRAILER_LENGTH = 258;
private const MAGIC_TRAILER_START = "\x01\xFF\xFE";
private const MAGIC_TRAILER_END = "\x03\x02\x01\x00";
private const MAGIC_TRAILER_END = "\x03\x02\x01\x00\x00";

/**
* @var MetadataInterfaceFactory
Expand Down Expand Up @@ -84,10 +84,10 @@ private function isXmp(SegmentInterface $segment): bool
*/
private function getXmpData(SegmentInterface $segment): string
{
$xmp = substr($segment->getData(), 13);
$xmp = substr($segment->getData(), 14);

if (substr($xmp, -self::MAGIC_TRAILER_LENGTH, 3) !== self::MAGIC_TRAILER_START
|| substr($xmp, -4) !== self::MAGIC_TRAILER_END
|| substr($xmp, -5) !== self::MAGIC_TRAILER_END
) {
throw new LocalizedException(__('XMP data is corrupted'));
}
Expand Down
Loading

0 comments on commit eed0caa

Please sign in to comment.