From 5e6069f574b10d31d4c16fc87dcf741ca126ff64 Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 9 Jun 2026 17:42:30 -0400 Subject: [PATCH] ML data always included in backup-metadata; remove --no-ml ML metadata (face detections, CLIP embeddings) is not a separate category from the rest of the metadata. It is always fetched and included. The only opt-in is --exif (or --all) which requires downloading every file for EXIF extraction. --- bin/quak.ts | 23 ++++++---------- src/metadata-backup.ts | 20 +++++--------- test/cli/metadata-backup.test.ts | 47 ++------------------------------ 3 files changed, 17 insertions(+), 73 deletions(-) diff --git a/bin/quak.ts b/bin/quak.ts index 1d9e48d..6726791 100644 --- a/bin/quak.ts +++ b/bin/quak.ts @@ -340,26 +340,19 @@ program "Dump all decrypted account metadata to a directory of JSON files", ) .argument("", "Output directory") - .option("--no-ml", "Skip ML data (face detections, CLIP embeddings)") .option( "--exif", "Download each file and extract full EXIF/IPTC/XMP metadata (slow)", ) .option("--all", "Alias for --exif") - .action( - async ( - dir: string, - opts: { ml?: boolean; exif?: boolean; all?: boolean }, - ) => { - await init(); - const client = requireSession(); - await runMetadataBackup(client, dir, { - mlData: opts.ml, - exif: opts.exif || opts.all, - onProgress: (msg) => stderr.write(msg + "\n"), - }); - }, - ); + .action(async (dir: string, opts: { exif?: boolean; all?: boolean }) => { + await init(); + const client = requireSession(); + await runMetadataBackup(client, dir, { + exif: opts.exif || opts.all, + onProgress: (msg) => stderr.write(msg + "\n"), + }); + }); program .command("backup") diff --git a/src/metadata-backup.ts b/src/metadata-backup.ts index b44f47f..aacf990 100644 --- a/src/metadata-backup.ts +++ b/src/metadata-backup.ts @@ -11,7 +11,6 @@ import type { EnteFile } from "./model/types.js"; export type ProgressCallback = (message: string) => void; export interface MetadataBackupOptions { - mlData?: boolean; exif?: boolean; onProgress?: ProgressCallback; } @@ -120,7 +119,6 @@ export const runMetadataBackup = async ( opts?: MetadataBackupOptions, ): Promise => { const log = opts?.onProgress ?? (() => {}); - const wantML = opts?.mlData ?? true; const wantExif = opts?.exif ?? false; mkdirSync(outDir, { recursive: true }); @@ -176,17 +174,13 @@ export const runMetadataBackup = async ( } } - // Fetch ML data in bulk if requested - let mlDataMap = new Map>(); - if (wantML) { - log("Fetching ML data (face detections, CLIP embeddings)..."); - mlDataMap = await fetchMLDataForFiles( - client, - [...fileKeys.keys()], - fileKeys, - ); - log(`Got ML data for ${mlDataMap.size} file(s)`); - } + log("Fetching ML data (face detections, CLIP embeddings)..."); + const mlDataMap = await fetchMLDataForFiles( + client, + [...fileKeys.keys()], + fileKeys, + ); + log(`Got ML data for ${mlDataMap.size} file(s)`); // Write per-file JSON (with optional ML data and EXIF) const writtenFileIDs = new Set(); diff --git a/test/cli/metadata-backup.test.ts b/test/cli/metadata-backup.test.ts index 09cc746..23d13ff 100644 --- a/test/cli/metadata-backup.test.ts +++ b/test/cli/metadata-backup.test.ts @@ -557,7 +557,7 @@ describe("quak backup-metadata", () => { expect(account.email).toBe(TEST_EMAIL); }); - it("fetches and decrypts ML data when --ml is set", async () => { + it("fetches and decrypts ML data by default", async () => { const outDir = join(testDir, "ml-data"); const client = await Client.login({ email: TEST_EMAIL, @@ -565,7 +565,7 @@ describe("quak backup-metadata", () => { apiOptions: { fetch: buildMetaFetch(mock) }, }); - await runMetadataBackup(client, outDir, { mlData: true }); + await runMetadataBackup(client, outDir); const collDirs = readdirSync(join(outDir, "collections")); const vacDir = collDirs.find((d) => d.includes("Vacation"))!; @@ -587,49 +587,6 @@ describe("quak backup-metadata", () => { expect(fileMeta.mlData.clip.embedding).toEqual([0.5, 0.6, 0.7]); }); - it("includes ML data by default", async () => { - const outDir = join(testDir, "ml-default"); - const client = await Client.login({ - email: TEST_EMAIL, - password: TEST_PASSWORD, - apiOptions: { fetch: buildMetaFetch(mock) }, - }); - - await runMetadataBackup(client, outDir); - - const collDirs = readdirSync(join(outDir, "collections")); - const vacDir = collDirs.find((d) => d.includes("Vacation"))!; - const fileMeta = JSON.parse( - readFileSync( - join(outDir, "collections", vacDir, "100.json"), - "utf-8", - ), - ); - expect(fileMeta.mlData).toBeDefined(); - expect(fileMeta.mlData.face.faces[0].faceID).toBe("face-abc"); - }); - - it("excludes ML data when mlData: false", async () => { - const outDir = join(testDir, "no-ml"); - const client = await Client.login({ - email: TEST_EMAIL, - password: TEST_PASSWORD, - apiOptions: { fetch: buildMetaFetch(mock) }, - }); - - await runMetadataBackup(client, outDir, { mlData: false }); - - const collDirs = readdirSync(join(outDir, "collections")); - const vacDir = collDirs.find((d) => d.includes("Vacation"))!; - const fileMeta = JSON.parse( - readFileSync( - join(outDir, "collections", vacDir, "100.json"), - "utf-8", - ), - ); - expect(fileMeta.mlData).toBeUndefined(); - }); - it("extracts EXIF from downloaded files when --exif is set", async () => { const outDir = join(testDir, "exif-data"); const client = await Client.login({