Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toReadable method #96

Merged
merged 4 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/Metadata/AbstractFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@

abstract class AbstractFormat implements MetadataFormatInterface
{
public function toReadable(array $data): array
{
$data = $this->prefixIntKeys($data, static::NAME.'_');

return array_map(
function ($value) {
return $this->ensureStringList($value);
},
$data
);
}

/**
* @param array|string $values
*/
Expand Down Expand Up @@ -60,4 +72,42 @@ protected function toUtf8(array $values): array
]
);
}

protected function prefixIntKeys(array $data, string $prefix): array
{
foreach ($data as $key => $value) {
if (\is_int($key)) {
$data[$prefix.$key] = $value;
unset($data[$key]);
}
}

return $data;
}

/**
* @return list<string>
*/
protected function ensureStringList($value): array
{
$value = array_map(
function ($value) {
if (\is_array($value)) {
return implode(', ', $this->ensureStringList($value));
}

return trim((string) $value);
},
(array) $value
);

return array_values(
array_filter(
$value,
static function ($value) {
return '' !== $value;
}
)
);
}
}
5 changes: 5 additions & 0 deletions src/Metadata/ExifFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ public function parse(string $binaryChunk): array
return $this->toUtf8($data);
}

public function toReadable(array $data): array
{
return parent::toReadable(array_merge([], ...array_values($data)));
}

private function buildExif(string $copyright, string $artist): string
{
$data = [];
Expand Down
83 changes: 83 additions & 0 deletions src/Metadata/IptcFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,89 @@ public function parse(string $binaryChunk): array
return $this->toUtf8($data);
}

public function toReadable(array $data): array
{
$keys = array_map(
static function ($key) {
return [
'2#000' => 'ApplicationRecordVersion',
'2#003' => 'ObjectTypeReference',
'2#004' => 'ObjectAttributeReference',
'2#005' => 'ObjectName',
'2#007' => 'EditStatus',
'2#008' => 'EditorialUpdate',
'2#010' => 'Urgency',
'2#012' => 'SubjectReference',
'2#015' => 'Category',
'2#020' => 'SupplementalCategories',
'2#022' => 'FixtureIdentifier',
'2#025' => 'Keywords',
'2#026' => 'ContentLocationCode',
'2#027' => 'ContentLocationName',
'2#030' => 'ReleaseDate',
'2#035' => 'ReleaseTime',
'2#037' => 'ExpirationDate',
'2#038' => 'ExpirationTime',
'2#040' => 'SpecialInstructions',
'2#042' => 'ActionAdvised',
'2#045' => 'ReferenceService',
'2#047' => 'ReferenceDate',
'2#050' => 'ReferenceNumber',
'2#055' => 'DateCreated',
'2#060' => 'TimeCreated',
'2#062' => 'DigitalCreationDate',
'2#063' => 'DigitalCreationTime',
'2#065' => 'OriginatingProgram',
'2#070' => 'ProgramVersion',
'2#075' => 'ObjectCycle',
'2#080' => 'By-line',
'2#085' => 'By-lineTitle',
'2#090' => 'City',
'2#092' => 'Sub-location',
'2#095' => 'Province-State',
'2#100' => 'Country-PrimaryLocationCode',
'2#101' => 'Country-PrimaryLocationName',
'2#103' => 'OriginalTransmissionReference',
'2#105' => 'Headline',
'2#110' => 'Credit',
'2#115' => 'Source',
'2#116' => 'CopyrightNotice',
'2#118' => 'Contact',
'2#120' => 'Caption-Abstract',
'2#121' => 'LocalCaption',
'2#122' => 'Writer-Editor',
'2#125' => 'RasterizedCaption',
'2#130' => 'ImageType',
'2#131' => 'ImageOrientation',
'2#135' => 'LanguageIdentifier',
'2#150' => 'AudioType',
'2#151' => 'AudioSamplingRate',
'2#152' => 'AudioSamplingResolution',
'2#153' => 'AudioDuration',
'2#154' => 'AudioOutcue',
'2#184' => 'JobID',
'2#185' => 'MasterDocumentID',
'2#186' => 'ShortDocumentID',
'2#187' => 'UniqueDocumentID',
'2#188' => 'OwnerID',
'2#200' => 'ObjectPreviewFileFormat',
'2#201' => 'ObjectPreviewFileVersion',
'2#202' => 'ObjectPreviewData',
'2#221' => 'Prefs',
'2#225' => 'ClassifyState',
'2#228' => 'SimilarityIndex',
'2#230' => 'DocumentNotes',
'2#231' => 'DocumentHistory',
'2#232' => 'ExifCameraInfo',
'2#255' => 'CatalogSets',
][$key] ?? $key;
},
array_keys($data)
);

return parent::toReadable(array_combine($keys, $data));
}

