Store live photos as their image and their video (closes #107)
check / check (push) Successful in 40s
check / check (push) Successful in 40s
A live photo, which Ente stores as one ZIP, is unpacked as it downloads into its image and its video, each `<fileID>.<ext>` with its extension from the ZIP, beside `<fileID>.livephoto.json`, which names the two. Both are checked against the recorded hash and renamed into place only when both are complete. A ZIP with a second image or video, or whose parts come to more than 20 times its size plus 16 MiB, is refused. The backup and the content cache count a live photo as stored only with both files, album folders link both, `quak get` writes both, and the content result gives the video as `videoPath`. A ZIP an earlier version stored is replaced. Model: opus-5-5
This commit is contained in:
+26
-3
@@ -10,10 +10,11 @@ import {
|
||||
copyFileSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
statSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { extname, join } from "node:path";
|
||||
import {
|
||||
type Client,
|
||||
type ClientSnapshot,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
thumbnailName,
|
||||
} from "./cli-output.js";
|
||||
import { freshCollections, freshFiles, freshFile } from "./cli-read.js";
|
||||
import { withExtension } from "./filename.js";
|
||||
import { runMetadataBackup } from "./metadata-backup.js";
|
||||
import { listMissingThumbnails, fixMissingThumbnails } from "./thumbnails.js";
|
||||
|
||||
@@ -305,8 +307,29 @@ export const getCommand = async (
|
||||
// Default name is the file's own title, as the pre-library CLI used
|
||||
// (not the editedName-preferring projection title) (issue #52).
|
||||
const outPath = opts.out ?? originalName(file);
|
||||
copyFileSync(result.path, outPath);
|
||||
ctx.stderr.write(`${result.bytes} bytes -> ${outPath}\n`);
|
||||
if (result.videoPath === undefined) {
|
||||
copyFileSync(result.path, outPath);
|
||||
ctx.stderr.write(`${result.bytes} bytes -> ${outPath}\n`);
|
||||
return 0;
|
||||
}
|
||||
// A live photo is written as its image and its video, each named after
|
||||
// the title with its own extension, as Ente's clients name them. With
|
||||
// --out, the image goes there and the video beside it.
|
||||
const imageOut =
|
||||
opts.out ?? withExtension(outPath, extname(result.path));
|
||||
const videoOut = withExtension(outPath, extname(result.videoPath));
|
||||
if (imageOut.toLowerCase() === videoOut.toLowerCase()) {
|
||||
ctx.stderr.write(
|
||||
`File ${fileID} is a live photo, and its video would also be written to ${imageOut}\n`,
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
copyFileSync(result.path, imageOut);
|
||||
copyFileSync(result.videoPath, videoOut);
|
||||
ctx.stderr.write(
|
||||
`${result.bytes} bytes -> ${imageOut}\n` +
|
||||
`${statSync(videoOut).size} bytes -> ${videoOut}\n`,
|
||||
);
|
||||
return 0;
|
||||
} finally {
|
||||
await lib.close();
|
||||
|
||||
Reference in New Issue
Block a user