diff --git a/index.js b/index.js index 4b8692a..130f3ec 100644 --- a/index.js +++ b/index.js @@ -133,8 +133,15 @@ app.ws('/download/:id/:quality', async (ws, req) => { quality: req.params.quality, type: 'video' } - const videoFormat = info.chooseFormat(videoOptions) - const videoStream = await info.download(videoOptions) + const videoFormat = chooseFormat(videoOptions, info['streaming_data']) + if (!videoFormat) { + ws.send('No matching formats found... Please try again later.') + return ws.close() + } + + const videoStream = await info.download({ + itag: videoFormat.itag + }) const videoWriteStream = fs.createWriteStream(`./output/${req.params.id}_video.mp4`) let videoTotal = videoFormat.content_length; @@ -234,6 +241,76 @@ function mergeIt(audioPath, videoPath, outputPath) { }); } +// stolen straight from youtubei.js +function chooseFormat(options, streaming_data) { + if (!streaming_data) return null + + const formats = [ + ...(streaming_data.formats || []), + ...(streaming_data.adaptive_formats || []) + ]; + + if (options.itag) { + const candidates = formats.filter((format) => format.itag === options.itag); + if (!candidates.length) return null + return candidates[0]; + } + + const requires_audio = options.type ? options.type.includes('audio') : true; + const requires_video = options.type ? options.type.includes('video') : true; + const language = options.language || 'original'; + const quality = options.quality || 'best'; + + let best_width = -1; + + const is_best = [ 'best', 'bestefficiency' ].includes(quality); + const use_most_efficient = quality !== 'best'; + + let candidates = formats.filter((format) => { + if (requires_audio && !format.has_audio) + return false; + if (requires_video && !format.has_video) + return false; + if (options.codec && !format.mime_type.includes(options.codec)) + return false; + if (options.format !== 'any' && !format.mime_type.includes(options.format || 'mp4')) + return false; + if (!is_best && format.quality_label !== quality) + return false; + if (format.width && (best_width < format.width)) + best_width = format.width; + return true; + }); + + if (!candidates.length) return null + + if (is_best && requires_video) + candidates = candidates.filter((format) => format.width === best_width); + + if (requires_audio && !requires_video) { + const audio_only = candidates.filter((format) => { + if (language !== 'original') { + return !format.has_video && !format.has_text && format.language === language; + } + return !format.has_video && !format.has_text && format.is_original; + + }); + if (audio_only.length > 0) { + candidates = audio_only; + } + } + + if (use_most_efficient) { + // Sort by bitrate (lower is better) + candidates.sort((a, b) => a.bitrate - b.bitrate); + } else { + // Sort by bitrate (higher is better) + candidates.sort((a, b) => b.bitrate - a.bitrate); + } + + return candidates.filter(v => v.content_length)[0]; +} + async function switchIps() { const currentIp = await (await fetch('http://localhost:8000/v1/publicip/ip', { headers: { diff --git a/package.json b/package.json index 5c0fe21..408c479 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "express-ws": "^5.0.2", "ffmpeg-static": "^5.2.0", "fluent-ffmpeg": "^2.1.3", - "youtubei.js": "^13.0.0" + "youtubei.js": "^13.1.0" } } diff --git a/yarn.lock b/yarn.lock index 23b6ebf..994556a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,10 +393,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -jintr@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jintr/-/jintr-3.2.0.tgz#38bfc2311c7ed4412ebe0bfc5c2e700f8f433921" - integrity sha512-psD1yf05kMKDNsUdW1l5YhO59pHScQ6OIHHb8W5SKSM2dCOFPsqolmIuSHgVA8+3Dc47NJR181CXZ4alCAPTkA== +jintr@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jintr/-/jintr-3.2.1.tgz#ea6719485c9ff5f14ca7f9131990040d4fdfe1be" + integrity sha512-yjKUBuwTTg4nc4izMysxuIk0BKh45hnbc1KnXE6LxagIGZn5od+I2elpuRY9IIm3EiKiUZxhxV89a0iX+xoEZg== dependencies: acorn "^8.8.0" @@ -663,12 +663,12 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -youtubei.js@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/youtubei.js/-/youtubei.js-13.0.0.tgz#f036bc6f5cb9ef8bd73da7e1079a2b70776bb2f7" - integrity sha512-b1QkN9bfgphK+5tI4qteSK54kNxmPhoedvMw0jl4uSn+L8gbDbJ4z52amNuYNcOdp4X/SI3JuUb+f5V0DPJ8Vw== +youtubei.js@^13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/youtubei.js/-/youtubei.js-13.1.0.tgz#1e15da01e58c782ab6fb7cc9191a1c99f821c57f" + integrity sha512-uL4TyojAYET0c5NGFD7+ScCod/k8Pc/B+D5tLrunFcz1GaBjRMOGRPcNGaRmnhwisegU7ibtw0iUxCN+BZ0ang== dependencies: "@bufbuild/protobuf" "^2.0.0" - jintr "^3.2.0" + jintr "^3.2.1" tslib "^2.5.0" undici "^5.19.1"