node-ytdl-core icon indicating copy to clipboard operation
node-ytdl-core copied to clipboard

403 error it stopped working

Open Dmytro-Tihunov opened this issue 1 year ago • 79 comments

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

Dmytro-Tihunov avatar Jul 09 '24 18:07 Dmytro-Tihunov

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

navetacandra avatar Jul 09 '24 19:07 navetacandra

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..

navetacandra avatar Jul 09 '24 19:07 navetacandra

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

kassyn avatar Jul 09 '24 19:07 kassyn

Same here, infinite loading..

kevinrss01 avatar Jul 09 '24 19:07 kevinrss01

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..

eymonk avatar Jul 09 '24 19:07 eymonk

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

lovegaoshi avatar Jul 09 '24 23:07 lovegaoshi

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

AliAryanTech avatar Jul 10 '24 01:07 AliAryanTech

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.

noidwasavailable avatar Jul 10 '24 04:07 noidwasavailable

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

kimgh06 avatar Jul 10 '24 04:07 kimgh06

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?

navetacandra avatar Jul 10 '24 04:07 navetacandra

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);
  }
}

noidwasavailable avatar Jul 10 '24 05:07 noidwasavailable

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

kevinrss01 avatar Jul 10 '24 06:07 kevinrss01

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.

navetacandra avatar Jul 10 '24 06:07 navetacandra

@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.

noidwasavailable avatar Jul 10 '24 07:07 noidwasavailable

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

kevinrss01 avatar Jul 10 '24 08:07 kevinrss01

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

mitsuki31 avatar Jul 10 '24 08:07 mitsuki31

@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}`);
})();

navetacandra avatar Jul 10 '24 09:07 navetacandra

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

mitsuki31 avatar Jul 10 '24 09:07 mitsuki31

@navetacandra from where did you generate the api key

sohamhaldar avatar Jul 10 '24 09:07 sohamhaldar

@sohamhaldar Google Developers Console

mitsuki31 avatar Jul 10 '24 09:07 mitsuki31

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

sohamhaldar avatar Jul 10 '24 09:07 sohamhaldar

@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

navetacandra avatar Jul 10 '24 10:07 navetacandra

@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

sohamhaldar avatar Jul 10 '24 10:07 sohamhaldar

@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.

bholagourav avatar Jul 10 '24 12:07 bholagourav

@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.

navetacandra avatar Jul 10 '24 13:07 navetacandra

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

kimgh06 avatar Jul 10 '24 13:07 kimgh06

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

lovegaoshi avatar Jul 10 '24 13:07 lovegaoshi

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??

bholagourav avatar Jul 10 '24 13:07 bholagourav

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!

bendaacz avatar Jul 10 '24 13:07 bendaacz

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.

bholagourav avatar Jul 10 '24 13:07 bholagourav