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

Make it possible to set default time zone for EXIF / IPTC data #5

Merged
merged 3 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 11 additions & 5 deletions src/main/php/img/io/ImageMetaData.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,23 @@ public function imageDimensions() {
* Returns an IptcData instance or NULL if this image does not contain
* IPTC Data
*
* @param ?util.TimeZone $tz
* @return ?img.util.IptcData
*/
public function iptcData() {
public function iptcData($tz= null) {
if (!($seg= $this->segmentsOf(IptcSegment::class))) return null;

$data= new IptcData();
$iptc= $seg[0]->rawData();

// Parse creation date
if (3 === sscanf($iptc['2#055'][0] ?? '', '%4d%2d%d', $year, $month, $day)) {
$created= Date::create($year, $month, $day, 0, 0, 0);
if (isset($iptc['2#060'])) {
sscanf($iptc['2#060'][0], '%2d%2d%2d', $hour, $minute, $second);
} else {
$hour= $minute= $second= 0;
}
$created= Date::create($year, $month, $day, $hour, $minute, $second, $tz);
} else {
$created= null;
}
Expand Down Expand Up @@ -141,9 +147,10 @@ protected static function lookup($exif, ... $keys) {
* Returns an ExifData instance or NULL if this image does not contain
* EXIF Data
*
* @param ?util.TimeZone $tz
* @return ?img.util.ExifData
*/
public function exifData() {
public function exifData($tz= null) {
if (!($seg= $this->segmentsOf(ExifSegment::class))) return null;

// Populate ExifData instance from ExifSegment's raw data
Expand Down Expand Up @@ -205,8 +212,7 @@ public function exifData() {
$data->withFlash(self::lookup($exif, 'Flash'));

if (null !== ($date= self::lookup($exif, 'DateTimeOriginal', 'DateTimeDigitized', 'DateTime'))) {
$t= sscanf($date, '%4d:%2d:%2d %2d:%2d:%2d');
$data->withDateTime(new Date(mktime($t[3], $t[4], $t[5], $t[1], $t[2], $t[0])));
$data->withDateTime(new Date($date, $tz));
}

if (null !== ($o= self::lookup($exif, 'Orientation'))) {
Expand Down
84 changes: 44 additions & 40 deletions src/test/php/img/unittest/MetaDataReaderTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,30 @@
use img\ImagingException;
use img\io\{Segment, CommentSegment, MetaDataReader, SOFNSegment, XMPSegment, ExifSegment, IptcSegment};
use img\util\{ExifData, IptcData};
use lang\ArrayType;
use test\{Assert, Before, Expect, Test};
use util\Date;
use test\{Assert, Before, Expect, Test, Values};
use util\{Date, TimeZone};

class MetaDataReaderTest {
private $fixture;

/**
* Returns a file for a classloader resource
* Extract meta data from a given resource
*
* @param string $name
* @param string $sub default NULL subpackage
* @return io.File
* @param ?string $sub
* @return img.io.ImageMetaData
*/
protected function resourceAsFile($name, $sub= null) {
private function extractFromFile($name, $sub= null) {
$package= typeof($this)->getPackage();
$container= $sub ? $package->getPackage($sub) : $package;
return $container->getResourceAsStream($name);
return $this->fixture->read($container->getResourceAsStream($name)->in(), $name);
}

/**
* Extract from file and return the instance
*
* @param string $name
* @param string $sub default NULL subpackage
* @return lang.Generic the instance
*/
protected function extractFromFile($name, $sub= null) {
return $this->fixture->read($this->resourceAsFile($name, $sub)->in(), $name);
}

/**
* Assertion helper
*
* @param string $type The expected type
* @param int $size The expected size
* @param var $value The value
* @throws unittest.AssertionFailedError
*/
protected function assertArrayOf($type, $size, $value) {
Assert::instance(new ArrayType($type), $value);
Assert::equals($size, sizeof($value));
/** @return iterable */
private function timezones() {
yield null;
yield TimeZone::getByName('UTC');
yield TimeZone::getByName('Europe/Berlin');
}

#[Before]
Expand All @@ -65,12 +47,18 @@ public function empty_file() {

#[Test]
public function all_segments() {
$this->assertArrayOf(Segment::class, 9, $this->extractFromFile('1x1.jpg')->allSegments());
$segments= $this->extractFromFile('1x1.jpg')->allSegments();

Assert::instance('img.io.Segment[]', $segments);
Assert::equals(9, sizeof($segments));
}

#[Test]
public function segments_named_dqt() {
$this->assertArrayOf(Segment::class, 2, $this->extractFromFile('1x1.jpg')->segmentsNamed('DQT'));
$segments= $this->extractFromFile('1x1.jpg')->segmentsNamed('DQT');

Assert::instance('img.io.Segment[]', $segments);
Assert::equals(2, sizeof($segments));
}

#[Test]
Expand Down Expand Up @@ -100,8 +88,8 @@ public function com_segment() {
#[Test]
public function xmp_segment() {
$segments= $this->extractFromFile('xmp.jpg')->segmentsOf(XMPSegment::class);
$this->assertArrayOf(XMPSegment::class, 1, $segments);

Assert::instance('img.io.XMPSegment[]', $segments);
Assert::matches('/^<.+/', $segments[0]->source);
Assert::instance(DOMDocument::class, $segments[0]->document());
}
Expand Down Expand Up @@ -138,25 +126,25 @@ public function no_iptc_data_in_1x1() {

#[Test]
public function exif_data_segments() {
$this->assertArrayOf(
ExifSegment::class, 1,
Assert::instance(
'img.io.ExifSegment[]',
$this->extractFromFile('exif-only.jpg')->segmentsOf(ExifSegment::class)
);
}

#[Test]
public function iptc_data_segments() {
$this->assertArrayOf(
IptcSegment::class, 1,
Assert::instance(
'img.io.IptcSegment[]',
$this->extractFromFile('iptc-only.jpg')->segmentsOf(IptcSegment::class)
);
}

#[Test]
public function exif_and_iptc_data_segments() {
$meta= $this->extractFromFile('exif-and-iptc.jpg');
$this->assertArrayOf(ExifSegment::class, 1, $meta->segmentsOf(ExifSegment::class));
$this->assertArrayOf(IptcSegment::class, 1, $meta->segmentsOf(IptcSegment::class));
Assert::instance('img.io.ExifSegment[]', $meta->segmentsOf(ExifSegment::class));
Assert::instance('img.io.IptcSegment[]', $meta->segmentsOf(IptcSegment::class));
}

#[Test]
Expand Down Expand Up @@ -519,7 +507,7 @@ public function detailed_iptc_data() {
->withUrgency(null)
->withCategory(null)
->withKeywords(null)
->withDateCreated(new Date('2011-12-07 00:00:00'))
->withDateCreated(new Date('2011-12-07 14:08:24'))
->withAuthor(null)
->withAuthorPosition(null)
->withCity(null)
Expand Down Expand Up @@ -559,4 +547,20 @@ public function gps_data() {
]
);
}

#[Test, Values(from: 'timezones')]
public function iptc_with_timezone($tz) {
Assert::equals(
new Date('2011-12-07 14:08:24', $tz),
$this->extractFromFile('detailed-iptc-embedded.jpg')->iptcData($tz)->dateCreated
);
}

#[Test, Values(from: 'timezones')]
public function exif_with_timezone($tz) {
Assert::equals(
new Date('2001:06:09 15:17:32', $tz),
$this->extractFromFile('canon-ixus.jpg', 'exif_org')->exifData($tz)->dateTime
);
}
}
Loading