Files
Jon Staab 1651ab9c99
Docker / build-and-push-image (push) Successful in 40s
Switch to bun, document docker
2026-04-14 11:06:52 -07:00

75 lines
1.8 KiB
JavaScript

import express from "express";
import ffmpeg from "fluent-ffmpeg";
import { PassThrough } from "stream";
import NodeCache from "node-cache";
const app = express();
const PORT = process.env.PORT || 3100;
const thumbnailCache = new NodeCache({
stdTTL: 3600,
checkperiod: 600,
useClones: false
});
app.get("/thumbnail", (req, res) => {
const videoUrl = req.query.url?.trim();
if (!videoUrl) {
return res.status(400).json({ error: "Missing 'url' query parameter" });
}
const cachedImage = thumbnailCache.get(videoUrl);
if (cachedImage) {
res.setHeader("Content-Type", "image/jpeg");
res.setHeader("Cache-Control", "public, max-age=86400");
return res.send(cachedImage);
}
try {
const passthrough = new PassThrough();
let imageBuffer = Buffer.alloc(0);
passthrough.on("data", (chunk) => {
imageBuffer = Buffer.concat([imageBuffer, chunk]);
});
passthrough.on("end", () => {
if (imageBuffer.length > 0) {
thumbnailCache.set(videoUrl, imageBuffer);
}
});
ffmpeg(videoUrl)
.seekInput(1)
.frames(1)
.format("image2")
.outputOptions("-vcodec", "mjpeg")
.on("error", (err) => {
console.error("ffmpeg error:", err.message);
if (!res.headersSent) {
res.status(500).json({ error: "Failed to generate thumbnail" });
}
})
.pipe(passthrough, { end: true });
res.setHeader("Content-Type", "image/jpeg");
res.setHeader("Cache-Control", "public, max-age=86400");
passthrough.pipe(res);
} catch (err) {
console.error("Error:", err);
if (!res.headersSent) {
res.status(500).json({ error: "Failed to process video" });
}
}
});
app.get("/health", (_req, res) => {
res.json({ status: "ok" });
});
app.listen(PORT, () => {
console.log(`Video thumbnail service running on port ${PORT}`);
});