fent / node-ytdl-core

YouTube video downloader in javascript.
MIT License
4.47k stars 788 forks source link

403 error it stopped working #1295

Open Dmytro-Tihunov opened 1 month ago

Dmytro-Tihunov commented 1 month ago

Hey guys! it stopped working we need an update :)

navetacandra commented 1 month ago

me also get 403. i tried with cloud vm and also get 403 :(

navetacandra commented 1 month ago

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

kassyn commented 1 month ago

me algo get 403

MinigetError: Status code: 403
    at ClientRequest.eval (webpack-internal:///(rsc)/./node_modules/miniget/dist/index.js:206:27)
    at Object.onceWrapper (node:events:633:26)
    at ClientRequest.emit (node:events:518:28)
    at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:698:27)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)
    at TLSSocket.socketOnData (node:_http_client:540:22)
    at TLSSocket.emit (node:events:518:28)
    at addChunk (node:internal/streams/readable:559:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
    at Readable.push (node:internal/streams/readable:390:5)
    at TLSWrap.onStreamRead (node:internal/stream_base_commons:190:23)
    at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  statusCode: 403
} writeStream error
kevinrss01 commented 1 month ago

Same here, infinite loading..

jaysun0 commented 1 month ago

Does anybody managed to get around it?

I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

lovegaoshi commented 1 month ago

look everywhere (yt-dlp, newpipe, etc) and everyone is having exactly this problem. google has dropped the ban hammer. just sit tight until some kind soul hacks this again

AliAryanTech commented 1 month ago

look everywhere (yt-dlp, newpipe, etc) and everyone is having exactly this problem. google has dropped the ban hammer. just sit tight until some kind soul hacks this again

Oh no

noidwasavailable commented 1 month ago

Does anybody managed to get around it?

I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

I could get the getInfo to work well, but the output format is wildly different from ytdl-core's ytdl.getInfo and you cannot simply plug the output of it into ytdl.downloadFromInfo. I haven't been able to get the fetch(video.url) part working.

kimgh06 commented 1 month ago

Same here.

me algo get 403

MinigetError: Status code: 403
    at ClientRequest.eval (webpack-internal:///(rsc)/./node_modules/miniget/dist/index.js:206:27)
    at Object.onceWrapper (node:events:633:26)
    at ClientRequest.emit (node:events:518:28)
    at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:698:27)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:119:17)
    at TLSSocket.socketOnData (node:_http_client:540:22)
    at TLSSocket.emit (node:events:518:28)
    at addChunk (node:internal/streams/readable:559:12)
    at readableAddChunkPushByteMode (node:internal/streams/readable:510:3)
    at Readable.push (node:internal/streams/readable:390:5)
    at TLSWrap.onStreamRead (node:internal/stream_base_commons:190:23)
    at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  statusCode: 403
} writeStream error
navetacandra commented 1 month ago

Does anybody managed to get around it? I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

I could get the getInfo to work well, but the output format is wildly different from ytdl-core's ytdl.getInfo and you cannot simply plug the output of it into ytdl.downloadFromInfo. I haven't been able to get the fetch(video.url) part working.

Have you tried manually visiting URL?

noidwasavailable commented 1 month ago

Does anybody managed to get around it? I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

I could get the getInfo to work well, but the output format is wildly different from ytdl-core's ytdl.getInfo and you cannot simply plug the output of it into ytdl.downloadFromInfo. I haven't been able to get the fetch(video.url) part working.

Have you tried manually visiting URL?

Yeah I did and it displays the video/audio file fine. I was able to just write my own code to then use fs.createWriteStream to download it. If anyone else wants to try it out, here it is:

import fs from 'fs';
import { Readable } from 'stream';

type DownloadOptions = {
  startTime?: number;
  endTime?: number;
  bitrate?: number;
  outputFile?: string;
};

/**
 * Downloads a specified part of an audio file from a URL.
 *
 * @param {string} url - The URL of the audio file.
 * @param {DownloadOptions} [options] - Options for downloading the audio part.
 * @returns {Promise<void>} - A promise that resolves when the download is complete.
 */
