2024-07-29 00:45:58 -05:00
|
|
|
const expressManager = require("./express.js"),
|
|
|
|
path = require("path"),
|
|
|
|
fs = require("fs"),
|
|
|
|
ytdl = require("@distube/ytdl-core"),
|
|
|
|
ffmpeg = require("ffmpeg-static"),
|
|
|
|
cp = require("child_process")
|
|
|
|
|
|
|
|
var qualityLabels = [
|
|
|
|
"144",
|
|
|
|
"240",
|
|
|
|
"360",
|
|
|
|
"480",
|
|
|
|
"720",
|
|
|
|
"1080",
|
|
|
|
"1440",
|
|
|
|
"2160"
|
|
|
|
]
|
|
|
|
|
|
|
|
var audioFormats = [
|
|
|
|
"mp3"
|
|
|
|
]
|
|
|
|
|
2024-11-06 19:34:05 -06:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2024-07-29 00:45:58 -05:00
|
|
|
expressManager.app.get("/download", async (req, res) => {
|
|
|
|
var url = req.query.url,
|
|
|
|
quality = req.query.quality,
|
2024-07-31 02:54:26 -05:00
|
|
|
format = req.query.format,
|
|
|
|
trimAudio = req.query.trimAudio && audioFormats.includes(format) || false
|
2024-07-29 00:45:58 -05:00
|
|
|
|
2024-11-06 19:35:42 -06:00
|
|
|
if (req.query.playInBrowser && !audioFormats.includes(format)) {
|
2024-11-06 19:34:05 -06:00
|
|
|
var metadata = req.url.substring(req.url.indexOf("?"))
|
|
|
|
res.redirect("/watch" + metadata)
|
|
|
|
} else if (ytdl.validateURL(url) && qualityLabels.includes(quality)) {
|
2024-07-29 00:45:58 -05:00
|
|
|
var needsVideo = !audioFormats.includes(format)
|
|
|
|
|
|
|
|
var info = await ytdl.getInfo(url)
|
|
|
|
|
2024-11-06 19:35:42 -06:00
|
|
|
var downloadType = "attachment"
|
|
|
|
if (req.query.playInBrowser) downloadType = "inline";
|
|
|
|
|
|
|
|
res.setHeader("Content-Disposition", `${downloadType}; filename="${info.videoDetails.title.replace(/[^a-z0-9 ]/gi, '')}.${format}"`)
|
2024-07-29 00:45:58 -05:00
|
|
|
|
|
|
|
var audioFormat = ytdl.chooseFormat(info.formats, { filter: (format) => {
|
|
|
|
return format.hasAudio && !format.hasVideo
|
|
|
|
} })
|
|
|
|
|
|
|
|
var videoFormat = undefined
|
|
|
|
|
|
|
|
var qualityLabel = quality
|
|
|
|
while (videoFormat == undefined) {
|
|
|
|
for (let i = 0; i < info.formats.length; i++) {
|
|
|
|
const format = info.formats[i];
|
2024-09-15 14:49:35 -05:00
|
|
|
if (format.hasVideo && !format.hasAudio && format.height && format.height.toString() == qualityLabel.toString() && (format.videoCodec.includes("avc1") || format.codecs.includes("avc1"))) {
|
|
|
|
// console.log(format.videoCodec)
|
2024-07-29 00:45:58 -05:00
|
|
|
videoFormat = format
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qualityLabels.indexOf(qualityLabel) - 1 < 0) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
qualityLabel = qualityLabels[qualityLabels.indexOf(qualityLabel) - 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!videoFormat) {
|
|
|
|
videoFormat = ytdl.chooseFormat(info.formats, { filter: "videoonly" })
|
|
|
|
}
|
|
|
|
|
|
|
|
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'
|
|
|
|
]
|
|
|
|
|
2024-07-31 02:54:26 -05:00
|
|
|
var bonusArgs = []
|
|
|
|
|
2024-07-29 00:45:58 -05:00
|
|
|
if (needsVideo) {
|
|
|
|
inputArgs = inputArgs.concat(['-i', 'pipe:5'])
|
|
|
|
mapArgs = mapArgs.concat(['-map', '1:v'])
|
|
|
|
}
|
|
|
|
|
2024-07-31 02:54:26 -05:00
|
|
|
if (trimAudio) {
|
|
|
|
bonusArgs = bonusArgs.concat(['-af', 'silenceremove=1:0:-50dB'])
|
|
|
|
}
|
|
|
|
|
|
|
|
var args = inputArgs.concat(mapArgs).concat(bonusArgs).concat(baseArgs)
|
2024-07-29 00:45:58 -05:00
|
|
|
|
|
|
|
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 = ytdl(url, { format: audioFormat || "audioonly" })
|
|
|
|
audio.pipe(ffmpegProcess.stdio[4])
|
|
|
|
|
|
|
|
if (needsVideo) {
|
|
|
|
var video = ytdl(url, { format: videoFormat || "videoonly" })
|
|
|
|
video.pipe(ffmpegProcess.stdio[5])
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
res.send("Invalid URL!")
|
|
|
|
}
|
|
|
|
})
|