import {Worker} from 'worker_threads' import Minimist from 'minimist' import fs from 'fs'; import express from 'express'; import mysql from 'mysql2'; import SendSeekable from 'send-seekable' import 'cnchar' import 'sort-array' import nodemailer from 'nodemailer' import Queue from 'js-queue' import process from 'node:process' try { fs.renameSync("log/latest.log", `log/${new Date().toISOString().replaceAll(":", ".")}.log`) } catch (e) { } let original = console.log; console.log = (str) => { if (!fs.existsSync("log")) { fs.mkdirSync("log") } fs.appendFileSync("log/latest.log", "["+new Date().toISOString() + "] " + str.toString()+"\n"); original(str); } import webp from 'webp-converter' import sortArray from "sort-array"; import pkg from 'opencc' import https from "node:https"; import nrc from "node-run-cmd"; import bodyParser from "body-parser"; let argv = Minimist(process.argv.slice(2)); const app = express(); const pool = mysql.createPool({ keepAliveInitialDelay: 0, enableKeepAlive: true, }) const Kind = { 0: "去和声伴奏", 1: "和声伴奏", 2: "人声", 3: "贝斯", 4: "鼓", 5: "其他", } const db = mysql.createConnection({ host: 'andyxie.cn', user: 'external', password: 'moyingren2015', database: "instrunet_data", pool: pool, enableKeepAlive: true, keepAliveInitialDelay: 0, }) const {OpenCC} = pkg webp.grant_permission() let queue = new Queue(); queue.autoRun = false; const converters2t = new OpenCC('s2t.json') const convertert2s = new OpenCC('t2s.json') app.use(bodyParser.json({"limit": "200mb"})); app.use(express.json()); app.use(SendSeekable) const transporter = nodemailer.createTransport({ host: 'smtp.qq.com', port: 465, secure: true, auth: { user: '3095864740@qq.com', pass: 'caemyuagapsadfff', } }) let ncmAPIUrl = "http://localhost:5999"; let currentTask = []; async function Submit(req) { let uuid = crypto.randomUUID() db.connect(function (err) { if (err) { console.log(err) } }) let albumCover = null; try { let resource = await fetch(req.body.file) if (!resource.ok) { GC() currentTask.shift(); console.log(queue.contents) console.log(currentTask) queue.next() return } let ab = await resource.arrayBuffer() fs.writeFileSync("./" + uuid, Buffer.from(ab)) if (URL.canParse(req.body.albumCover)) { console.log("can parse ") fetch(req.body.albumCover).then(res => { res.arrayBuffer().then(async r => { if (r.byteLength === 0) { GC() currentTask.shift(); console.log(queue.contents) console.log(currentTask) queue.next() return } if (!fs.existsSync("tmp")) { fs.mkdirSync("tmp") } fs.writeFileSync("tmp/" + uuid, new Buffer.from(r)); await webp.cwebp("tmp/" + uuid, "tmp/" + uuid + ".webp", "-q 50 -mt ", "-v") try { albumCover = fs.readFileSync("tmp/" + uuid + ".webp") } catch (e) { console.log(e) fs.rmSync("tmp/" + uuid); albumCover = null; } }) }) } } catch (err) { GC() currentTask.shift(); console.log(queue.contents) console.log(currentTask) console.log(err) queue.next() return } const callback = function (d) { console.log(d.toString()); } const errcb = function (d) { console.log(d.toString()); } let kind_of = []; switch (req.body.kind) { case 0: kind_of[0] = `audio-separator ./${uuid} --model_filename UVR-MDX-NET-Inst_HQ_4.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --mdx_batch_size 300 --output_format mp3 --output_dir output` kind_of[1] = `./output/${uuid}_(Instrumental)_UVR-MDX-NET-Inst_HQ_4.mp3` kind_of[2] = `${uuid}_(Instrumental)_UVR-MDX-NET-Inst_HQ_4.mp3` kind_of[3] = `./output/${uuid}_(Vocals)_UVR-MDX-NET-Inst_HQ_4.mp3` break; case 1: kind_of[0] = `audio-separator ./${uuid} --model_filename UVR_MDXNET_KARA_2.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --mdx_batch_size 300 --output_format mp3 --output_dir output` kind_of[1] = `./output/${uuid}_(Instrumental)_UVR_MDXNET_KARA_2.mp3` kind_of[2] = `${uuid}_(Instrumental)_UVR_MDXNET_KARA_2.mp3` kind_of[3] = `./output/${uuid}_(Vocals)_UVR_MDXNET_KARA_2.mp3` break; case 2: kind_of[0] = `audio-separator ./${uuid} --model_filename UVR_MDXNET_KARA.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --mdx_batch_size 300 --output_format mp3 --output_dir output` kind_of[1] = `./output/${uuid}_(Vocals)_UVR_MDXNET_KARA.mp3` kind_of[2] = `${uuid}_(Vocals)_UVR_MDXNET_KARA.mp3` kind_of[3] = `./output/${uuid}_(Instrumental)_UVR_MDXNET_KARA.mp3` break; case 3: kind_of[0] = `audio-separator ./${uuid} --model_filename kuielab_a_bass.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --output_format mp3 --mdx_batch_size 300 --output_dir output` kind_of[1] = `./output/${uuid}_(Bass)_kuielab_a_bass.mp3` kind_of[2] = `${uuid}_(Bass)_kuielab_a_bass.mp3` kind_of[3] = `./output/${uuid}_(No Bass)_kuielab_a_bass.mp3` break; case 4: kind_of[0] = `audio-separator ./${uuid} --model_filename kuielab_a_drums.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --output_format mp3 --mdx_batch_size 300 --output_dir output` kind_of[1] = `./output/${uuid}_(Drums)_kuielab_a_drums.mp3` kind_of[2] = `${uuid}_(Drums)_kuielab_a_drums.mp3` kind_of[3] = `./output/${uuid}_(No Drums)_kuielab_a_drums.mp3` break; case 5: kind_of[0] = `audio-separator ./${uuid} --model_filename UVR_MDXNET_Main.onnx --mdx_enable_denoise --mdx_segment_size 4000 --mdx_overlap 0.85 --mdx_batch_size 300 --output_format mp3 --output_dir output` kind_of[1] = `./output/${uuid}_(Vocals)_UVR_MDXNET_Main.mp3` kind_of[2] = `${uuid}_(Vocals)_UVR_MDXNET_Main.mp3` kind_of[3] = `./output/${uuid}_(Instrumental)_UVR_MDXNET_Main.mp3` break; } function GC() { try { fs.rm(kind_of[1], (err) => { if (err) { console.log(err); } }) fs.rm(kind_of[3], (err) => { if (err) { console.log(err); } }) fs.rm(uuid, (err) => { if (err) { console.log(err); } }) fs.rm("tmp/" + uuid, (err) => { if (err) { console.log(err); } }) fs.rm("tmp/" + uuid + ".webp", (err) => { if (err) { console.log(err); } }) } catch (err) { console.log(err); } } try { nrc.run([kind_of[0]], { onData: callback, onError: errcb }).then(() => { try { db.execute(("INSERT INTO instrunet_entry (uuid, song_name, album_name, link_to, databinary, artist,kind, albumcover, email) VALUES (?,?,?,?,?,?,?,?,?)"), [uuid, req.body.name, req.body.albumName, req.body.link, fs.readFileSync(kind_of[1]), req.body.artist, req.body.kind, albumCover === null ? new Buffer.from([]) : albumCover, (req.body.email === null || req.body.email === undefined || req.body.email === "") ? "" : req.body.email]) } catch (err) { GC(); currentTask.shift(); console.log(queue.contents) console.log(currentTask) queue.next() return; } GC(); if (req.body.email !== undefined && req.body.email !== "") { console.log(req.body.email) transporter.sendMail({ from: '"xiey0" ', to: req.body.email, subject: "你的音频已处理完成。", text: "你的音频已处理完成。", html: fs.readFileSync("./Template.html", "utf8").toString().replace("{song_name}", req.body.name).replace("{href_link}", "https://andyxie.cn:4000/player?play=" + uuid).replace("{kind}", Kind[req.body.kind]).replace("{album}", req.body.albumName) }).then((result) => { console.log("Message sent: %s", result.messageId) }) } currentTask.shift(); console.log(queue.contents) console.log(currentTask) queue.next() }) } catch (e) { GC() currentTask.shift(); console.log(queue.contents) console.log(currentTask) queue.next() console.error(e) } } app.get('/queue', (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.end(JSON.stringify(currentTask)); }) app.post('/submit', SubmitWrapper) async function SubmitWrapper(req, res) { // TODO // May extract logic for dupe check in the future. if (req.body.file === undefined || req.body.file === null || req.body.file === "" || req.body.kind === undefined || req.body.kind === null || req.body.kind === "") { res.status(500).header("Access-Control-Allow-Origin", "*").end("想都别想"); return } if (req.body.artist === undefined || req.body.albumName === undefined || req.body.artist === null || req.body.albumName === null || req.body.artist === "" || req.body.albumName === "") { req.body.artist = "未知艺术家" req.body.albumName = "未知专辑" } if (req.body.name === undefined || req.body.name === null || req.body.name === "") { res.status(500).header("Access-Control-Allow-Origin", "*").end("想都别想"); return } if (!URL.canParse(req.body.file)) { res.status(500).header("Access-Control-Allow-Origin", "*").end("想都别想"); return } db.execute(("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name = ? and artist = ? and kind = ?"), [req.body.name, req.body.artist, req.body.kind], async (err, rowsO) => { db.execute(("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name = ? and artist = ? and kind = ?"), [await converters2t.convertPromise(req.body.name), await converters2t.convertPromise(req.body.artist), req.body.kind], async (err, rowsT) => { db.execute("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name = ? and artist = ? and kind = ?", [await convertert2s.convertPromise(req.body.name), await convertert2s.convertPromise(req.body.artist), req.body.kind], (err, rowsS) => { // Feb 1 2025 I give up. let dedupe = []; rowsT.forEach(row => { dedupe = dedupe.concat(row); }) rowsS.forEach(row => { if (JSON.stringify(dedupe).indexOf(JSON.stringify(row)) === -1) { dedupe = dedupe.concat(row); } }) rowsO.forEach(row => { if (JSON.stringify(dedupe).indexOf(JSON.stringify(row)) === -1) { dedupe = dedupe.concat(row); } }) if (dedupe.length === 0) { // Verify if (req.body.file.substring(0, 5) !== "data:") { res.status(500).header("Access-Control-Allow-Origin", "*").end("想都别想"); return } for (const item of currentTask) { if (req.body.name === item.name) { if (req.body.albumName === item.albumName) { if (req.body.kind === item.kind) { res.header("Access-Control-Allow-Origin", "*"); res.statusCode = 500 res.end("和数据库中条目重复") return } } } } for (let prop of [req.body.name, req.body.albumName, req.body.artist]) { if (prop === undefined || prop === "" || prop === null) { res.status(500).header("Access-Control-Allow-Origin", "*").end("想都别想"); return } } if (req.body.link === undefined) { req.body.link = "" } queue.add(() => { Submit(req) }) currentTask.push({ name: req.body.name, albumName: req.body.albumName, kind: req.body.kind, artist: req.body.artist, email: req.body.email !== undefined ? req.body.email : null, }) console.log(queue.contents) console.log(currentTask) if (currentTask.length === 1) { queue.next() } res.header("Access-Control-Allow-Origin", "*"); res.end("api_success") } else { res.header("Access-Control-Allow-Origin", "*"); res.statusCode = 500 res.end("和数据库中条目重复") } }) }) }) } app.post('/lyric', async (req, res) => { let name = req.body.name let artist = req.body.artist; let album = req.body.albumName; let lrc = null; await fetchThatShit() async function fetchThatShit() { try { lrc = (await (await fetch(`http://192.168.1.166:28883/jsonapi?title=${convertert2s.convertSync(name)}&artist=${convertert2s.convertSync(artist)}&album=${convertert2s.convertSync(album)}`)).json()) } catch (err) { await fetchThatShit() } } res.header("Access-Control-Allow-Origin", "*"); res.json(lrc) }) app.options('/lyric', async (req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end() }) app.options('/submit', function (req, res) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end() }) app.post('/search_api', async function (req, res) { db.execute("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name like ? or album_name like ? or artist like ?", [`%${req.body.searchStr}%`, `%${req.body.searchStr}%`, `%${req.body.searchStr}%`], async (err, rowsO) => { if (err) { console.log(err) } db.execute("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name like ? or album_name like ? or artist like ?", [`%${await converters2t.convertPromise(req.body.searchStr)}%`, `%${await converters2t.convertPromise(req.body.searchStr)}%`, `%${await converters2t.convertPromise(req.body.searchStr)}%`], async (err, rowsT) => { if (err) { console.log(err) } db.execute("SELECT uuid, song_name, album_name, artist, kind FROM instrunet_entry WHERE song_name like ? or album_name like ? or artist like ?", [`%${await convertert2s.convertPromise(req.body.searchStr)}%`, `%${await convertert2s.convertPromise(req.body.searchStr)}%`, `%${await convertert2s.convertPromise(req.body.searchStr)}%`], (err, rowsS) => { if (err) { console.log(err) } try { let prepare = []; rowsT.forEach(row => { prepare = prepare.concat(row); }) rowsS.forEach(row => { if (JSON.stringify(prepare).indexOf(JSON.stringify(row)) === -1) { prepare = prepare.concat(row); } }) rowsO.forEach((row) => { if (JSON.stringify(prepare).indexOf(JSON.stringify(row)) === -1) { prepare = prepare.concat(row); } }) for (let obj of prepare) { obj.stroke = obj.song_name[0].stroke() // Problematic as shit. Do not use. obj.spell = obj.song_name.spell() } sortArray(prepare, { by: 'spell' }) res.header("Access-Control-Allow-Origin", "*"); res.json(prepare); } catch (e) { console.log(e) } }); }) }) }) app.options('/search_api', function (req, res) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end() }) app.get("/getSingle", function (req, res) { let uuid = crypto.randomUUID() if (req.query.id) { if (req.query.albumcover === "true") { db.execute("SELECT albumcover FROM instrunet_entry WHERE uuid = ?", [req.query.id], async function (err, rows) { if (err) { console.log(err); } res.contentType("application/json"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end(JSON.stringify(rows[0])); }) } else db.execute("SELECT song_name, album_name, artist, kind FROM instrunet_entry WHERE uuid = ?", [req.query.id], async function (err, rows) { if (err) { console.log(err); } res.contentType("application/json"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end(JSON.stringify(rows[0])); }) } else { res.contentType("application/json"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end("{}") } }) // 163 app.options('/ncm/url', function (req, res) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end() }) app.post("/ncm/url", function (req, res) { if (Number.isNaN(Number(req.body.id))) { res.status(500).header("Access-Control-Allow-Origin", "*").end("格式错误"); return } if (req.body.id && (req.body.kind || req.body.kind === 0)) { try { let id = req.body.id fetch(ncmAPIUrl + "/song/download/url/v1?id=" + id + "&level=hires", { headers: { Cookie: "MUSIC_U=0087F9D8E102A1C1661EBE1792412F3351DA64D1BD3D862BA77E45E9024524725F3A1983345D9B5A4014C725D19C069DD71081F6FE3659F9E1FD412DC427FB809FAF7789AEEA10E9DE6F06C58D1959BA209D2A83C3FA753261036C4CFD0D143B6C7748B8A6D2DD5C2E96E75D1E847E4AAE035CB2C86B175D9AFC6A164C522ED76E24AE654740AB6BAF5B29597F7E3B0158B2EC1C37F2688279871873FA7ADAEF8280A059E84C4BBFB9E4F225F9A2065DF652247D5496587A7B1E3D35DB0CD3F825C06FE5BFE5CFEF1770847099704360504B73C9B396E37CECE4F9DDEE6001588C3C4F5B2861D9ADF339FC47DD480858CA800620785EA032215B63B81025304DB3331F384793FF8EE681247E34C7931176F2F618B66C122F0602F1EA15F963E422DEC79C257F3577A197BECE71E316C751C3B9F5F3CD07BFDC0270A287A1BB6576" } }).then(async result => { if (result.status === 200 || result.status === 304) { let result_json = null; try { result_json = await result.json() } catch (e) { res.status(500).header("Access-Control-Allow-Origin", "*").end("格式错误"); return } if (result_json.data.url !== null) { let infos = await (await fetch(ncmAPIUrl + "/song/detail?ids=" + id)).json(); req.body.file = "data:audio/flac;base64," + Buffer.from(await (await fetch(result_json.data.url)).arrayBuffer()).toString("base64") req.body.name = infos.songs[0].name; req.body.albumName = infos.songs[0].al.name req.body.albumCover = infos.songs[0].al.picUrl req.body.link = result_json.data.url req.body.artist = infos.songs[0].ar[0].name /// Complete SubmitWrapper(req, res) } else { res.status(500).header("Access-Control-Allow-Origin", "*").end("不存在"); } } else { res.status(404).header("Access-Control-Allow-Origin", "*").send("未找到或出现错误") } }) } catch (err) { res.status(500).header("Access-Control-Allow-Origin", "*"); console.log(err) } } else { res.contentType("application/json"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.end("傻逼。") } }) app.get("/favicon.ico", function (req, res) { res.end(""); }) let availCache = {}; setInterval(() => { db.execute("SELECT 1", (err, result) => { }) }, 10000) // Fetch app.get('/:uuid', async function (req, res) { let uuid = req.params.uuid; let pitch = req.query.pitch; async function Provider(b) { try { /** @type {ArrayBuffer}*/ if (!pitch) { res.contentType("audio/mp3"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.header('Content-Disposition', `attachment; filename="${encodeURI(availCache[uuid].song_name)}.mp3"`); res.sendSeekable(availCache[uuid].databinary) } else { const worker = new Worker('./pitched.js', { workerData: { binary: availCache[uuid].databinary, pitch: pitch } }) worker.on("message", message => { res.contentType("audio/wav"); res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Content-Type"); res.header("Access-Control-Allow-Origin", "*"); res.sendSeekable(Buffer.from(message)) }) } } catch (e) { res.contentType("text/plain"); res.status(500).header("Access-Control-Allow-Origin", "*").end("Err."); console.log(e) console.log("Triggered err"); } } if (availCache[uuid]) { await Provider(); } else { db.execute("SELECT song_name, databinary FROM instrunet_entry WHERE uuid = ?", [uuid], (err, rows) => { availCache[uuid] = rows[0] Provider() }) } }) if (argv.https === "true") { https.createServer({ key: fs.readFileSync('andyxie.cn.key'), cert: fs.readFileSync('andyxie.cn.pem') }, app).listen(8080) console.log("Listening on port 8080 with TLS") } else { app.listen(8080) console.log("Listening on port 8080") }