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 FROM workspace-source AS node-runtime
WORKDIR /app 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 FROM workspace-source AS admin-build
WORKDIR /app WORKDIR /app

View File

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

View File

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