Something went wrong. Try again.
Mirror of https://github.com/kualta/site — web infrastructure
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129import sharp from "sharp";import { execFile } from "node:child_process";import { promisify } from "node:util";import { stat, writeFile } from "node:fs/promises";const exec = promisify(execFile);const cameraTags = [ "Make", "Model", "LensMake", "LensModel", "FNumber", "ExposureTime", "ISO", "FocalLength", "FocalLengthIn35mmFormat",];
export async function processMedia(input, output, { kind, policy, maxBytes = 900_000 }) { if (!["photo", "video"].includes(kind) || !["remove-location", "remove-all"].includes(policy)) throw new Error("Invalid media policy"); if (kind === "photo") { let image = sharp(input, { limitInputPixels: 80_000_000, animated: false }) .rotate() .resize({ width: 4000, height: 4000, fit: "inside", withoutEnlargement: true }); // Start with clean pixels. Never copy GPS, XMP, IPTC, serial numbers, previews, // maker notes, comments, or arbitrary original EXIF into destination files. const { stdout } = await exec("exiftool", ["-json", ...cameraTags.map((tag) => `-${tag}`), input]); const metadata = JSON.parse(stdout)[0]; const safe = Object.fromEntries( cameraTags.filter((tag) => metadata[tag] !== undefined).map((tag) => [tag, String(metadata[tag])]), ); let quality = 92; let dimension = 4000; let result; while (true) { result = await image .clone() .resize({ width: dimension, height: dimension, fit: "inside", withoutEnlargement: true }) .jpeg({ quality, mozjpeg: true }) .toBuffer(); if (result.length <= maxBytes - 4096) break; if (quality > 50) quality -= 8; else { dimension = Math.floor(dimension * 0.8); quality = 84; } if (dimension < 320) throw new Error("Image cannot fit the destination size limit"); } await writeFile(output, result); if (policy === "remove-location" && Object.keys(safe).length) { await exec("exiftool", [ "-overwrite_original", ...Object.entries(safe).map(([key, value]) => `-${key}=${value}`), output, ]); } if ((await stat(output)).size > maxBytes) throw new Error("Image exceeds destination size limit after metadata processing"); const info = await sharp(output).metadata(); return { kind, width: info.width, height: info.height, bytes: (await stat(output)).size, contentType: "image/jpeg", }; } // Decode/re-encode rather than copying potentially metadata-bearing streams. // Keep only video/audio; drop subtitles, data tracks, chapters and source tags. await exec( process.env.FFMPEG_PATH || "ffmpeg", [ "-nostdin", "-y", "-i", input, "-map", "0:v:0", "-map", "0:a:0?", "-map_metadata", "-1", "-map_chapters", "-1", "-c:v", "libx264", "-preset", "fast", "-crf", "20", "-pix_fmt", "yuv420p", "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2", "-c:a", "aac", "-b:a", "192k", "-bsf:v", "filter_units=remove_types=6", "-movflags", "+faststart", output, ], { timeout: 15 * 60 * 1000, maxBuffer: 2 * 1024 * 1024 }, ); await exec("exiftool", ["-overwrite_original", "-all=", output]); const { stdout } = await exec(process.env.FFPROBE_PATH || "ffprobe", [ "-v", "error", "-show_streams", "-show_format", "-of", "json", output, ]); const probe = JSON.parse(stdout); const video = probe.streams.find((stream) => stream.codec_type === "video"); if (!video) throw new Error("No video stream"); return { kind, width: video.width, height: video.height, duration: Number(probe.format.duration), bytes: (await stat(output)).size, contentType: "video/mp4", };}