private function buildIptc(array $metadata): string
{
$iptc = "\x1C\x01".\chr(90); // 1:90 Coded Character Set
Expand Down
5 changes: 5 additions & 0 deletions src/Metadata/MetadataFormatInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ public function serialize(ImageMetadata $metadata, array $preserveKeys): string;
* @throws InvalidImageMetadataException
*/
public function parse(string $binaryChunk): array;

/**
* @return array<string,list<string>>
*/
public function toReadable(array $data): array;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of array do I have to expect here? Maybe worth a comment in the interface?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7c69e59

}
14 changes: 14 additions & 0 deletions src/Metadata/MetadataReaderWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ public function serializeFormat(string $format, ImageMetadata $metadata, array $
return $this->formats[$format]->serialize($metadata, $preserveKeys);
}

/**
* @return array<string,array<string,list<string>>>
*/
public function toReadable(ImageMetadata $metadata): array
{
$readable = [];

foreach ($metadata->getAll() as $format => $data) {
$readable[$format] = $this->formats[$format]->toReadable($data);
}

return $readable;
}

/**
* @param resource $stream
*/
Expand Down
107 changes: 107 additions & 0 deletions src/Metadata/XmpFormat.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,98 @@ class XmpFormat extends AbstractFormat
'http://ns.adobe.com/photoshop/1.0/' => ['Source', 'Credit'],
];

