-
Notifications
You must be signed in to change notification settings - Fork 804
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
Comments
me also get 403. i tried with cloud vm and also get 403 :( |
me algo get 403
|
Same here, infinite loading.. |
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/
|
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 |
I could get the |
Same here.
|
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 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. |
@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. |
@navetacandra Your code works but I don't have any sound in the video, do you have a solution? |
@navetacandra I tried your code and it works, but it doesn't include audio stream. |
@mitsuki31 @kevinrss01 you can just simply merge video and audio using fluent-ffmpeg. 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 Thanks, it worked. Maybe it will be a good idea to use this as fallback downloader. |
@navetacandra from where did you generate the api key |
@navetacandra Ok thanks, btw will my youtube data api key will work here |
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 |
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. |
I use this package for making music blob data, is it possible? |
seems like folks at distubejs just slapped a fix: |
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? |
@corwin-of-amber in production youtube will ban your ip and ask for captcha, in local it works also |
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. |
@corwin-of-amber in production it means deploy it and try to use, NOT LOCALLY ) |
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. |
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. |
Guys it went down again |
Someone should probably merge the issues. |
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. |
no body share better patch code |
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 |
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? |
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. |
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. |
It should work but DON'T restrict your API key so that it'll work for all platforms... |
Hello I send API code to your email address please check it and let me know |
I'm facing same issue, @Distube isn't working properly... I'm still receiving 403 page instead |
Please, check it. It didn't work few hours ago |
Downloaded latest version of @Distube/ytdl-core Ran
No errors. Got
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. |
@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? |
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 |
For me, Innertube player requests with a non-web client are still working fine for downloading adaptive streams. But not for some (distubejs#76). |
Hey guys! it stopped working we need an update :)
The text was updated successfully, but these errors were encountered: