Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

403 error it stopped working #1295

Open
Dmytro-Tihunov opened this issue Jul 9, 2024 · 78 comments
Open

403 error it stopped working #1295

Dmytro-Tihunov opened this issue Jul 9, 2024 · 78 comments

Comments

@Dmytro-Tihunov
Copy link

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

@navetacandra
Copy link

navetacandra commented Jul 9, 2024

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

@navetacandra
Copy link

navetacandra commented Jul 9, 2024

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
Copy link

kassyn commented Jul 9, 2024

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
Copy link

Same here, infinite loading..

@jaysun0
Copy link

jaysun0 commented Jul 9, 2024

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
Copy link

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
Copy link

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
Copy link

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
Copy link

kimgh06 commented Jul 10, 2024

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
Copy link

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
Copy link

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
Copy link

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
Copy link

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
Copy link

noidwasavailable commented Jul 10, 2024

@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
Copy link

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

@mitsuki31
Copy link

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

@navetacandra
Copy link

navetacandra commented Jul 10, 2024

@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
Copy link

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

@sohamhaldar
Copy link

@navetacandra from where did you generate the api key

@mitsuki31
Copy link

@sohamhaldar
Copy link

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

@navetacandra
Copy link

navetacandra commented Jul 10, 2024

@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
Copy link

@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
Copy link

@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
Copy link

@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
Copy link

kimgh06 commented Jul 10, 2024

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

@lovegaoshi
Copy link

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

@red1ul
Copy link

red1ul commented Jul 26, 2024

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?

I'm also encountering a similar issue where access to https://rr4---sn-qxaeenlz.googlevideo.com is being denied with a status code 403. Does anyone have a solution for this?

@Dmytro-Tihunov
Copy link
Author

@corwin-of-amber in production youtube will ban your ip and ask for captcha, in local it works also

@corwin-of-amber
Copy link

Sorry @Dmytro-Tihunov, I have no idea what is this "production". I think your scenario is different from what most users are facing and reporting.

@Dmytro-Tihunov
Copy link
Author

Dmytro-Tihunov commented Jul 26, 2024

@corwin-of-amber in production it means deploy it and try to use, NOT LOCALLY )

@corwin-of-amber
Copy link

I assume by "deploy" you mean some cloud service...? It's just that I cannot see how this library fits into such a scenario, but it is entirely possible that Google have more strict limitations on requests originating from such IPs.

@isaacs
Copy link

isaacs commented Jul 29, 2024

I got around this by coping in the user-agent header from my browser. It seems to be blocking anything that doesn't smell browsery.

@AliAryanTech
Copy link

Guys it went down again

@corwin-of-amber
Copy link

#1301 (comment)

Someone should probably merge the issues.

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 2, 2024

Just an observation, exploring this issue. On the YouTube website, videoplayback requests are now using POST method. And that GET method is forbidden on video files that are not shorts (<1 minute). The only GET requests that seem to work on longer videos files are 360p with audio and video combined.

Update: Regular downloading works up until 1 minute of video, thereafter, the servers require that requests have a post body that is an encoded uint8array comprising various metadata, config and cookie data, created by umpteen interlinked functions in base.js. Only the non-adaptive format streams and video streams less than one minute appear to work in HTML video tags.

It may be different on mobile devices.

@hextor1
Copy link

hextor1 commented Aug 4, 2024

no body share better patch code

@gatecrasher777
Copy link
Contributor

Innertube info (/youtubei/v1/player) requests post a copy of the session's context client. Solved my problems (at least for now) by modifying the client from web to non-web for videos with duration greater than 1 minute. No more 403s on adaptive streams and resulting video streams will also play in HTML video tags.

The problem with this library is that it does not use the innertube API so I am not sure how you can modify the client type on a watch page. But the watch page does provide everything you need to make a subsequent innertube API request with modified client for streams that will work.

@hextor1
Copy link

hextor1 commented Aug 5, 2024

Innertube info (/youtubei/v1/player) requests post a copy of the session's context client. Solved my problems (at least for now) by modifying the client from web to non-web for videos with duration greater than 1 minute. No more 403s on adaptive streams and resulting video streams will also play in HTML video tags.

The problem with this library is that it does not use the innertube API so I am not sure how you can modify the client type on a watch page. But the watch page does provide everything you need to make a subsequent innertube API request with modified client for streams that will work.

My friend is using innnertube js API. Its working fine no more 403 error last few months. so you mean we move to innertube API @gatecrasher777

@gatecrasher777
Copy link
Contributor

My friend is using innnertube js API. Its working fine no more 403 error last few months. so you mean we move to innertube API @gatecrasher777

