Files
Goldebek/scripts/strip-image-metadata.mjs
Per Hoener 0d45331f5c
Deploy / validate-dispatch (push) Successful in 43s
Deploy / scheduled-rebuild-dev (push) Skipped
Deploy / scheduled-rebuild-master (push) Skipped
Deploy / rollback-dev (push) Skipped
Deploy / rollback-master (push) Skipped
Deploy / changes (push) Successful in 13s
Deploy / secret-scan (push) Successful in 9s
Deploy / app-quality (push) Successful in 11m21s
Deploy / security-deps (push) Successful in 14s
Deploy / context (push) Failing after 1s
Deploy / app-image (push) Skipped
Deploy / deploy-dev (push) Skipped
Deploy / deploy-master (push) Skipped
Initial commit
2026-09-18 23:37:19 +02:00

84 lines
2.4 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import sharp from "sharp";
const PUBLIC_IMAGES_DIR = path.resolve(import.meta.dirname, "../public/images");
const SUPPORTED_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]);
const METADATA_FIELDS = ["exif", "icc", "iptc", "xmp"];
const CHECK_ONLY = process.argv.includes("--check");
function collectImages(directory) {
if (!fs.existsSync(directory)) return [];
const files = [];
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
files.push(...collectImages(fullPath));
continue;
}
if (entry.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
files.push(fullPath);
}
}
return files;
}
function getEncoder(pipeline, extension) {
if (extension === ".png") {
return pipeline.png({ compressionLevel: 9, adaptiveFiltering: true });
}
if (extension === ".webp") {
return pipeline.webp({ quality: 80 });
}
if (extension === ".avif") {
return pipeline.avif({ quality: 50 });
}
return pipeline.jpeg({ quality: 85, progressive: true, mozjpeg: true });
}
async function hasMetadata(filePath) {
const metadata = await sharp(filePath).metadata();
return METADATA_FIELDS.some((field) => Boolean(metadata[field]));
}
async function stripMetadata(filePath) {
const extension = path.extname(filePath).toLowerCase();
const tempPath = `${filePath}.tmp`;
if (fs.existsSync(tempPath)) {
fs.rmSync(tempPath);
}
const pipeline = sharp(filePath).rotate();
await getEncoder(pipeline, extension).toFile(tempPath);
fs.renameSync(tempPath, filePath);
}
const files = collectImages(PUBLIC_IMAGES_DIR);
const filesWithMetadata = [];
for (const file of files) {
if (await hasMetadata(file)) {
filesWithMetadata.push(file);
}
}
if (CHECK_ONLY) {
for (const file of filesWithMetadata) {
console.error(`[images:check-metadata] Metadata found: ${file}`);
}
if (filesWithMetadata.length > 0) {
process.exitCode = 1;
} else {
console.log(`[images:check-metadata] ${files.length} images checked; no metadata blocks found.`);
}
} else {
for (const file of filesWithMetadata) {
await stripMetadata(file);
}
console.log(
`[images:strip-metadata] ${filesWithMetadata.length} of ${files.length} images rewritten.`,
);
}