diff --git a/index.js b/index.js index 9714372..8ce5bf7 100644 --- a/index.js +++ b/index.js @@ -2,32 +2,19 @@ const ytdl = require('ytdl-core'), fs = require('fs'), path = require('path'), express = require('express'), - ffmpeg = require('ffmpeg'), - bodyParser = require('body-parser') + bodyParser = require('body-parser'), + cp = require("child_process"), + ffmpeg = require('ffmpeg-static') + const PORT = process.env.PORT || 8080 const app = express() -if (fs.existsSync(path.join(__dirname, 'cached'))) { - fs.rmSync(path.join(__dirname, 'cached'), { recursive: true, force: true }) + +var formats = { + "matroska": "mkv" } -const characters = "abcdefghijklmnopqrstuvwxyz!@$%^*()[]_-=+" - -function formatBytes(bytes, decimals = 2) { - if (!+bytes) return '0 Bytes' - - const k = 1024 - const dm = decimals < 0 ? 0 : decimals - const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] - - const i = Math.floor(Math.log(bytes) / Math.log(k)) - - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` -} - -fs.mkdirSync(path.join(__dirname, 'cached')) - -const cacheDuration = 30 || process.env.CACHEDUR +const characters = "abcdefghijklmnopqrstuvwxyz!@$%^*()[]_-=+ " process.on('uncaughtException', (err, origin) => { fs.writeSync( @@ -39,11 +26,11 @@ process.on('uncaughtException', (err, origin) => { app.use(bodyParser.urlencoded({ extended: false })) -app.get("/getLink", async (req, res) => { +app.get("/download", async (req, res) => { const url = req.query.url const format = req.query.format const quality = req.query.quality - const redir = req.query.redirect + const defin = req.query.definition res.setHeader("X-Accel-Buffering", "no") @@ -64,158 +51,68 @@ app.get("/getLink", async (req, res) => { if ((characters + characters.toUpperCase()).includes(letter)) { filename += letter } - } - if (["mp4", "webm"].includes(format)) { - var ytvid = ytdl(url, { 'quality': quality, 'format': format, 'filter': 'videoandaudio' }) - var debounce = false - ytvid.on("progress", (chunk, cd, td) => { - if (!debounce) { - res.setHeader('Content-Disposition', `attachment; filename="${filename}.${format}";`) - res.setHeader('Content-Type', `video/${format}`) - console.log(`${filename}.${format}"`) - res.setHeader("Content-Length", td) - ytvid.pipe(res) - debounce = true - } + if (format in ["mp3", "ogg", "wav"]) { + var audio = ytdl(url, { filter: 'audioonly', quality: quality }) + audio.pipe(res) + + } else if (defin == "hd") { + var video = ytdl(url, { filter: 'videoonly', quality: quality }) + var audio = ytdl(url, { filter: 'audioonly', highWaterMark: 1<<25 }) + + const ffmpegProcess = cp.spawn(ffmpeg, [ + '-i', `pipe:3`, + '-i', `pipe:4`, + '-map','0:v', + '-map','1:a', + '-c:v', 'copy', + '-c:a', 'libmp3lame', + '-crf','27', + '-preset','veryfast', + '-movflags','frag_keyframe+empty_moov', + '-f', format, + '-loglevel','error', + '-' + ], { + stdio: [ + 'pipe', 'pipe', 'pipe', 'pipe', 'pipe', + ], }) + + video.pipe(ffmpegProcess.stdio[3]) + audio.pipe(ffmpegProcess.stdio[4]) + + res.setHeader('Content-Disposition', `attachment; filename="${filename}.${formats[format] || format}"`); + ffmpegProcess.stdio[1].pipe(res) + } else { - if (redir != "redirect") { - res.setHeader("Content-Type", "text/html"); + var video = ytdl(url, { filter: 'videoandaudio', quality: quality }) - res.write(``) - res.write(` - - `) - } + const ffmpegProcess = cp.spawn(ffmpeg, [ + '-i', `pipe:3`, + '-c:v', 'copy', + '-c:a', 'libmp3lame', + '-crf','27', + '-preset','veryfast', + '-movflags','frag_keyframe+empty_moov', + '-f', format, + '-loglevel','error', + '-' + ], { + stdio: [ + 'pipe', 'pipe', 'pipe', 'pipe', 'pipe', + ], + }) + video.pipe(ffmpegProcess.stdio[3]) - // String(Math.floor(Math.random() * 100000)) + - var ytpath = path.join(__dirname, 'cached/' + ytdl.getVideoID(url) + '.mp4') + res.setHeader('Content-Disposition', `attachment; filename="${filename}.${formats[format] || format}"`); + // res.setHeader('Content-Length', fs.readFileSync(dest).length) - if (redir != "redirect") { - res.write(`

Starting download...

`) - } - var lastp = 0 - if ("mp3 ogg wav".includes(format)) { - var ytvid = ytdl(url, { 'quality': quality, 'format': 'mp4', filter: "audioonly" }) - } else { - var ytvid = ytdl(url, { 'quality': quality, 'format': 'mp4', filter: "videoandaudio" }) - } - - if (redir != "redirect") { - ytvid.on("progress", (data, ctotal, etotal) => { - var integer = 1 - var percent = (Math.ceil(ctotal / etotal * 100)) - if (percent % integer == 0 && lastp != percent) { - res.write(`

Downloading... ${percent}% (${formatBytes(ctotal)})

`) - res.write(` - - `) - lastp = percent - } - }) - } - ytvid.pipe(fs.createWriteStream(ytpath)) - .on("close", () => { - if (redir != "redirect") { - res.write("

Downloaded!

") - } - var proc = new ffmpeg(ytpath) - // res.write(`

Converting... (May take a while, doesn't support progress updates yet)

`) - var endpath = ('cached/' + ytdl.getVideoID(url) + String(Math.floor(Math.random() * 10000000)) + "Converted." + format) - if (redir != "redirect") { - res.write(`

Starting Video Conversion... Conversion may be slow depending on instance.

`) - } - proc.then(function (video) { - video - .setVideoDuration(vidinfo.videoDetails.lengthSeconds) - .setVideoFormat(format) - .save(endpath, (error, file) => { - if (redir != "redirect") { - res.write(`

Converted! (Why doesn't conversion support %s?)

`) - res.write(` - - `) - res.write(`

Link to video download! (Link is deleted after ${cacheDuration} minutes!)

`) - if (redir == "redirectjs") { - res.write(` - - `) - } - } else { - res.redirect(`/download?path=${path.basename(file)}&filename=${filename}.${format}`) - } - setTimeout(() => { - if (fs.existsSync(ytpath)) { - fs.unlinkSync(ytpath) - } - if (fs.existsSync(file)) { - fs.unlinkSync(file) - } - }, 60 * 1000) - }) - }) - - var count = 0 - - async function update() { - if (fs.existsSync(endpath)) { - var ffile = await fs.readFileSync(endpath) - var yt = await fs.readFileSync(ytpath) - res.write(`

Converting... (${formatBytes(ffile.length)})

`) - res.write(` - - `) - count += 1 - // } - } - if ( redir != "redirect") { - setTimeout(() => { - update() - }, 2000); - } - } - update() - }) + ffmpegProcess.stdio[1].pipe(res) } -}); -app.get('/download', (req, res) => { - var file = req.query.path - var filename = req.query.filename - var dest = path.join(__dirname, 'cached/', file) - - if (fs.existsSync(dest)) { - res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); - res.setHeader('Content-Length', fs.readFileSync(dest).length) - fs.createReadStream(dest).pipe(res) - } else { - res.header("Content-Type", "text/html") - res.write(``) - res.write(`

Uh oh! It seems that video couldn't be found... Maybe the URL expired? Or, the link was invalid.
If you believe this was a mistake, please put a issue on Github

`, () => { - res.end() - }) - } }) app.use(express.static(path.join(__dirname, 'static'))) diff --git a/package-lock.json b/package-lock.json index 07e6c4f..509c96a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,39 @@ { "name": "youtubeconverter", - "version": "0.2.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "youtubeconverter", - "version": "1.2.0", + "version": "2.1.0", "license": "MIT", "dependencies": { "body-parser": "^1.20.2", "express": "^4.18.2", - "ffmpeg": "^0.0.4", + "ffmpeg-static": "^5.2.0", "ytdl-core": "^4.11.5" } }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -27,6 +46,38 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -55,6 +106,11 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -75,6 +131,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -145,6 +220,14 @@ "node": ">= 0.8" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -236,12 +319,19 @@ "node": ">= 0.8" } }, - "node_modules/ffmpeg": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz", - "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==", + "node_modules/ffmpeg-static": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.2.0.tgz", + "integrity": "sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==", + "hasInstallScript": true, "dependencies": { - "when": ">= 0.0.1" + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" } }, "node_modules/finalhandler": { @@ -332,6 +422,47 @@ "node": ">= 0.8" } }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -459,6 +590,11 @@ "node": ">= 0.8" } }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -472,6 +608,14 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -520,6 +664,19 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -617,6 +774,14 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -637,6 +802,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -645,6 +815,11 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -661,11 +836,6 @@ "node": ">= 0.8" } }, - "node_modules/when": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", - "integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==" - }, "node_modules/ytdl-core": { "version": "4.11.5", "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz", diff --git a/package.json b/package.json index b8d907e..037ca3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "youtubeconverter", - "version": "2.1.0", + "name": "univerter", + "version": "3.0.0", "description": "A web youtube converter for converting videos to mp4, mp3, and other supported formats", "main": "index.js", "scripts": { @@ -25,7 +25,7 @@ "dependencies": { "body-parser": "^1.20.2", "express": "^4.18.2", - "ffmpeg": "^0.0.4", + "ffmpeg-static": "^5.2.0", "ytdl-core": "^4.11.5" } } diff --git a/static/index.html b/static/index.html index 0e64264..22b991a 100644 --- a/static/index.html +++ b/static/index.html @@ -8,15 +8,15 @@ - Youtube Downloader + Univerter -

Youtube Downloader

v2.2

+

Univerter

v3.0


-
+

Video URL:

@@ -24,24 +24,26 @@

Quality:

+
+ +
- +

Format:

- -
-

Redirect:

- -


+

The SD option only supports 720p and below, HD supports all qualities. HD is experimental, so please report issues :D

Please consider donating or host the website yourself! Anything - helps! (Check github for more info)

+ helps! (Check Codeberg for more info)

\ No newline at end of file