189 lines
6.3 KiB
TypeScript
189 lines
6.3 KiB
TypeScript
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 ytSearch from 'yt-search';
|
|
import { writeMDFPWMv3 } from './mdfpwmWriter';
|
|
let ytdlpPath = "yt-dlp"
|
|
if(existsSync("./yt-dlp")) {
|
|
ytdlpPath = "./yt-dlp"
|
|
}
|
|
|
|
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 <number>x<number>', 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 <number>x<number> 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,
|
|
}
|