Decrypt and persist all file metadata layers

Extends RawEnteFile and EnteFile with optional magicMetadata and
pubMagicMetadata fields. Both are secretstream blobs under the file
key, decrypted to arbitrary JSON (Record<string, unknown>).

pubMagicMetadata carries ML-derived data from the Ente clients:
camera make/model, image dimensions, datetime with timezone offset,
and (when present) captions, editedName, face labels, keywords.

magicMetadata carries private mutable fields like visibility.

Backup now writes per-file JSON at originals/<fileID>.json containing
all three metadata layers (basic + magic + pubMagic).

Live-tested: all 11 files in the dev account have pubMagicMetadata
with SONY DSC-RX1RM3 camera info and 3000x2000 dimensions.
This commit is contained in:
2026-05-13 19:07:16 -07:00
parent 7baa9b585a
commit d4098c711a
4 changed files with 49 additions and 0 deletions
+19
View File
@@ -7,6 +7,7 @@ import type {
FileType,
RawCollection,
RawEnteFile,
RawMagicMetadata,
} from "./types.js";
const KNOWN_COLLECTION_TYPES = new Set([
@@ -88,14 +89,32 @@ export const decryptFile = (
hash: metadataJSON.hash,
};
const magicMetadata = decryptMagicMetadata(raw.magicMetadata, key);
const pubMagicMetadata = decryptMagicMetadata(raw.pubMagicMetadata, key);
return {
id: raw.id,
collectionID: raw.collectionID,
ownerID: raw.ownerID,
key,
metadata,
magicMetadata,
pubMagicMetadata,
file: { decryptionHeader: raw.file.decryptionHeader },
thumbnail: { decryptionHeader: raw.thumbnail.decryptionHeader },
updationTime: raw.updationTime,
};
};
const decryptMagicMetadata = (
raw: RawMagicMetadata | undefined,
key: Uint8Array,
): Record<string, unknown> | undefined => {
if (!raw?.data || !raw?.header) return undefined;
const bytes = decryptBlob(
fromBase64(raw.data),
fromBase64(raw.header),
key,
);
return JSON.parse(new TextDecoder().decode(bytes));
};