export async function downloadAudioFromUrl(
  url: string,
  options: DownloadOptions = {},
): Promise<void> {
  const {
    startTime = 0,
    endTime = Infinity,
    bitrate = 50548,
    outputFile = `your/desired/default/path/here.mp4`,
  } = options;

  try {
    // Convert start and end time from seconds to bytes
    const startByte = Math.floor((startTime * bitrate) / 8);
    const endByte = isFinite(endTime)
      ? Math.floor((endTime * bitrate) / 8) - 1
      : '';

    // Fetch the specified byte range
    const response = await fetch(url, {
      headers: {
        Range: `bytes=${startByte}-${endByte}`,
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // Convert response body to a Uint8Array and then to a readable stream
    const arrayBuffer = await response.arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);

    // Create a readable stream and push the data
    const readableStream = new Readable({
      read() {
        this.push(uint8Array);
        this.push(null); // Indicate the end of the stream
      },
    });

    // Create a writable stream to save the audio part
    const fileStream = fs.createWriteStream(outputFile);

    // Pipe the response data to the file
    readableStream.pipe(fileStream);
    fileStream.on('finish', () => {
      console.log('Download complete');
    });
  } catch (error) {
    console.error('Error downloading audio part:', error);
  }
}
kevinrss01 commented 1 month ago

Does anybody managed to get around it? I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

I could get the getInfo to work well, but the output format is wildly different from ytdl-core's ytdl.getInfo and you cannot simply plug the output of it into ytdl.downloadFromInfo. I haven't been able to get the fetch(video.url) part working.

Have you tried manually visiting URL?

Yeah I did and it displays the video/audio file fine. I was able to just write my own code to then use fs.createWriteStream to download it. If anyone else wants to try it out, here it is:

import fs from 'fs';
import { Readable } from 'stream';

type DownloadOptions = {
  startTime?: number;
  endTime?: number;
  bitrate?: number;
  outputFile?: string;
};

/**
 * Downloads a specified part of an audio file from a URL.
 *
 * @param {string} url - The URL of the audio file.
 * @param {DownloadOptions} [options] - Options for downloading the audio part.
 * @returns {Promise<void>} - A promise that resolves when the download is complete.
 */
export async function downloadAudioFromUrl(
  url: string,
  options: DownloadOptions = {},
): Promise<void> {
  const {
    startTime = 0,
    endTime = Infinity,
    bitrate = 50548,
    outputFile = `your/desired/default/path/here.mp4`,
  } = options;

  try {
    // Convert start and end time from seconds to bytes
    const startByte = Math.floor((startTime * bitrate) / 8);
    const endByte = isFinite(endTime)
      ? Math.floor((endTime * bitrate) / 8) - 1
      : '';

    // Fetch the specified byte range
    const response = await fetch(url, {
      headers: {
        Range: `bytes=${startByte}-${endByte}`,
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // Convert response body to a Uint8Array and then to a readable stream
    const arrayBuffer = await response.arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);

    // Create a readable stream and push the data
    const readableStream = new Readable({
      read() {
        this.push(uint8Array);
        this.push(null); // Indicate the end of the stream
      },
    });

    // Create a writable stream to save the audio part
    const fileStream = fs.createWriteStream(outputFile);

    // Pipe the response data to the file
    readableStream.pipe(fileStream);
    fileStream.on('finish', () => {
      console.log('Download complete');
    });
  } catch (error) {
    console.error('Error downloading audio part:', error);
  }
}

Doesn't work

navetacandra commented 1 month ago

Does anybody managed to get around it? I tried it, but didn't manage to fix the promlem. May be did smth wrong, not sure/

me also get 403. i tried with cloud vm and also get 403 :(

i tried konsumer's solution at #1289 and it works. I don't know why it works, because when I try yt-dlp executable it returns 403 too..

I could get the getInfo to work well, but the output format is wildly different from ytdl-core's ytdl.getInfo and you cannot simply plug the output of it into ytdl.downloadFromInfo. I haven't been able to get the fetch(video.url) part working.

Have you tried manually visiting URL?

Yeah I did and it displays the video/audio file fine. I was able to just write my own code to then use fs.createWriteStream to download it. If anyone else wants to try it out, here it is:

import fs from 'fs';
import { Readable } from 'stream';

type DownloadOptions = {
  startTime?: number;
  endTime?: number;
  bitrate?: number;
  outputFile?: string;
};

/**
 * Downloads a specified part of an audio file from a URL.
 *
 * @param {string} url - The URL of the audio file.
 * @param {DownloadOptions} [options] - Options for downloading the audio part.
 * @returns {Promise<void>} - A promise that resolves when the download is complete.
 */
export async function downloadAudioFromUrl(
  url: string,
  options: DownloadOptions = {},
): Promise<void> {
  const {
    startTime = 0,
    endTime = Infinity,
    bitrate = 50548,
    outputFile = `your/desired/default/path/here.mp4`,
  } = options;

  try {
    // Convert start and end time from seconds to bytes
    const startByte = Math.floor((startTime * bitrate) / 8);
    const endByte = isFinite(endTime)
      ? Math.floor((endTime * bitrate) / 8) - 1
      : '';

    // Fetch the specified byte range
    const response = await fetch(url, {
      headers: {
        Range: `bytes=${startByte}-${endByte}`,
      },
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    // Convert response body to a Uint8Array and then to a readable stream
    const arrayBuffer = await response.arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);

    // Create a readable stream and push the data
    const readableStream = new Readable({
      read() {
        this.push(uint8Array);
        this.push(null); // Indicate the end of the stream
      },
    });

    // Create a writable stream to save the audio part
    const fileStream = fs.createWriteStream(outputFile);

    // Pipe the response data to the file
    readableStream.pipe(fileStream);
    fileStream.on('finish', () => {
      console.log('Download complete');
    });
  } catch (error) {
    console.error('Error downloading audio part:', error);
  }
}

Doesn't work

const fs = require('fs');
const { Readable } = require('stream');
const { finished } = require('stream/promises');

async function getInfo(videoId) {
  const apiKey = 'AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc'
  const headers = {
    'X-YouTube-Client-Name': '5',
    'X-YouTube-Client-Version': '19.09.3',
    Origin: 'https://www.youtube.com',
    'User-Agent': 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
    'content-type': 'application/json'
  }

  const b = {
    context: {
      client: {
        clientName: 'IOS',
        clientVersion: '19.09.3',
        deviceModel: 'iPhone14,3',
        userAgent: 'com.google.ios.youtube/19.09.3 (iPhone14,3; U; CPU iOS 15_6 like Mac OS X)',
        hl: 'en',
        timeZone: 'UTC',
        utcOffsetMinutes: 0
      }
    },
    videoId,
    playbackContext: { contentPlaybackContext: { html5Preference: 'HTML5_PREF_WANTS' } },
    contentCheckOk: true,
    racyCheckOk: true
  }

  const res = await fetch(`https://www.youtube.com/youtubei/v1/player?key${apiKey}&prettyPrint=false`, { method: 'POST', body: JSON.stringify(b), headers });
  // throw an error when failed to get info
  if(!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  const json = await res.json();
  return json;
}

;(async function() {
  // get video info by id
  const info = await getInfo('oDAw7vW7H0c');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const selectedFormat = formats[2];
  const ext = selectedFormat.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const writer = fs.createWriteStream(filename);
  const res = await fetch(selectedFormat.url);
  // throw an error when failed to download
  if(!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  // waiting for download is finished
  await finished(Readable.fromWeb(res.body).pipe(writer));
  console.log(`Downloaded ${filename}`);
})();

try it. it works on my machine.

noidwasavailable commented 1 month ago

@kevinrss01 try @navetacandra's code. I didn't post the full example and my code is a little bit specialized because I use it as a part of a bigger codebase and I probably missed parts that only work because of other parts of my code and I only need the audio file. His example is more versatile and has an example code as well.

kevinrss01 commented 1 month ago

@navetacandra Your code works but I don't have any sound in the video, do you have a solution?

mitsuki31 commented 1 month ago

@navetacandra I tried your code and it works, but it doesn't include audio stream.

navetacandra commented 1 month ago

@mitsuki31 @kevinrss01 you can just simply merge video and audio using fluent-ffmpeg. example:

const ffmpeg = require('fluent-ffmpeg');

....

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  ffmpeg()
    .input('tmp_' + filename)
    .input(Readable.fromWeb(audioRes.body))
    .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
    .saveToFile(filename)
    .on('start', () => console.log('Merge video with audio'))
    .on('end', () => {
      fs.unlinkSync('tmp_' + filename);
      console.log(`Downloaded ${filename}`);
    });
})();

or asynchronously

const ffmpeg = require('fluent-ffmpeg');

....

// audio can be Readable stream or filepath
const mergeVideoWithAudio = (videoPath, audio, output) => {
  return new Promise((resolve, reject) => {
    ffmpeg()
      .input(videoPath)
      .input(audio)
      .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
      .save(output)
      .on('start', () => console.log('Merge video with audio'))
      .on('end', () => {
        fs.unlinkSync(videoPath);
        resolve();
      })
      .on('error', err => reject(err))
  });
}

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  await mergeVideoWithAudio('tmp_' + filename, Readable.fromWeb(audioRes.body), filename);
  console.log(`Downloaded ${filename}`);
})();
mitsuki31 commented 1 month ago

@navetacandra Thanks, it worked. Maybe it will be a good idea to use this as fallback downloader.

sohamhaldar commented 1 month ago

@navetacandra from where did you generate the api key

mitsuki31 commented 1 month ago

@sohamhaldar Google Developers Console

sohamhaldar commented 1 month ago

@navetacandra Ok thanks, btw will my youtube data api key will work here

navetacandra commented 1 month ago

@navetacandra from where did you generate the api key

i just put hardcoded api key from yt-dlp repo https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/youtube.py

sohamhaldar commented 1 month ago

@navetacandra from where did you generate the api key

i just put hardcoded api key from yt-dlp repo https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/youtube.py

Ok thanks a lot

bholagourav commented 1 month ago

@mitsuki31 @kevinrss01 you can just simply merge video and audio using fluent-ffmpeg. example:

const ffmpeg = require('fluent-ffmpeg');

....

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  ffmpeg()
    .input('tmp_' + filename)
    .input(Readable.fromWeb(audioRes.body))
    .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
    .saveToFile(filename)
    .on('start', () => console.log('Merge video with audio'))
    .on('end', () => {
      fs.unlinkSync('tmp_' + filename);
      console.log(`Downloaded ${filename}`);
    });
})();

or asynchronously

const ffmpeg = require('fluent-ffmpeg');

....

// audio can be Readable stream or filepath
const mergeVideoWithAudio = (videoPath, audio, output) => {
  return new Promise((resolve, reject) => {
    ffmpeg()
      .input(videoPath)
      .input(audio)
      .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
      .save(output)
      .on('start', () => console.log('Merge video with audio'))
      .on('end', () => {
        fs.unlinkSync(videoPath);
        resolve();
      })
      .on('error', err => reject(err))
  });
}

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  await mergeVideoWithAudio('tmp_' + filename, Readable.fromWeb(audioRes.body), filename);
  console.log(`Downloaded ${filename}`);
})();

