UNPKG

google-photos-migrate

Version:

A tool to fix EXIF data and recover filenames from a Google Photos takeout.

77 lines (76 loc) 3.15 kB
import { readFile, utimes } from 'fs/promises'; import { exhaustiveCheck } from '../ts.js'; import { MetaType } from './MetaType.js'; import { ExifToolError, MissingMetaError, WrongExtensionError, } from './apply-meta-errors.js'; export async function applyMetaFile(mediaFile, migCtx) { const metaJson = (await readFile(mediaFile.jsonPath)).toString(); const meta = JSON.parse(metaJson); // time const timeTakenTimestamp = meta?.photoTakenTime?.timestamp; if (timeTakenTimestamp === undefined) return new MissingMetaError(mediaFile, 'photoTakenTime'); const timeTaken = new Date(parseInt(timeTakenTimestamp) * 1000); // always UTC as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString const timeTakenUTC = timeTaken.toISOString(); const tags = {}; switch (mediaFile.ext.metaType) { case MetaType.EXIF: tags.SubSecDateTimeOriginal = timeTakenUTC; tags.SubSecCreateDate = timeTakenUTC; tags.SubSecModifyDate = timeTakenUTC; break; case MetaType.QUICKTIME: tags.DateTimeOriginal = timeTakenUTC; tags.CreateDate = timeTakenUTC; tags.ModifyDate = timeTakenUTC; tags.TrackCreateDate = timeTakenUTC; tags.TrackModifyDate = timeTakenUTC; tags.MediaCreateDate = timeTakenUTC; tags.MediaModifyDate = timeTakenUTC; break; case MetaType.NONE: break; default: exhaustiveCheck(mediaFile.ext.metaType); } tags.ModifyDate = timeTakenUTC; // description const description = meta?.description; tags.Description = description; tags['Caption-Abstract'] = description; tags.ImageDescription = description; // gps const [alt, lat, lon] = [ meta?.geoData?.altitude, meta?.geoData?.latitude, meta?.geoData?.longitude, ]; if (![alt, lat, lon].some((axis) => axis === undefined)) { tags.GPSAltitude = alt; tags.GPSAltitudeRef = `${alt}`; tags.GPSLatitude = lat; tags.GPSLatitudeRef = `${lat}`; tags.GPSLongitude = lon; tags.GPSLongitudeRef = `${lon}`; } try { await migCtx.exiftool.write(mediaFile.path, tags, { writeArgs: migCtx.exiftoolArgs, }); // Set file modification times to the photo taken timestamp await utimes(mediaFile.path, timeTaken, timeTaken); } catch (e) { if (e instanceof Error) { const wrongExtMatch = e.message.match(/Not a valid (?<current>\w+) \(looks more like a (?<actual>\w+)\)/); const current = wrongExtMatch?.groups?.['current']; const actual = wrongExtMatch?.groups?.['actual']; if (current !== undefined && actual !== undefined) { return new WrongExtensionError(mediaFile, `.${current.toLowerCase()}`, `.${actual.toLowerCase()}`); } return new ExifToolError(mediaFile, e); } return new ExifToolError(mediaFile, new Error(`${e}`)); } return null; }