/**
* @see https://github.com/exiftool/exiftool/blob/624ac0d/lib/Image/ExifTool/XMP.pm
*/
private const NAMESPACE_ALIAS = [
'http://ns.adobe.com/exif/1.0/aux/' => 'aux',
'http://ns.adobe.com/album/1.0/' => 'album',
'http://creativecommons.org/ns#' => 'cc',
'http://ns.adobe.com/camera-raw-defaults/1.0/' => 'crd',
'http://ns.adobe.com/camera-raw-settings/1.0/' => 'crs',
'http://ns.adobe.com/camera-raw-saved-settings/1.0/' => 'crss',
'http://purl.org/dc/elements/1.1/' => 'dc',
'http://ns.adobe.com/exif/1.0/' => 'exif',
'http://cipa.jp/exif/1.0/' => 'exifEX',
'http://ns.adobe.com/iX/1.0/' => 'iX',
'http://ns.adobe.com/pdf/1.3/' => 'pdf',
'http://ns.adobe.com/pdfx/1.3/' => 'pdfx',
'http://ns.adobe.com/photoshop/1.0/' => 'photoshop',
'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
'http://www.w3.org/2000/01/rdf-schema#' => 'rdfs',
'http://ns.adobe.com/xap/1.0/sType/Dimensions#' => 'stDim',
'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#' => 'stEvt',
'http://ns.adobe.com/xap/1.0/sType/Font#' => 'stFnt',
'http://ns.adobe.com/xap/1.0/sType/Job#' => 'stJob',
'http://ns.adobe.com/xap/1.0/sType/ResourceRef#' => 'stRef',
'http://ns.adobe.com/xap/1.0/sType/Version#' => 'stVer',
'http://ns.adobe.com/xap/1.0/sType/ManifestItem#' => 'stMfs',
'http://ns.adobe.com/photoshop/1.0/camera-profile' => 'stCamera',
'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/' => 'crlcp',
'http://ns.adobe.com/tiff/1.0/' => 'tiff',
'adobe:ns:meta' => 'x',
'http://ns.adobe.com/xap/1.0/g/' => 'xmpG',
'http://ns.adobe.com/xap/1.0/g/img/' => 'xmpGImg',
'http://ns.adobe.com/xap/1.0/' => 'xmp',
'http://ns.adobe.com/xap/1.0/bj/' => 'xmpBJ',
'http://ns.adobe.com/xmp/1.0/DynamicMedia/' => 'xmpDM',
'http://ns.adobe.com/xap/1.0/mm/' => 'xmpMM',
'http://ns.adobe.com/xap/1.0/rights/' => 'xmpRights',
'http://ns.adobe.com/xmp/note/' => 'xmpNote',
'http://ns.adobe.com/xap/1.0/t/pg/' => 'xmpTPg',
'http://ns.adobe.com/xmp/Identifier/qual/1.0/' => 'xmpidq',
'http://ns.adobe.com/xap/1.0/PLUS/' => 'xmpPLUS',
'http://ns.optimasc.com/dex/1.0/' => 'dex',
'http://ns.iview-multimedia.com/mediapro/1.0/' => 'mediapro',
'http://ns.microsoft.com/expressionmedia/1.0/' => 'expressionmedia',
'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => 'Iptc4xmpCore',
'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => 'Iptc4xmpExt',
'http://ns.microsoft.com/photo/1.0' => 'MicrosoftPhoto',
'http://ns.microsoft.com/photo/1.1' => 'MP1',
'http://ns.microsoft.com/photo/1.2/' => 'MP',
'http://ns.microsoft.com/photo/1.2/t/RegionInfo#' => 'MPRI',
'http://ns.microsoft.com/photo/1.2/t/Region#' => 'MPReg',
'http://ns.adobe.com/lightroom/1.0/' => 'lr',
'http://ns.adobe.com/DICOM/' => 'DICOM',
'http://www.dji.com/drone-dji/1.0/' => 'drone-dji',
'http://www.w3.org/2000/svg' => 'svg',
'http://ns.exiftool.org/1.0/' => 'et',
'http://ns.useplus.org/ldf/xmp/1.0/' => 'plus',
'http://prismstandard.org/namespaces/basic/2.0/' => 'prism',
'http://prismstandard.org/namespaces/prl/2.1/' => 'prl',
'http://prismstandard.org/namespaces/prismusagerights/2.1/' => 'pur',
'http://prismstandard.org/namespaces/pmi/2.2/' => 'pmi',
'http://prismstandard.org/namespaces/prm/3.0/' => 'prm',
'http://ns.acdsee.com/iptc/1.0/' => 'acdsee',
'http://www.digikam.org/ns/1.0/' => 'digiKam',
'http://ns.adobe.com/swf/1.0/' => 'swf',
'http://developer.sonyericsson.com/cell/1.0/' => 'cell',
'http://ns.apple.com/adjustment-settings/1.0/' => 'aas',
'http://www.metadataworkinggroup.com/schemas/regions/' => 'mwg-rs',
'http://www.metadataworkinggroup.com/schemas/keywords/' => 'mwg-kw',
'http://www.metadataworkinggroup.com/schemas/collections/' => 'mwg-coll',
'http://ns.adobe.com/xmp/sType/Area#' => 'stArea',
'http://ns.extensis.com/extensis/1.0/' => 'extensis',
'http://ns.idimager.com/ics/1.0/' => 'ics',
'http://ns.fastpictureviewer.com/fpv/1.0/' => 'fpv',
'http://ns.adobe.com/creatorAtom/1.0/' => 'creatorAtom',
'http://ns.apple.com/faceinfo/1.0/' => 'apple-fi',
'http://ns.google.com/photos/1.0/audio/' => 'GAudio',
'http://ns.google.com/photos/1.0/image/' => 'GImage',
'http://ns.google.com/photos/1.0/panorama/' => 'GPano',
'http://ns.google.com/videos/1.0/spherical/' => 'GSpherical',
'http://ns.google.com/photos/1.0/depthmap/' => 'GDepth',
'http://ns.google.com/photos/1.0/focus/' => 'GFocus',
'http://ns.google.com/photos/1.0/camera/' => 'GCamera',
'http://ns.google.com/photos/1.0/creations/' => 'GCreations',
'http://rs.tdwg.org/dwc/index.htm' => 'dwc',
'http://xmp.gettyimages.com/gift/1.0/' => 'GettyImagesGIFT',
'http://ns.leiainc.com/photos/1.0/image/' => 'LImage',
'http://ns.google.com/photos/dd/1.0/profile/' => 'Profile',
'http://ns.nikon.com/sdc/1.0/' => 'sdc',
'http://ns.nikon.com/asteroid/1.0/' => 'ast',
'http://ns.nikon.com/nine/1.0/' => 'nine',
'http://ns.adobe.com/hdr-metadata/1.0/' => 'hdr_metadata',
];