Downloading speed is very poor for the above code.

navetacandra commented 1 month ago

@mitsuki31 @kevinrss01 you can just simply merge video and audio using fluent-ffmpeg. example:

const ffmpeg = require('fluent-ffmpeg');

....

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  ffmpeg()
    .input('tmp_' + filename)
    .input(Readable.fromWeb(audioRes.body))
    .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
    .saveToFile(filename)
    .on('start', () => console.log('Merge video with audio'))
    .on('end', () => {
      fs.unlinkSync('tmp_' + filename);
      console.log(`Downloaded ${filename}`);
    });
})();

or asynchronously

const ffmpeg = require('fluent-ffmpeg');

....

// audio can be Readable stream or filepath
const mergeVideoWithAudio = (videoPath, audio, output) => {
  return new Promise((resolve, reject) => {
    ffmpeg()
      .input(videoPath)
      .input(audio)
      .addOption(['-c:v', 'copy', '-c:a', 'aac', '-map', '0:v:0', '-map', '1:a:0', '-shortest'])
      .save(output)
      .on('start', () => console.log('Merge video with audio'))
      .on('end', () => {
        fs.unlinkSync(videoPath);
        resolve();
      })
      .on('error', err => reject(err))
  });
}

;(async function() {
  // get video info by id
  const info = await getInfo('xvFZjo5PgG0');
  // check playability status
  if(info.playabilityStatus.status !== 'OK') throw new Error(info.playabilityStatus.reason);

  // get formats
  const formats = info.streamingData.adaptiveFormats;
  const video = formats.filter(f => f.mimeType.match(/^video\/\w+/))[0];
  const audio = formats.filter(f => f.mimeType.match(/^audio\/\w+/))[0];
  const ext = video.mimeType.match(/^\w+\/(\w+)/)[1];
  // create filename for video
  const filename = `${info.videoDetails.title}-${info.videoDetails.videoId}.${ext}`;

  // download video
  console.log(`Downloading ${filename}`);
  const tmpVideo = fs.createWriteStream('tmp_' + filename);
  const videoRes = await fetch(video.url);
  const audioRes = await fetch(audio.url);
  // throw an error when failed to download
  if(!videoRes.ok) throw new Error(`${videoRes.status} ${videoRes.statusText}`);
  if(!audioRes.ok) throw new Error(`${audioRes.status} ${audioRes.statusText}`);
  // download video first
  await finished(Readable.fromWeb(videoRes.body).pipe(tmpVideo));
  // then merge video with audio
  await mergeVideoWithAudio('tmp_' + filename, Readable.fromWeb(audioRes.body), filename);
  console.log(`Downloaded ${filename}`);
})();

