google-photos-migrate
Version:
A tool to fix EXIF data and recover filenames from a Google Photos takeout.
87 lines (86 loc) • 3.63 kB
JavaScript
import { basename, dirname } from 'path';
import { supportedExtensions } from '../config/extensions.js';
import { NoMetaFileError } from '../meta/NoMetaFileError.js';
import { WrongExtensionError } from '../meta/apply-meta-errors.js';
import { applyMetaFile } from '../meta/apply-meta-file.js';
import { findMetaFile } from '../meta/find-meta-file.js';
import { readMetaTitle } from '../meta/read-meta-title.js';
import { InvalidExtError } from './InvalidExtError.js';
import { saveToDir } from './save-to-dir.js';
function applyExt(path, from, to) {
return path.slice(0, path.length - from.length) + to;
}
async function renameExt(migCtx, file, from, to) {
const destDir = dirname(file.path);
const saveBase = applyExt(basename(file.path), from, to);
file.path = await saveToDir(file.path, destDir, migCtx, true, saveBase);
return file.path;
}
function getOutExtRename(ext, metaTitle) {
const renames = (ext.aliases ?? []).filter((x) => typeof x === 'object');
return renames.find((r) => metaTitle.endsWith(r.suffix));
}
export async function migrateMediaFile(originalPath, migCtx) {
const mediaFileInfo = {
originalPath,
path: originalPath,
};
const ext = supportedExtensions.reduce((longestMatch, cur) => {
if (!originalPath.endsWith(cur.suffix))
return longestMatch;
if (longestMatch === null)
return cur;
return cur.suffix.length > longestMatch.suffix.length
? cur
: longestMatch;
}, null);
if (!ext) {
mediaFileInfo.path = await saveToDir(originalPath, migCtx.errorDir, migCtx);
return new InvalidExtError(mediaFileInfo);
}
const jsonPath = await findMetaFile(originalPath, ext, migCtx);
if (!jsonPath) {
mediaFileInfo.path = await saveToDir(originalPath, migCtx.errorDir, migCtx);
return new NoMetaFileError(mediaFileInfo);
}
mediaFileInfo.jsonPath = jsonPath;
const metaBase = (await readMetaTitle(mediaFileInfo)) ?? basename(mediaFileInfo.path);
const outExtRename = getOutExtRename(ext, metaBase ?? basename(mediaFileInfo.path));
const outBase = outExtRename
? applyExt(metaBase, outExtRename.suffix, outExtRename.out)
: metaBase;
mediaFileInfo.path = await saveToDir(originalPath, migCtx.outputDir, migCtx, false, outBase);
const mediaFile = {
...mediaFileInfo,
ext,
jsonPath,
};
const err = await applyMetaFile(mediaFile, migCtx);
return handleApplyMetaErr(mediaFile, migCtx, err, outExtRename);
}
async function handleApplyMetaErr(mediaFile, migCtx, err, outExtRename) {
if (err) {
if (err instanceof WrongExtensionError) {
if (migCtx.skipCorrections) {
return mediaFile;
}
if (outExtRename) {
migCtx.warnLog(`Extension ${err.currentExt} should be ${err.actualExt}, but was forcibly set: ${mediaFile.path}`);
return mediaFile;
}
await renameExt(migCtx, mediaFile, err.currentExt, err.actualExt);
migCtx.warnLog(`Renamed wrong extension ${err.currentExt} to ${err.actualExt}: ${mediaFile.path}`);
err = await applyMetaFile(mediaFile, migCtx);
if (!err) {
return mediaFile;
}
}
const savedPaths = await Promise.all([
saveToDir(mediaFile.path, migCtx.errorDir, migCtx, true),
saveToDir(mediaFile.jsonPath, migCtx.errorDir, migCtx),
]);
mediaFile.path = savedPaths[0];
return err;
}
return mediaFile;
}