renderizando projetos no mmpCreator utilizando o lmms

This commit is contained in:
JotaChina committed 2025-12-28 14:41:30 -03:00
1 parent e902e8d735
commit ca2c2fa15a
2 files changed
+213 -1

No files matched your search

+136
View File
@@ -9,6 +9,10 @@ const { Server } = require("socket.io");
const fs = require("fs");
const path = require("path");
const pino = require("pino");
const os = require("os");
const crypto = require("crypto");
const { spawn } = require("child_process");
//import { LOG_SERVER } from "../utils.js"
const LOG_SERVER = `/var/www/html/trens/src_mmpSearch/logs/creation_logs/server`
const SESSION_JSON = `/var/www/html/trens/src_mmpSearch/logs/creation_logs/sessions`
@@ -627,6 +631,138 @@ io.on("connection", (socket) => {
});
});
// Conversão de projeto em áudio no mmpCreator
app.use(express.json({ limit: "25mb" }));
app.use((req, res, next) => {
const origin = req.headers.origin;
// ajuste se você usa outros hosts no dev
const allowed = new Set([
"https://alice.ufsj.edu.br",
]);
if (origin && allowed.has(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Vary", "Origin");
}
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
if (req.method === "OPTIONS") return res.sendStatus(204);
next();
});
const LMMS_BIN = process.env.LMMS_BIN || "lmms";
const LMMS_TIMEOUT_MS = Number(process.env.LMMS_TIMEOUT_MS || 5 * 60 * 1000);
// opcional (pra servidor sem display): LMMS_USE_XVFB=1
function buildLmmsCommand(inputMmp, outputAudio) {
const useXvfb =
process.env.LMMS_USE_XVFB === "1" ||
(!process.env.DISPLAY && process.env.LMMS_USE_XVFB !== "0");
if (useXvfb) {
// xvfb-run -a lmms -r in.mmp -o out.wav
return { cmd: "xvfb-run", args: ["-a", LMMS_BIN, "-r", inputMmp, "-o", outputAudio] };
}
return { cmd: LMMS_BIN, args: ["-r", inputMmp, "-o", outputAudio] };
}
function sanitizeFileName(name) {
return String(name || "projeto")
.normalize("NFKD")
.replace(/[^\w\s.-]/g, "")
.trim()
.replace(/\s+/g, "_")
.slice(0, 120) || "projeto";
}
function run(cmd, args, { timeoutMs } = {}) {
return new Promise((resolve, reject) => {
const p = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
let out = "";
let err = "";
p.stdout.on("data", (d) => (out += d.toString()));
p.stderr.on("data", (d) => (err += d.toString()));
const t = setTimeout(() => {
try { p.kill("SIGKILL"); } catch {}
reject(new Error(`LMMS timeout (${timeoutMs}ms)\n${err || out}`));
}, timeoutMs || LMMS_TIMEOUT_MS);
p.on("error", (e) => {
clearTimeout(t);
reject(e);
});
p.on("close", (code) => {
clearTimeout(t);
if (code === 0) return resolve({ out, err });
reject(new Error(`LMMS exit code=${code}\n${err || out}`));
});
});
}
// trava simples pra não renderizar a mesma sala em paralelo
const renderLocks = new Map();
app.post("/render", async (req, res) => {
const { roomName, xml, format, name } = req.body || {};
const ext = String(format || "wav").toLowerCase();
const allowed = new Set(["wav", "ogg", "flac", "mp3"]);
if (!allowed.has(ext)) {
return res.status(400).json({ ok: false, error: "invalid_format", allowed: [...allowed] });
}
// prioridade: sala -> pega do estado autoritativo
let projectXml = null;
if (roomName) {
if (renderLocks.get(roomName)) {
return res.status(429).json({ ok: false, error: "render_in_progress" });
}
projectXml = ensureRoom(roomName)?.projectXml || null;
}
// fallback: se mandou xml direto
if (!projectXml) projectXml = xml;
if (!projectXml || String(projectXml).trim().length === 0) {
return res.status(400).json({ ok: false, error: "missing_xml_or_room" });
}
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lmms-render-"));
const inputPath = path.join(tmpDir, "project.mmp");
const outputPath = path.join(tmpDir, `render.${ext}`);
if (roomName) renderLocks.set(roomName, true);
try {
fs.writeFileSync(inputPath, projectXml, "utf8");
const { cmd, args } = buildLmmsCommand(inputPath, outputPath);
await run(cmd, args, { timeoutMs: LMMS_TIMEOUT_MS });
const fileBase = sanitizeFileName(name || roomName || "projeto");
const downloadName = `${fileBase}.${ext}`;
return res.download(outputPath, downloadName, (err) => {
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
if (roomName) renderLocks.delete(roomName);
if (err) console.error("[/render] download error:", err);
});
} catch (e) {
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {}
if (roomName) renderLocks.delete(roomName);
console.error("[/render] fail:", e);
return res.status(500).json({ ok: false, error: "render_failed", details: String(e?.message || e) });
}
});
// --- ENDPOINT DE NOTIFICAÇÃO EXTERNA ---
app.post("/notify-update", express.json(), (req, res) => {
const { updateType } = req.body;