Integrate
This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
/** @file Dealing with the JSON metadata in Google Takeouts */
|
||||
/** @file Dealing with the JSON metadata sidecar files */
|
||||
|
||||
import { ensureElectron } from "@/base/electron";
|
||||
import { nameAndExtension } from "@/base/file";
|
||||
import log from "@/base/log";
|
||||
import type { UploadItem } from "@/new/photos/services/upload/types";
|
||||
import { NULL_LOCATION } from "@/new/photos/services/upload/types";
|
||||
import type { Location } from "@/new/photos/types/metadata";
|
||||
import { readStream } from "@/new/photos/utils/native-stream";
|
||||
|
||||
/**
|
||||
* The data we read from the JSON metadata sidecar files.
|
||||
*
|
||||
* Originally these were used to read the JSON metadata sidecar files present in
|
||||
* a Google Takeout. However, during our own export, we also write out files
|
||||
* with a similar structure.
|
||||
*/
|
||||
export interface ParsedMetadataJSON {
|
||||
creationTime: number;
|
||||
modificationTime: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
creationTime?: number;
|
||||
modificationTime?: number;
|
||||
location?: { latitude: number; longitude: number };
|
||||
}
|
||||
|
||||
export const MAX_FILE_NAME_LENGTH_GOOGLE_EXPORT = 46;
|
||||
@@ -100,19 +104,13 @@ const uploadItemText = async (uploadItem: UploadItem) => {
|
||||
}
|
||||
};
|
||||
|
||||
const NULL_PARSED_METADATA_JSON: ParsedMetadataJSON = {
|
||||
creationTime: null,
|
||||
modificationTime: null,
|
||||
...NULL_LOCATION,
|
||||
};
|
||||
|
||||
const parseMetadataJSONText = (text: string) => {
|
||||
const metadataJSON: object = JSON.parse(text);
|
||||
if (!metadataJSON) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsedMetadataJSON = { ...NULL_PARSED_METADATA_JSON };
|
||||
const parsedMetadataJSON: ParsedMetadataJSON = {};
|
||||
|
||||
// The metadata provided by Google does not include the time zone where the
|
||||
// photo was taken, it only has an epoch seconds value.
|
||||
@@ -129,6 +127,7 @@ const parseMetadataJSONText = (text: string) => {
|
||||
parsedMetadataJSON.creationTime =
|
||||
metadataJSON["creationTime"]["timestamp"] * 1e6;
|
||||
}
|
||||
|
||||
if (
|
||||
metadataJSON["modificationTime"] &&
|
||||
metadataJSON["modificationTime"]["timestamp"]
|
||||
@@ -137,24 +136,26 @@ const parseMetadataJSONText = (text: string) => {
|
||||
metadataJSON["modificationTime"]["timestamp"] * 1e6;
|
||||
}
|
||||
|
||||
let locationData: Location = { ...NULL_LOCATION };
|
||||
if (
|
||||
metadataJSON["geoData"] &&
|
||||
(metadataJSON["geoData"]["latitude"] !== 0.0 ||
|
||||
metadataJSON["geoData"]["longitude"] !== 0.0)
|
||||
) {
|
||||
locationData = metadataJSON["geoData"];
|
||||
parsedMetadataJSON.location = {
|
||||
latitude: metadataJSON["geoData"]["latitude"],
|
||||
longitude: metadataJSON["geoData"]["longitude"],
|
||||
};
|
||||
} else if (
|
||||
metadataJSON["geoDataExif"] &&
|
||||
(metadataJSON["geoDataExif"]["latitude"] !== 0.0 ||
|
||||
metadataJSON["geoDataExif"]["longitude"] !== 0.0)
|
||||
) {
|
||||
locationData = metadataJSON["geoDataExif"];
|
||||
}
|
||||
if (locationData !== null) {
|
||||
parsedMetadataJSON.latitude = locationData.latitude;
|
||||
parsedMetadataJSON.longitude = locationData.longitude;
|
||||
parsedMetadataJSON.location = {
|
||||
latitude: metadataJSON["geoDataExif"]["latitude"],
|
||||
longitude: metadataJSON["geoDataExif"]["longitude"],
|
||||
};
|
||||
}
|
||||
|
||||
return parsedMetadataJSON;
|
||||
};
|
||||
|
||||
|
||||
@@ -763,14 +763,20 @@ const extractImageOrVideoMetadata = async (
|
||||
|
||||
const hash = await computeHash(uploadItem, worker);
|
||||
|
||||
const modificationTime = lastModifiedMs * 1000;
|
||||
const parsedMetadataJSON = matchTakeoutMetadata(
|
||||
fileName,
|
||||
collectionID,
|
||||
parsedMetadataJSONMap,
|
||||
);
|
||||
|
||||
const modificationTime =
|
||||
parsedMetadataJSON.modificationTime ?? lastModifiedMs * 1000;
|
||||
const creationTime =
|
||||
parsedMetadataJSON.creationTime ??
|
||||
parsedMetadata.creationTime ??
|
||||
tryParseEpochMicrosecondsFromFileName(fileName) ??
|
||||
modificationTime;
|
||||
|
||||
const { width: w, height: h, location } = parsedMetadata;
|
||||
|
||||
const metadata: Metadata = {
|
||||
fileType,
|
||||
title: fileName,
|
||||
@@ -778,25 +784,19 @@ const extractImageOrVideoMetadata = async (
|
||||
modificationTime,
|
||||
hash,
|
||||
};
|
||||
|
||||
const location = parsedMetadataJSON.location ?? parsedMetadata.location;
|
||||
if (location) {
|
||||
metadata.latitude = location.latitude;
|
||||
metadata.longitude = location.longitude;
|
||||
}
|
||||
|
||||
const { width: w, height: h } = parsedMetadata;
|
||||
|
||||
const publicMagicMetadata: PublicMagicMetadata = {};
|
||||
if (w) publicMagicMetadata.w = w;
|
||||
if (h) publicMagicMetadata.h = h;
|
||||
|
||||
const takeoutMetadata = matchTakeoutMetadata(
|
||||
fileName,
|
||||
collectionID,
|
||||
parsedMetadataJSONMap,
|
||||
);
|
||||
|
||||
if (takeoutMetadata)
|
||||
for (const [key, value] of Object.entries(takeoutMetadata))
|
||||
if (value) metadata[key] = value;
|
||||
|
||||
return { metadata, publicMagicMetadata };
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user