mirror of
https://github.com/ente-io/ente.git
synced 2025-08-08 23:39:30 +00:00
Parse
This commit is contained in:
parent
9e48010ee6
commit
cfea740511
@ -1,4 +1,5 @@
|
|||||||
import { haveWindow } from "@/base/env";
|
import { haveWindow } from "@/base/env";
|
||||||
|
import { type Location } from "@/base/location";
|
||||||
import { styled } from "@mui/material";
|
import { styled } from "@mui/material";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { MapButton } from "./MapButton";
|
import { MapButton } from "./MapButton";
|
||||||
@ -29,7 +30,7 @@ const MapBoxEnableContainer = styled(MapBoxContainer)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface MapBoxProps {
|
interface MapBoxProps {
|
||||||
location: { latitude: number; longitude: number };
|
location: Location;
|
||||||
mapEnabled: boolean;
|
mapEnabled: boolean;
|
||||||
openUpdateMapConfirmationDialog: () => void;
|
openUpdateMapConfirmationDialog: () => void;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ import { EnteDrawer } from "@/base/components/EnteDrawer";
|
|||||||
import { Titlebar } from "@/base/components/Titlebar";
|
import { Titlebar } from "@/base/components/Titlebar";
|
||||||
import { EllipsizedTypography } from "@/base/components/Typography";
|
import { EllipsizedTypography } from "@/base/components/Typography";
|
||||||
import { nameAndExtension } from "@/base/file";
|
import { nameAndExtension } from "@/base/file";
|
||||||
|
import type { Location } from "@/base/location";
|
||||||
import log from "@/base/log";
|
import log from "@/base/log";
|
||||||
import type { ParsedMetadata } from "@/media/file-metadata";
|
import type { ParsedMetadata } from "@/media/file-metadata";
|
||||||
import {
|
import {
|
||||||
|
fileLocation,
|
||||||
getUICreationDate,
|
getUICreationDate,
|
||||||
updateRemotePublicMagicMetadata,
|
updateRemotePublicMagicMetadata,
|
||||||
type ParsedMetadataDate,
|
type ParsedMetadataDate,
|
||||||
@ -97,16 +99,9 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
|||||||
const [openRawExif, setOpenRawExif] = useState(false);
|
const [openRawExif, setOpenRawExif] = useState(false);
|
||||||
|
|
||||||
const location = useMemo(() => {
|
const location = useMemo(() => {
|
||||||
if (file && file.metadata) {
|
if (file) {
|
||||||
if (
|
const location = fileLocation(file);
|
||||||
(file.metadata.latitude || file.metadata.latitude === 0) &&
|
if (location) return location;
|
||||||
!(file.metadata.longitude === 0 && file.metadata.latitude === 0)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
latitude: file.metadata.latitude,
|
|
||||||
longitude: file.metadata.longitude,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return exif?.parsed?.location;
|
return exif?.parsed?.location;
|
||||||
}, [file, exif]);
|
}, [file, exif]);
|
||||||
@ -181,7 +176,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
|||||||
!mapEnabled ||
|
!mapEnabled ||
|
||||||
publicCollectionGalleryContext.accessedThroughSharedURL ? (
|
publicCollectionGalleryContext.accessedThroughSharedURL ? (
|
||||||
<Link
|
<Link
|
||||||
href={getOpenStreetMapLink(location)}
|
href={openStreetMapLink(location)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
sx={{ fontWeight: "bold" }}
|
sx={{ fontWeight: "bold" }}
|
||||||
@ -205,7 +200,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
|||||||
}
|
}
|
||||||
customEndButton={
|
customEndButton={
|
||||||
<CopyButton
|
<CopyButton
|
||||||
code={getOpenStreetMapLink(location)}
|
code={openStreetMapLink(location)}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="medium"
|
||||||
/>
|
/>
|
||||||
@ -531,11 +526,8 @@ const BasicDeviceCamera: React.FC<{ parsedExif: ExifInfo }> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOpenStreetMapLink = (location: {
|
const openStreetMapLink = ({ latitude, longitude }: Location) =>
|
||||||
latitude: number;
|
`https://www.openstreetmap.org/?mlat=${latitude}&mlon=${longitude}#map=15/${latitude}/${longitude}`;
|
||||||
longitude: number;
|
|
||||||
}) =>
|
|
||||||
`https://www.openstreetmap.org/?mlat=${location.latitude}&mlon=${location.longitude}#map=15/${location.latitude}/${location.longitude}`;
|
|
||||||
|
|
||||||
interface RawExifProps {
|
interface RawExifProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ensureElectron } from "@/base/electron";
|
import { ensureElectron } from "@/base/electron";
|
||||||
import log from "@/base/log";
|
import log from "@/base/log";
|
||||||
import type { Metadata } from "@/media/file-metadata";
|
import { fileLocation, type Metadata } from "@/media/file-metadata";
|
||||||
import { FileType } from "@/media/file-type";
|
import { FileType } from "@/media/file-type";
|
||||||
import { decodeLivePhoto } from "@/media/live-photo";
|
import { decodeLivePhoto } from "@/media/live-photo";
|
||||||
import downloadManager from "@/new/photos/services/download";
|
import downloadManager from "@/new/photos/services/download";
|
||||||
@ -1383,6 +1383,7 @@ const getGoogleLikeMetadataFile = (fileExportName: string, file: EnteFile) => {
|
|||||||
(metadata.modificationTime ?? metadata.creationTime) / 1000000,
|
(metadata.modificationTime ?? metadata.creationTime) / 1000000,
|
||||||
);
|
);
|
||||||
const captionValue: string = file?.pubMagicMetadata?.data?.caption;
|
const captionValue: string = file?.pubMagicMetadata?.data?.caption;
|
||||||
|
const geoData = fileLocation(file);
|
||||||
return JSON.stringify(
|
return JSON.stringify(
|
||||||
{
|
{
|
||||||
title: fileExportName,
|
title: fileExportName,
|
||||||
@ -1395,10 +1396,7 @@ const getGoogleLikeMetadataFile = (fileExportName: string, file: EnteFile) => {
|
|||||||
timestamp: modificationTime,
|
timestamp: modificationTime,
|
||||||
formatted: formatDateTimeShort(modificationTime * 1000),
|
formatted: formatDateTimeShort(modificationTime * 1000),
|
||||||
},
|
},
|
||||||
geoData: {
|
geoData,
|
||||||
latitude: metadata.latitude,
|
|
||||||
longitude: metadata.longitude,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { ensureElectron } from "@/base/electron";
|
import { ensureElectron } from "@/base/electron";
|
||||||
import { nameAndExtension } from "@/base/file";
|
import { nameAndExtension } from "@/base/file";
|
||||||
|
import { type Location } from "@/base/location";
|
||||||
import log from "@/base/log";
|
import log from "@/base/log";
|
||||||
import type { UploadItem } from "@/new/photos/services/upload/types";
|
import type { UploadItem } from "@/new/photos/services/upload/types";
|
||||||
import { readStream } from "@/new/photos/utils/native-stream";
|
import { readStream } from "@/new/photos/utils/native-stream";
|
||||||
@ -16,7 +17,7 @@ import { readStream } from "@/new/photos/utils/native-stream";
|
|||||||
export interface ParsedMetadataJSON {
|
export interface ParsedMetadataJSON {
|
||||||
creationTime?: number;
|
creationTime?: number;
|
||||||
modificationTime?: number;
|
modificationTime?: number;
|
||||||
location?: { latitude: number; longitude: number };
|
location?: Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_FILE_NAME_LENGTH_GOOGLE_EXPORT = 46;
|
export const MAX_FILE_NAME_LENGTH_GOOGLE_EXPORT = 46;
|
||||||
@ -112,53 +113,60 @@ const parseMetadataJSONText = (text: string) => {
|
|||||||
|
|
||||||
const parsedMetadataJSON: ParsedMetadataJSON = {};
|
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.
|
|
||||||
if (
|
|
||||||
metadataJSON["photoTakenTime"] &&
|
|
||||||
metadataJSON["photoTakenTime"]["timestamp"]
|
|
||||||
) {
|
|
||||||
parsedMetadataJSON.creationTime =
|
parsedMetadataJSON.creationTime =
|
||||||
metadataJSON["photoTakenTime"]["timestamp"] * 1e6;
|
parseGTTimestamp(metadataJSON["photoTakenTime"]) ??
|
||||||
} else if (
|
parseGTTimestamp(metadataJSON["creationTime"]);
|
||||||
metadataJSON["creationTime"] &&
|
|
||||||
metadataJSON["creationTime"]["timestamp"]
|
|
||||||
) {
|
|
||||||
parsedMetadataJSON.creationTime =
|
|
||||||
metadataJSON["creationTime"]["timestamp"] * 1e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
parsedMetadataJSON.modificationTime = parseGTTimestamp(
|
||||||
metadataJSON["modificationTime"] &&
|
metadataJSON["modificationTime"],
|
||||||
metadataJSON["modificationTime"]["timestamp"]
|
);
|
||||||
) {
|
|
||||||
parsedMetadataJSON.modificationTime =
|
|
||||||
metadataJSON["modificationTime"]["timestamp"] * 1e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
parsedMetadataJSON.location =
|
||||||
metadataJSON["geoData"] &&
|
parseGTLocation(metadataJSON["geoData"]) ??
|
||||||
(metadataJSON["geoData"]["latitude"] !== 0.0 ||
|
parseGTLocation(metadataJSON["geoDataExif"]);
|
||||||
metadataJSON["geoData"]["longitude"] !== 0.0)
|
|
||||||
) {
|
|
||||||
parsedMetadataJSON.location = {
|
|
||||||
latitude: metadataJSON["geoData"]["latitude"],
|
|
||||||
longitude: metadataJSON["geoData"]["longitude"],
|
|
||||||
};
|
|
||||||
} else if (
|
|
||||||
metadataJSON["geoDataExif"] &&
|
|
||||||
(metadataJSON["geoDataExif"]["latitude"] !== 0.0 ||
|
|
||||||
metadataJSON["geoDataExif"]["longitude"] !== 0.0)
|
|
||||||
) {
|
|
||||||
parsedMetadataJSON.location = {
|
|
||||||
latitude: metadataJSON["geoDataExif"]["latitude"],
|
|
||||||
longitude: metadataJSON["geoDataExif"]["longitude"],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedMetadataJSON;
|
return parsedMetadataJSON;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a nullish epoch seconds timestamp from a field in a Google Takeout
|
||||||
|
* JSON, converting it into epoch microseconds if it is found.
|
||||||
|
*
|
||||||
|
* Note that the metadata provided by Google does not include the time zone
|
||||||
|
* where the photo was taken, it only has an epoch seconds value.
|
||||||
|
*/
|
||||||
|
const parseGTTimestamp = (o: unknown) => {
|
||||||
|
if (
|
||||||
|
o &&
|
||||||
|
typeof o == "object" &&
|
||||||
|
"timestamp" in o &&
|
||||||
|
typeof o.timestamp == "number"
|
||||||
|
) {
|
||||||
|
const { timestamp } = o;
|
||||||
|
if (timestamp) return timestamp * 1e6;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom parser (instead of parseLatLng) that retains the existing behaviour
|
||||||
|
* of ignoring (0, 0) lat lng pairs when reading Google Takeout JSONs.
|
||||||
|
*/
|
||||||
|
const parseGTLocation = (o: unknown) => {
|
||||||
|
if (
|
||||||
|
o &&
|
||||||
|
typeof o == "object" &&
|
||||||
|
"latitude" in o &&
|
||||||
|
typeof o.latitude == "number" &&
|
||||||
|
"longitude" in o &&
|
||||||
|
typeof o.longitude == "number"
|
||||||
|
) {
|
||||||
|
const { latitude, longitude } = o;
|
||||||
|
if (latitude !== 0 || longitude !== 0) return { latitude, longitude };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the matching entry (if any) from {@link parsedMetadataJSONMap} for the
|
* Return the matching entry (if any) from {@link parsedMetadataJSONMap} for the
|
||||||
* {@link fileName} and {@link collectionID} combination.
|
* {@link fileName} and {@link collectionID} combination.
|
||||||
|
25
web/packages/base/location.ts
Normal file
25
web/packages/base/location.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { nullToUndefined } from "@/utils/transform";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A location, represented as a (latitude, longitude) pair.
|
||||||
|
*/
|
||||||
|
export interface Location {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a pair of nullish latitude and longitude values into a
|
||||||
|
* {@link Location} if both of them are present.
|
||||||
|
*/
|
||||||
|
export const parseLatLng = (
|
||||||
|
latitudeN: number | undefined | null,
|
||||||
|
longitudeN: number | undefined | null,
|
||||||
|
): Location | undefined => {
|
||||||
|
const latitude = nullToUndefined(latitudeN);
|
||||||
|
const longitude = nullToUndefined(longitudeN);
|
||||||
|
|
||||||
|
if (latitude === undefined || longitude === undefined) return undefined;
|
||||||
|
|
||||||
|
return { latitude, longitude };
|
||||||
|
};
|
@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* A location, represented as a (latitude, longitude) pair.
|
|
||||||
*/
|
|
||||||
export interface Location {
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
}
|
|
@ -1,14 +1,13 @@
|
|||||||
import { decryptMetadataJSON, encryptMetadataJSON } from "@/base/crypto";
|
import { decryptMetadataJSON, encryptMetadataJSON } from "@/base/crypto";
|
||||||
import { authenticatedRequestHeaders, ensureOk } from "@/base/http";
|
import { authenticatedRequestHeaders, ensureOk } from "@/base/http";
|
||||||
|
import type { Location } from "@/base/location";
|
||||||
import { apiURL } from "@/base/origins";
|
import { apiURL } from "@/base/origins";
|
||||||
import type { Location } from "@/base/types";
|
|
||||||
import {
|
import {
|
||||||
type EnteFile,
|
type EnteFile,
|
||||||
type FilePublicMagicMetadata,
|
type FilePublicMagicMetadata,
|
||||||
} from "@/new/photos/types/file";
|
} from "@/new/photos/types/file";
|
||||||
import { mergeMetadata1 } from "@/new/photos/utils/file";
|
import { mergeMetadata1 } from "@/new/photos/utils/file";
|
||||||
import { ensure } from "@/utils/ensure";
|
import { ensure } from "@/utils/ensure";
|
||||||
import { nullToUndefined } from "@/utils/transform";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { FileType } from "./file-type";
|
import { FileType } from "./file-type";
|
||||||
|
|
||||||
@ -773,11 +772,4 @@ export const fileLocation = (enteFile: EnteFile): Location | undefined => {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!enteFile.metadata) return undefined;
|
if (!enteFile.metadata) return undefined;
|
||||||
|
|
||||||
const latitude = nullToUndefined(enteFile.metadata.latitude);
|
|
||||||
const longitude = nullToUndefined(enteFile.metadata.longitude);
|
|
||||||
|
|
||||||
if (latitude === undefined || longitude === undefined) return undefined;
|
|
||||||
|
|
||||||
return { latitude, longitude };
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user