Was thinking of doing a PR here, but I see that @distube/ytdl-core, a fork of this library already appears to do an ios innertube player request.

@hextor1
Copy link

hextor1 commented Aug 5, 2024

My friend is using innnertube js API. Its working fine no more 403 error last few months. so you mean we move to innertube API @gatecrasher777

Was thinking of doing a PR here, but I see that @distube/ytdl-core, a fork of this library already appears to do an ios innertube player request.

So what should we do now? Whats the next steps?

@gatecrasher777
Copy link
Contributor

gatecrasher777 commented Aug 5, 2024

So what should we do now? Whats the next steps?

I don't know what your use case is, but while this issue persists I would suggest using https://github.com/distubejs/ytdl-core . It is also on NPM so it should be relatively easy to replace this library in your js/node projects.

There is a non-zero probability that YouTube will also put similar restrictions on non-web clients in the near future. If that happens I will try and crack the POST body encoding which happens with regular web client streaming on the YouTube website. But for now this non-web workaround is good enough for my projects.

@hextor1
Copy link

hextor1 commented Aug 6, 2024

do you have you email address i wanna sending you my API code and you can check it and let me know what we need to change?

@gatecrasher777
Copy link
Contributor

do you have you email address i wanna sending you my API code and you can check it and let me know what we need to change?

AFAIK email is on my profile.

@TechBroCode
Copy link

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

It should work but DON'T restrict your API key so that it'll work for all platforms...

@hextor1
Copy link

hextor1 commented Aug 7, 2024

do you have you email address i wanna sending you my API code and you can check it and let me know what we need to change?

AFAIK email is on my profile.

Hello I send API code to your email address please check it and let me know

@TechBroCode
Copy link

Guys it went down again

I'm facing same issue, @Distube isn't working properly... I'm still receiving 403 page instead

@TechBroCode
Copy link

So what should we do now? Whats the next steps?

I don't know what your use case is, but while this issue persists I would suggest using https://github.com/distubejs/ytdl-core . It is also on NPM so it should be relatively easy to replace this library in your js/node projects.

There is a non-zero probability that YouTube will also put similar restrictions on non-web clients in the near future. If that happens I will try and crack the POST body encoding which happens with regular web client streaming on the YouTube website. But for now this non-web workaround is good enough for my projects.

Please, check it. It didn't work few hours ago

@gatecrasher777
Copy link
Contributor

Please, check it. It didn't work few hours ago

Downloaded latest version of @Distube/ytdl-core

Ran

const fs = require('fs');
const ytdl = require('./lib/index.js');
ytdl('http://www.youtube.com/watch?v=4jnaYhnmYlo')
  .pipe(fs.createWriteStream('video.mp4'));

No errors. Got

2024/08/07  14:10       191 108 392 video.mp4

Ran a similar test in #1301 with this original project (after replacing the extractNCode function) and it downloaded at 360p with sound.

@Distube downloaded a much higher quality video (1080p), but without sound, so clearly the adaptive streams are working because of it using a non-web client call to the innertube API.

@Lampropoulosss
Copy link

Please, check it. It didn't work few hours ago

Downloaded latest version of @Distube/ytdl-core

Ran

const fs = require('fs');
const ytdl = require('./lib/index.js');
ytdl('http://www.youtube.com/watch?v=4jnaYhnmYlo')
  .pipe(fs.createWriteStream('video.mp4'));

No errors. Got

2024/08/07  14:10       191 108 392 video.mp4

Ran a similar test in #1301 with this original project (after replacing the extractNCode function) and it downloaded at 360p with sound.

@Distube downloaded a much higher quality video (1080p), but without sound, so clearly the adaptive streams are working because of it using a non-web client call to the innertube API.

I can verify, @distube/ytdl-core works without any issues.

@hextor1
Copy link

hextor1 commented Aug 8, 2024

@Distube is not working and no code is working

@gatecrasher777
Copy link
Contributor

@Distube is not working and no code is working

See distubejs#76

@hextor1
Copy link

hextor1 commented Aug 8, 2024

@Distube is not working and no code is working

See distubejs#76

Hello please share your email I wanna send you my API code and you add the cookie code then I will add the cookie can you help me?

@corwin-of-amber
Copy link

This is a different problem. It is not related to the nTransform anymore. I am getting 403 for all but one of the formats. The getInfo does go through, and I am not getting the "sign in to prove you are not a bot" error, though.

@gatecrasher777
Copy link
Contributor

This is a different problem. It is not related to the nTransform anymore. I am getting 403 for all but one of the formats. The getInfo does go through, and I am not getting the "sign in to prove you are not a bot" error, though.

For me, Innertube player requests with a non-web client are still working fine for downloading adaptive streams. But not for some (distubejs#76).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests