cloud-ytdl 1.0.1-rc → 1.0.5-next

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 CHANGED
@@ -1,1063 +1,789 @@
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.
1
+ # cloud-ytdl next version
2
+
3
+ **cloud-ytdl** is a modern, production-ready YouTube downloader and data scraper built on top of official YouTube InnerTube API clients (primarily the Android client). Unlike many other libraries, it provides robust support for downloading and scraping video, live streams, community posts (text, images, polls), playlists, and subtitles/captions, while also handling signature/cipher decryption internally.
4
+
5
+
6
+
7
+ **Author:** [AlfiDev](https://github.com/cloudkuimages)
8
+ **Repository:** [github.com/cloudkuimages/cloud-ytdl](https://github.com/cloudkuimages/cloud-ytdl)
9
+ **Fork:**[YT-DLP](https://github.com/yt-dlp/yt-dlp)
10
+
11
+ ---
12
+
13
+ ## 🌟 Key Features
14
+
15
+ - **Official InnerTube Client** Uses real YouTube Android API for reliable data extraction.
16
+ - **Signature Decoding** Handles encrypted signature/cipher transparently.
17
+ - **Restricted Videos** Download age-restricted/region-locked/member-only videos using cookies.
18
+ - **Community Post Extraction** Scrape text, images, and polls from YouTube community posts.
19
+ - **Subtitle/Caption Download** – Download subtitles as XML or SRT.
20
+ - **Playlist Extraction** – Get all videos in playlists/radios.
21
+ - **Multi-Format Support** – MP4, WebM, M4A, DASH, HLS, and more.
22
+ - **Stream Support** – HLS and DASH streaming.
23
+ - **Cookie/Proxy Agent Support** – Use cookies from browser or proxy for restricted/private videos.
24
+ - **Modern Node.js** – Requires Node 18+ (uses undici and modern async/await patterns).
25
+ - **Event-based Streaming** – Download with progress, error, and info events.
26
+
27
+ ---
28
+
29
+ ## 🚀 Quick Start
30
+
31
+ ### Installation
32
+
33
+ ```bash
34
+ npm install cloud-ytdl
35
+ ```
36
+
37
+ ### Basic Video Download
38
+
39
+ ```js
40
+ const ytdl = require('cloud-ytdl');
41
+ const fs = require('fs');
42
+
43
+ ytdl('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
44
+ .pipe(fs.createWriteStream('video.mp4'));
45
+ ```
46
+
47
+ ### Download with Specific Quality or Filters
48
+
49
+ ```js
50
+ // Highest quality
51
+ ytdl('VIDEO_URL', { quality: 'highest' })
52
+ .pipe(fs.createWriteStream('video.mp4'));
53
+
54
+ // 1080p only (ITAG 137)
55
+ ytdl('VIDEO_URL', { quality: 137 })
56
+ .pipe(fs.createWriteStream('video-1080p.mp4'));
57
+
58
+ // Audio only
59
+ ytdl('VIDEO_URL', { filter: 'audioonly' })
60
+ .pipe(fs.createWriteStream('audio.m4a'));
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 📚 API Documentation
66
+
67
+ ### Core Functionality
68
+
69
+ Below are the main functions and classes exposed by the library, with detailed descriptions and usage.
70
+
71
+ ---
72
+
73
+ ### ytdl(url, [options])
74
+
75
+ Download a YouTube video as a readable stream. Emits Node.js stream events (`info`, `progress`, `error`, etc.).
76
+
77
+ - **Parameters:**
78
+ - `url` (string): YouTube video URL or ID.
79
+ - `options` (object, optional): Download options.
80
+
81
+ - **Returns:** `ReadableStream`
82
+
83
+ - **Events:**
84
+ - `info` – Video info and selected format.
85
+ - `progress` – Download progress (chunk size, downloaded, total).
86
+ - `data` Raw data chunk.
87
+ - `end` – Download finished.
88
+ - `error` – Error occurred.
89
+
90
+ #### Example
91
+
92
+ ```js
93
+ const ytdl = require('cloud-ytdl');
94
+ const fs = require('fs');
95
+
96
+ const video = ytdl('https://www.youtube.com/watch?v=dQw4w9WgXcQ', { quality: 'highest' });
97
+ video.on('progress', (chunkLength, downloaded) => {
98
+ console.log(`Downloaded: ${downloaded} bytes`);
99
+ });
100
+ video.pipe(fs.createWriteStream('video.mp4'));
101
+ ```
102
+
103
+ ---
104
+
105
+ ### ytdl.getInfo(url, [options])
106
+
107
+ Fetch full metadata and format info for a video.
108
+
109
+ - **Parameters:**
110
+ - `url` (string): Video URL or ID.
111
+ - `options` (object, optional): { lang, requestOptions, agent, ... }
112
+
113
+ - **Returns:** `Promise<Object>` (video info structure)
114
+
115
+ #### Example
116
+
117
+ ```js
118
+ const info = await ytdl.getInfo('https://youtu.be/dQw4w9WgXcQ');
119
+ console.log(info.videoDetails.title);
120
+ console.log(info.formats.map(f => f.qualityLabel));
121
+ ```
122
+
123
+ #### Response Structure
124
+
125
+ ```js
126
+ {
127
+ videoDetails: {/* ... */},
128
+ player_response: {/* ... */},
129
+ formats: [/* ... */],
130
+ html5player: "url"
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ### ytdl.getBasicInfo(url, [options])
137
+
138
+ Fetch lighter, faster video info (title, channel, duration) without format details.
139
+
140
+ - **Returns:** `Promise<Object>`
141
+
142
+ #### Example
143
+
144
+ ```js
145
+ const info = await ytdl.getBasicInfo('dQw4w9WgXcQ');
146
+ console.log(info.videoDetails.title, info.videoDetails.lengthSeconds);
147
+ ```
148
+
149
+ ---
150
+
151
+ ### ytdl.downloadFromInfo(info, [options])
152
+
153
+ Stream download directly from a previously obtained `info` object.
154
+
155
+ - **Parameters:**
156
+ - `info` (object): Result from `ytdl.getInfo()`
157
+ - `options` (object): As in main download.
158
+
159
+ - **Returns:** `ReadableStream`
160
+
161
+ #### Example
162
+
163
+ ```js
164
+ const info = await ytdl.getInfo('VIDEO_URL');
165
+ const format = ytdl.chooseFormat(info.formats, { quality: 'highest' });
166
+ ytdl.downloadFromInfo(info, { format }).pipe(fs.createWriteStream('video.mp4'));
167
+ ```
168
+
169
+ ---
170
+
171
+ ### ytdl.chooseFormat(formats, [options])
172
+
173
+ Select a format from a list based on quality or filter.
174
+
175
+ - **Parameters:**
176
+ - `formats` (array): Formats from info/formats.
177
+ - `options` (object): { quality, filter, format }
178
+
179
+ - **Returns:** `Object` (format)
180
+
181
+ #### Example
182
+
183
+ ```js
184
+ const info = await ytdl.getInfo('VIDEO_URL');
185
+ const best = ytdl.chooseFormat(info.formats, { quality: 'highest' });
186
+ ```
187
+
188
+ ---
189
+
190
+ ### ytdl.filterFormats(formats, [filter])
191
+
192
+ Filter formats by type (audio/video/progressive) or custom function.
193
+
194
+ - **Parameters:**
195
+ - `formats` (array): Array of format objects.
196
+ - `filter` (string | function): Predefined string or custom function.
197
+
198
+ - **Returns:** `Array`
199
+
200
+ #### Example
201
+
202
+ ```js
203
+ const info = await ytdl.getInfo('VIDEO_URL');
204
+ const audioFormats = ytdl.filterFormats(info.formats, 'audioonly');
205
+ ```
206
+
207
+ ---
208
+
209
+ ### ytdl.getSubtitles(videoId, [options])
210
+
211
+ Get subtitles as XML or converted SRT.
212
+
213
+ - **Parameters:**
214
+ - `videoId` (string): Video ID or URL.
215
+ - `options` (object): { lang, format, cookie }
216
+
217
+ - **Returns:** `Promise<string|null>`
218
+
219
+ #### Example
220
+
221
+ ```js
222
+ const subs = await ytdl.getSubtitles('dQw4w9WgXcQ', { lang: 'en', format: 'srt' });
223
+ if (subs) fs.writeFileSync('subs.srt', subs);
224
+ ```
225
+
226
+ ---
227
+
228
+ ### ytdl.getPlaylistInfo(url, [options])
229
+
230
+ Get information and video list of a playlist.
231
+
232
+ - **Parameters:**
233
+ - `url` (string): Playlist URL.
234
+ - `options` (object): { agent }
235
+
236
+ - **Returns:** `Promise<Object>`
237
+
238
+ #### Example
239
+
240
+ ```js
241
+ const playlist = await ytdl.getPlaylistInfo('https://www.youtube.com/playlist?list=PL...');
242
+ console.log(playlist.title, playlist.items.length);
243
+ ```
244
+
245
+ ---
246
+
247
+ ### ytdl.getPostInfo(url, [options])
248
+
249
+ Extract data from YouTube Community Posts.
250
+
251
+ - **Parameters:**
252
+ - `url` (string): Post URL.
253
+ - `options` (object): { lang, headers }
254
+
255
+ - **Returns:** `Promise<Object>`
256
+
257
+ #### Example
258
+
259
+ ```js
260
+ const post = await ytdl.getPostInfo('https://www.youtube.com/post/Ugkx...');
261
+ console.log(post.content, post.images, post.poll);
262
+ ```
263
+
264
+ ---
265
+
266
+ ### ytdl.validateID(id)
267
+
268
+ Validate if a string is a valid YouTube video ID.
269
+
270
+ - **Returns:** `boolean`
271
+
272
+ #### Example
273
+
274
+ ```js
275
+ ytdl.validateID('dQw4w9WgXcQ'); // true
276
+ ```
277
+
278
+ ---
279
+
280
+ ### ytdl.validateURL(url)
281
+
282
+ Validate if a string is a valid YouTube URL.
283
+
284
+ - **Returns:** `boolean`
285
+
286
+ #### Example
287
+
288
+ ```js
289
+ ytdl.validateURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); // true
290
+ ```
291
+
292
+ ---
293
+
294
+ ### ytdl.getURLVideoID(url)
295
+
296
+ Extract the video ID from a YouTube URL.
297
+
298
+ - **Returns:** `string` (ID)
299
+
300
+ - **Throws:** If the ID cannot be found or is invalid.
301
+
302
+ #### Example
303
+
304
+ ```js
305
+ ytdl.getURLVideoID('https://youtu.be/dQw4w9WgXcQ'); // 'dQw4w9WgXcQ'
306
+ ```
307
+
308
+ ---
309
+
310
+ ### ytdl.getVideoID(str)
311
+
312
+ Extract or validate a string as a video ID.
313
+
314
+ - **Returns:** `string` (ID)
315
+
316
+ - **Throws:** If invalid.
317
+
318
+ #### Example
319
+
320
+ ```js
321
+ ytdl.getVideoID('dQw4w9WgXcQ'); // 'dQw4w9WgXcQ'
322
+ ```
323
+
324
+ ---
325
+
326
+ ### ytdl.createAgent([cookies], [options])
327
+
328
+ Create an HTTP agent with attached cookies for restricted videos.
329
+
330
+ - **Parameters:**
331
+ - `cookies` (array | string): Cookie array or cookie string.
332
+ - `options` (object): Agent options.
333
+
334
+ - **Returns:** `object` (agent)
335
+
336
+ #### Example
337
+
338
+ ```js
339
+ const agent = ytdl.createAgent('VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...');
340
+ ytdl('VIDEO_URL', { agent }).pipe(fs.createWriteStream('video.mp4'));
341
+ ```
342
+
343
+ ---
344
+
345
+ ### ytdl.createProxyAgent(options, [cookies])
346
+
347
+ Create an HTTP agent with proxy and cookies.
348
+
349
+ - **Parameters:**
350
+ - `options` (object | string): Proxy URI or options.
351
+ - `cookies` (array | string): Optional cookies.
352
+
353
+ - **Returns:** `object` (agent)
354
+
355
+ #### Example
356
+
357
+ ```js
358
+ const agent = ytdl.createProxyAgent('http://proxy.example.com:8080');
359
+ ytdl('VIDEO_URL', { agent }).pipe(fs.createWriteStream('video.mp4'));
360
+ ```
361
+
362
+ ---
363
+
364
+ ### ytdl.pickAudioTrack(info, options)
365
+
366
+ Select the best audio track for a given language and info object. Useful for multi-audio videos.
367
+
368
+ - **Parameters:**
369
+ - `info` (object): Info object from `getInfo`.
370
+ - `options` (object): { lang, cookies }
371
+
372
+ - **Returns:** `object` (track info or null)
373
+
374
+ #### Example
375
+
376
+ ```js
377
+ const info = await ytdl.getInfo('VIDEO_URL');
378
+ const track = ytdl.pickAudioTrack(info, { lang: 'en', cookies: '...' });
379
+ console.log(track.langName, track.bestFormat);
380
+ ```
381
+
382
+ ---
383
+
384
+ ### ytdl.listAudioTrack(info, options)
385
+
386
+ List all available audio tracks in a video.
387
+
388
+ - **Parameters:**
389
+ - `info` (object): Info object.
390
+ - `options` (object): { silent }
391
+
392
+ - **Returns:** `array` (list of tracks)
393
+
394
+ #### Example
395
+
396
+ ```js
397
+ const info = await ytdl.getInfo('VIDEO_URL');
398
+ ytdl.listAudioTrack(info);
399
+ ```
400
+
401
+ ---
402
+
403
+ ## 🎯 Download Options
404
+
405
+ All download and info functions accept a rich options object:
406
+
407
+ ```js
408
+ {
409
+ quality: 'lowest' | 'highest' | 'highestaudio' | ... | number,
410
+ filter: 'audioandvideo' | 'audioonly' | function,
411
+ format: {}, // specific format object
412
+ requestOptions: {
413
+ headers: { Cookie: '...' },
414
+ agent: {},
415
+ dispatcher: {},
416
+ timeout: 30000,
417
+ maxRetries: 3,
418
+ backoff: { inc: 500, max: 5000 }
419
+ },
420
+ agent: {}, // from createAgent/createProxyAgent
421
+ range: { start: 0, end: 1000000 },
422
+ begin: 0 | '00:01:00',
423
+ liveBuffer: 20000,
424
+ highWaterMark: 512 * 1024,
425
+ IPv6Block: '2001:db8::/64'
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ## 🧩 Example: Using Low-Level Functions
432
+
433
+ You can use building blocks directly for advanced use-cases.
434
+
435
+ ### Example: Extract Audio (Best M4A)
436
+
437
+ ```js
438
+ const info = await ytdl.getInfo('VIDEO_URL');
439
+ const bestAudio = ytdl.filterFormats(info.formats, 'audioonly')
440
+ .sort((a, b) => (b.audioBitrate || 0) - (a.audioBitrate || 0))[0];
441
+ ytdl('VIDEO_URL', { format: bestAudio }).pipe(fs.createWriteStream('audio.m4a'));
442
+ ```
443
+
444
+ ### Example: Download Subtitles as SRT
445
+
446
+ ```js
447
+ const subtitles = await ytdl.getSubtitles('VIDEO_ID', { lang: 'en', format: 'srt' });
448
+ fs.writeFileSync('subtitles.srt', subtitles);
449
+ ```
450
+
451
+ ---
452
+
453
+ ## 🔑 Downloading Restricted Videos
454
+
455
+ YouTube restricts some videos (age, region, membership). To bypass:
456
+
457
+ ### 1. Export Cookies from Browser
458
+
459
+ - Use [Cookie-Editor](https://cookie-editor.com/) to export YouTube cookies.
460
+ - Paste them into your code:
461
+
462
+ ```js
463
+ const agent = ytdl.createAgent('VISITOR_INFO1_LIVE=xxx; YSC=yyy; ...');
464
+ ytdl('VIDEO_URL', { agent }).pipe(fs.createWriteStream('video.mp4'));
465
+ ```
466
+
467
+ ### 2. From Cookie File
468
+
469
+ ```js
470
+ const cookieString = fs.readFileSync('.youtube-cookies.txt', 'utf8');
471
+ const agent = ytdl.createAgent(cookieString);
472
+ ytdl('VIDEO_URL', { agent }).pipe(fs.createWriteStream('video.mp4'));
473
+ ```
474
+
475
+ ---
476
+
477
+ ```card
478
+ {
479
+ "title": "Security Warning",
480
+ "content": "Treat cookie strings as passwords. Never share or commit them to version control."
481
+ }
482
+ ```
483
+
484
+ ---
485
+
486
+ ## 🔬 ITAG Reference Table
487
+
488
+ ITAGs define available formats per video. Use `getInfo()` to list all for a specific video.
489
+
490
+ | ITAG | Container | Quality | Video | Audio | Description |
491
+ |------|-----------|-----------|-------|-------|---------------------|
492
+ | 18 | mp4 | 360p | ✔ | ✔ | Progressive, H.264 |
493
+ | 22 | mp4 | 720p | ✔ | ✔ | Progressive, H.264 |
494
+ | 137 | mp4 | 1080p | ✔ | - | Video-only, H.264 |
495
+ | 140 | m4a | audio | - | ✔ | Audio-only, AAC |
496
+ | 251 | webm | audio | - | ✔ | Audio-only, Opus |
497
+ | ... | ... | ... | ... | ... | ... |
498
+
499
+ Full map: see [format.js](./format.js).
500
+
501
+ ---
502
+
503
+ ## ⚙️ Advanced Examples
504
+
505
+ ### Download Video and Audio Separately
506
+
507
+ ```js
508
+ const info = await ytdl.getInfo('VIDEO_URL');
509
+ const bestVideo = ytdl.chooseFormat(info.formats, { quality: 'highestvideo' });
510
+ const bestAudio = ytdl.chooseFormat(info.formats, { quality: 'highestaudio' });
511
+
512
+ ytdl.downloadFromInfo(info, { format: bestVideo }).pipe(fs.createWriteStream('video-only.mp4'));
513
+ ytdl.downloadFromInfo(info, { format: bestAudio }).pipe(fs.createWriteStream('audio-only.m4a'));
514
+ ```
515
+
516
+ ### Download with Progress
517
+
518
+ ```js
519
+ const video = ytdl('VIDEO_URL');
520
+ let downloaded = 0;
521
+ video.on('progress', (chunk, total) => {
522
+ downloaded += chunk;
523
+ console.log(`Progress: ${((downloaded / total) * 100).toFixed(2)}%`);
524
+ });
525
+ video.pipe(fs.createWriteStream('video.mp4'));
526
+ ```
527
+
528
+ ### Get Playlist Info
529
+
530
+ ```js
531
+ const playlist = await ytdl.getPlaylistInfo('PLAYLIST_URL');
532
+ console.log(`Playlist: ${playlist.title} (${playlist.items.length} videos)`);
533
+ ```
534
+
535
+ ### Get Community Post Info
536
+
537
+ ```js
538
+ const post = await ytdl.getPostInfo('https://www.youtube.com/post/Ugkx...');
539
+ console.log(post.content, post.images, post.poll);
540
+ ```
541
+
542
+ ---
543
+
544
+ ## 🛠️ Troubleshooting
545
+
546
+ ### 403 Forbidden
547
+
548
+ - Use valid cookies for age/region/member videos.
549
+
550
+ ### Format Not Found
551
+
552
+ - Check available formats with `getInfo()`.
553
+
554
+ ### Signature Decoding Error
555
+
556
+ - The library handles signature decryption. If you see errors, update cookies.
557
+
558
+ ---
559
+
560
+ ## 🏗️ Library Structure and Internal Modules
561
+
562
+ Here's an overview of the project's main files and their responsibilities:
563
+
564
+ ```mermaid
565
+ flowchart TD
566
+ A[index.js - Main Entry] -->|Calls| B[info.js - Video Info]
567
+ A --> C[format-utils.js - Format Selection]
568
+ A --> D[subtitle.js - Subtitles]
569
+ A --> E[playlist.js - Playlists]
570
+ A --> F[post.js - Community Posts]
571
+ A --> G[track.js - Audio Tracks]
572
+ B --> H[innertube.js - InnerTube API]
573
+ H --> I[sig-decoder.js - Signature Decoder]
574
+ A --> J[agents.js - HTTP/Cookie Agents]
575
+ A --> K[url-utils.js - URL/ID Parsing]
576
+ A --> L[load.js - Cookie Loader]
577
+ D --> M[xmlToSrt.js - XML to SRT Converter]
578
+ B --> N[cache.js - In-memory Cache]
579
+ C --> O[format.js - ITAG Format Map]
580
+ A --> P[utils.js - Shared Functions]
581
+ ```
582
+
583
+ ---
584
+
585
+ ## 📦 Classes and Utilities
586
+
587
+ ### Cache (cache.js)
588
+
589
+ In-memory cache with time-based expiry.
590
+
591
+ - **Methods:**
592
+ - `get(key)`
593
+ - `set(key, value)`
594
+ - `getOrSet(key, fn)`
595
+ - `delete(key)`
596
+ - `clear()`
597
+
598
+ **Usage:**
599
+
600
+ ```js
601
+ const Cache = require('./cache');
602
+ const c = new Cache(60_000); // 1 min expiry
603
+ ```
604
+
605
+ ---
606
+
607
+ ### SignatureDecoder (sig-decoder.js)
608
+
609
+ Handles extraction and execution of YouTube's signature/cipher algorithms.
610
+
611
+ - **Methods:**
612
+ - `getCachedPlayerScript()`
613
+ - `getCipherScript(url)`
614
+ - `applyCipher(sig, cipher)`
615
+ - `transformNParameter(n, cipher)`
616
+ - `resolveFormatUrl(format, playerScript)`
617
+ - `clearCache()`
618
+
619
+ **Exceptions:**
620
+ Throws when cipher extraction or signature decryption fails.
621
+
622
+ ---
623
+
624
+ ### Agent Utilities (agents.js)
625
+
626
+ - `createAgent(cookies, options)` – Returns HTTP agent with cookies.
627
+ - `createProxyAgent(options, cookies)` Returns agent with proxy and cookies.
628
+ - `addCookies(jar, cookies)` – Adds cookies to jar.
629
+ - `addCookiesFromString(jar, string)` – Parses and adds cookies.
630
+
631
+ ---
632
+
633
+ ### Utility Functions (utils.js)
634
+
635
+ - String and JSON extraction (`between`, `extractYouTubeJSON`)
636
+ - Error handling (`playError`, `UnrecoverableError`)
637
+ - Network helpers (`request`, `applyDefaultAgent`, etc.)
638
+ - IPv6 randomization for proxy rotation
639
+ - Misc: parse time, parse abbreviated numbers, etc.
640
+
641
+ ---
642
+
643
+ ### Format Utilities (format-utils.js)
644
+
645
+ - `chooseFormat(formats, options)` Selects format by quality/filter.
646
+ - `filterFormats(formats, filter)` Returns filtered list.
647
+ - `addFormatMeta(format)` – Normalizes format structure.
648
+
649
+ ---
650
+
651
+ ### Audio Track Utilities (track.js)
652
+
653
+ - `pickAudioTrack(info, options)` – Choose best track for a language.
654
+ - `listAudioTrack(info, options)` – List all available tracks.
655
+
656
+ ---
657
+
658
+ ### URL Utilities (url-utils.js)
659
+
660
+ - `validateID(id)` Checks if string is a valid video ID.
661
+ - `validateURL(url)` – Checks if string is a valid YouTube URL.
662
+ - `getURLVideoID(url)` Extracts ID from URL.
663
+ - `getVideoID(str)` – Gets ID from string or URL.
664
+
665
+ ---
666
+
667
+ ### Cookie Loading (load.js)
668
+
669
+ - `loadCookieHeader(input)` – Reads cookies from string, file, or browser's JSON export.
670
+
671
+ ---
672
+
673
+ ### Subtitles (subtitle.js, xmlToSrt.js)
674
+
675
+ - `getSubtitles(videoId, options)` – Fetches subtitles.
676
+ - `xmlToSrt(xml)` Converts XML to SRT.
677
+
678
+ ---
679
+
680
+ ### Playlists (playlist.js)
681
+
682
+ - `getPlaylistInfo(url, options)` – Fetches playlist data and items.
683
+
684
+ ---
685
+
686
+ ### Community Posts (post.js)
687
+
688
+ - `getPostInfo(url)` – Fetches and parses post content, images, polls.
689
+
690
+ ---
691
+
692
+ ## 📝 API Endpoints (Extracted from Post/Playlist Fetching)
693
+
694
+ ### Get Playlist Info (GET)
695
+
696
+ #### /playlist (YouTube playlist page)
697
+
698
+ ```api
699
+ {
700
+ "title": "Get Playlist Info",
701
+ "description": "Extracts playlist meta and all video items from a YouTube playlist page",
702
+ "method": "GET",
703
+ "baseUrl": "https://www.youtube.com",
704
+ "endpoint": "/playlist?list={playlistId}",
705
+ "headers": [
706
+ { "key": "user-agent", "value": "Mozilla/5.0 ...", "required": true }
707
+ ],
708
+ "queryParams": [
709
+ { "key": "list", "value": "Playlist ID", "required": true }
710
+ ],
711
+ "pathParams": [],
712
+ "bodyType": "none",
713
+ "requestBody": "",
714
+ "responses": {
715
+ "200": {
716
+ "description": "Playlist and video items",
717
+ "body": "{ \"id\": \"...\", \"title\": \"...\", \"items\": [ ... ] }"
718
+ },
719
+ "404": {
720
+ "description": "Not found or private",
721
+ "body": "{ \"error\": \"ytInitialData not found\" }"
722
+ }
723
+ }
724
+ }
725
+ ```
726
+
727
+ ---
728
+
729
+ ### Get Community Post Info (GET)
730
+
731
+ #### /post (YouTube community post page)
732
+
733
+ ```api
734
+ {
735
+ "title": "Get Community Post Info",
736
+ "description": "Fetches a YouTube Community Post's text, images, likes, poll, and author info.",
737
+ "method": "GET",
738
+ "baseUrl": "https://www.youtube.com",
739
+ "endpoint": "/post/{postId}",
740
+ "headers": [
741
+ { "key": "user-agent", "value": "Mozilla/5.0 ...", "required": true }
742
+ ],
743
+ "queryParams": [],
744
+ "pathParams": [
745
+ { "key": "postId", "value": "Post ID", "required": true }
746
+ ],
747
+ "bodyType": "none",
748
+ "requestBody": "",
749
+ "responses": {
750
+ "200": {
751
+ "description": "Community post data",
752
+ "body": "{ \"author\": \"...\", \"content\": \"...\", \"images\": [ ... ] }"
753
+ },
754
+ "404": {
755
+ "description": "Not found",
756
+ "body": "{ \"error\": \"Community post not found\" }"
757
+ }
758
+ }
759
+ }
760
+ ```
761
+
762
+ ---
763
+
764
+ ## 🛡️ Requirements
765
+
766
+ - Node.js 18+
767
+ - npm or yarn
768
+
769
+ ---
770
+
771
+ ## 📜 License
772
+
773
+ MIT
774
+
775
+ ---
776
+
777
+ ## 🙏 Credits
778
+
779
+ Created by [AlfiDev](https://github.com/cloudkuimages)
780
+ See [github.com/cloudkuimages/cloud-ytdl](https://github.com/cloudkuimages/cloud-ytdl) for source, issues, and updates.
781
+
782
+ ---
783
+
784
+ ```card
785
+ {
786
+ "title": "Responsible Use",
787
+ "content": "Please use this library in accordance with YouTube's Terms of Service. Automated large-scale downloads are discouraged."
788
+ }
789
+ ```