Files
yt2press/server.js
T
2026-07-13 20:12:51 +07:00

172 lines
4.2 KiB
JavaScript

// @ts-check
import { WebSocketServer } from "ws";
import { readFileSync, mkdirSync, writeFileSync } from "fs";
import { join as joinPath } from "path";
import matter from "gray-matter";
import sharp from "sharp";
import { renderFile } from "./group-similar.js";
const MD_ROOT_DIR = "docs";
const IMG_ROOT_DIR = "docs/public/images";
const MAX_W = 800;
const MAX_H = 600;
const FORMAT = "webp";
const WEBP_QUALITY = 75; // tweak 60-85 if you want smaller/larger
const PORT = 8080;
const wss = new WebSocketServer({ port: PORT });
console.log(`WebSocket server running on ws://localhost:${PORT}`);
wss.on("connection", (ws) => {
console.log("New client connected");
let filename = "";
ws.on("message", (message, isBinary) => {
if (message instanceof Buffer && isBinary) {
sharp(message)
.rotate() // respects EXIF orientation
.resize({
width: MAX_W,
height: MAX_H,
fit: "inside", // preserve aspect, fit within box
withoutEnlargement: true, // don't upscale small images
})
.toFormat(FORMAT, {
quality: WEBP_QUALITY,
effort: 4,
})
.toFile(joinPath(IMG_ROOT_DIR, filename));
return;
}
const { name, ...payload } = JSON.parse(message.toString("utf-8"));
switch (name) {
case "download_q":
{
const { q, duration } = payload;
console.log(`Start ${q}`);
const md = loadMdFile(q);
const q_stem = make_q_stem(q);
try {
mkdirSync(joinPath(IMG_ROOT_DIR, q_stem));
} catch (e) {}
let isChanged = false;
const ts = [];
md.data.video = "/" + q;
const { interval } = md.data;
if (typeof interval === "number") {
for (let t = 0; t < duration; t += interval) {
const min = Math.floor(t / 60);
const sec = Math.floor(t - min * 60);
md.content += `\n${min}:${sec.toString().padStart(2, "0")}\n`;
}
}
const re = /^(?<min>\d{1,2}):(?<sec>\d{2})\s*$/;
const lines = md.content.split("\n");
let newContent = "";
for (const line of lines) {
const m = re.exec(line);
if (m?.groups) {
const { min, sec } = m.groups;
const t = `${min}_${sec}`;
ts.push(t);
newContent += `![${line}](/images/${q_stem}/${t}.webp)\n`;
newContent += `${line}\n`;
newContent += `\n`;
isChanged = true;
} else {
newContent += `${line}\n`;
}
}
if (isChanged) {
writeFileSync(md.filename, matter.stringify(newContent, md.data));
ws.send(JSON.stringify({ name: "seek_q_ts", q, ts }));
}
}
break;
case "timed_q":
{
const { q, t } = payload;
const q_stem = make_q_stem(q);
filename = t ? joinPath(q_stem, `${t}.webp`) : "";
}
break;
case "done":
{
const { q } = payload;
const md = loadMdFile(q);
console.log(`Done basic parsing ${q}`);
const { dedup } = md.data;
if (dedup) {
console.log(`Deduping ${md.filename}`);
renderFile(
md.filename,
md.filename,
typeof dedup === "number" ? dedup : undefined,
).then((stats) => {
// Don't log a thing yet.
console.log(`Done deduping ${md.filename}`);
});
}
console.log(`Done ${q}`);
}
break;
}
});
ws.on("close", () => {
console.log("Client disconnected");
});
});
/**
*
* @param {string} q
* @returns
*/
function loadMdFile(q) {
const q_stem = make_q_stem(q);
const filename = joinPath(MD_ROOT_DIR, `${q_stem}.md`);
try {
// create a blank md file if not exists
readFileSync(filename, { flag: "a" });
} catch (e) {}
return { filename, ...matter(readFileSync(filename, "utf-8")) };
}
/**
*
* @param {string} q
* @returns
*/
function make_q_stem(q) {
return q
.replace(/\.[a-z0-9]+$/, "")
.replace(/\[.+?\]/g, "")
.trim()
.replace(/[%\s]+/g, "_");
}