Downloading speed is very poor for the above code.

Yes. It is because you must download video and audio instead of only downloading video or audio.

kimgh06 commented 1 month ago

I use this package for making music blob data, is it possible?

lovegaoshi commented 1 month ago

seems like folks at distubejs just slapped a fix: https://github.com/distubejs/ytdl-core/commit/3df824e57fe4ce3037a91efd124b729dea38c01f also strictly for my purposes, youtubei still works just fine. just FYI

bholagourav commented 1 month ago

seems like folks at distubejs just slapped a fix: distubejs@3df824e also strictly for my purposes, youtubei still works just fine. just FYI

when node-ytdl-core will slap a fix??

bendaacz commented 1 month ago

when node-ytdl-core will slap a fix??

Just install @distubejs/ytdl-core and in your files change const ytdl = require("ytdl-core"); to const ytdl = require("@distube/ytdl-core"); This worked for me!

bholagourav commented 1 month ago

bholagourav

Just install @distubejs/ytdl-core and in your files change const ytdl = require("ytdl-core"); to const ytdl = require("@distube/ytdl-core"); This worked for me!

@bendaacz your forked package branch is behind with x number of commits i am wondering if i use require("@distube/ytdl-core") something else could break.

bendaacz commented 1 month ago

something else could break.

YOLO! Just try it in dev mode, no? Everything works so far.

