Univerter/downloader.js
2025-06-13 03:12:41 -05:00

147 lines
No EOL
4.2 KiB
JavaScript

const expressManager = require("./express.js"),
path = require("path"),
fs = require("fs"),
ffmpeg = require("ffmpeg-static"),
cp = require("child_process"),
axios = require("axios"),
{ Readable } = require("stream")
const qualityLabels = [
"144",
"240",
"360",
"480",
"720",
"1080",
"1440",
"2160",
"4320"
]
var audioFormats = [
"mp3"
]
expressManager.app.get("/watch", async (req, res) => {
var html = fs.readFileSync(path.join(__dirname, "resources/playInBrowser.html")).toString()
var finalMetadata = "?"
for (var i in req.query) {
if (i != "playInBrowser") {
finalMetadata += i + "=" + req.query[i] + "&"
}
}
html = html.replaceAll("{DOWNLOAD_URL}", finalMetadata)
res.send(html)
})
expressManager.app.get("/download", async (req, res) => {
var url = req.query.url,
quality = req.query.quality,
format = req.query.format,
trimAudio = req.query.trimAudio && audioFormats.includes(format) || false
// url = url.substring(url.indexOf("v=") + 2)
// if (url.includes("&")) url = url.substring(0, url.indexOf("&"))
if (req.query.playInBrowser && !audioFormats.includes(format)) {
var metadata = req.url.substring(req.url.indexOf("?"))
res.redirect("/watch" + metadata)
} else if (url && qualityLabels.includes(quality)) {
var needsVideo = !audioFormats.includes(format)
// var info = await fetch("https://cobalt.univerter.dev/", {
// method: 'POST',
// headers: {
// Accept: "application/json",
// "Content-Type": "application/json"
// },
// body: JSON.stringify({
// url
// }),
// })
var downloadMode = "audio"
if (needsVideo) downloadMode = "auto"
var info = await axios.post("https://cobalt.univerter.dev/", {
url,
downloadMode,
videoQuality: quality,
localProcessing: true,
}, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
console.log(info)
var downloadType = "attachment"
if (req.query.playInBrowser) downloadType = "inline";
res.setHeader("Content-Disposition", `${downloadType}; filename="${info.data.output.filename.replace(/[^a-z0-9 ]/gi, '')}.${format}"`)
var baseArgs = [
// Remove ffmpeg's console spamming
'-loglevel', 'error',
// // Set inputs
// '-i', 'pipe:4',
// '-i', 'pipe:5',
// Map audio & video from streams
// '-map', '0:a',
// '-map', '1:v',
// Keep encoding
'-c:v', 'copy',
'-movflags','frag_keyframe+empty_moov',
'-f', format,
// Define output file
'-',
]
var inputArgs = [
'-i', 'pipe:4'
]
var mapArgs = [
'-map', '0:a'
]
var bonusArgs = []
if (needsVideo) {
inputArgs = inputArgs.concat(['-i', 'pipe:5'])
mapArgs = mapArgs.concat(['-map', '1:v'])
}
if (trimAudio) {
bonusArgs = bonusArgs.concat(['-af', 'silenceremove=1:0:-50dB'])
}
var args = inputArgs.concat(mapArgs).concat(bonusArgs).concat(baseArgs)
const ffmpegProcess = cp.spawn(ffmpeg, args, {
windowsHide: true,
stdio: [
/* Standard: stdin, stdout, stderr */
'pipe', 'pipe', 'pipe',
/* Custom: pipe:3, pipe:4, pipe:5 */
'pipe', 'pipe', 'pipe',
],
});
ffmpegProcess.stdio[1].pipe(res)
var audio = await fetch(info.data.tunnel[1] || info.data.tunnel[0])
Readable.fromWeb(audio.body).pipe(ffmpegProcess.stdio[4])
if (needsVideo) {
var video = await fetch(info.data.tunnel[0])
Readable.fromWeb(video.body).pipe(ffmpegProcess.stdio[5])
}
} else {
res.send("Invalid URL!")
}
})