renderizando projetos no mmpCreator utilizando o lmms
This commit is contained in:
1 parent
e902e8d735
commit
ca2c2fa15a
2 files changed
+213
-1
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user