feat: generate multiple thumbs and posters
This commit is contained in:
9
apps/worker/src/__tests__/variants-sizes.test.ts
Normal file
9
apps/worker/src/__tests__/variants-sizes.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { computeImageVariantPlan } from "../variants";
|
||||
|
||||
test("computeImageVariantPlan includes 256 and 768 thumbs", () => {
|
||||
expect(computeImageVariantPlan()).toEqual([
|
||||
{ kind: "thumb", size: 256 },
|
||||
{ kind: "thumb", size: 768 },
|
||||
]);
|
||||
});
|
||||
@@ -7,6 +7,8 @@ import { Readable } from "stream";
|
||||
|
||||
import sharp from "sharp";
|
||||
|
||||
import { computeImageVariantPlan, computeVideoPosterPlan } from "./variants";
|
||||
|
||||
import {
|
||||
CopyObjectCommand,
|
||||
GetObjectCommand,
|
||||
@@ -426,53 +428,37 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
if (updates.width === null && imgMeta.width) updates.width = imgMeta.width;
|
||||
if (updates.height === null && imgMeta.height) updates.height = imgMeta.height;
|
||||
|
||||
const thumb256Path = join(tempDir, "thumb_256.jpg");
|
||||
const thumb768Path = join(tempDir, "thumb_768.jpg");
|
||||
await sharp(inputPath)
|
||||
.rotate()
|
||||
.resize(256, 256, { fit: "inside", withoutEnlargement: true })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(thumb256Path);
|
||||
await sharp(inputPath)
|
||||
.rotate()
|
||||
.resize(768, 768, { fit: "inside", withoutEnlargement: true })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(thumb768Path);
|
||||
const imagePlan = computeImageVariantPlan();
|
||||
const thumbKeys: Record<number, string> = {};
|
||||
for (const item of imagePlan) {
|
||||
const size = item.size;
|
||||
const thumbPath = join(tempDir, `thumb_${size}.jpg`);
|
||||
await sharp(inputPath)
|
||||
.rotate()
|
||||
.resize(size, size, { fit: "inside", withoutEnlargement: true })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(thumbPath);
|
||||
|
||||
const thumb256Key = `thumbs/${asset.id}/image_256.jpg`;
|
||||
const thumb768Key = `thumbs/${asset.id}/image_768.jpg`;
|
||||
await uploadObject({
|
||||
bucket: asset.bucket,
|
||||
key: thumb256Key,
|
||||
filePath: thumb256Path,
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
await uploadObject({
|
||||
bucket: asset.bucket,
|
||||
key: thumb768Key,
|
||||
filePath: thumb768Path,
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
await upsertVariant({
|
||||
assetId: asset.id,
|
||||
kind: "thumb",
|
||||
size: 256,
|
||||
key: thumb256Key,
|
||||
mimeType: "image/jpeg",
|
||||
width: typeof updates.width === "number" ? updates.width : null,
|
||||
height: typeof updates.height === "number" ? updates.height : null,
|
||||
});
|
||||
await upsertVariant({
|
||||
assetId: asset.id,
|
||||
kind: "thumb",
|
||||
size: 768,
|
||||
key: thumb768Key,
|
||||
mimeType: "image/jpeg",
|
||||
width: typeof updates.width === "number" ? updates.width : null,
|
||||
height: typeof updates.height === "number" ? updates.height : null,
|
||||
});
|
||||
updates.thumb_small_key = thumb256Key;
|
||||
updates.thumb_med_key = thumb768Key;
|
||||
const thumbKey = `thumbs/${asset.id}/image_${size}.jpg`;
|
||||
await uploadObject({
|
||||
bucket: asset.bucket,
|
||||
key: thumbKey,
|
||||
filePath: thumbPath,
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
await upsertVariant({
|
||||
assetId: asset.id,
|
||||
kind: "thumb",
|
||||
size,
|
||||
key: thumbKey,
|
||||
mimeType: "image/jpeg",
|
||||
width: typeof updates.width === "number" ? updates.width : null,
|
||||
height: typeof updates.height === "number" ? updates.height : null,
|
||||
});
|
||||
thumbKeys[size] = thumbKey;
|
||||
}
|
||||
updates.thumb_small_key = thumbKeys[256] ?? null;
|
||||
updates.thumb_med_key = thumbKeys[768] ?? null;
|
||||
} else if (asset.media_type === "video") {
|
||||
rawTags = await tryReadExifTags();
|
||||
maybeSetCaptureDateFromTags(rawTags);
|
||||
@@ -512,36 +498,42 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
|
||||
rawTags = { ...rawTags, ffprobe: ffprobeData };
|
||||
|
||||
const posterPath = join(tempDir, "poster_256.jpg");
|
||||
await runCommand("ffmpeg", [
|
||||
"-i",
|
||||
inputPath,
|
||||
"-vf",
|
||||
"scale=256:256:force_original_aspect_ratio=decrease",
|
||||
"-vframes",
|
||||
"1",
|
||||
"-q:v",
|
||||
"2",
|
||||
"-y",
|
||||
posterPath
|
||||
]);
|
||||
const posterKey = `thumbs/${asset.id}/poster_256.jpg`;
|
||||
await uploadObject({
|
||||
bucket: asset.bucket,
|
||||
key: posterKey,
|
||||
filePath: posterPath,
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
await upsertVariant({
|
||||
assetId: asset.id,
|
||||
kind: "poster",
|
||||
size: 256,
|
||||
key: posterKey,
|
||||
mimeType: "image/jpeg",
|
||||
width: typeof updates.width === "number" ? updates.width : null,
|
||||
height: typeof updates.height === "number" ? updates.height : null,
|
||||
});
|
||||
updates.poster_key = posterKey;
|
||||
const posterPlan = computeVideoPosterPlan();
|
||||
const posterKeys: Record<number, string> = {};
|
||||
for (const item of posterPlan) {
|
||||
const size = item.size;
|
||||
const posterPath = join(tempDir, `poster_${size}.jpg`);
|
||||
await runCommand("ffmpeg", [
|
||||
"-i",
|
||||
inputPath,
|
||||
"-vf",
|
||||
`scale=${size}:${size}:force_original_aspect_ratio=decrease`,
|
||||
"-vframes",
|
||||
"1",
|
||||
"-q:v",
|
||||
"2",
|
||||
"-y",
|
||||
posterPath
|
||||
]);
|
||||
const posterKey = `thumbs/${asset.id}/poster_${size}.jpg`;
|
||||
await uploadObject({
|
||||
bucket: asset.bucket,
|
||||
key: posterKey,
|
||||
filePath: posterPath,
|
||||
contentType: "image/jpeg",
|
||||
});
|
||||
await upsertVariant({
|
||||
assetId: asset.id,
|
||||
kind: "poster",
|
||||
size,
|
||||
key: posterKey,
|
||||
mimeType: "image/jpeg",
|
||||
width: typeof updates.width === "number" ? updates.width : null,
|
||||
height: typeof updates.height === "number" ? updates.height : null,
|
||||
});
|
||||
posterKeys[size] = posterKey;
|
||||
}
|
||||
updates.poster_key = posterKeys[256] ?? null;
|
||||
}
|
||||
|
||||
if (asset.media_type === "video" && typeof updates.poster_key !== "string") {
|
||||
|
||||
18
apps/worker/src/variants.ts
Normal file
18
apps/worker/src/variants.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export type VariantPlanItem = {
|
||||
kind: "thumb" | "poster";
|
||||
size: number;
|
||||
};
|
||||
|
||||
export function computeImageVariantPlan(): VariantPlanItem[] {
|
||||
return [
|
||||
{ kind: "thumb", size: 256 },
|
||||
{ kind: "thumb", size: 768 },
|
||||
];
|
||||
}
|
||||
|
||||
export function computeVideoPosterPlan(): VariantPlanItem[] {
|
||||
return [
|
||||
{ kind: "poster", size: 256 },
|
||||
{ kind: "poster", size: 768 },
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user