Support HEIF library imports in production

This commit is contained in:
vance 2026-04-08 10:50:40 -07:00
parent 6657125a1e
commit 6c8092d4ad
3 changed files with 68 additions and 54 deletions

View File

@ -19,6 +19,12 @@ COPY . .
FROM workspace-source AS node-runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libheif1 \
libheif-plugin-libde265 \
libde265-0 \
&& rm -rf /var/lib/apt/lists/*
FROM workspace-source AS admin-build
WORKDIR /app

View File

@ -1,4 +1,8 @@
services:
api:
volumes:
- ./assets/import-library:/app/assets/import-library:ro
admin:
build:
context: .

View File

@ -10,7 +10,7 @@ interface ImportedAssetRecord {
consent: ContributorConsent;
}
const supportedExtensions = new Set([".jpg", ".jpeg", ".png", ".webp"]);
const supportedExtensions = new Set([".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif"]);
const repoRoot = path.dirname(config.storageDir);
const importLibraryDir = path.join(repoRoot, "assets", "import-library");
export const libraryWatchDirs = [importLibraryDir];
@ -135,63 +135,67 @@ export const createLibraryAssets = async () => {
const imported: ImportedAssetRecord[] = [];
for (const file of files) {
const baseName = path.parse(file.relativeName).name;
const baseId = `library-photo-${toSlug(baseName)}`;
const displayTitle = baseName.replace(/[-_]+/g, " ");
const originalRelativePath = path.join("runtime", "library", `${baseId}-original.jpg`);
const originalAbsolutePath = path.join(config.storageDir, originalRelativePath);
try {
const baseName = path.parse(file.relativeName).name;
const baseId = `library-photo-${toSlug(baseName)}`;
const displayTitle = baseName.replace(/[-_]+/g, " ");
const originalRelativePath = path.join("runtime", "library", `${baseId}-original.jpg`);
const originalAbsolutePath = path.join(config.storageDir, originalRelativePath);
const sourceImage = sharp(file.absolutePath).rotate();
const metadata = await sourceImage.metadata();
const width = metadata.width ?? 1600;
const height = metadata.height ?? 900;
const sourceImage = sharp(file.absolutePath).rotate();
const metadata = await sourceImage.metadata();
const width = metadata.width ?? 1600;
const height = metadata.height ?? 900;
await sourceImage.clone().jpeg({ quality: 92 }).toFile(originalAbsolutePath);
const dominantColor = await getDominantColor(sourceImage);
const thumbRelativePath = path.join("runtime", "library", `${baseId}-thumb.jpg`);
const previewRelativePath = path.join("runtime", "library", `${baseId}-preview.jpg`);
const renderRelativePath = path.join("runtime", "library", `${baseId}-render.jpg`);
await sourceImage.clone().jpeg({ quality: 92 }).toFile(originalAbsolutePath);
const dominantColor = await getDominantColor(sourceImage);
const thumbRelativePath = path.join("runtime", "library", `${baseId}-thumb.jpg`);
const previewRelativePath = path.join("runtime", "library", `${baseId}-preview.jpg`);
const renderRelativePath = path.join("runtime", "library", `${baseId}-render.jpg`);
await sourceImage
.clone()
.resize({ width: 320, height: 320, fit: "cover", position: "attention" })
.jpeg({ quality: 84 })
.toFile(path.join(config.storageDir, thumbRelativePath));
await sourceImage
.clone()
.resize({
width: 1280,
height: 1280,
fit: "inside",
withoutEnlargement: true
})
.jpeg({ quality: 86 })
.toFile(path.join(config.storageDir, previewRelativePath));
await sourceImage
.clone()
.resize({
width: 1920,
height: 1920,
fit: "inside",
withoutEnlargement: true
})
.jpeg({ quality: 88 })
.toFile(path.join(config.storageDir, renderRelativePath));
await sourceImage
.clone()
.resize({ width: 320, height: 320, fit: "cover", position: "attention" })
.jpeg({ quality: 84 })
.toFile(path.join(config.storageDir, thumbRelativePath));
await sourceImage
.clone()
.resize({
width: 1280,
height: 1280,
fit: "inside",
withoutEnlargement: true
})
.jpeg({ quality: 86 })
.toFile(path.join(config.storageDir, previewRelativePath));
await sourceImage
.clone()
.resize({
width: 1920,
height: 1920,
fit: "inside",
withoutEnlargement: true
})
.jpeg({ quality: 88 })
.toFile(path.join(config.storageDir, renderRelativePath));
imported.push(
toImportedRecord({
id: baseId,
title: displayTitle,
originalKey: `/uploads/${originalRelativePath}`,
thumbKey: `/uploads/${thumbRelativePath}`,
previewKey: `/uploads/${previewRelativePath}`,
renderKey: `/uploads/${renderRelativePath}`,
mimeType: "image/jpeg",
width,
height,
dominantColor
})
);
imported.push(
toImportedRecord({
id: baseId,
title: displayTitle,
originalKey: `/uploads/${originalRelativePath}`,
thumbKey: `/uploads/${thumbRelativePath}`,
previewKey: `/uploads/${previewRelativePath}`,
renderKey: `/uploads/${renderRelativePath}`,
mimeType: "image/jpeg",
width,
height,
dominantColor
})
);
} catch (error) {
console.warn(`[library-import] Skipping ${file.relativeName}:`, error);
}
}
return imported;