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/lib/agents.js ADDED
@@ -0,0 +1,114 @@
1
+ 'use strict'
2
+
3
+ const { ProxyAgent } = require('undici')
4
+ const { HttpsProxyAgent } = require('https-proxy-agent')
5
+ const { Cookie, CookieJar, canonicalDomain } = require('tough-cookie')
6
+ const { CookieAgent } = require('http-cookie-agent/undici')
7
+
8
+ const mapSameSite = s =>
9
+ s === 'strict' || s === 'lax' || s === 'none' ? s : 'none'
10
+
11
+ const toCookie = c => {
12
+ if (c instanceof Cookie) return c
13
+ if (!c || !c.name) return null
14
+
15
+ return new Cookie({
16
+ key: c.name,
17
+ value: c.value ?? '',
18
+ expires:
19
+ typeof c.expirationDate === 'number'
20
+ ? new Date(c.expirationDate * 1000)
21
+ : 'Infinity',
22
+ domain: canonicalDomain(c.domain || 'youtube.com'),
23
+ path: c.path || '/',
24
+ secure: !!c.secure,
25
+ httpOnly: !!c.httpOnly,
26
+ sameSite: mapSameSite(c.sameSite),
27
+ hostOnly: !!c.hostOnly,
28
+ })
29
+ }
30
+
31
+ const ensureSOCS = cookies => {
32
+ if (!Array.isArray(cookies)) return
33
+
34
+ if (!cookies.some(c => c?.name === 'SOCS')) {
35
+ cookies.push({
36
+ name: 'SOCS',
37
+ value: 'CAI',
38
+ domain: '.youtube.com',
39
+ path: '/',
40
+ secure: true,
41
+ httpOnly: false,
42
+ sameSite: 'lax',
43
+ hostOnly: false,
44
+ })
45
+ }
46
+ }
47
+
48
+
49
+ const addCookies = (exports.addCookies = (jar, cookies = []) => {
50
+ if (!Array.isArray(cookies)) return
51
+
52
+ ensureSOCS(cookies)
53
+
54
+ for (const c of cookies) {
55
+ const ck = toCookie(c)
56
+ if (!ck) continue
57
+ jar.setCookieSync(ck, 'https://www.youtube.com')
58
+ }
59
+ })
60
+
61
+ exports.addCookiesFromString = (jar, str) => {
62
+ if (typeof str !== 'string') return
63
+ const parsed = str
64
+ .split(';')
65
+ .map(s => Cookie.parse(s))
66
+ .filter(Boolean)
67
+
68
+ addCookies(jar, parsed)
69
+ }
70
+
71
+
72
+ const createAgent = (exports.createAgent = (cookies, opts) => {
73
+ if (cookies && !Array.isArray(cookies) && typeof cookies === 'object') {
74
+ opts = cookies
75
+ cookies = []
76
+ }
77
+
78
+ cookies ??= []
79
+ opts ??= {}
80
+
81
+ const jar = opts.cookies?.jar || new CookieJar()
82
+
83
+ if (!opts.cookies) addCookies(jar, cookies)
84
+
85
+ return {
86
+ dispatcher: new CookieAgent({
87
+ ...opts,
88
+ cookies: { jar },
89
+ }),
90
+ jar,
91
+ localAddress: opts.localAddress,
92
+ }
93
+ })
94
+
95
+ exports.createProxyAgent = (options, cookies = []) => {
96
+ if (typeof options === 'string') {
97
+ options = { uri: options }
98
+ }
99
+
100
+ const jar = new CookieJar()
101
+ addCookies(jar, cookies)
102
+
103
+ return {
104
+ agent: new HttpsProxyAgent(options.uri),
105
+ dispatcher: new CookieAgent({
106
+ dispatcher: new ProxyAgent(options),
107
+ cookies: { jar },
108
+ }),
109
+ jar,
110
+ localAddress: options.localAddress,
111
+ }
112
+ }
113
+
114
+ exports.defaultAgent = createAgent()
package/lib/cache.js ADDED
@@ -0,0 +1,45 @@
1
+ const { setTimeout, clearTimeout } = require("timers");
2
+
3
+ module.exports = class Cache extends Map {
4
+ constructor(timeout = 1000) {
5
+ super();
6
+ this.timeout = timeout;
7
+ }
8
+
9
+ set(key, value) {
10
+ const old = super.get(key);
11
+ if (old) clearTimeout(old.tid);
12
+
13
+ const tid = setTimeout(() => this.delete(key), this.timeout);
14
+ tid.unref?.();
15
+
16
+ return super.set(key, { tid, value });
17
+ }
18
+
19
+ get(key) {
20
+ return super.get(key)?.value ?? null;
21
+ }
22
+
23
+ getOrSet(key, fn) {
24
+ if (this.has(key)) return this.get(key);
25
+
26
+ const value = fn();
27
+ this.set(key, value);
28
+
29
+ Promise.resolve(value).catch(() => this.delete(key));
30
+ return value;
31
+ }
32
+
33
+ delete(key) {
34
+ const entry = super.get(key);
35
+ if (!entry) return false;
36
+
37
+ clearTimeout(entry.tid);
38
+ return super.delete(key);
39
+ }
40
+
41
+ clear() {
42
+ for (const { tid } of super.values()) clearTimeout(tid);
43
+ super.clear();
44
+ }
45
+ };
@@ -0,0 +1,148 @@
1
+ const utils = require("./utils");
2
+ const FORMATS = require("./format");
3
+
4
+ const audioRanks = ["mp4a", "mp3", "vorbis", "aac", "opus", "flac"];
5
+ const videoRanks = ["mp4v", "avc1", "Sorenson H.283", "MPEG-4 Visual", "VP8", "VP9", "H.264"];
6
+
7
+ const vBitrate = f => f.bitrate || 0;
8
+ const aBitrate = f => f.audioBitrate || 0;
9
+ const vRank = f => videoRanks.findIndex(e => f.codecs?.includes(e));
10
+ const aRank = f => audioRanks.findIndex(e => f.codecs?.includes(e));
11
+
12
+ const sortBy = (a, b, fns) => {
13
+ for (const fn of fns) {
14
+ const r = fn(b) - fn(a);
15
+ if (r) return r;
16
+ }
17
+ return 0;
18
+ };
19
+
20
+ const sortVideo = (a, b) =>
21
+ sortBy(a, b, [f => +f.qualityLabel, vBitrate, vRank]);
22
+
23
+ const sortAudio = (a, b) =>
24
+ sortBy(a, b, [aBitrate, aRank]);
25
+
26
+ const sortFormats = (a, b) =>
27
+ sortBy(a, b, [
28
+ f => +!!f.isHLS,
29
+ f => +!!f.isDashMPD,
30
+ f => +(f.contentLength > 0),
31
+ f => +(f.hasVideo && f.hasAudio),
32
+ f => +f.hasVideo,
33
+ f => +f.qualityLabel || 0,
34
+ vBitrate,
35
+ aBitrate,
36
+ vRank,
37
+ aRank,
38
+ ]);
39
+
40
+ const byQuality = (quality, formats) => {
41
+ const find = q => formats.find(f => `${f.itag}` === `${q}`);
42
+ return Array.isArray(quality)
43
+ ? find(quality.find(q => find(q)))
44
+ : find(quality);
45
+ };
46
+
47
+ const chooseFormat = (formats, opts = {}) => {
48
+ formats = Array.isArray(formats) ? formats : [formats];
49
+
50
+ if (typeof opts.format === "object") {
51
+ if (!opts.format.url) throw Error("Invalid format");
52
+ return opts.format;
53
+ }
54
+
55
+ if (opts.filter) formats = filterFormats(formats, opts.filter);
56
+
57
+ if (formats.some(f => f.isHLS)) {
58
+ formats = formats.filter(f => f.isHLS || !f.isLive);
59
+ }
60
+
61
+ const q = opts.quality || "highest";
62
+ let f;
63
+
64
+ switch (q) {
65
+ case "highest":
66
+ f = formats[0];
67
+ break;
68
+ case "lowest":
69
+ f = formats[formats.length - 1];
70
+ break;
71
+ case "highestaudio": {
72
+ formats = filterFormats(formats, "audio").sort(sortAudio);
73
+ const best = formats[0];
74
+ const same = formats.filter(x => sortAudio(best, x) === 0);
75
+ const worstV = Math.min(...same.map(x => +x.qualityLabel || 0));
76
+ f = same.find(x => (+x.qualityLabel || 0) === worstV);
77
+ break;
78
+ }
79
+ case "lowestaudio":
80
+ f = filterFormats(formats, "audio").sort(sortAudio).at(-1);
81
+ break;
82
+ case "highestvideo": {
83
+ formats = filterFormats(formats, "video").sort(sortVideo);
84
+ const best = formats[0];
85
+ const same = formats.filter(x => sortVideo(best, x) === 0);
86
+ const worstA = Math.min(...same.map(x => x.audioBitrate || 0));
87
+ f = same.find(x => (x.audioBitrate || 0) === worstA);
88
+ break;
89
+ }
90
+ case "lowestvideo":
91
+ f = filterFormats(formats, "video").sort(sortVideo).at(-1);
92
+ break;
93
+ default:
94
+ f = byQuality(q, formats);
95
+ }
96
+
97
+ if (!f) throw Error(`No such format found: ${q}`);
98
+ return f;
99
+ };
100
+
101
+ const filterFormats = (formats, filter) => {
102
+ let fn;
103
+ switch (filter) {
104
+ case "videoandaudio":
105
+ case "audioandvideo":
106
+ fn = f => f.hasVideo && f.hasAudio;
107
+ break;
108
+ case "video":
109
+ fn = f => f.hasVideo;
110
+ break;
111
+ case "videoonly":
112
+ fn = f => f.hasVideo && !f.hasAudio;
113
+ break;
114
+ case "audio":
115
+ fn = f => f.hasAudio;
116
+ break;
117
+ case "audioonly":
118
+ fn = f => !f.hasVideo && f.hasAudio;
119
+ break;
120
+ default:
121
+ if (typeof filter !== "function") {
122
+ throw TypeError(`Given filter (${filter}) is not supported`);
123
+ }
124
+ fn = filter;
125
+ }
126
+ return formats.filter(f => f.url && fn(f));
127
+ };
128
+
129
+ const addFormatMeta = f => {
130
+ f = { ...FORMATS[f.itag], ...f };
131
+ f.hasVideo = !!f.qualityLabel;
132
+ f.hasAudio = !!f.audioBitrate;
133
+ f.container = f.mimeType?.split(";")[0].split("/")[1] || null;
134
+ f.codecs = f.mimeType ? utils.between(f.mimeType, 'codecs="', '"') : null;
135
+ f.videoCodec = f.hasVideo && f.codecs ? f.codecs.split(", ")[0] : null;
136
+ f.audioCodec = f.hasAudio && f.codecs ? f.codecs.split(", ").at(-1) : null;
137
+ f.isLive = /\bsource[/=]yt_live_broadcast\b/.test(f.url);
138
+ f.isHLS = /\/manifest\/hls_(variant|playlist)\//.test(f.url);
139
+ f.isDashMPD = /\/manifest\/dash\//.test(f.url);
140
+ return f;
141
+ };
142
+
143
+ module.exports = {
144
+ sortFormats,
145
+ chooseFormat,
146
+ filterFormats,
147
+ addFormatMeta,
148
+ };
package/lib/format.js ADDED
@@ -0,0 +1,82 @@
1
+ module.exports = {
2
+ 5:{mimeType:'video/flv; codecs="Sorenson H.283, mp3"',qualityLabel:"240p",bitrate:250000,audioBitrate:64},
3
+ 6:{mimeType:'video/flv; codecs="Sorenson H.263, mp3"',qualityLabel:"270p",bitrate:800000,audioBitrate:64},
4
+ 13:{mimeType:'video/3gp; codecs="MPEG-4 Visual, aac"',qualityLabel:null,bitrate:500000,audioBitrate:null},
5
+ 17:{mimeType:'video/3gp; codecs="MPEG-4 Visual, aac"',qualityLabel:"144p",bitrate:50000,audioBitrate:24},
6
+ 18:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"360p",bitrate:500000,audioBitrate:96},
7
+ 22:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"720p",bitrate:2000000,audioBitrate:192},
8
+ 34:{mimeType:'video/flv; codecs="H.264, aac"',qualityLabel:"360p",bitrate:500000,audioBitrate:128},
9
+ 35:{mimeType:'video/flv; codecs="H.264, aac"',qualityLabel:"480p",bitrate:800000,audioBitrate:128},
10
+ 36:{mimeType:'video/3gp; codecs="MPEG-4 Visual, aac"',qualityLabel:"240p",bitrate:175000,audioBitrate:32},
11
+ 37:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"1080p",bitrate:3000000,audioBitrate:192},
12
+ 38:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"3072p",bitrate:3500000,audioBitrate:192},
13
+ 43:{mimeType:'video/webm; codecs="VP8, vorbis"',qualityLabel:"360p",bitrate:500000,audioBitrate:128},
14
+ 44:{mimeType:'video/webm; codecs="VP8, vorbis"',qualityLabel:"480p",bitrate:1000000,audioBitrate:128},
15
+ 45:{mimeType:'video/webm; codecs="VP8, vorbis"',qualityLabel:"720p",bitrate:2000000,audioBitrate:192},
16
+ 46:{mimeType:'audio/webm; codecs="vp8, vorbis"',qualityLabel:"1080p",bitrate:null,audioBitrate:192},
17
+ 82:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"360p",bitrate:500000,audioBitrate:96},
18
+ 83:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"240p",bitrate:500000,audioBitrate:96},
19
+ 84:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"720p",bitrate:2000000,audioBitrate:192},
20
+ 85:{mimeType:'video/mp4; codecs="H.264, aac"',qualityLabel:"1080p",bitrate:3000000,audioBitrate:192},
21
+ 91:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"144p",bitrate:100000,audioBitrate:48},
22
+ 92:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"240p",bitrate:150000,audioBitrate:48},
23
+ 93:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"360p",bitrate:500000,audioBitrate:128},
24
+ 94:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"480p",bitrate:800000,audioBitrate:128},
25
+ 95:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"720p",bitrate:1500000,audioBitrate:256},
26
+ 96:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"1080p",bitrate:2500000,audioBitrate:256},
27
+ 100:{mimeType:'audio/webm; codecs="VP8, vorbis"',qualityLabel:"360p",bitrate:null,audioBitrate:128},
28
+ 101:{mimeType:'audio/webm; codecs="VP8, vorbis"',qualityLabel:"360p",bitrate:null,audioBitrate:192},
29
+ 102:{mimeType:'audio/webm; codecs="VP8, vorbis"',qualityLabel:"720p",bitrate:null,audioBitrate:192},
30
+ 120:{mimeType:'video/flv; codecs="H.264, aac"',qualityLabel:"720p",bitrate:2000000,audioBitrate:128},
31
+ 127:{mimeType:'audio/ts; codecs="aac"',qualityLabel:null,bitrate:null,audioBitrate:96},
32
+ 128:{mimeType:'audio/ts; codecs="aac"',qualityLabel:null,bitrate:null,audioBitrate:96},
33
+ 132:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"240p",bitrate:150000,audioBitrate:48},
34
+ 133:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"240p",bitrate:200000,audioBitrate:null},
35
+ 134:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"360p",bitrate:300000,audioBitrate:null},
36
+ 135:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"480p",bitrate:500000,audioBitrate:null},
37
+ 136:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"720p",bitrate:1000000,audioBitrate:null},
38
+ 137:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"1080p",bitrate:2500000,audioBitrate:null},
39
+ 138:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"4320p",bitrate:13500000,audioBitrate:null},
40
+ 139:{mimeType:'audio/mp4; codecs="aac"',qualityLabel:null,bitrate:null,audioBitrate:48},
41
+ 140:{mimeType:'audio/m4a; codecs="aac"',qualityLabel:null,bitrate:null,audioBitrate:128},
42
+ 141:{mimeType:'audio/mp4; codecs="aac"',qualityLabel:null,bitrate:null,audioBitrate:256},
43
+ 151:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"720p",bitrate:50000,audioBitrate:24},
44
+ 160:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"144p",bitrate:100000,audioBitrate:null},
45
+ 171:{mimeType:'audio/webm; codecs="vorbis"',qualityLabel:null,bitrate:null,audioBitrate:128},
46
+ 172:{mimeType:'audio/webm; codecs="vorbis"',qualityLabel:null,bitrate:null,audioBitrate:192},
47
+ 231:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"480p",bitrate:500000,audioBitrate:null},
48
+ 232:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"720p",bitrate:800000,audioBitrate:null},
49
+ 242:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"240p",bitrate:100000,audioBitrate:null},
50
+ 243:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"360p",bitrate:250000,audioBitrate:null},
51
+ 244:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"480p",bitrate:500000,audioBitrate:null},
52
+ 247:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"720p",bitrate:700000,audioBitrate:null},
53
+ 248:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1080p",bitrate:1500000,audioBitrate:null},
54
+ 249:{mimeType:'audio/webm; codecs="opus"',qualityLabel:null,bitrate:null,audioBitrate:48},
55
+ 250:{mimeType:'audio/webm; codecs="opus"',qualityLabel:null,bitrate:null,audioBitrate:64},
56
+ 251:{mimeType:'audio/webm; codecs="opus"',qualityLabel:null,bitrate:null,audioBitrate:160},
57
+ 264:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"1440p",bitrate:4000000,audioBitrate:null},
58
+ 266:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"2160p",bitrate:12500000,audioBitrate:null},
59
+ 270:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"1080p",bitrate:2500000,audioBitrate:null},
60
+ 271:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1440p",bitrate:9000000,audioBitrate:null},
61
+ 272:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"4320p",bitrate:20000000,audioBitrate:null},
62
+ 278:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"144p 30fps",bitrate:80000,audioBitrate:null},
63
+ 298:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"720p",bitrate:3000000,audioBitrate:null},
64
+ 299:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"1080p",bitrate:5500000,audioBitrate:null},
65
+ 300:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"720p",bitrate:1318000,audioBitrate:48},
66
+ 301:{mimeType:'video/ts; codecs="H.264, aac"',qualityLabel:"1080p",bitrate:3000000,audioBitrate:128},
67
+ 302:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"720p HFR",bitrate:2500000,audioBitrate:null},
68
+ 303:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1080p HFR",bitrate:5000000,audioBitrate:null},
69
+ 308:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1440p HFR",bitrate:10000000,audioBitrate:null},
70
+ 311:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"720p",bitrate:1250000,audioBitrate:null},
71
+ 312:{mimeType:'video/mp4; codecs="H.264"',qualityLabel:"1080p",bitrate:2500000,audioBitrate:null},
72
+ 313:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"2160p",bitrate:13000000,audioBitrate:null},
73
+ 315:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"2160p HFR",bitrate:20000000,audioBitrate:null},
74
+ 330:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"144p HDR, HFR",bitrate:80000,audioBitrate:null},
75
+ 331:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"240p HDR, HFR",bitrate:100000,audioBitrate:null},
76
+ 332:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"360p HDR, HFR",bitrate:250000,audioBitrate:null},
77
+ 333:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"240p HDR, HFR",bitrate:500000,audioBitrate:null},
78
+ 334:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"720p HDR, HFR",bitrate:1000000,audioBitrate:null},
79
+ 335:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1080p HDR, HFR",bitrate:1500000,audioBitrate:null},
80
+ 336:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"1440p HDR, HFR",bitrate:5000000,audioBitrate:null},
81
+ 337:{mimeType:'video/webm; codecs="VP9"',qualityLabel:"2160p HDR, HFR",bitrate:12000000,audioBitrate:null}
82
+ };
package/lib/index.js ADDED
@@ -0,0 +1,169 @@
1
+ const { PassThrough } = require("stream");
2
+ const miniget = require("miniget");
3
+ const m3u8stream = require("m3u8stream");
4
+ const { parseTimestamp } = require("m3u8stream");
5
+ const { request } = require("undici");
6
+ const getInfo = require("./info");
7
+ const utils = require("./utils");
8
+ const formatUtils = require("./format-utils");
9
+ const getPlaylistInfo = require('./playlist').getPlaylistInfo
10
+ const urlUtils = require("./url-utils");
11
+ const agent = require("./agents.js");
12
+ const subtitle = require('./subtitle')
13
+ const getPostInfo = require("./post");
14
+ const { version } = require("../package.json");
15
+
16
+ const createUndiciStream = (url, opts = {}) => {
17
+ const s = new PassThrough();
18
+
19
+ (async () => {
20
+ try {
21
+ const res = await request(url, {
22
+ method: "GET",
23
+ headers: opts.headers || {},
24
+ dispatcher: opts.dispatcher,
25
+ });
26
+
27
+ s.emit("response", { statusCode: res.statusCode, headers: res.headers });
28
+ if (res.statusCode < 200 || res.statusCode >= 400) {
29
+ const e = new Error(`Status code: ${res.statusCode}`);
30
+ e.statusCode = res.statusCode;
31
+ return s.emit("error", e);
32
+ }
33
+
34
+ for await (const c of res.body) {
35
+ if (s.destroyed) break;
36
+ s.push(c);
37
+ }
38
+ if (!s.destroyed) s.push(null);
39
+ } catch (e) {
40
+ if (!s.destroyed) s.emit("error", e);
41
+ }
42
+ })();
43
+
44
+ return s;
45
+ };
46
+
47
+ const createStream = opts => {
48
+ const s = new PassThrough({ highWaterMark: opts?.highWaterMark || 512 * 1024 });
49
+ s._destroy = () => (s.destroyed = true);
50
+ return s;
51
+ };
52
+
53
+ const pipeEvents = (req, stream, end) => {
54
+ ["abort", "request", "response", "error", "redirect", "retry", "reconnect"]
55
+ .forEach(e => req.prependListener(e, stream.emit.bind(stream, e)));
56
+ req.pipe(stream, { end });
57
+ };
58
+
59
+ const downloadFromInfoCallback = (stream, info, opts = {}) => {
60
+ const err = utils.playError(info.player_response);
61
+ if (err) return stream.emit("error", err);
62
+ if (!info.formats.length)
63
+ return stream.emit("error", Error("This video is unavailable"));
64
+
65
+ let format;
66
+ try {
67
+ format = formatUtils.chooseFormat(info.formats, opts);
68
+ } catch (e) {
69
+ return stream.emit("error", e);
70
+ }
71
+
72
+ stream.emit("info", info, format);
73
+ if (stream.destroyed) return;
74
+
75
+ let downloaded = 0;
76
+ const ondata = c => {
77
+ downloaded += c.length;
78
+ stream.emit("progress", c.length, downloaded);
79
+ };
80
+
81
+ utils.applyDefaultHeaders(opts);
82
+ opts.requestOptions ||= {};
83
+ opts.requestOptions.headers ||= {};
84
+
85
+ if (opts.agent) {
86
+ opts.requestOptions.agent = opts.agent.agent;
87
+ if (opts.agent.jar) {
88
+ utils.setPropInsensitive(
89
+ opts.requestOptions.headers,
90
+ "cookie",
91
+ opts.agent.jar.getCookieStringSync("https://www.youtube.com"),
92
+ );
93
+ }
94
+ if (opts.agent.localAddress) {
95
+ opts.requestOptions.localAddress = opts.agent.localAddress;
96
+ }
97
+ }
98
+
99
+ let req;
100
+
101
+ if (format.isHLS || format.isDashMPD) {
102
+ req = m3u8stream(format.url, {
103
+ begin: opts.begin || (format.isLive && Date.now()),
104
+ liveBuffer: opts.liveBuffer,
105
+ requestOptions: opts.requestOptions,
106
+ parser: format.isDashMPD ? "dash-mpd" : "m3u8",
107
+ id: format.itag,
108
+ });
109
+
110
+ req.on("progress", (seg, total) =>
111
+ stream.emit("progress", seg.size, seg.num, total),
112
+ );
113
+
114
+ pipeEvents(req, stream, true);
115
+ } else {
116
+ const ro = { ...opts.requestOptions };
117
+
118
+ if (opts.begin) format.url += `&begin=${parseTimestamp(opts.begin)}`;
119
+ if (opts.range) {
120
+ ro.headers = {
121
+ ...ro.headers,
122
+ Range: `bytes=${opts.range.start || 0}-${opts.range.end || ""}`,
123
+ };
124
+ }
125
+
126
+ req = miniget(format.url, ro);
127
+ req.on("data", ondata);
128
+ pipeEvents(req, stream, true);
129
+ }
130
+
131
+ stream._destroy = () => {
132
+ stream.destroyed = true;
133
+ req?.destroy?.();
134
+ };
135
+ };
136
+
137
+ const ytdl = (link, opts) => {
138
+ const s = createStream(opts);
139
+ ytdl.getInfo(link, opts).then(
140
+ info => downloadFromInfoCallback(s, info, opts),
141
+ e => s.emit("error", e),
142
+ );
143
+ return s;
144
+ };
145
+
146
+ module.exports = ytdl;
147
+
148
+ ytdl.getBasicInfo = getInfo.getBasicInfo;
149
+ ytdl.getInfo = getInfo.getInfo;
150
+ ytdl.chooseFormat = formatUtils.chooseFormat;
151
+ ytdl.getPostInfo = getPostInfo.getPostInfo;
152
+ ytdl.filterFormats = formatUtils.filterFormats;
153
+ ytdl.validateID = urlUtils.validateID;
154
+ ytdl.getPlaylistInfo = getPlaylistInfo
155
+ ytdl.validateURL = urlUtils.validateURL;
156
+ ytdl.getSubtitles = subtitle.getSubtitles
157
+ ytdl.getURLVideoID = urlUtils.getURLVideoID;
158
+ ytdl.getVideoID = urlUtils.getVideoID;
159
+ ytdl.createAgent = agent.createAgent;
160
+ ytdl.createProxyAgent = agent.createProxyAgent;
161
+ ytdl.cache = { info: getInfo.cache, watch: getInfo.watchPageCache };
162
+ ytdl.version = version;
163
+
164
+ ytdl.downloadFromInfo = (info, opts) => {
165
+ if (!info.full) throw Error("Invalid info object");
166
+ const s = createStream(opts);
167
+ setImmediate(() => downloadFromInfoCallback(s, info, opts));
168
+ return s;
169
+ };