bholagourav commented 1 month ago

@bendaacz it works on dev mode.

RafaelDuque049 commented 1 month ago

when node-ytdl-core will slap a fix??

Just install @distubejs/ytdl-core and in your files change const ytdl = require("ytdl-core"); to const ytdl = require("@distube/ytdl-core"); This worked for me!

Thank you very much, it worked for me, I switched to this library (which is a fork of the current package) and it worked again.

Muito obrigado, funcionou para mim, fiz a troca para essa biblioteca (Que é um fork do package atual) e voltou a funcionar. Code:

// const ytdl = require('ytdl-core');
const ytdl = require('@distube/ytdl-core');

let stream = ytdl(url, {
        quality: 'highestaudio',
        format: 'mp3',
        highWaterMark: 1 << 30,
        liveBuffer: 1 << 30,
    })
    .on('error', erro => {
        downloadSucess = undefined;
        console.error(erro)
    });

    ffmpeg(stream)
        .audioBitrate(96)
        .audioChannels(1)
        .format('mp3')
        .save(`${__dirname}/midias/audio/${nameVideo}.mp3`)
        .on('error', erro => {
            downloadSucess = undefined;
            console.error(erro)
        })
        .on('end', () => {
            downloadSucess = true;
        })

same performance

lovegaoshi commented 1 month ago

