import { create } from 'youtube-dl-exec'; import { env } from '$env/dynamic/private'; import { spawn } from 'node:child_process'; import supportedFormats from '$lib/common/supportedFormats.json'; import { logger, mimeTypeMap } from '$lib/server/helpers'; const YTDLP_PATH: string = env.YTDLP_PATH as string; const HTTPS_PROXY: string = env.HTTPS_PROXY as string; export const ytdl = create(YTDLP_PATH); /** * Fetch YouTube metadata (title, uploader/artist) */ export async function getYouTubeMetadata(link: string) { const options = { dumpSingleJson: true, noCheckCertificates: true, noWarnings: true, preferFreeFormats: true }; if (HTTPS_PROXY) { options.proxy = HTTPS_PROXY; } return await ytdl(link, options); } /** * Streams the YouTube video/audio using youtube-dl-exec */ export function streamYouTube(link: string, format: string): ReadableStream { logger.debug(`Starting to stream: ${link}`); const mimeType: string | undefined = mimeTypeMap.get(format); if (!mimeType) { throw new Error('Unsupported format'); } logger.debug(`Given format is compatible: ${mimeType}`); return new ReadableStream({ start(controller) { const args = ['--no-write-thumbnail', '-o', '-']; if (HTTPS_PROXY) { args.push('--proxy', HTTPS_PROXY); } if (mimeType?.includes('audio')) { args.push( ...['--extract-audio', '--embed-metadata', '--embed-thumbnail', '--audio-format', format] ); } else if (mimeType.includes('video')) { args.push(...['--embed-metadata', '--embed-thumbnail', '--format', format]); } const cmd = `${YTDLP_PATH} ${args.join(' ')} ${link}`; logger.debug(`Running: ${cmd}`); const process = spawn(YTDLP_PATH, [...args, link], { cwd: '/tmp', stdio: ['ignore', 'pipe', 'pipe'] }); process.stdout.on('data', (chunk) => { try { controller.enqueue(chunk); } catch (ex) { process.kill(); } }); process.stderr.on('data', (chunk) => logger.debug(chunk.toString())); process.stdout.on('end', () => { try { controller.close(); } catch (ex) { logger.error(ex); } }); process.stdout.on('error', (err) => { logger.error('Stream error:', err); controller.error(err); }); } }); }