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 +789 -1063
- package/lib/index.js +5 -2
- package/lib/playlist.js +39 -24
- package/lib/post.js +44 -52
- package/lib/track.js +149 -0
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,1063 +1,789 @@
|
|
|
1
|
-
# ytdl
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
- `
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
**
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
### ytdl.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
**
|
|
299
|
-
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
###
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
]
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
-
|
|
767
|
-
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
+
```
|