cloud-ytdl 1.0.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1063 -0
- package/lib/agents.js +114 -0
- package/lib/cache.js +45 -0
- package/lib/format-utils.js +148 -0
- package/lib/format.js +82 -0
- package/lib/index.js +169 -0
- package/lib/info.js +209 -0
- package/lib/innertube.js +1 -0
- package/lib/load.js +38 -0
- package/lib/playlist.js +57 -0
- package/lib/post.js +88 -0
- package/lib/sig-decoder.js +1 -0
- package/lib/subtitle.js +106 -0
- package/lib/url-utils.js +58 -0
- package/lib/utils.js +294 -0
- package/lib/xmlTosrt.js +49 -0
- package/package.json +80 -0
- package/types/index.d.ts +488 -0
package/README.md
ADDED
|
@@ -0,0 +1,1063 @@
|
|
|
1
|
+
# ytdl-cloud
|
|
2
|
+
|
|
3
|
+
A reliable YouTube downloader and scraper using InnerTube clients. Includes video, stream, and Community Post extraction (text, images, polls). Production-ready for Node.js 18+.
|
|
4
|
+
|
|
5
|
+
**Created by:** [AlfiDev](https://github.com/cloudkuimages)
|
|
6
|
+
**Repository:** [github.com/cloudkuimages/ytdl-cloud](https://github.com/cloudkuimages/ytdl-cloud)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- ✅ **Android InnerTube Client** - Reliable format extraction using official YouTube Android API
|
|
11
|
+
- ✅ **Automatic Signature Decoding** - Handles encrypted format URLs transparently
|
|
12
|
+
- ✅ **Restricted Videos** - Download age-restricted and region-locked videos with cookies
|
|
13
|
+
- ✅ **Community Posts** - Extract YouTube Community Posts (text, images, polls)
|
|
14
|
+
- ✅ **Subtitle Extraction** - Download subtitles/captions in XML or SRT format
|
|
15
|
+
- ✅ **Playlist Support** - Extract playlist information and video lists
|
|
16
|
+
- ✅ **Multi-format** - Supports various formats (MP4, WebM, M4A, etc.)
|
|
17
|
+
- ✅ **Stream Support** - Supports HLS and DASH streams
|
|
18
|
+
- ✅ **Production Ready** - Stable and tested with YouTube 2025
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install ytdl-cloud
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Basic Video Download
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
const ytdl = require('ytdl-cloud');
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
|
|
34
|
+
// Download video with best quality
|
|
35
|
+
ytdl('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
|
36
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Download with Quality Selection
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const ytdl = require('ytdl-cloud');
|
|
43
|
+
const fs = require('fs');
|
|
44
|
+
|
|
45
|
+
// Highest quality
|
|
46
|
+
ytdl('VIDEO_URL', { quality: 'highest' })
|
|
47
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
48
|
+
|
|
49
|
+
// Specific format (1080p)
|
|
50
|
+
ytdl('VIDEO_URL', { quality: 137 })
|
|
51
|
+
.pipe(fs.createWriteStream('video-1080p.mp4'));
|
|
52
|
+
|
|
53
|
+
// Audio only
|
|
54
|
+
ytdl('VIDEO_URL', { filter: 'audioonly' })
|
|
55
|
+
.pipe(fs.createWriteStream('audio.m4a'));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Complete API Documentation
|
|
61
|
+
|
|
62
|
+
### ytdl(url, [options])
|
|
63
|
+
|
|
64
|
+
Main function to download video from YouTube.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
- `url` (string): Video URL or Video ID
|
|
68
|
+
- `options` (object): Download options (optional)
|
|
69
|
+
|
|
70
|
+
**Returns:** ReadableStream
|
|
71
|
+
|
|
72
|
+
**Example:**
|
|
73
|
+
```javascript
|
|
74
|
+
const stream = ytdl('https://www.youtube.com/watch?v=dQw4w9WgXcQ', {
|
|
75
|
+
quality: 'highest',
|
|
76
|
+
filter: 'audioandvideo'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
stream.pipe(fs.createWriteStream('video.mp4'));
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### ytdl.getInfo(url, [options])
|
|
85
|
+
|
|
86
|
+
Get complete video information without downloading.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
- `url` (string): Video URL or Video ID
|
|
90
|
+
- `options` (object): Options (optional)
|
|
91
|
+
|
|
92
|
+
**Returns:** Promise<Object>
|
|
93
|
+
|
|
94
|
+
**Options:**
|
|
95
|
+
```javascript
|
|
96
|
+
{
|
|
97
|
+
lang?: string, // Language (default: 'en')
|
|
98
|
+
requestOptions?: {
|
|
99
|
+
headers?: Record<string, string>, // Custom headers (e.g., cookies)
|
|
100
|
+
timeout?: number, // Timeout in ms
|
|
101
|
+
agent?: any, // Custom agent
|
|
102
|
+
dispatcher?: any // Custom dispatcher
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Example:**
|
|
108
|
+
```javascript
|
|
109
|
+
ytdl.getInfo('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
|
110
|
+
.then(info => {
|
|
111
|
+
console.log('Title:', info.videoDetails.title);
|
|
112
|
+
console.log('Channel:', info.videoDetails.author.name);
|
|
113
|
+
console.log('Duration:', info.videoDetails.lengthSeconds, 'seconds');
|
|
114
|
+
console.log('Format count:', info.formats.length);
|
|
115
|
+
console.log('View count:', info.videoDetails.viewCount);
|
|
116
|
+
|
|
117
|
+
// List all formats
|
|
118
|
+
info.formats.forEach(format => {
|
|
119
|
+
console.log(`ITAG ${format.itag}: ${format.qualityLabel} (${format.container})`);
|
|
120
|
+
});
|
|
121
|
+
})
|
|
122
|
+
.catch(err => {
|
|
123
|
+
console.error('Error:', err.message);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Response Object:**
|
|
128
|
+
```javascript
|
|
129
|
+
{
|
|
130
|
+
videoDetails: {
|
|
131
|
+
videoId: string,
|
|
132
|
+
title: string,
|
|
133
|
+
lengthSeconds: string,
|
|
134
|
+
keywords: string[],
|
|
135
|
+
channelId: string,
|
|
136
|
+
viewCount: string,
|
|
137
|
+
author: string,
|
|
138
|
+
thumbnails: Array<{url: string, width: number, height: number}>,
|
|
139
|
+
// ... other properties
|
|
140
|
+
},
|
|
141
|
+
formats: [
|
|
142
|
+
{
|
|
143
|
+
itag: number,
|
|
144
|
+
url: string,
|
|
145
|
+
mimeType: string,
|
|
146
|
+
bitrate: number,
|
|
147
|
+
width: number,
|
|
148
|
+
height: number,
|
|
149
|
+
qualityLabel: string, // e.g., "720p", "1080p", "1440p", "2160p"
|
|
150
|
+
container: string,
|
|
151
|
+
hasVideo: boolean,
|
|
152
|
+
hasAudio: boolean,
|
|
153
|
+
codecs: string,
|
|
154
|
+
// ... other properties
|
|
155
|
+
}
|
|
156
|
+
],
|
|
157
|
+
// ... other properties
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### ytdl.getBasicInfo(url, [options])
|
|
164
|
+
|
|
165
|
+
Get basic video information (faster than getInfo).
|
|
166
|
+
|
|
167
|
+
**Parameters:**
|
|
168
|
+
- `url` (string): Video URL or Video ID
|
|
169
|
+
- `options` (object): Options (same as getInfo)
|
|
170
|
+
|
|
171
|
+
**Returns:** Promise<Object>
|
|
172
|
+
|
|
173
|
+
**Example:**
|
|
174
|
+
```javascript
|
|
175
|
+
ytdl.getBasicInfo('VIDEO_URL')
|
|
176
|
+
.then(info => {
|
|
177
|
+
console.log('Title:', info.videoDetails.title);
|
|
178
|
+
console.log('Duration:', info.videoDetails.lengthSeconds);
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### ytdl.downloadFromInfo(info, [options])
|
|
185
|
+
|
|
186
|
+
Download video from a previously obtained info object.
|
|
187
|
+
|
|
188
|
+
**Parameters:**
|
|
189
|
+
- `info` (object): Info object from getInfo()
|
|
190
|
+
- `options` (object): Download options
|
|
191
|
+
|
|
192
|
+
**Returns:** ReadableStream
|
|
193
|
+
|
|
194
|
+
**Example:**
|
|
195
|
+
```javascript
|
|
196
|
+
// Get info first
|
|
197
|
+
const info = await ytdl.getInfo('VIDEO_URL');
|
|
198
|
+
|
|
199
|
+
// Choose format
|
|
200
|
+
const format = ytdl.chooseFormat(info.formats, { quality: 'highest' });
|
|
201
|
+
|
|
202
|
+
// Download with selected format
|
|
203
|
+
const stream = ytdl.downloadFromInfo(info, { format });
|
|
204
|
+
|
|
205
|
+
stream.pipe(fs.createWriteStream('video.mp4'));
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### ytdl.chooseFormat(formats, [options])
|
|
211
|
+
|
|
212
|
+
Select format from format array based on quality and filter.
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
- `formats` (Array): Format array from info.formats
|
|
216
|
+
- `options` (object): Selection options
|
|
217
|
+
|
|
218
|
+
**Returns:** Object (format object)
|
|
219
|
+
|
|
220
|
+
**Options:**
|
|
221
|
+
```javascript
|
|
222
|
+
{
|
|
223
|
+
quality?: 'lowest' | 'highest' | 'highestaudio' | 'lowestaudio' |
|
|
224
|
+
'highestvideo' | 'lowestvideo' | number | number[],
|
|
225
|
+
filter?: 'audioandvideo' | 'videoandaudio' | 'video' | 'videoonly' |
|
|
226
|
+
'audio' | 'audioonly' | Function,
|
|
227
|
+
format?: Object // Specific format object
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Example:**
|
|
232
|
+
```javascript
|
|
233
|
+
const info = await ytdl.getInfo('VIDEO_URL');
|
|
234
|
+
|
|
235
|
+
// Select highest quality
|
|
236
|
+
const format1 = ytdl.chooseFormat(info.formats, { quality: 'highest' });
|
|
237
|
+
|
|
238
|
+
// Select best audio
|
|
239
|
+
const format2 = ytdl.chooseFormat(info.formats, { quality: 'highestaudio' });
|
|
240
|
+
|
|
241
|
+
// Select specific format (ITAG 137 = 1080p)
|
|
242
|
+
const format3 = ytdl.chooseFormat(info.formats, { quality: 137 });
|
|
243
|
+
|
|
244
|
+
// Select with custom filter
|
|
245
|
+
const format4 = ytdl.chooseFormat(info.formats, {
|
|
246
|
+
quality: 'highest',
|
|
247
|
+
filter: format => format.container === 'mp4' && format.hasAudio
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### ytdl.filterFormats(formats, [filter])
|
|
254
|
+
|
|
255
|
+
Filter format array based on criteria.
|
|
256
|
+
|
|
257
|
+
**Parameters:**
|
|
258
|
+
- `formats` (Array): Format array
|
|
259
|
+
- `filter` (string | Function): Filter or custom function
|
|
260
|
+
|
|
261
|
+
**Returns:** Array (filtered formats)
|
|
262
|
+
|
|
263
|
+
**Available Filters:**
|
|
264
|
+
- `'audioandvideo'` or `'videoandaudio'` - Formats with video and audio
|
|
265
|
+
- `'video'` - Formats with video
|
|
266
|
+
- `'videoonly'` - Video formats without audio
|
|
267
|
+
- `'audio'` - Formats with audio
|
|
268
|
+
- `'audioonly'` - Audio-only formats
|
|
269
|
+
|
|
270
|
+
**Example:**
|
|
271
|
+
```javascript
|
|
272
|
+
const info = await ytdl.getInfo('VIDEO_URL');
|
|
273
|
+
|
|
274
|
+
// Filter audio only
|
|
275
|
+
const audioFormats = ytdl.filterFormats(info.formats, 'audioonly');
|
|
276
|
+
|
|
277
|
+
// Filter video only
|
|
278
|
+
const videoFormats = ytdl.filterFormats(info.formats, 'videoonly');
|
|
279
|
+
|
|
280
|
+
// Filter with custom function
|
|
281
|
+
const mp4Formats = ytdl.filterFormats(info.formats, format =>
|
|
282
|
+
format.container === 'mp4'
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Filter 1080p or higher
|
|
286
|
+
const hdFormats = ytdl.filterFormats(info.formats, format => {
|
|
287
|
+
const quality = parseInt(format.qualityLabel) || 0;
|
|
288
|
+
return quality >= 1080;
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
### ytdl.getSubtitles(videoId, [options])
|
|
295
|
+
|
|
296
|
+
Get subtitles/captions for a video in XML or SRT format.
|
|
297
|
+
|
|
298
|
+
**Parameters:**
|
|
299
|
+
- `videoId` (string): Video ID or URL
|
|
300
|
+
- `options` (object): Options (optional)
|
|
301
|
+
- `lang?: string` - Language code (default: 'id')
|
|
302
|
+
- `format?: 'xml' | 'srt'` - Output format (default: 'xml')
|
|
303
|
+
- `cookie?: string` - Cookie string for restricted videos
|
|
304
|
+
|
|
305
|
+
**Returns:** Promise<string | null>
|
|
306
|
+
|
|
307
|
+
**Example:**
|
|
308
|
+
```javascript
|
|
309
|
+
const ytdl = require('ytdl-cloud');
|
|
310
|
+
|
|
311
|
+
// Get subtitles in XML format (default)
|
|
312
|
+
ytdl.getSubtitles('dQw4w9WgXcQ', { lang: 'en' })
|
|
313
|
+
.then(subtitle => {
|
|
314
|
+
if (subtitle) {
|
|
315
|
+
console.log('Subtitles:', subtitle);
|
|
316
|
+
fs.writeFileSync('subtitles.xml', subtitle);
|
|
317
|
+
} else {
|
|
318
|
+
console.log('No subtitles available');
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Get subtitles in SRT format
|
|
323
|
+
ytdl.getSubtitles('dQw4w9WgXcQ', {
|
|
324
|
+
lang: 'en',
|
|
325
|
+
format: 'srt'
|
|
326
|
+
})
|
|
327
|
+
.then(subtitle => {
|
|
328
|
+
if (subtitle) {
|
|
329
|
+
fs.writeFileSync('subtitles.srt', subtitle);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// With cookies for restricted videos
|
|
334
|
+
const cookieString = 'VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...';
|
|
335
|
+
ytdl.getSubtitles('VIDEO_ID', {
|
|
336
|
+
lang: 'en',
|
|
337
|
+
format: 'srt',
|
|
338
|
+
cookie: cookieString
|
|
339
|
+
})
|
|
340
|
+
.then(subtitle => {
|
|
341
|
+
if (subtitle) {
|
|
342
|
+
fs.writeFileSync('subtitles.srt', subtitle);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Note:** If no subtitles are found for the specified language, the function returns `null`.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### ytdl.getPlaylistInfo(url, [options])
|
|
352
|
+
|
|
353
|
+
Get playlist information including all videos in the playlist.
|
|
354
|
+
|
|
355
|
+
**Parameters:**
|
|
356
|
+
- `url` (string): Playlist URL
|
|
357
|
+
- `options` (object): Options (optional)
|
|
358
|
+
- `agent?: Object` - Custom agent with cookies
|
|
359
|
+
|
|
360
|
+
**Returns:** Promise<Object>
|
|
361
|
+
|
|
362
|
+
**Example:**
|
|
363
|
+
```javascript
|
|
364
|
+
const ytdl = require('ytdl-cloud');
|
|
365
|
+
|
|
366
|
+
ytdl.getPlaylistInfo('https://www.youtube.com/playlist?list=PLxxxxx')
|
|
367
|
+
.then(playlist => {
|
|
368
|
+
console.log('Playlist ID:', playlist.id);
|
|
369
|
+
console.log('Playlist Title:', playlist.title);
|
|
370
|
+
console.log('Playlist Type:', playlist.type); // 'playlist' or 'radio'
|
|
371
|
+
console.log('Video Count:', playlist.items.length);
|
|
372
|
+
|
|
373
|
+
playlist.items.forEach((video, index) => {
|
|
374
|
+
console.log(`${index + 1}. ${video.title}`);
|
|
375
|
+
console.log(` Video ID: ${video.videoId}`);
|
|
376
|
+
console.log(` Author: ${video.author}`);
|
|
377
|
+
console.log(` Duration: ${video.lengthSeconds} seconds`);
|
|
378
|
+
});
|
|
379
|
+
})
|
|
380
|
+
.catch(err => {
|
|
381
|
+
console.error('Error:', err.message);
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Response Object:**
|
|
386
|
+
```javascript
|
|
387
|
+
{
|
|
388
|
+
id: string, // Playlist ID
|
|
389
|
+
title: string, // Playlist title
|
|
390
|
+
type: 'playlist' | 'radio', // Playlist type
|
|
391
|
+
items: [ // Array of videos
|
|
392
|
+
{
|
|
393
|
+
videoId: string, // Video ID
|
|
394
|
+
title: string, // Video title
|
|
395
|
+
author: string, // Channel name
|
|
396
|
+
lengthSeconds: number // Duration in seconds
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
### ytdl.getPostInfo(url, [options])
|
|
405
|
+
|
|
406
|
+
Get YouTube Community Post information (text, images, polls).
|
|
407
|
+
|
|
408
|
+
**Parameters:**
|
|
409
|
+
- `url` (string): Community Post URL
|
|
410
|
+
- `options` (object): Options (optional)
|
|
411
|
+
- `lang?: string` - Language
|
|
412
|
+
- `headers?: Record<string, string>` - Custom headers
|
|
413
|
+
|
|
414
|
+
**Returns:** Promise<YTPostInfo>
|
|
415
|
+
|
|
416
|
+
**Example (CommonJS):**
|
|
417
|
+
```javascript
|
|
418
|
+
const { getPostInfo } = require('ytdl-cloud');
|
|
419
|
+
|
|
420
|
+
const postUrl = 'https://www.youtube.com/post/Ugkx...';
|
|
421
|
+
getPostInfo(postUrl)
|
|
422
|
+
.then(post => {
|
|
423
|
+
console.log('Author:', post.author);
|
|
424
|
+
console.log('Author URL:', post.authorUrl);
|
|
425
|
+
console.log('Published:', post.published);
|
|
426
|
+
console.log('Content:', post.content);
|
|
427
|
+
console.log('Images:', post.images);
|
|
428
|
+
console.log('Likes:', post.likes);
|
|
429
|
+
|
|
430
|
+
if (post.poll) {
|
|
431
|
+
console.log('Poll Question:', post.poll.question);
|
|
432
|
+
console.log('Poll Options:', post.poll.options);
|
|
433
|
+
console.log('Total Votes:', post.poll.totalVotes);
|
|
434
|
+
}
|
|
435
|
+
})
|
|
436
|
+
.catch(err => {
|
|
437
|
+
console.error('Error:', err.message);
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
**Response Object:**
|
|
444
|
+
```javascript
|
|
445
|
+
{
|
|
446
|
+
author: string, // Author/channel name
|
|
447
|
+
authorUrl: string, // Channel author URL
|
|
448
|
+
published: string, // Publish time (text)
|
|
449
|
+
content: string, // Post text content
|
|
450
|
+
images: string[], // Array of image URLs
|
|
451
|
+
likes: string, // Like count (text)
|
|
452
|
+
poll?: { // Poll data (if available)
|
|
453
|
+
question: string, // Poll question
|
|
454
|
+
options: Array<{ // Array of options
|
|
455
|
+
text: string, // Option text
|
|
456
|
+
voteCount: number, // Vote count
|
|
457
|
+
isCorrect?: boolean // Whether answer is correct (if available)
|
|
458
|
+
}>,
|
|
459
|
+
totalVotes: number, // Total votes
|
|
460
|
+
correctAnswer?: string | null // Correct answer (if available)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### ytdl.validateID(id)
|
|
468
|
+
|
|
469
|
+
Validate if string is a valid Video ID.
|
|
470
|
+
|
|
471
|
+
**Parameters:**
|
|
472
|
+
- `id` (string): Video ID to validate
|
|
473
|
+
|
|
474
|
+
**Returns:** boolean
|
|
475
|
+
|
|
476
|
+
**Example:**
|
|
477
|
+
```javascript
|
|
478
|
+
ytdl.validateID('dQw4w9WgXcQ'); // true
|
|
479
|
+
ytdl.validateID('invalid-id'); // false
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### ytdl.validateURL(url)
|
|
485
|
+
|
|
486
|
+
Validate if string is a valid YouTube URL.
|
|
487
|
+
|
|
488
|
+
**Parameters:**
|
|
489
|
+
- `url` (string): URL to validate
|
|
490
|
+
|
|
491
|
+
**Returns:** boolean
|
|
492
|
+
|
|
493
|
+
**Example:**
|
|
494
|
+
```javascript
|
|
495
|
+
ytdl.validateURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); // true
|
|
496
|
+
ytdl.validateURL('https://youtube.com/shorts/abc123'); // true
|
|
497
|
+
ytdl.validateURL('invalid-url'); // false
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
### ytdl.getURLVideoID(url)
|
|
503
|
+
|
|
504
|
+
Extract Video ID from YouTube URL.
|
|
505
|
+
|
|
506
|
+
**Parameters:**
|
|
507
|
+
- `url` (string): YouTube URL
|
|
508
|
+
|
|
509
|
+
**Returns:** string (Video ID)
|
|
510
|
+
|
|
511
|
+
**Throws:** Error if Video ID not found
|
|
512
|
+
|
|
513
|
+
**Example:**
|
|
514
|
+
```javascript
|
|
515
|
+
ytdl.getURLVideoID('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
516
|
+
// Returns: 'dQw4w9WgXcQ'
|
|
517
|
+
|
|
518
|
+
ytdl.getURLVideoID('https://youtu.be/dQw4w9WgXcQ');
|
|
519
|
+
// Returns: 'dQw4w9WgXcQ'
|
|
520
|
+
|
|
521
|
+
ytdl.getURLVideoID('https://youtube.com/shorts/abc123def45');
|
|
522
|
+
// Returns: 'abc123def45'
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
### ytdl.getVideoID(str)
|
|
528
|
+
|
|
529
|
+
Extract Video ID from string (can be URL or Video ID directly).
|
|
530
|
+
|
|
531
|
+
**Parameters:**
|
|
532
|
+
- `str` (string): URL or Video ID
|
|
533
|
+
|
|
534
|
+
**Returns:** string (Video ID)
|
|
535
|
+
|
|
536
|
+
**Throws:** Error if Video ID not found
|
|
537
|
+
|
|
538
|
+
**Example:**
|
|
539
|
+
```javascript
|
|
540
|
+
ytdl.getVideoID('dQw4w9WgXcQ'); // Returns: 'dQw4w9WgXcQ'
|
|
541
|
+
ytdl.getVideoID('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
|
|
542
|
+
// Returns: 'dQw4w9WgXcQ'
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
### ytdl.createAgent([cookies], [options])
|
|
548
|
+
|
|
549
|
+
Create custom agent with cookies for accessing restricted videos.
|
|
550
|
+
|
|
551
|
+
**Parameters:**
|
|
552
|
+
- `cookies` (Array | string): Array of cookie objects or cookie string
|
|
553
|
+
- `options` (object): Agent options
|
|
554
|
+
|
|
555
|
+
**Returns:** Object (agent object)
|
|
556
|
+
|
|
557
|
+
**Example:**
|
|
558
|
+
```javascript
|
|
559
|
+
// With cookie string
|
|
560
|
+
const cookieString = 'VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...';
|
|
561
|
+
const agent = ytdl.createAgent(cookieString);
|
|
562
|
+
|
|
563
|
+
// With array cookies
|
|
564
|
+
const cookies = [
|
|
565
|
+
{ name: 'VISITOR_INFO1_LIVE', value: 'xxx', domain: '.youtube.com' },
|
|
566
|
+
{ name: 'YSC', value: 'yyy', domain: '.youtube.com' }
|
|
567
|
+
];
|
|
568
|
+
const agent2 = ytdl.createAgent(cookies);
|
|
569
|
+
|
|
570
|
+
// Use agent
|
|
571
|
+
ytdl('VIDEO_URL', { agent })
|
|
572
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
### ytdl.createProxyAgent(options, [cookies])
|
|
578
|
+
|
|
579
|
+
Create agent with proxy support.
|
|
580
|
+
|
|
581
|
+
**Parameters:**
|
|
582
|
+
- `options` (object | string): Proxy options (URI string or object)
|
|
583
|
+
- `cookies` (Array | string): Cookies (optional)
|
|
584
|
+
|
|
585
|
+
**Returns:** Object (agent object)
|
|
586
|
+
|
|
587
|
+
**Example:**
|
|
588
|
+
```javascript
|
|
589
|
+
// With proxy URI
|
|
590
|
+
const agent = ytdl.createProxyAgent('http://proxy.example.com:8080');
|
|
591
|
+
|
|
592
|
+
// With full options
|
|
593
|
+
const agent2 = ytdl.createProxyAgent({
|
|
594
|
+
uri: 'http://proxy.example.com:8080',
|
|
595
|
+
localAddress: '192.168.1.1'
|
|
596
|
+
}, cookieString);
|
|
597
|
+
|
|
598
|
+
// Use agent
|
|
599
|
+
ytdl('VIDEO_URL', { agent })
|
|
600
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Download Options (downloadOptions)
|
|
606
|
+
|
|
607
|
+
Options that can be used with `ytdl()` and `downloadFromInfo()`:
|
|
608
|
+
|
|
609
|
+
```javascript
|
|
610
|
+
{
|
|
611
|
+
// Quality
|
|
612
|
+
quality?: 'lowest' | 'highest' | 'highestaudio' | 'lowestaudio' |
|
|
613
|
+
'highestvideo' | 'lowestvideo' | number | number[],
|
|
614
|
+
|
|
615
|
+
// Filter
|
|
616
|
+
filter?: 'audioandvideo' | 'videoandaudio' | 'video' | 'videoonly' |
|
|
617
|
+
'audio' | 'audioonly' | Function,
|
|
618
|
+
|
|
619
|
+
// Specific format
|
|
620
|
+
format?: Object,
|
|
621
|
+
|
|
622
|
+
// Request options
|
|
623
|
+
requestOptions?: {
|
|
624
|
+
headers?: Record<string, string>, // Custom headers
|
|
625
|
+
agent?: any, // Custom agent
|
|
626
|
+
dispatcher?: any, // Custom dispatcher
|
|
627
|
+
timeout?: number, // Timeout in ms
|
|
628
|
+
maxRetries?: number, // Maximum retries
|
|
629
|
+
backoff?: { // Backoff strategy
|
|
630
|
+
inc: number,
|
|
631
|
+
max: number
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
// Agent
|
|
636
|
+
agent?: Object, // Custom agent with cookies
|
|
637
|
+
|
|
638
|
+
// Range download
|
|
639
|
+
range?: {
|
|
640
|
+
start?: number,
|
|
641
|
+
end?: number
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
// Live stream options
|
|
645
|
+
begin?: string | number | Date, // Start time for live stream
|
|
646
|
+
liveBuffer?: number, // Buffer for live stream
|
|
647
|
+
|
|
648
|
+
// Stream options
|
|
649
|
+
highWaterMark?: number, // Buffer size (default: 512KB)
|
|
650
|
+
|
|
651
|
+
// IPv6 rotation
|
|
652
|
+
IPv6Block?: string // IPv6 block for rotation
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
## Downloading Restricted Videos (Age-Restricted, Region-Locked)
|
|
657
|
+
|
|
658
|
+
For videos with restricted access (age-restricted, region-locked, or member-only), you need to provide YouTube cookies from a logged-in account.
|
|
659
|
+
|
|
660
|
+
### Method 1: Using Cookie Editor Extension (Recommended)
|
|
661
|
+
|
|
662
|
+
1. Install the [Cookie-Editor](https://cookie-editor.com/) extension:
|
|
663
|
+
- [Chrome/Edge](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
|
|
664
|
+
- [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/)
|
|
665
|
+
|
|
666
|
+
2. Open [YouTube](https://www.youtube.com) and log in to your account
|
|
667
|
+
|
|
668
|
+
3. Click the Cookie-Editor extension icon
|
|
669
|
+
|
|
670
|
+
4. Click "Export" → "Header String" (this will copy the cookie string to clipboard)
|
|
671
|
+
|
|
672
|
+
5. Use the cookie string in your code:
|
|
673
|
+
|
|
674
|
+
```javascript
|
|
675
|
+
const ytdl = require('ytdl-cloud');
|
|
676
|
+
const fs = require('fs');
|
|
677
|
+
|
|
678
|
+
const cookieString = 'VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...';
|
|
679
|
+
|
|
680
|
+
ytdl('VIDEO_URL', {
|
|
681
|
+
requestOptions: {
|
|
682
|
+
headers: {
|
|
683
|
+
'Cookie': cookieString
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}).pipe(fs.createWriteStream('video.mp4'));
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Method 2: Using Agent with Cookies
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
const ytdl = require('ytdl-cloud');
|
|
693
|
+
const fs = require('fs');
|
|
694
|
+
|
|
695
|
+
const cookieString = 'VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...';
|
|
696
|
+
const agent = ytdl.createAgent(cookieString);
|
|
697
|
+
|
|
698
|
+
ytdl('VIDEO_URL', { agent })
|
|
699
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Method 3: Save Cookie to File
|
|
703
|
+
|
|
704
|
+
```javascript
|
|
705
|
+
const ytdl = require('ytdl-cloud');
|
|
706
|
+
const fs = require('fs');
|
|
707
|
+
|
|
708
|
+
// Save cookie string to file (don't commit to git!)
|
|
709
|
+
fs.writeFileSync('.youtube-cookies.txt', 'YOUR_COOKIE_STRING');
|
|
710
|
+
|
|
711
|
+
// Read and use
|
|
712
|
+
const cookieString = fs.readFileSync('.youtube-cookies.txt', 'utf8');
|
|
713
|
+
const agent = ytdl.createAgent(cookieString);
|
|
714
|
+
|
|
715
|
+
ytdl('VIDEO_URL', { agent })
|
|
716
|
+
.pipe(fs.createWriteStream('video.mp4'));
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
**⚠️ Security Warning:**
|
|
720
|
+
- Cookie strings contain authentication tokens. Treat them like passwords!
|
|
721
|
+
- Do not commit cookies to git repositories
|
|
722
|
+
- Add `.youtube-cookies.txt` to `.gitignore`
|
|
723
|
+
- Regenerate cookies if exposed (logout and login again)
|
|
724
|
+
|
|
725
|
+
**💡 Cookie Notes:**
|
|
726
|
+
- YouTube cookies typically last 1-2 weeks
|
|
727
|
+
- If downloads start failing with 403 errors, refresh your cookies
|
|
728
|
+
|
|
729
|
+
## Common ITAG Formats
|
|
730
|
+
|
|
731
|
+
### Video + Audio (Progressive)
|
|
732
|
+
- **18**: 360p MP4 (H.264, AAC)
|
|
733
|
+
- **22**: 720p MP4 (H.264, AAC) - Not always available
|
|
734
|
+
|
|
735
|
+
### Video Only (Adaptive)
|
|
736
|
+
- **133**: 240p MP4 (H.264)
|
|
737
|
+
- **134**: 360p MP4 (H.264)
|
|
738
|
+
- **135**: 480p MP4 (H.264)
|
|
739
|
+
- **136**: 720p MP4 (H.264)
|
|
740
|
+
- **137**: 1080p MP4 (H.264)
|
|
741
|
+
- **138**: 4320p MP4 (H.264) - Ultra high resolution
|
|
742
|
+
- **160**: 144p MP4 (H.264)
|
|
743
|
+
- **242**: 240p WebM (VP9)
|
|
744
|
+
- **243**: 360p WebM (VP9)
|
|
745
|
+
- **244**: 480p WebM (VP9)
|
|
746
|
+
- **247**: 720p WebM (VP9)
|
|
747
|
+
- **248**: 1080p WebM (VP9)
|
|
748
|
+
- **264**: 1440p MP4 (H.264)
|
|
749
|
+
- **266**: 2160p (4K) MP4 (H.264)
|
|
750
|
+
- **271**: 1440p WebM (VP9)
|
|
751
|
+
- **272**: 4320p WebM (VP9)
|
|
752
|
+
- **298**: 720p60 MP4 (H.264, 60fps)
|
|
753
|
+
- **299**: 1080p60 MP4 (H.264, 60fps)
|
|
754
|
+
- **302**: 720p60 HFR WebM (VP9, High Frame Rate)
|
|
755
|
+
- **303**: 1080p60 HFR WebM (VP9, High Frame Rate)
|
|
756
|
+
- **308**: 1440p60 HFR WebM (VP9, High Frame Rate)
|
|
757
|
+
- **313**: 2160p (4K) WebM (VP9)
|
|
758
|
+
- **315**: 2160p60 HFR WebM (VP9, High Frame Rate)
|
|
759
|
+
- **330-337**: HDR formats (144p-2160p with HDR and HFR)
|
|
760
|
+
|
|
761
|
+
### Audio Only (Adaptive)
|
|
762
|
+
- **139**: 48kbps M4A (AAC) - Lowest quality
|
|
763
|
+
- **140**: 128kbps M4A (AAC) - Medium quality
|
|
764
|
+
- **141**: 256kbps M4A (AAC) - High quality
|
|
765
|
+
- **249**: 48kbps WebM (Opus)
|
|
766
|
+
- **250**: 64kbps WebM (Opus)
|
|
767
|
+
- **251**: 160kbps WebM (Opus) - Highest quality
|
|
768
|
+
|
|
769
|
+
### Live Stream Formats
|
|
770
|
+
- **91-96**: HLS formats (144p-1080p)
|
|
771
|
+
- **127-128**: Audio-only HLS (AAC)
|
|
772
|
+
|
|
773
|
+
**Note:** Available formats depend on the video. Use `getInfo()` to see all available formats for a specific video.
|
|
774
|
+
|
|
775
|
+
## Events
|
|
776
|
+
|
|
777
|
+
Download stream emits standard Node.js events:
|
|
778
|
+
|
|
779
|
+
```javascript
|
|
780
|
+
const video = ytdl('VIDEO_URL');
|
|
781
|
+
|
|
782
|
+
video.on('info', (info, format) => {
|
|
783
|
+
console.log('Downloading:', info.videoDetails.title);
|
|
784
|
+
console.log('Format:', format.qualityLabel);
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
video.on('progress', (chunkLength, downloaded, total) => {
|
|
788
|
+
const percent = ((downloaded / total) * 100).toFixed(2);
|
|
789
|
+
console.log(`Progress: ${percent}%`);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
video.on('data', (chunk) => {
|
|
793
|
+
console.log('Received', chunk.length, 'bytes');
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
video.on('end', () => {
|
|
797
|
+
console.log('Download complete!');
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
video.on('error', (error) => {
|
|
801
|
+
console.error('Error:', error.message);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
video.pipe(fs.createWriteStream('video.mp4'));
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
## Advanced Usage Examples
|
|
808
|
+
|
|
809
|
+
### Download with Progress Tracking
|
|
810
|
+
|
|
811
|
+
```javascript
|
|
812
|
+
const ytdl = require('ytdl-cloud');
|
|
813
|
+
const fs = require('fs');
|
|
814
|
+
|
|
815
|
+
ytdl.getInfo('VIDEO_URL').then(info => {
|
|
816
|
+
const format = ytdl.chooseFormat(info.formats, { quality: 'highest' });
|
|
817
|
+
const video = ytdl.downloadFromInfo(info, { format });
|
|
818
|
+
|
|
819
|
+
let downloaded = 0;
|
|
820
|
+
const total = parseInt(format.contentLength);
|
|
821
|
+
|
|
822
|
+
video.on('data', chunk => {
|
|
823
|
+
downloaded += chunk.length;
|
|
824
|
+
const percent = ((downloaded / total) * 100).toFixed(2);
|
|
825
|
+
console.log(`Progress: ${percent}% (${(downloaded / 1024 / 1024).toFixed(2)} MB)`);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
video.on('end', () => {
|
|
829
|
+
console.log('Download complete!');
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
video.pipe(fs.createWriteStream('video.mp4'));
|
|
833
|
+
});
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### Download Audio with Best Format
|
|
837
|
+
|
|
838
|
+
```javascript
|
|
839
|
+
const ytdl = require('ytdl-cloud');
|
|
840
|
+
const fs = require('fs');
|
|
841
|
+
|
|
842
|
+
const url = 'VIDEO_URL';
|
|
843
|
+
const audioFormat = ytdl.filterFormats(
|
|
844
|
+
await ytdl.getInfo(url).then(i => i.formats),
|
|
845
|
+
'audioonly'
|
|
846
|
+
).sort((a, b) => (b.audioBitrate || 0) - (a.audioBitrate || 0))[0];
|
|
847
|
+
|
|
848
|
+
ytdl(url, { format: audioFormat })
|
|
849
|
+
.pipe(fs.createWriteStream('audio.m4a'));
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### Custom Filter Function
|
|
853
|
+
|
|
854
|
+
```javascript
|
|
855
|
+
const ytdl = require('ytdl-cloud');
|
|
856
|
+
const fs = require('fs');
|
|
857
|
+
|
|
858
|
+
ytdl('VIDEO_URL', {
|
|
859
|
+
filter: format => {
|
|
860
|
+
return format.container === 'mp4' &&
|
|
861
|
+
format.hasAudio &&
|
|
862
|
+
format.hasVideo &&
|
|
863
|
+
format.qualityLabel === '720p';
|
|
864
|
+
}
|
|
865
|
+
}).pipe(fs.createWriteStream('video-720p.mp4'));
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Download Multiple Formats
|
|
869
|
+
|
|
870
|
+
```javascript
|
|
871
|
+
const ytdl = require('ytdl-cloud');
|
|
872
|
+
const fs = require('fs');
|
|
873
|
+
|
|
874
|
+
const url = 'VIDEO_URL';
|
|
875
|
+
const info = await ytdl.getInfo(url);
|
|
876
|
+
|
|
877
|
+
// Download best video
|
|
878
|
+
const bestVideo = ytdl.chooseFormat(info.formats, { quality: 'highestvideo' });
|
|
879
|
+
ytdl.downloadFromInfo(info, { format: bestVideo })
|
|
880
|
+
.pipe(fs.createWriteStream('video-only.mp4'));
|
|
881
|
+
|
|
882
|
+
// Download best audio
|
|
883
|
+
const bestAudio = ytdl.chooseFormat(info.formats, { quality: 'highestaudio' });
|
|
884
|
+
ytdl.downloadFromInfo(info, { format: bestAudio })
|
|
885
|
+
.pipe(fs.createWriteStream('audio-only.m4a'));
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### Get Subtitles
|
|
889
|
+
|
|
890
|
+
```javascript
|
|
891
|
+
const ytdl = require('ytdl-cloud');
|
|
892
|
+
const fs = require('fs');
|
|
893
|
+
|
|
894
|
+
async function downloadSubtitles() {
|
|
895
|
+
try {
|
|
896
|
+
// Get subtitles in SRT format
|
|
897
|
+
const subtitle = await ytdl.getSubtitles('VIDEO_ID', {
|
|
898
|
+
lang: 'en',
|
|
899
|
+
format: 'srt'
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
if (subtitle) {
|
|
903
|
+
fs.writeFileSync('subtitles.srt', subtitle);
|
|
904
|
+
console.log('Subtitles downloaded successfully!');
|
|
905
|
+
} else {
|
|
906
|
+
console.log('No subtitles available for this video');
|
|
907
|
+
}
|
|
908
|
+
} catch (error) {
|
|
909
|
+
console.error('Error:', error.message);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
downloadSubtitles();
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
### Get Playlist Information
|
|
917
|
+
|
|
918
|
+
```javascript
|
|
919
|
+
const ytdl = require('ytdl-cloud');
|
|
920
|
+
|
|
921
|
+
async function getPlaylist() {
|
|
922
|
+
try {
|
|
923
|
+
const playlist = await ytdl.getPlaylistInfo('PLAYLIST_URL');
|
|
924
|
+
|
|
925
|
+
console.log('=== Playlist Info ===');
|
|
926
|
+
console.log('Title:', playlist.title);
|
|
927
|
+
console.log('ID:', playlist.id);
|
|
928
|
+
console.log('Type:', playlist.type);
|
|
929
|
+
console.log('Total Videos:', playlist.items.length);
|
|
930
|
+
|
|
931
|
+
console.log('\n=== Videos ===');
|
|
932
|
+
playlist.items.forEach((video, index) => {
|
|
933
|
+
console.log(`${index + 1}. ${video.title}`);
|
|
934
|
+
console.log(` ID: ${video.videoId}`);
|
|
935
|
+
console.log(` Channel: ${video.author}`);
|
|
936
|
+
console.log(` Duration: ${video.lengthSeconds}s`);
|
|
937
|
+
});
|
|
938
|
+
} catch (error) {
|
|
939
|
+
console.error('Error:', error.message);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
getPlaylist();
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Get Community Post Info
|
|
947
|
+
|
|
948
|
+
```javascript
|
|
949
|
+
const { getPostInfo } = require('ytdl-cloud');
|
|
950
|
+
|
|
951
|
+
async function getPost() {
|
|
952
|
+
try {
|
|
953
|
+
const post = await getPostInfo('https://www.youtube.com/post/Ugkx...');
|
|
954
|
+
|
|
955
|
+
console.log('=== Community Post Info ===');
|
|
956
|
+
console.log('Author:', post.author);
|
|
957
|
+
console.log('Author URL:', post.authorUrl);
|
|
958
|
+
console.log('Published:', post.published);
|
|
959
|
+
console.log('Content:', post.content);
|
|
960
|
+
console.log('Likes:', post.likes);
|
|
961
|
+
console.log('Images:', post.images.length, 'images');
|
|
962
|
+
|
|
963
|
+
if (post.poll) {
|
|
964
|
+
console.log('\n=== Poll Info ===');
|
|
965
|
+
console.log('Question:', post.poll.question);
|
|
966
|
+
console.log('Total Votes:', post.poll.totalVotes);
|
|
967
|
+
post.poll.options.forEach((option, index) => {
|
|
968
|
+
console.log(`Option ${index + 1}: ${option.text} - ${option.voteCount} votes`);
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error('Error:', error.message);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
getPost();
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
## Troubleshooting
|
|
980
|
+
|
|
981
|
+
### Error 403 Forbidden
|
|
982
|
+
|
|
983
|
+
**Problem:** Download fails with HTTP 403 error
|
|
984
|
+
|
|
985
|
+
**Solution:**
|
|
986
|
+
1. Add cookies from a logged-in YouTube account (see "Downloading Restricted Videos")
|
|
987
|
+
2. Check if video is region-locked or requires login
|
|
988
|
+
3. Refresh cookies if they have expired
|
|
989
|
+
|
|
990
|
+
### Format Not Found
|
|
991
|
+
|
|
992
|
+
**Problem:** Requested format/quality is not available
|
|
993
|
+
|
|
994
|
+
**Solution:** Check available formats first:
|
|
995
|
+
```javascript
|
|
996
|
+
ytdl.getInfo('VIDEO_URL').then(info => {
|
|
997
|
+
console.log('Available formats:');
|
|
998
|
+
info.formats.forEach(format => {
|
|
999
|
+
console.log(`${format.itag}: ${format.qualityLabel} (${format.container})`);
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
### Video Unavailable
|
|
1005
|
+
|
|
1006
|
+
**Problem:** Error "Video is unavailable"
|
|
1007
|
+
|
|
1008
|
+
**Possible Causes:**
|
|
1009
|
+
- Video is private or deleted
|
|
1010
|
+
- Video is region-locked (try with cookies from appropriate region)
|
|
1011
|
+
- Live stream has ended
|
|
1012
|
+
- Video is member-only (requires membership cookies)
|
|
1013
|
+
|
|
1014
|
+
### Signature Decoding Error
|
|
1015
|
+
|
|
1016
|
+
**Problem:** Error when decoding signature
|
|
1017
|
+
|
|
1018
|
+
**Solution:**
|
|
1019
|
+
- Library automatically handles signature decoding
|
|
1020
|
+
- If error occurs, try with valid YouTube cookies
|
|
1021
|
+
- Make sure you're using the latest version of the library
|
|
1022
|
+
|
|
1023
|
+
## Differences from ytdl-core
|
|
1024
|
+
|
|
1025
|
+
This library is designed as a drop-in replacement for `ytdl-core` with the following improvements:
|
|
1026
|
+
|
|
1027
|
+
✅ **API Compatible** - Same API as ytdl-core, just change the require statement
|
|
1028
|
+
✅ **More Reliable** - Uses Android InnerTube client that works consistently
|
|
1029
|
+
✅ **Simpler** - No complex multi-client fallback logic
|
|
1030
|
+
✅ **Cleaner** - No browser automation or anti-bot detection needed
|
|
1031
|
+
✅ **Community Posts** - Support for YouTube Community Post extraction
|
|
1032
|
+
✅ **Subtitle Support** - Built-in subtitle/caption extraction
|
|
1033
|
+
✅ **Playlist Support** - Extract playlist information
|
|
1034
|
+
|
|
1035
|
+
### Migration from ytdl-core
|
|
1036
|
+
|
|
1037
|
+
```javascript
|
|
1038
|
+
// Before
|
|
1039
|
+
const ytdl = require('ytdl-core');
|
|
1040
|
+
|
|
1041
|
+
// After
|
|
1042
|
+
const ytdl = require('ytdl-cloud');
|
|
1043
|
+
|
|
1044
|
+
// All existing code continues to work!
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
## Requirements
|
|
1048
|
+
|
|
1049
|
+
- Node.js 18 or higher
|
|
1050
|
+
- npm or yarn
|
|
1051
|
+
|
|
1052
|
+
## License
|
|
1053
|
+
|
|
1054
|
+
MIT
|
|
1055
|
+
|
|
1056
|
+
## Credits
|
|
1057
|
+
|
|
1058
|
+
**Created by:** [AlfiDev](https://github.com/cloudkuimages)
|
|
1059
|
+
**Repository:** [github.com/cloudkuimages/ytdl-cloud](https://github.com/cloudkuimages/ytdl-cloud)
|
|
1060
|
+
|
|
1061
|
+
---
|
|
1062
|
+
|
|
1063
|
+
**Note:** This library uses Android InnerTube client for format extraction. Please use responsibly and in accordance with YouTube's Terms of Service.
|