import { Hono } from 'hono' const app = new Hono() const joypixels = require("emoji-toolkit"); import { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readFile, writeFile } from 'fs' import { $ } from 'bun'; import { cors } from 'hono/cors' import ytSearch from 'yt-search'; import { writeMDFPWMv3 } from './mdfpwmWriter'; let ytdlpPath = "yt-dlp" if(existsSync("./yt-dlp")) { ytdlpPath = "./yt-dlp" } const openApi = JSON.parse(readFileSync("openapi.json").toString("utf8")) const index = readFileSync("index.html").toString("utf8") app.use("*", cors()) app.get("/", async (c) => { return c.html(index) }) app.get("/openapi.json", async (c) => { return c.json(openApi); }) app.post("/youtube", async (c) => { const auth = c.req.header('Authorization') if (auth !== process.env.PASSWORD) { return c.text('Unauthorized', 401) } const body = await c.req.json().catch(() => null); if (!body || typeof body.title !== 'string' || typeof body.artist !== 'string') { return c.text('Missing or invalid title/artist', 400); } let title = body.title; let artist = body.artist; let filename; if(body.mdfpwm) { filename = "cache/"+Bun.hash(title+"-"+artist)+".mdfpwm" if(!existsSync(filename)) { const searchResult = await ytSearch(`${artist} ${title}`); const video = searchResult.videos[0]; if (!video) throw new Error("No video found"); console.log(video) await $`${ytdlpPath} --audio-format opus -x ${video.url} -o test`; console.timeEnd("yt-dlp") // this makes left.dfpwm & right.dfpwm await $`ffmpeg -i test.opus -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left]highpass=f=60,lowpass=f=15000,dynaudnorm[leftf]; [right]highpass=f=60,lowpass=f=15000,dynaudnorm[rightf]" -map "[leftf]" -ar 48000 -f dfpwm left.dfpwm -map "[rightf]" -ar 48000 -f dfpwm right.dfpwm` rmSync("test.opus") writeFileSync(filename, writeMDFPWMv3( readFileSync("left.dfpwm"), readFileSync("right.dfpwm"), { title: title, artist: artist, album: title } )) rmSync("right.dfpwm") rmSync("left.dfpwm") } } else { filename = "cache/"+Bun.hash(title+"-"+artist)+".dfpwm" if(!existsSync(filename)) { const searchResult = await ytSearch(`${artist} ${title}`); const video = searchResult.videos[0]; if (!video) throw new Error("No video found"); console.log(video) await $`${ytdlpPath} --audio-format opus -x ${video.url} -o test`; await $`ffmpeg -i test.opus -af "highpass=f=60, lowpass=f=15000, dynaudnorm" -ar 48000 -ac 1 -f dfpwm "${filename}"`; rmSync("test.opus") } } return c.body(readFileSync(filename), { headers: { 'Content-Type': `audio/vnd.${body.mdfpwm ? "m" : ""}dfpwm`, 'Content-Disposition': `attachment; filename="${filename}"`, }, }); }) app.post('/render', async (c) => { const auth = c.req.header('Authorization') if (auth !== process.env.PASSWORD) { return c.text('Unauthorized', 401) } const body = await c.req.json().catch(() => null) let extraArgs = "-b" let filename; let size; if (body.size) { if (typeof body.size !== 'string' || !/^\d{1,4}x\d{1,4}$/.test(body.size)) { return c.text('size must be a string in the format x', 400); } const [w, h] = body.size.split('x').map(Number); if ( isNaN(w) || isNaN(h) || w < 1 || h < 1 || w > 1000 || h > 1000 ) { return c.text('size must be in the format x with max 1000x1000', 400); } size = { width: w, height: h }; extraArgs += ` -W ${w} -H ${h}`; } if(body?.extraArgs) { if (typeof body.extraArgs !== 'string') { return c.text('extraArgs must be a string', 400) } extraArgs = body.extraArgs; } if(body.emoji) { if(size) { const inputSize = Math.min(size.width, size.height); const allowedSizes = [32, 64, 128]; let closest = allowedSizes.reduce((prev, curr) => Math.abs(curr - inputSize) < Math.abs(prev - inputSize) ? curr : prev ); joypixels.emojiSize = closest; } const emojiHtml = joypixels.toImage(body.emoji) const urlMatch = emojiHtml.match(/src="([^"]*)"/) if (!urlMatch) { return c.text('Could not extract emoji URL', 500) } let emojiUrl = urlMatch[1] console.log(emojiUrl) filename = "cache/" + emojiUrl.split('/').pop().split(".")[0] + `-${size ? body.size : joypixels.emojiSize}.png`; if(!existsSync(filename)) { // Download the image const res = await fetch(emojiUrl) if (!res.ok) { return c.text('Failed to download emoji image', 500) } const arrayBuffer = await res.arrayBuffer() const buffer = Buffer.from(arrayBuffer) // Ensure cache directory exists if (!existsSync('cache')) { mkdirSync('cache') } // Get filename from URL writeFileSync(filename, buffer) } } else if(body.imageUrl) { const imageUrl = body.imageUrl; filename = `cache/${imageUrl.split('/').pop()}-${size ? body.size : "original"}`; if (!existsSync(filename)) { const res = await fetch(imageUrl); if (!res.ok) { return c.text('Failed to download image', 500); } const arrayBuffer = await res.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); if (!existsSync('cache')) { mkdirSync('cache'); } writeFileSync(filename, buffer); } } else { return c.text('Please provide either emoji or imageUrl', 400); } if(!existsSync(filename+".bimg")) { let sanjuuniPath = "sanjuuni"; if(existsSync("./sanjuuni")) { sanjuuniPath = "./sanjuuni"; } console.log(`${sanjuuniPath} --disable-opencl ${extraArgs} -i ${filename} -o ${filename+".bimg"}`) await $`${sanjuuniPath} --disable-opencl ${extraArgs} -i ${filename} -o ${filename+".bimg"}` } const bimg = readFileSync(filename + ".bimg", "utf8"); return c.text(bimg) }) export default { port: 7427, host: "127.0.0.1", fetch: app.fetch, }