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