i digged in a bit deeper and the ntransform function has failed to extract. the best course of action is to switch to distube's branch. unfortunately i failed to adapt the distube branch myself so i'm stuck at the upstream branch, but copying distube's ntranform extraction logic seems to resolve this issue, just FYI https://github.com/lovegaoshi/rn-ytdl-core/commit/55d73611d3bd42050ae3f42c067d682518a83c59

DanielBUBU commented 1 month ago

change dependency to @distube/ytdl-core works this is the reason why I use both ytdl and ytdlp in my bot

when node-ytdl-core will slap a fix??

Just install @distubejs/ytdl-core and in your files change const ytdl = require("ytdl-core"); to const ytdl = require("@distube/ytdl-core"); This worked for me!

Thank you very much, it worked for me, I switched to this library (which is a fork of the current package) and it worked again.

Muito obrigado, funcionou para mim, fiz a troca para essa biblioteca (Que é um fork do package atual) e voltou a funcionar. Code:

// const ytdl = require('ytdl-core');
const ytdl = require('@distube/ytdl-core');

let stream = ytdl(url, {
        quality: 'highestaudio',
        format: 'mp3',
        highWaterMark: 1 << 30,
        liveBuffer: 1 << 30,
    })
    .on('error', erro => {
        downloadSucess = undefined;
        console.error(erro)
    });

    ffmpeg(stream)
        .audioBitrate(96)
        .audioChannels(1)
        .format('mp3')
        .save(`${__dirname}/midias/audio/${nameVideo}.mp3`)
        .on('error', erro => {
            downloadSucess = undefined;
            console.error(erro)
        })
        .on('end', () => {
            downloadSucess = true;
        })

same performance: image

Lampropoulosss commented 1 month ago

I am simply starting node in ssh and I type these commands one by one:

  1. const ytdl = require("ytdl-core");
  2. await ytdl.getBasicInfo("http://www.youtube.com/watch?v=aqz-KE-bpKQ")

Returns:

Uncaught UnrecoverableError: This video is unavailable at exports.playError (/root/project/node_modules/@distube/ytdl-core/lib/utils.js:164:12) at exports.getBasicInfo (/root/project/node_modules/@distube/ytdl-core/lib/info.js:52:25) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Happens for every video.

Environment

mitsuki31 commented 1 month ago

I am simply starting node in ssh and I type these commands one by one:

  1. const ytdl = require("ytdl-core");
  2. await ytdl.getBasicInfo("http://www.youtube.com/watch?v=aqz-KE-bpKQ")

Returns:

Uncaught UnrecoverableError: This video is unavailable at exports.playError (/root/project/node_modules/@distube/ytdl-core/lib/utils.js:164:12) at exports.getBasicInfo (/root/project/node_modules/@distube/ytdl-core/lib/info.js:52:25) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Happens for every video.

Environment

  • ytdl-core version: 4.11.5
  • Node.js version: v20.3.1
  • Operating system: Ubuntu 24.04

Did you mean to use require('@distube/ytdl-core')? Because your error message refers to different module.

And also, I tried your code with ytdl-core module with version the same as yours (v4.11.5), and it works just fine.

Lampropoulosss commented 1 month ago

I am simply starting node in ssh and I type these commands one by one:

  1. const ytdl = require("ytdl-core");
  2. await ytdl.getBasicInfo("http://www.youtube.com/watch?v=aqz-KE-bpKQ")

Returns:

Uncaught UnrecoverableError: This video is unavailable at exports.playError (/root/project/node_modules/@distube/ytdl-core/lib/utils.js:164:12) at exports.getBasicInfo (/root/project/node_modules/@distube/ytdl-core/lib/info.js:52:25) at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Happens for every video.

Environment

  • ytdl-core version: 4.11.5
  • Node.js version: v20.3.1
  • Operating system: Ubuntu 24.04

Did you mean to use require('@distube/ytdl-core')? Because your error message refers to different module.

And also, I tried your code with ytdl-core module with version the same as yours (v4.11.5), and it works just fine.

Well, no, it refers to a different module but it happens with ytdl-core and with @distube/ytdl-core too. Can it be a Ubuntu 24.04 issue?

kevinrss01 commented 1 month ago

