exif: improve date parsing
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/563/head
parent
0c17d7e209
commit
86ceaf7bb2
87
lib/Exif.php
87
lib/Exif.php
|
@ -120,6 +120,7 @@ class Exif
|
|||
// Ignore zero dates
|
||||
$dateFields = [
|
||||
'DateTimeOriginal',
|
||||
'SubSecDateTimeOriginal',
|
||||
'CreateDate',
|
||||
'ModifyDate',
|
||||
'TrackCreateDate',
|
||||
|
@ -157,53 +158,56 @@ class Exif
|
|||
/**
|
||||
* Parse date from exif format and throw error if invalid.
|
||||
*
|
||||
* @param mixed $date
|
||||
* @param array $exif
|
||||
*
|
||||
* @return int unix timestamp
|
||||
*/
|
||||
public static function parseExifDate($date)
|
||||
public static function parseExifDate(array $exif)
|
||||
{
|
||||
$dt = $date;
|
||||
if (isset($dt) && \is_string($dt) && !empty($dt)) {
|
||||
$dt = explode('-', explode('+', $dt, 2)[0], 2)[0]; // get rid of timezone if present
|
||||
$dt = explode('.', $dt, 2)[0]; // timezone may be after a dot (https://github.com/pulsejet/memories/pull/397)
|
||||
$dt = explode('Z', $dt, 2)[0]; // get rid of Z if present (https://github.com/pulsejet/memories/issues/485)
|
||||
// Get date from exif
|
||||
$exifDate = $exif['SubSecDateTimeOriginal'] ?? $exif['DateTimeOriginal'] ?? $exif['CreateDate'] ?? null;
|
||||
if (null === $exifDate || empty($exifDate) || !\is_string($exifDate)) {
|
||||
throw new \Exception('No date found in exif');
|
||||
}
|
||||
|
||||
// Some cameras don't add seconds to the date if it's 00
|
||||
// Get timezone from exif
|
||||
$exifTz = $exif["OffsetTimeOriginal"] ?? $exif["OffsetTime"] ?? null;
|
||||
try {
|
||||
$parseTz = new \DateTimeZone($exifTz);
|
||||
} catch (\Error $e) {
|
||||
$parseTz = new \DateTimeZone('UTC');
|
||||
}
|
||||
|
||||
// https://github.com/pulsejet/memories/pull/397
|
||||
// https://github.com/pulsejet/memories/issues/485
|
||||
if (3 === substr_count($dt, ':')) {
|
||||
$dt .= ':00';
|
||||
}
|
||||
|
||||
$dt = \DateTime::createFromFormat('Y:m:d H:i:s', $dt);
|
||||
if (!$dt) {
|
||||
throw new \Exception("Invalid date: {$date}");
|
||||
}
|
||||
if ($dt && $dt->getTimestamp() > -5364662400) { // 1800 A.D.
|
||||
return $dt->getTimestamp();
|
||||
}
|
||||
$formats = [
|
||||
'Y:m:d H:i', // 2023:03:05 18:58
|
||||
'Y:m:d H:iO', // 2023:03:05 18:58+05:00
|
||||
'Y:m:d H:i:s', // 2023:03:05 18:58:17
|
||||
'Y:m:d H:i:sO', // 2023:03:05 10:58:17+05:00
|
||||
'Y:m:d H:i:s.u', // 2023:03:05 10:58:17.000
|
||||
'Y:m:d H:i:s.uO', // 2023:03:05 10:58:17.000Z
|
||||
];
|
||||
|
||||
throw new \Exception("Date too old: {$date}");
|
||||
} else {
|
||||
throw new \Exception('No date provided');
|
||||
/** @var \DateTime $dt */
|
||||
$parsedDate = null;
|
||||
|
||||
foreach ($formats as $format) {
|
||||
if ($parsedDate = \DateTime::createFromFormat($format, $exifDate, $parseTz)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the timezone for an epoch timestamp and get the same
|
||||
* time epoch for UTC.
|
||||
*/
|
||||
public static function forgetTimezone(int $epoch)
|
||||
{
|
||||
$dt = new \DateTime();
|
||||
$dt->setTimestamp($epoch);
|
||||
$tz = getenv('TZ'); // at least works on debian ...
|
||||
if ($tz) {
|
||||
$dt->setTimezone(new \DateTimeZone($tz));
|
||||
if (!$parsedDate) {
|
||||
throw new \Exception("Invalid date: {$exifDate}");
|
||||
}
|
||||
$utc = new \DateTime($dt->format('Y-m-d H:i:s'), new \DateTimeZone('UTC'));
|
||||
|
||||
return $utc->getTimestamp();
|
||||
if ($parsedDate->getTimestamp() < -5364662400) { // 1800 A.D.
|
||||
throw new \Exception("Date too old: {$exifDate}");
|
||||
}
|
||||
|
||||
return $parsedDate->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,25 +215,16 @@ class Exif
|
|||
*
|
||||
* @return int unix timestamp
|
||||
*/
|
||||
public static function getDateTaken(File &$file, array &$exif)
|
||||
public static function getDateTaken(File $file, array $exif)
|
||||
{
|
||||
// Try to parse the date from exif metadata
|
||||
$dt = $exif['DateTimeOriginal'] ?? null;
|
||||
if (!isset($dt) || empty($dt)) {
|
||||
$dt = $exif['CreateDate'] ?? null;
|
||||
}
|
||||
|
||||
// Check if found something
|
||||
try {
|
||||
return self::parseExifDate($dt);
|
||||
return self::parseExifDate($exif);
|
||||
} catch (\Exception $ex) {
|
||||
} catch (\ValueError $ex) {
|
||||
}
|
||||
|
||||
// Fall back to modification time
|
||||
$dateTaken = $file->getMtime();
|
||||
|
||||
return self::forgetTimezone($dateTaken);
|
||||
return $file->getMtime();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const EXIF_FIELDS_LIST = [
|
||||
// Date/Time
|
||||
'DateTimeOriginal' => true,
|
||||
'SubSecDateTimeOriginal' => true,
|
||||
'CreateDate' => true,
|
||||
'OffsetTimeOriginal' => true,
|
||||
'OffsetTime' => true,
|
||||
|
|
|
@ -176,12 +176,14 @@ export default defineComponent({
|
|||
|
||||
/** Date taken info */
|
||||
dateOriginal(): moment.Moment | null {
|
||||
const dt = this.exif["DateTimeOriginal"] || this.exif["CreateDate"];
|
||||
if (!dt) return null;
|
||||
|
||||
const m = moment.utc(dt, "YYYY:MM:DD HH:mm:ss");
|
||||
const m = moment.utc(this.baseInfo.datetaken * 1000);
|
||||
if (!m.isValid()) return null;
|
||||
m.locale(getCanonicalLocale());
|
||||
|
||||
// set timezeon
|
||||
const tz = this.exif["OffsetTimeOriginal"] || this.exif["OffsetTime"];
|
||||
if (tz) m.utcOffset(tz);
|
||||
|
||||
return m;
|
||||
},
|
||||
|
||||
|
@ -193,15 +195,7 @@ export default defineComponent({
|
|||
dateOriginalTime(): string[] | null {
|
||||
if (!this.dateOriginal) return null;
|
||||
|
||||
// Try to get timezone
|
||||
let tz = this.exif["OffsetTimeOriginal"] || this.exif["OffsetTime"];
|
||||
tz = tz ? "GMT" + tz : "";
|
||||
|
||||
let parts = [];
|
||||
parts.push(this.dateOriginal.format("h:mm A"));
|
||||
if (tz) parts.push(tz);
|
||||
|
||||
return parts;
|
||||
return [this.dateOriginal.format("h:mm A Z")];
|
||||
},
|
||||
|
||||
/** Camera make and model info */
|
||||
|
|
Loading…
Reference in New Issue