cloud-ytdl 1.0.0-rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1063 -0
- package/lib/agents.js +114 -0
- package/lib/cache.js +45 -0
- package/lib/format-utils.js +148 -0
- package/lib/format.js +82 -0
- package/lib/index.js +169 -0
- package/lib/info.js +209 -0
- package/lib/innertube.js +1 -0
- package/lib/load.js +38 -0
- package/lib/playlist.js +57 -0
- package/lib/post.js +88 -0
- package/lib/sig-decoder.js +1 -0
- package/lib/subtitle.js +106 -0
- package/lib/url-utils.js +58 -0
- package/lib/utils.js +294 -0
- package/lib/xmlTosrt.js +49 -0
- package/package.json +80 -0
- package/types/index.d.ts +488 -0
package/lib/info.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const sax = require("sax");
|
|
2
|
+
const { setTimeout } = require("timers");
|
|
3
|
+
|
|
4
|
+
const utils = require("./utils");
|
|
5
|
+
const formatutils = require("./format-utils");
|
|
6
|
+
const urlutils = require("./url-utils");
|
|
7
|
+
const cache = require("./cache");
|
|
8
|
+
const innertube = require("./innertube");
|
|
9
|
+
|
|
10
|
+
const baseurl = "https://www.youtube.com/watch?v=";
|
|
11
|
+
|
|
12
|
+
const cacheinstance = new cache();
|
|
13
|
+
const watchpagecache = new cache();
|
|
14
|
+
|
|
15
|
+
const agerestrictedurls = [
|
|
16
|
+
"support.google.com/youtube/?p=age_restrictions",
|
|
17
|
+
"youtube.com/t/community_guidelines",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const getbasicinfo = async (id, options = {}) => {
|
|
21
|
+
return getinfo(id, options);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getwatchhtmlurl = (id, options) =>
|
|
25
|
+
`${baseurl + id}&hl=${options.lang || "en"}&bpctr=${Math.ceil(Date.now() / 1000)}&has_verified=1`;
|
|
26
|
+
|
|
27
|
+
const getwatchhtmlpagebody = (id, options) => {
|
|
28
|
+
const url = getwatchhtmlurl(id, options);
|
|
29
|
+
return watchpagecache.getOrSet(url, () => utils.request(url, options));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const embedurl = "https://www.youtube.com/embed/";
|
|
33
|
+
|
|
34
|
+
const getembedpagebody = (id, options) => {
|
|
35
|
+
const url = `${embedurl + id}?hl=${options.lang || "en"}`;
|
|
36
|
+
return utils.request(url, options);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const gethtml5player = body => {
|
|
40
|
+
const res =
|
|
41
|
+
/<script\s+src="([^"]+)"(?:\s+type="text\/javascript")?\s+name="player_ias\/base"\s*>|"jsUrl":"([^"]+)"/.exec(body);
|
|
42
|
+
return res?.[1] || res?.[2];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const retryfunc = async (func, args, options) => {
|
|
46
|
+
let currenttry = 0;
|
|
47
|
+
let result;
|
|
48
|
+
|
|
49
|
+
if (!options.maxRetries) options.maxRetries = 3;
|
|
50
|
+
if (!options.backoff) options.backoff = { inc: 500, max: 5000 };
|
|
51
|
+
|
|
52
|
+
while (currenttry <= options.maxRetries) {
|
|
53
|
+
try {
|
|
54
|
+
result = await func(...args);
|
|
55
|
+
break;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err?.statusCode < 500 || currenttry >= options.maxRetries) throw err;
|
|
58
|
+
const wait = Math.min(++currenttry * options.backoff.inc, options.backoff.max);
|
|
59
|
+
await new Promise(r => setTimeout(r, wait));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const jsonclosingchars = /^[)\]}'\s]+/;
|
|
66
|
+
|
|
67
|
+
const parsejson = (source, name, json) => {
|
|
68
|
+
if (!json || typeof json === "object") return json;
|
|
69
|
+
json = json.replace(jsonclosingchars, "");
|
|
70
|
+
return JSON.parse(json);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const findjson = (source, name, body, left, right, prepend) => {
|
|
74
|
+
const str = utils.between(body, left, right);
|
|
75
|
+
if (!str) throw new Error(`could not find ${name} in ${source}`);
|
|
76
|
+
return parsejson(source, name, utils.cutAfterJS(`${prepend}${str}`));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const findplayerresponse = (source, info) => {
|
|
80
|
+
if (!info) return {};
|
|
81
|
+
const data =
|
|
82
|
+
info.args?.playerresponse ||
|
|
83
|
+
info.playerresponse ||
|
|
84
|
+
info.playerResponse ||
|
|
85
|
+
info.embeddedplayerresponse;
|
|
86
|
+
return parsejson(source, "playerresponse", data);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getwatchhtmlpage = async (id, options) => {
|
|
90
|
+
const body = await getwatchhtmlpagebody(id, options);
|
|
91
|
+
const info = { page: "watch" };
|
|
92
|
+
|
|
93
|
+
info.playerresponse =
|
|
94
|
+
utils.extractYouTubeJSON(body, "ytInitialPlayerResponse") ||
|
|
95
|
+
utils.extractYouTubeJSON(body, "initialPlayerResponse");
|
|
96
|
+
|
|
97
|
+
info.response =
|
|
98
|
+
utils.extractYouTubeJSON(body, "ytInitialData") ||
|
|
99
|
+
utils.extractYouTubeJSON(body, "initialData");
|
|
100
|
+
|
|
101
|
+
info.html5player = gethtml5player(body);
|
|
102
|
+
return info;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const parseformats = playerresponse =>
|
|
106
|
+
(playerresponse?.streamingData?.formats || []).concat(
|
|
107
|
+
playerresponse?.streamingData?.adaptiveFormats || []
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const getinfo = async (id, options = {}) => {
|
|
111
|
+
utils.applyIPv6Rotations(options);
|
|
112
|
+
utils.applyDefaultHeaders(options);
|
|
113
|
+
utils.applyDefaultAgent(options);
|
|
114
|
+
utils.applyOldLocalAddress(options);
|
|
115
|
+
|
|
116
|
+
const data = await innertube.getInfo(id, {
|
|
117
|
+
headers: options.requestOptions?.headers,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (!data.success) throw new Error("failed to fetch info");
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
full: true,
|
|
124
|
+
videoDetails: data.videodetails,
|
|
125
|
+
player_response: {
|
|
126
|
+
playabilityStatus: { status: "OK" },
|
|
127
|
+
streamingData: {
|
|
128
|
+
formats: data.formats.filter(f => f.hasvideo && f.hasaudio),
|
|
129
|
+
adaptiveFormats: data.formats
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
formats: data.formats.map(f => ({
|
|
133
|
+
...f,
|
|
134
|
+
hasVideo: f.hasvideo,
|
|
135
|
+
hasAudio: f.hasaudio,
|
|
136
|
+
mimeType: f.mimeType,
|
|
137
|
+
url: f.url,
|
|
138
|
+
isHLS: false,
|
|
139
|
+
isDashMPD: false
|
|
140
|
+
})),
|
|
141
|
+
html5player: data.html5player
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
const getdashmanifest = (url, options) =>
|
|
147
|
+
new Promise((resolve, reject) => {
|
|
148
|
+
const formats = {};
|
|
149
|
+
const parser = sax.parser(false);
|
|
150
|
+
|
|
151
|
+
let adaptationset;
|
|
152
|
+
|
|
153
|
+
parser.onerror = reject;
|
|
154
|
+
parser.onopentag = node => {
|
|
155
|
+
if (node.name === "ADAPTATIONSET") adaptationset = node.attributes;
|
|
156
|
+
if (node.name === "REPRESENTATION") {
|
|
157
|
+
const itag = parseInt(node.attributes.ID);
|
|
158
|
+
if (!isNaN(itag)) {
|
|
159
|
+
formats[url] = {
|
|
160
|
+
itag,
|
|
161
|
+
url,
|
|
162
|
+
bitrate: parseInt(node.attributes.BANDWIDTH),
|
|
163
|
+
mimeType: `${adaptationset.MIMETYPE}; codecs="${node.attributes.CODECS}"`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
parser.onend = () => resolve(formats);
|
|
170
|
+
|
|
171
|
+
utils.request(new URL(url, baseurl).toString(), options).then(res => {
|
|
172
|
+
parser.write(res);
|
|
173
|
+
parser.close();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const getm3u8 = async (url, options) => {
|
|
178
|
+
const body = await utils.request(new URL(url, baseurl).toString(), options);
|
|
179
|
+
const formats = {};
|
|
180
|
+
|
|
181
|
+
body
|
|
182
|
+
.split("\n")
|
|
183
|
+
.filter(l => l.startsWith("http"))
|
|
184
|
+
.forEach(l => {
|
|
185
|
+
const itag = parseInt(l.match(/\/itag\/(\d+)\//)[1]);
|
|
186
|
+
formats[l] = { itag, url: l };
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return formats;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const wrapcache = name => async (link, options = {}) => {
|
|
193
|
+
const id = await urlutils.getVideoID(link);
|
|
194
|
+
const key = [name, id, options.lang].join("-");
|
|
195
|
+
const fn = name === "getbasicinfo" ? getbasicinfo : getinfo;
|
|
196
|
+
return cacheinstance.getOrSet(key, () => fn(id, options));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
cacheInstance: cacheinstance,
|
|
201
|
+
watchPageCache: watchpagecache,
|
|
202
|
+
getBasicInfo: wrapcache("getbasicinfo"),
|
|
203
|
+
getInfo: wrapcache("getinfo"),
|
|
204
|
+
validateID: urlutils.validateID,
|
|
205
|
+
validateURL: urlutils.validateURL,
|
|
206
|
+
getURLVideoID: urlutils.getURLVideoID,
|
|
207
|
+
getVideoID: urlutils.getVideoID,
|
|
208
|
+
};
|
|
209
|
+
|