|
1 | 1 | "use server"; |
2 | 2 |
|
3 | 3 | import ffmpeg from "fluent-ffmpeg"; |
4 | | -import ytdl from "@distube/ytdl-core"; |
| 4 | +import * as oldYtdl from "@distube/ytdl-core"; |
5 | 5 | import { PassThrough, Readable } from "stream"; |
6 | 6 | import path from "path"; |
7 | 7 | import * as fs from "fs"; |
8 | | -import { initializeConf, sanitizeFilename } from "@/lib/serverUtils"; |
| 8 | +import { |
| 9 | + initializeConf, |
| 10 | + sanitizeFilename, |
| 11 | + selectYtDlpPath, |
| 12 | +} from "@/lib/serverUtils"; |
9 | 13 | import { FormatData, VideoData } from "@/lib/types"; |
10 | 14 | import { NextResponse } from "next/server"; |
11 | 15 | import { PrismaClient } from "@prisma/client"; |
@@ -54,18 +58,32 @@ const downloadStreams = async ( |
54 | 58 | url: string, |
55 | 59 | audio_path: string, |
56 | 60 | video_path: string, |
57 | | - quality?: string |
| 61 | + formatSelector?: string |
58 | 62 | ) => { |
59 | | - quality = quality || "highestvideo"; |
60 | | - |
61 | | - const audioStream = ytdl(url, { |
62 | | - quality: "highestaudio", |
63 | | - }); |
64 | | - |
65 | | - const videoStream = ytdl(url, { |
66 | | - quality: quality, |
67 | | - filter: "videoonly", |
68 | | - }); |
| 63 | + const ytdl = await selectYtDlpPath(); |
| 64 | + formatSelector = formatSelector || "bv"; |
| 65 | + |
| 66 | + const audioStream = ytdl.exec(url, { |
| 67 | + noCheckCertificates: true, |
| 68 | + noWarnings: true, |
| 69 | + preferFreeFormats: true, |
| 70 | + addHeader: ["referer:youtube.com", "user-agent:googlebot"], |
| 71 | + format: "ba", |
| 72 | + output: "-", |
| 73 | + }).stdout; |
| 74 | + |
| 75 | + const videoStream = ytdl.exec(url, { |
| 76 | + noCheckCertificates: true, |
| 77 | + noWarnings: true, |
| 78 | + preferFreeFormats: true, |
| 79 | + addHeader: ["referer:youtube.com", "user-agent:googlebot"], |
| 80 | + format: formatSelector, |
| 81 | + output: "-", |
| 82 | + }).stdout; |
| 83 | + if (!audioStream || !videoStream) { |
| 84 | + throw new Error("Failed to download audio or video stream"); |
| 85 | + } |
| 86 | + console.log("Downloading audio and video streams..."); |
69 | 87 |
|
70 | 88 | const audioWriteStream = fs.createWriteStream(audio_path); |
71 | 89 | const videoWriteStream = fs.createWriteStream(video_path); |
@@ -181,13 +199,17 @@ export async function GET(request: Request) { |
181 | 199 | ffmpeg.setFfmpegPath(ffmpegPath); |
182 | 200 | } |
183 | 201 |
|
184 | | - const video = await ytdl.getBasicInfo(url); |
| 202 | + const video = await oldYtdl.getBasicInfo(url); |
185 | 203 | const formatMap = new Map(); |
186 | 204 | (video.player_response.streamingData.adaptiveFormats as FormatData[]).forEach( |
187 | 205 | (format: FormatData) => { |
188 | | - if (!formatMap.has(format.qualityLabel)) { |
| 206 | + if (format.qualityLabel && !formatMap.has(format.qualityLabel)) { |
189 | 207 | formatMap.set(format.qualityLabel, format); |
190 | 208 | } |
| 209 | + const itagKey = String(format.itag); |
| 210 | + if (!formatMap.has(itagKey)) { |
| 211 | + formatMap.set(itagKey, format); |
| 212 | + } |
191 | 213 | } |
192 | 214 | ); |
193 | 215 |
|
@@ -225,9 +247,18 @@ export async function GET(request: Request) { |
225 | 247 | }; |
226 | 248 |
|
227 | 249 | if (quality === "audio") { |
228 | | - const audioStream = ytdl(url, { |
229 | | - quality: "highestaudio", |
230 | | - }); |
| 250 | + const ytdl = await selectYtDlpPath(); |
| 251 | + const audioStream = ytdl.exec(url, { |
| 252 | + noCheckCertificates: true, |
| 253 | + noWarnings: true, |
| 254 | + preferFreeFormats: true, |
| 255 | + addHeader: ["referer:youtube.com", "user-agent:googlebot"], |
| 256 | + format: "ba", |
| 257 | + output: "-", |
| 258 | + }).stdout; |
| 259 | + if (!audioStream) { |
| 260 | + throw new Error("Failed to download audio stream"); |
| 261 | + } |
231 | 262 |
|
232 | 263 | //Convert audio stream to mp3 |
233 | 264 | const audioPassThrough = new PassThrough(); |
@@ -302,7 +333,29 @@ export async function GET(request: Request) { |
302 | 333 | }); |
303 | 334 |
|
304 | 335 | if (!requestedFile) { |
305 | | - await downloadStreams(url, AUDIO_FILE_PATH, VIDEO_FILE_PATH, quality); |
| 336 | + const selectedFormat = formatMap.get(quality); |
| 337 | + if (!selectedFormat) { |
| 338 | + console.log("formatMap", formatMap); |
| 339 | + console.log("quality", quality); |
| 340 | + console.error( |
| 341 | + `Cannot find the requested format: ${quality}. Available formats are: ${Array.from( |
| 342 | + formatMap.keys() |
| 343 | + ).join(", ")}` |
| 344 | + ); |
| 345 | + |
| 346 | + return new Response( |
| 347 | + `Cannot find the requested format. Available formats are: ${Array.from( |
| 348 | + formatMap.keys() |
| 349 | + ).join(", ")}`, |
| 350 | + { status: 400 } |
| 351 | + ); |
| 352 | + } |
| 353 | + await downloadStreams( |
| 354 | + url, |
| 355 | + AUDIO_FILE_PATH, |
| 356 | + VIDEO_FILE_PATH, |
| 357 | + String(selectedFormat.itag) |
| 358 | + ); |
306 | 359 |
|
307 | 360 | await mergeAudioVideo( |
308 | 361 | VIDEO_FILE_PATH, |
|
0 commit comments