Skip to content
This repository has been archived by the owner on Mar 9, 2024. It is now read-only.

Commit

Permalink
Reject requests if the queue is too long
Browse files Browse the repository at this point in the history
  • Loading branch information
soruly committed Mar 3, 2024
1 parent d1a99d4 commit e63365f
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 133 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ SERVER_ADDR=127.0.0.1
TRACE_MEDIA_SALT=TRACE_MEDIA_SALT
TRACE_API_SECRET=TRACE_API_SECRET
IP_WHITELIST=192.168.1.100
MAX_QUEUE=8
7 changes: 4 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ app.use((req, res, next) => {

app.use(
rateLimit({
max: 30, // 30 requests per IP address (per node.js process)
windowMs: 60 * 1000, // per 1 minute
max: 60, // limit each IP to 60 requests per 60 seconds
delayMs: 0, // disable delaying - full speed until the max limit is reached
}),
);
app.locals.queue = 0;

app.get("/", (req, res) => res.send("ok"));

Expand All @@ -74,7 +75,7 @@ app.get("/image/:anilistID/:filename", image);

app.use("/file/:anilistID/:filename", checkSecret, file);

app.use("/list", checkSecret, list);
app.use("/list", list);

app.use("/admin", checkIP, admin);

Expand Down
72 changes: 41 additions & 31 deletions src/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,43 @@ import fs from "fs-extra";
import crypto from "crypto";
import child_process from "child_process";

const { VIDEO_PATH = "/mnt/", TRACE_MEDIA_SALT } = process.env;
const { VIDEO_PATH = "/mnt/", TRACE_MEDIA_SALT, MAX_QUEUE } = process.env;

const generateImagePreview = (filePath, t, size = "m") => {
const ffmpeg = child_process.spawnSync("ffmpeg", [
"-hide_banner",
"-loglevel",
"error",
"-nostats",
"-y",
"-ss",
t - 10,
"-i",
filePath,
"-ss",
"10",
"-vf",
`scale=${{ l: 640, m: 320, s: 160 }[size]}:-2`,
"-c:v",
"mjpeg",
"-vframes",
"1",
"-f",
"image2pipe",
"pipe:1",
]);
if (ffmpeg.stderr.length) {
console.log(ffmpeg.stderr.toString());
}
return ffmpeg.stdout;
};
const generateImagePreview = async (filePath, t, size = "m") =>
new Promise((resolve) => {
const ffmpeg = child_process.spawn("ffmpeg", [
"-hide_banner",
"-loglevel",
"error",
"-nostats",
"-y",
"-ss",
t - 10,
"-i",
filePath,
"-ss",
"10",
"-vf",
`scale=${{ l: 640, m: 320, s: 160 }[size]}:-2`,
"-c:v",
"mjpeg",
"-vframes",
"1",
"-f",
"image2pipe",
"pipe:1",
]);
ffmpeg.stderr.on("data", (data) => {
console.log(data.toString());
});
let chunks = Buffer.alloc(0);
ffmpeg.stdout.on("data", (data) => {
chunks = Buffer.concat([chunks, data]);
});
ffmpeg.on("close", () => {
resolve(chunks);
});
});

export default async (req, res) => {
if (
Expand Down Expand Up @@ -67,19 +74,22 @@ export default async (req, res) => {
if (!videoFilePath.startsWith(VIDEO_PATH)) {
return res.status(403).send("Forbidden");
}
if (!fs.existsSync(videoFilePath)) {
if (!(await fs.exists(videoFilePath))) {
return res.status(404).send("Not found");
}
const size = req.query.size || "m";
if (!["l", "m", "s"].includes(size)) {
return res.status(400).send("Bad Request. Invalid param: size");
}
if (req.app.locals.queue > MAX_QUEUE) return res.status(503).send("Service Unavailable");
req.app.locals.queue++;
try {
const image = generateImagePreview(videoFilePath, t, size);
const image = await generateImagePreview(videoFilePath, t, size);
res.set("Content-Type", "image/jpg");
res.send(image);
} catch (e) {
console.log(e);
res.status(500).send("Internal Server Error");
}
req.app.locals.queue--;
};
52 changes: 27 additions & 25 deletions src/lib/detect-scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default async (filePath, t, minDuration) => {
return null;
}

const videoDuration = getVideoDuration(filePath);
const videoDuration = await getVideoDuration(filePath);
if (videoDuration === null || t > videoDuration) {
return null;
}
Expand All @@ -32,31 +32,33 @@ export default async (filePath, t, minDuration) => {
const height = 18;

const tempPath = path.join(os.tmpdir(), `videoPreview${process.hrtime().join("")}`);
fs.removeSync(tempPath);
fs.ensureDirSync(tempPath);
const ffmpeg = child_process.spawnSync(
"ffmpeg",
[
"-y",
"-ss",
trimStart - 10,
"-i",
filePath,
"-ss",
"10",
"-t",
trimEnd - trimStart,
"-an",
"-vf",
`fps=${fps},scale=${width}:${height}`,
`${tempPath}/%04d.jpg`,
],
{ encoding: "utf-8" },
);
// console.log(ffmpeg.stderr);
await fs.remove(tempPath);
await fs.ensureDir(tempPath);
await new Promise((resolve) => {
const ffmpeg = child_process.spawn(
"ffmpeg",
[
"-y",
"-ss",
trimStart - 10,
"-i",
filePath,
"-ss",
"10",
"-t",
trimEnd - trimStart,
"-an",
"-vf",
`fps=${fps},scale=${width}:${height}`,
`${tempPath}/%04d.jpg`,
],
{ encoding: "utf-8" },
);
ffmpeg.on("close", resolve);
});

const imageDataList = await Promise.all(
fs.readdirSync(tempPath).map(
(await fs.readdir(tempPath)).map(
(file) =>
new Promise(async (resolve) => {
const canvas = Canvas.createCanvas(width, height);
Expand All @@ -67,7 +69,7 @@ export default async (filePath, t, minDuration) => {
}),
),
);
fs.removeSync(tempPath);
await fs.remove(tempPath);

const getImageDiff = (a, b) => {
let diff = 0;
Expand Down
31 changes: 19 additions & 12 deletions src/lib/get-video-duration.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import child_process from "child_process";

export default (filePath) => {
const stdLog = child_process.spawnSync(
"ffprobe",
["-i", filePath, "-show_entries", "format=duration", "-v", "quiet"],
{ encoding: "utf-8" },
).stdout;
const result = /duration=((\d|\.)+)/.exec(stdLog);
if (result === null) {
return null;
}
return parseFloat(result[1]);
};
export default async (filePath) =>
new Promise((resolve) => {
const ffprobe = child_process.spawn(
"ffprobe",
["-i", filePath, "-show_entries", "format=duration", "-v", "quiet"],
{ encoding: "utf-8" },
);
let stdLog = "";
ffprobe.stdout.on("data", (data) => {
stdLog += data.toString();
});
ffprobe.on("close", () => {
const result = /duration=((\d|\.)+)/.exec(stdLog);
if (result === null) {
resolve(null);
}
resolve(parseFloat(result[1]));
});
});
Loading

0 comments on commit e63365f

Please sign in to comment.