garzj / google-photos-migrate

A tool to fix EXIF data and recover filenames from a Google Photos takeout, preserving albums within the directory structure.
https://npmjs.com/package/google-photos-migrate
MIT License
104 stars 11 forks source link

Potential timezone info loss or mess #4

Closed p0n1 closed 12 months ago

p0n1 commented 12 months ago

Currently, standard date.toISOString is used for DateTimeOriginal. https://github.com/garzj/google-photos-migrate/blob/97084ec170a152c79aa227351bdfe0027d7b6127/src/meta/apply-meta-file.ts#L35-L48

This is how the ISO string looks like 2017-07-10T13:55:11.000Z. When this UTC+0 stuff written into the exif tags, many photos of mine just got wrong time in Immich because it uses the following logics to detect timezone. Check https://immich.app/docs/FAQ#why-does-my-uploaded-photo-show-up-with-the-wrong-date-or-time-in-immich and https://github.com/photostructure/exiftool-vendored.js#dates. In my case, all photos just displayed as 8 hours (UTC+8) before the real time they taken.

I fix this by converting the timeTaken into a ISOString with timezone like 2017-07-10T21:55:11+08:00. Just as in https://github.com/photostructure/exiftool-vendored.js/blob/6408b855b80fe322a8dda3f1c4807f1cc1333844/src/WriteTask.spec.ts#L216 and https://github.com/photostructure/exiftool-vendored.js/blob/6408b855b80fe322a8dda3f1c4807f1cc1333844/src/Tags.ts#L98.

And here is how it look like now by run exiftool. The timezone +08:00 is appended for videos but not for photos. Not sure why.

DateTimeOriginal                : 2017-07-10T21:55:11+08:00
CreateDate                      : 2017-07-10T21:55:11+08:00
ModifyDate                      : 2017-07-10T21:55:11+08:00

But I think it is not a perfect solution because photos could be taken in different timezone. How I wish the DateTimeOriginal could accept unix timestamp!

Here is what I have changed.

--- a/src/meta/apply-meta-file.ts
+++ b/src/meta/apply-meta-file.ts
@@ -12,6 +12,25 @@ import {
 } from './apply-meta-errors';
 import { MigrationContext } from '../media/migrate-google-dir';

+
+function toISOStringWithTimezone(date: Date): string {
+  const tzo = -date.getTimezoneOffset();
+  const dif = tzo >= 0 ? '+' : '-';
+  const pad = function(num: number) {
+      const norm = Math.floor(Math.abs(num));
+      return (norm < 10 ? '0' : '') + norm;
+  };
+
+  return date.getFullYear() +
+      '-' + pad(date.getMonth() + 1) +
+      '-' + pad(date.getDate()) +
+      'T' + pad(date.getHours()) +
+      ':' + pad(date.getMinutes()) +
+      ':' + pad(date.getSeconds()) +
+      dif + pad(tzo / 60) +
+      ':' + pad(tzo % 60);
+}
+
 export async function applyMetaFile(
   mediaFile: MediaFile,
   migCtx: MigrationContext
@@ -23,6 +42,8 @@ export async function applyMetaFile(
   if (timeTakenTimestamp === undefined)
     return new MissingMetaError(mediaFile, 'photoTakenTime');
   const timeTaken = new Date(parseInt(timeTakenTimestamp) * 1000);
+  const timeTakeWithTz = toISOStringWithTimezone(timeTaken)
+  console.log("timeTaken", timeTaken, "timeTakeWithTz", timeTakeWithTz);

   // const timeCreatedTimestamp = meta?.creationTime?.timestamp;
   // const timeCreated = timeCreatedTimestamp
@@ -34,18 +55,18 @@ export async function applyMetaFile(

   switch (mediaFile.ext.metaType) {
     case MetaType.EXIF:
-      tags.DateTimeOriginal = timeTaken.toISOString();
-      tags.CreateDate = timeTaken.toISOString();
-      tags.ModifyDate = timeTaken.toISOString();
+      tags.DateTimeOriginal = timeTakeWithTz;
+      tags.CreateDate = timeTakeWithTz;
+      tags.ModifyDate = timeTakeWithTz;
       break;
     case MetaType.QUICKTIME:
-      tags.DateTimeOriginal = timeTaken.toISOString();
-      tags.CreateDate = timeTaken.toISOString();
-      tags.ModifyDate = timeTaken.toISOString();
-      tags.TrackCreateDate = timeTaken.toISOString();
-      tags.TrackModifyDate = timeTaken.toISOString();
-      tags.MediaCreateDate = timeTaken.toISOString();
-      tags.MediaModifyDate = timeTaken.toISOString();
+      tags.DateTimeOriginal = timeTakeWithTz;
+      tags.CreateDate = timeTakeWithTz;
+      tags.ModifyDate = timeTakeWithTz;
+      tags.TrackCreateDate = timeTakeWithTz;
+      tags.TrackModifyDate = timeTakeWithTz;
+      tags.MediaCreateDate = timeTakeWithTz;
+      tags.MediaModifyDate = timeTakeWithTz;
       break;
     case MetaType.NONE:
       break;
garzj commented 12 months ago

I looked into it and it seems like the OffsetTimeOriginal tag should be set properly, this should work for all photos then. As for QuickTime, the timestamps should always be stored in UTC, but I had to enable the quicktimeutc API on ExifTool. Thanks for sharing, let me know if you think there's still an issue.

p0n1 commented 12 months ago

Looks like a much better solution. Good to know that we can set OffsetTimeOriginal by setting SubSecDateTimeOriginal. Where did you find that?