I have big latency when recovering the video with distube/ytdl-core. Do people have the same?

DanielBUBU commented 1 month ago

I have big latency when recovering the video with distube/ytdl-core. Do people have the same?

Nope, I'm fine using @distube/ytdl-core

kevinrss01 commented 1 month ago

I have big latency when recovering the video with distube/ytdl-core. Do people have the same?

Nope, I'm fine using @distube/ytdl-core

Ca n you show what params you are using ? I'm using:

const info = await ytdl.getInfo(link);
const format = ytdl.chooseFormat(info.formats, {
  quality: "highest",
  filter:  "videoonly",
});
return new Promise((resolve, reject) => {
        ytdl
          .downloadFromInfo(info, { format: format })
          .pipe(outputStream)
          .on('finish', () => {
            resolve(outputFilePath);
            console.debug('Video or audio saved.');
          })
          .on('error', (err) => {
            console.error(err);
            reject(err);
          });
      });
DanielBUBU commented 1 month ago

I have big latency when recovering the video with distube/ytdl-core. Do people have the same?

Nope, I'm fine using @distube/ytdl-core

Ca n you show what params you are using ? I'm using:

const info = await ytdl.getInfo(link);
const format = ytdl.chooseFormat(info.formats, {
  quality: "highest",
  filter:  "videoonly",
});
return new Promise((resolve, reject) => {
        ytdl
          .downloadFromInfo(info, { format: format })
          .pipe(outputStream)
          .on('finish', () => {
            resolve(outputFilePath);
            console.debug('Video or audio saved.');
          })
          .on('error', (err) => {
            console.error(err);
            reject(err);
          });
      });

I've customized cookie, highwatermark and Chunk size, and I use it for streaming since ytdlp is better in downloading files. Below is the section that I use in the discord bot, I preserve ytdlp version too in case ytdl-core is dead again. Keep in mind that this use @distube/ytdl-core

                        //YTDLP

                        //var audio_stream = this.createYTDLPStream(url);

                        //ytdl
                        var audio_stream = ytdl(url, {
                            filter: "audioonly",
                            //liveBuffer: 2000,
                            highWaterMark: 16384,
                            dlChunkSize: 65536,
                            quality: 'highestaudio',
                            //begin: BT,
                            requestOptions: {
                                headers: {
                                    cookie: YT_COOKIE,
                                    // Optional. If not given, ytdl-core will try to find it.
                                    // You can find this by going to a video's watch page, viewing the source,
                                    // and searching for "ID_TOKEN".
                                    // 'x-youtube-identity-token': 1324,
                                },
                            },
                        });
                        this.playAudioResauce(this.wrapStreamToResauce(audio_stream, begin_t));
gabrielstuff commented 1 month ago

I confirm https://www.npmjs.com/package/@distube/ytdl-core works fine. Just switched.

Dmytro-Tihunov commented 1 month ago

Yes, it works =) thanks guys

fpsone commented 1 month ago

I don't think we should close this issue since the fix is installing another fork.

Brendon3578 commented 1 month ago

still have this bug, switch for https://www.npmjs.com/package/@distube/ytdl-core works nice.

Dmytro-Tihunov commented 1 month ago

as I understand we all faced with new problem that is "Sign in to confirm you’re not a bot" and that means youtube asks for captcha, is there anybody who find solution?

corwin-of-amber commented 1 month ago

@Brendon3578 based on @distubejs I was able to make this patch to lib/sig.js: https://github.com/fent/node-ytdl-core/issues/1305#issuecomment-2253373635

I am not getting the "Sign in to confirm you’re not a bot" error that @Dmytro-Tihunov has mentioned. So this may not be the only issue.

Dmytro-Tihunov commented 1 month ago

@corwin-of-amber have you tried in production ?

corwin-of-amber commented 1 month ago

@Dmytro-Tihunov What do you mean by "production"? I have patched the file in my node_modules/ directory, then ran these lines in node:

ytdl = require('ytdl-core');
ytdl('https://www.youtube.com/watch?v=8gWFUuSCWk8').pipe(fs.createWriteStream('/tmp/out.mp4'))

and I got the file. This is on my local machine. Nothing fancy.