public function serialize(ImageMetadata $metadata, array $preserveKeys): string
Expand Down Expand Up @@ -114,6 +202,25 @@ public function parse(string $binaryChunk): array
return $this->toUtf8(array_merge_recursive(...$metadata));
}

public function toReadable(array $data): array
{
$readable = [];

foreach ($data as $namespace => $attributes) {
$prefix = self::NAMESPACE_ALIAS[$namespace] ?? $namespace;

foreach ($attributes as $attribute => $value) {
if ('rdf' === $prefix && 'about' === $attribute && !$value) {
continue;
}

$readable["$prefix:$attribute"] = $value;
}
}

return parent::toReadable($readable);
}

private function buildXmp(array $metadata): string
{
$dom = $this->loadXml(
Expand Down
35 changes: 34 additions & 1 deletion tests/Metadata/ExifFormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ class ExifFormatTest extends TestCase
/**
* @dataProvider getParse
*/
public function testParse(string $source, array $expected): void
public function testParse(string $source, array $expected, array $expectedReadable): void
{
if (!$expected) {
$this->expectException(InvalidImageMetadataException::class);
}

$this->assertSame($expected, (new ExifFormat())->parse($source));
$this->assertSame($expectedReadable, (new ExifFormat())->toReadable((new ExifFormat())->parse($source)));
}

public function getParse(): \Generator
Expand All @@ -45,6 +46,10 @@ public function getParse(): \Generator
'Artist' => 'Arti',
],
],
[
'Copyright' => ['Copyright'],
'Artist' => ['Arti'],
],
];

yield [
Expand All @@ -59,11 +64,16 @@ public function getParse(): \Generator
'Artist' => 'Arti',
],
],
[
'Copyright' => ['Copyright'],
'Artist' => ['Arti'],
],
];

yield [
'NOT EXIF',
[],
[],
];
}

Expand Down Expand Up @@ -123,4 +133,27 @@ public function getSerialize(): \Generator
'',
];
}

public function testToReadable(): void
{
$source = [
'IFD0' => [
'Copyright' => 'Copyright',
'Artist' => [[['Arti', 123, .4, [true]]]],
'test',
['test'],
[['key' => 'test']],
],
];

$expected = [
'Copyright' => ['Copyright'],
'Artist' => ['Arti, 123, 0.4, 1'],
'exif_0' => ['test'],
'exif_1' => ['test'],
'exif_2' => ['test'],
];

$this->assertSame($expected, (new ExifFormat())->toReadable($source));
}
}
1 change: 1 addition & 0 deletions tests/Metadata/GifFormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class GifFormatTest extends TestCase
public function testParse(string $source, array $expected): void
{
$this->assertSame($expected, (new GifFormat())->parse($source));
$this->assertSame($expected, (new GifFormat())->toReadable((new GifFormat())->parse($source)));
}

public function getParse(): \Generator
Expand Down
Loading