@oreohq/ytdl-core 4.15.1 → 4.16.5
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 +46 -137
- package/lib/agent.js +9 -1
- package/lib/format-utils.js +2 -4
- package/lib/index.js +9 -6
- package/lib/info-extras.js +19 -32
- package/lib/info.js +106 -52
- package/lib/sig.js +68 -70
- package/lib/utils.js +96 -81
- package/package.json +13 -18
- package/typings/index.d.ts +4 -3
- package/LICENSE +0 -21
package/README.md
CHANGED
@@ -1,50 +1,42 @@
|
|
1
1
|
# @oreohq/ytdl-core
|
2
|
+
Oreo fork of `ytdl-core`. This fork is dedicated to fixing bugs and adding features that are not merged into the original repo as soon as possible.
|
2
3
|
|
3
|
-
|
4
|
+

|
4
5
|
|
5
|
-
|
6
|
+
<p align="center">
|
7
|
+
<img src="https://i.imgur.com/cBiIazb.png" alt="badge" />
|
8
|
+
<img src="https://i.imgur.com/1EIFuXp.png" alt="badge" />
|
9
|
+
</p>
|
6
10
|
|
11
|
+
|
12
|
+
## 📦 Installation
|
7
13
|
```bash
|
8
14
|
npm install @oreohq/ytdl-core@latest
|
9
15
|
```
|
10
16
|
|
11
|
-
Make sure you're installing the latest version of `@oreohq/ytdl-core` to keep up with the latest fixes.
|
12
|
-
|
13
|
-
## Usage
|
14
17
|
|
18
|
+
## 🪴 Usage
|
15
19
|
```js
|
16
|
-
const ytdl = require(
|
17
|
-
// TypeScript: import ytdl from '@oreohq/ytdl-core'; with --esModuleInterop
|
18
|
-
// TypeScript: import * as ytdl from '@oreohq/ytdl-core'; with --allowSyntheticDefaultImports
|
19
|
-
// TypeScript: import ytdl = require('@oreohq/ytdl-core'); with neither of the above
|
20
|
-
|
21
|
-
// Download a video
|
22
|
-
ytdl("https://www.youtube.com/watch?v=G9KVla9gwbY").pipe(require("fs").createWriteStream("video.mp4"));
|
20
|
+
const ytdl = require('@oreohq/ytdl-core');
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
console.log(info.videoDetails.title);
|
27
|
-
});
|
22
|
+
const videoInfo = await ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc');
|
23
|
+
console.log(videoInfo);
|
28
24
|
|
29
|
-
//
|
30
|
-
ytdl
|
31
|
-
console.log(info.formats);
|
32
|
-
});
|
25
|
+
// Download a video
|
26
|
+
ytdl('https://www.youtube.com/watch?v=2QjJMT554Bc').pipe(require('fs').createWriteStream('video.mp4'));
|
33
27
|
```
|
34
28
|
|
35
|
-
### Cookies Support
|
36
29
|
|
30
|
+
### 🍪 Cookies Support
|
37
31
|
```js
|
38
|
-
const ytdl = require(
|
32
|
+
const ytdl = require('@oreohq/ytdl-core');
|
39
33
|
|
40
|
-
// (Optional) Below are examples, NOT the recommended options
|
41
34
|
const cookies = [
|
42
35
|
{ name: "cookie1", value: "COOKIE1_HERE" },
|
43
36
|
{ name: "cookie2", value: "COOKIE2_HERE" },
|
44
37
|
];
|
45
38
|
|
46
|
-
//
|
47
|
-
// Below are examples, NOT the recommended options
|
39
|
+
// http-cookie-agent / undici agent options
|
48
40
|
const agentOptions = {
|
49
41
|
pipelining: 5,
|
50
42
|
maxRedirections: 0,
|
@@ -53,13 +45,11 @@ const agentOptions = {
|
|
53
45
|
|
54
46
|
// agent should be created once if you don't want to change your cookie
|
55
47
|
const agent = ytdl.createAgent(cookies, agentOptions);
|
56
|
-
|
57
|
-
ytdl.getBasicInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent });
|
58
|
-
ytdl.getInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent });
|
48
|
+
ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc' { agent });
|
59
49
|
```
|
60
50
|
|
61
|
-
#### How to get cookies
|
62
51
|
|
52
|
+
#### How to get cookies
|
63
53
|
- Install [EditThisCookie](http://www.editthiscookie.com/) extension for your browser.
|
64
54
|
- Go to [YouTube](https://www.youtube.com/).
|
65
55
|
- Log in to your account. (You should use a new account for this purpose)
|
@@ -71,139 +61,58 @@ ytdl.getInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent });
|
|
71
61
|
> You can delete your browser's cookies to log it out on your browser.
|
72
62
|
> Or use incognito mode to get your cookies then close it.
|
73
63
|
|
74
|
-
> [!WARNING]
|
75
|
-
> Paste all the cookies array from clipboard into `createAgent` function. Don't remove/edit any cookies if you don't know what you're doing.
|
76
|
-
|
77
|
-
> [!WARNING]
|
78
|
-
> Make sure your account, which logged in when you getting your cookies, use 1 IP at the same time only. It will make your cookies alive longer.
|
79
|
-
|
80
|
-
```js
|
81
|
-
const ytdl = require("@oreohq/ytdl-core");
|
82
|
-
const agent = ytdl.createAgent([
|
83
|
-
{
|
84
|
-
domain: ".youtube.com",
|
85
|
-
expirationDate: 1234567890,
|
86
|
-
hostOnly: false,
|
87
|
-
httpOnly: true,
|
88
|
-
name: "---xxx---",
|
89
|
-
path: "/",
|
90
|
-
sameSite: "no_restriction",
|
91
|
-
secure: true,
|
92
|
-
session: false,
|
93
|
-
value: "---xxx---",
|
94
|
-
},
|
95
|
-
{
|
96
|
-
"...": "...",
|
97
|
-
},
|
98
|
-
]);
|
99
|
-
```
|
100
|
-
|
101
|
-
- Or you can paste your cookies array into a file and use `fs.readFileSync` to read it.
|
102
|
-
|
103
|
-
```js
|
104
|
-
const ytdl = require("@oreohq/ytdl-core");
|
105
|
-
const fs = require("fs");
|
106
|
-
const agent = ytdl.createAgent(JSON.parse(fs.readFileSync("cookies.json")));
|
107
|
-
```
|
108
64
|
|
109
65
|
### Proxy Support
|
110
|
-
|
111
66
|
```js
|
112
|
-
const ytdl = require(
|
67
|
+
const ytdl = require('@oreohq/ytdl-core');
|
113
68
|
|
114
|
-
const agent = ytdl.createProxyAgent({ uri:
|
69
|
+
const agent = ytdl.createProxyAgent({ uri: 'my.proxy.server' });
|
70
|
+
const agentWithCookies = ytdl.createProxyAgent({ uri: 'my.proxy.server' }, [{ name: 'cookie', value: 'COOKIE_HERE' }]);
|
115
71
|
|
116
|
-
ytdl.getBasicInfo(
|
117
|
-
ytdl.
|
72
|
+
ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc' { agent });
|
73
|
+
ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc' { agentWithCookies });
|
118
74
|
```
|
119
75
|
|
120
|
-
Use both proxy and cookies:
|
121
|
-
|
122
|
-
```js
|
123
|
-
const ytdl = require("@oreohq/ytdl-core");
|
124
|
-
|
125
|
-
const agent = ytdl.createProxyAgent({ uri: "my.proxy.server" }, [{ name: "cookie", value: "COOKIE_HERE" }]);
|
126
|
-
|
127
|
-
ytdl.getBasicInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent });
|
128
|
-
ytdl.getInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent });
|
129
|
-
```
|
130
76
|
|
131
77
|
### IP Rotation
|
132
|
-
|
133
|
-
_Built-in ip rotation (`getRandomIPv6`) won't be updated and will be removed in the future, create your own ip rotation instead._
|
134
|
-
|
135
78
|
To implement IP rotation, you need to assign the desired IP address to the `localAddress` property within `undici.Agent.Options`.
|
136
79
|
Therefore, you'll need to use a different `ytdl.Agent` for each IP address you want to use.
|
137
|
-
|
138
80
|
```js
|
139
|
-
const
|
140
|
-
const
|
141
|
-
|
142
|
-
const agentForARandomIP = ytdl.createAgent(undefined, {
|
143
|
-
localAddress: getRandomIPv6("2001:2::/48"),
|
144
|
-
});
|
81
|
+
const { getRandomIPv6 } = require('@oreohq/ytdl-core/lib/utils');
|
82
|
+
const ytdl = require('@oreohq/ytdl-core');
|
145
83
|
|
146
|
-
ytdl.
|
84
|
+
const proxyAgent1 = ytdl.createAgent(undefined, { localAddress: getRandomIPv6('2001:2::/48') });
|
85
|
+
ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc', { agent: proxyAgent1 });
|
147
86
|
|
148
|
-
const
|
149
|
-
|
150
|
-
});
|
151
|
-
|
152
|
-
ytdl.getInfo("https://www.youtube.com/watch?v=G9KVla9gwbY", { agent: agentForAnotherRandomIP });
|
87
|
+
const proxyAgent2 = ytdl.createAgent(undefined, { localAddress: getRandomIPv6('2001:2::/48') });
|
88
|
+
ytdl.getBasicInfo('https://www.youtube.com/watch?v=2QjJMT554Bc', { agent: proxyAgent2 });
|
153
89
|
```
|
154
90
|
|
155
|
-
## API
|
156
|
-
|
157
|
-
You can find the API documentation in the [original repo](https://github.com/fent/node-ytdl-core#api). Except a few changes:
|
158
|
-
|
159
|
-
### `ytdl.getInfoOptions`
|
160
|
-
|
161
|
-
- `requestOptions` is now `undici`'s [`RequestOptions`](https://github.com/nodejs/undici#undicirequesturl-options-promise).
|
162
|
-
- `agent`: [`ytdl.Agent`](https://github.com/oreohq/ytdl-core/blob/master/typings/index.d.ts#L10-L14)
|
163
|
-
- `playerClients`: An array of player clients to use. Accepts `WEB`, `WEB_CREATOR`, `IOS`, and `ANDROID`. Defaults to `["WEB_CREATOR", "IOS"]`.
|
164
|
-
|
165
|
-
### `ytdl.createAgent([cookies]): ytdl.Agent`
|
166
|
-
|
167
|
-
`cookies`: an array of json cookies exported with [EditThisCookie](http://www.editthiscookie.com/).
|
168
|
-
|
169
|
-
### `ytdl.createProxyAgent(proxy[, cookies]): ytdl.Agent`
|
170
|
-
|
171
|
-
`proxy`: [`ProxyAgentOptions`](https://github.com/nodejs/undici/blob/main/docs/api/ProxyAgent.md#parameter-proxyagentoptions) contains your proxy server information.
|
172
|
-
|
173
|
-
#### How to implement `ytdl.Agent` with your own Dispatcher
|
174
|
-
|
175
|
-
You can find the example [here](https://github.com/oreohq/ytdl-core/blob/master/lib/cookie.js#L73-L86)
|
176
|
-
|
177
|
-
## Limitations
|
178
|
-
|
179
|
-
ytdl cannot download videos that fall into the following
|
180
|
-
|
181
|
-
- Regionally restricted (requires a [proxy](#proxy-support))
|
182
|
-
- Private (if you have access, requires [cookies](#cookies-support))
|
183
|
-
- Rentals (if you have access, requires [cookies](#cookies-support))
|
184
|
-
- YouTube Premium content (if you have access, requires [cookies](#cookies-support))
|
185
|
-
- Only [HLS Livestreams](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) are currently supported. Other formats will get filtered out in ytdl.chooseFormats
|
186
|
-
|
187
|
-
Generated download links are valid for 6 hours, and may only be downloadable from the same IP address.
|
188
|
-
|
189
|
-
## Rate Limiting
|
190
91
|
|
92
|
+
## 👻 Rate Limiting
|
191
93
|
When doing too many requests YouTube might block. This will result in your requests getting denied with HTTP-StatusCode 429. The following steps might help you:
|
192
94
|
|
193
95
|
- Update `@oreohq/ytdl-core` to the latest version
|
194
96
|
- Use proxies (you can find an example [here](#proxy-support))
|
195
97
|
- Extend the Proxy Idea by rotating (IPv6-)Addresses
|
196
|
-
|
197
|
-
- Use cookies (you can find an example [here](#cookies-support))
|
198
|
-
- for this to take effect you have to FIRST wait for the current rate limit to expire
|
98
|
+
- Use cookies (you can find an example [here](#cookies-support)) (wait for current ratelimits to expire first)
|
199
99
|
- Wait it out (it usually goes away within a few days)
|
200
100
|
|
201
|
-
## Update Checks
|
202
|
-
|
203
|
-
The issue of using an outdated version of ytdl-core became so prevalent, that ytdl-core now checks for updates at run time, and every 12 hours. If it finds an update, it will print a warning to the console advising you to update. Due to the nature of this library, it is important to always use the latest version as YouTube continues to update.
|
204
101
|
|
102
|
+
# 🚛 Update Checks
|
103
|
+
The issue of using an outdated version of ytdl-core became so prevalent, that ytdl-core now checks for updates at run time, and every 12 hours.
|
104
|
+
Due to the nature of this library, it is important to always use the latest version as YouTube continues to update.
|
205
105
|
If you'd like to disable this update check, you can do so by providing the `YTDL_NO_UPDATE` env variable.
|
106
|
+
```
|
107
|
+
env YTDL_NO_UPDATE=1
|
108
|
+
```
|
206
109
|
|
110
|
+
If you'd like to have it send a webhook message on discord, you can do so by providing the following env variables.
|
207
111
|
```
|
208
|
-
|
209
|
-
|
112
|
+
YTDL_WEBHOOK_URL="Your Webhook Url"
|
113
|
+
YTDL_WEBHOOK_MENTIONS="String with mentions"
|
114
|
+
```
|
115
|
+
|
116
|
+
|
117
|
+
## **❔ Support**
|
118
|
+
<a href="https://discord.gg/invite/GaczkwfgV9"><img src="https://invidget.switchblade.xyz/GaczkwfgV9" alt="Discord"></a>
|
package/lib/agent.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
const { ProxyAgent } = require("undici");
|
2
|
+
const { HttpsProxyAgent } = require("https-proxy-agent");
|
2
3
|
const { Cookie, CookieJar, canonicalDomain } = require("tough-cookie");
|
3
4
|
const { CookieAgent, CookieClient } = require("http-cookie-agent/undici");
|
4
5
|
|
@@ -94,7 +95,14 @@ exports.createProxyAgent = (options, cookies = []) => {
|
|
94
95
|
},
|
95
96
|
options,
|
96
97
|
);
|
97
|
-
|
98
|
+
|
99
|
+
// ProxyAgent type that node httplibrary supports
|
100
|
+
const agent = new HttpsProxyAgent(options.uri);
|
101
|
+
|
102
|
+
// ProxyAgent type that undici supports
|
103
|
+
const dispatcher = new ProxyAgent(proxyOptions);
|
104
|
+
|
105
|
+
return { dispatcher, agent, jar, localAddress: options.localAddress };
|
98
106
|
};
|
99
107
|
|
100
108
|
exports.defaultAgent = createAgent();
|
package/lib/format-utils.js
CHANGED
@@ -6,11 +6,9 @@ const audioEncodingRanks = ["mp4a", "mp3", "vorbis", "aac", "opus", "flac"];
|
|
6
6
|
const videoEncodingRanks = ["mp4v", "avc1", "Sorenson H.283", "MPEG-4 Visual", "VP8", "VP9", "H.264"];
|
7
7
|
|
8
8
|
const getVideoBitrate = format => format.bitrate || 0;
|
9
|
-
const getVideoEncodingRank = format =>
|
10
|
-
videoEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
|
9
|
+
const getVideoEncodingRank = format => videoEncodingRanks.findIndex(enc => format.codecs?.includes(enc));
|
11
10
|
const getAudioBitrate = format => format.audioBitrate || 0;
|
12
|
-
const getAudioEncodingRank = format =>
|
13
|
-
audioEncodingRanks.findIndex(enc => format.codecs && format.codecs.includes(enc));
|
11
|
+
const getAudioEncodingRank = format => audioEncodingRanks.findIndex(enc => format.codecs?.includes(enc));
|
14
12
|
|
15
13
|
/**
|
16
14
|
* Sort formats by a list of functions.
|
package/lib/index.js
CHANGED
@@ -42,9 +42,7 @@ ytdl.cache = {
|
|
42
42
|
ytdl.version = require("../package.json").version;
|
43
43
|
|
44
44
|
const createStream = options => {
|
45
|
-
const stream = new PassThrough({
|
46
|
-
highWaterMark: (options && options.highWaterMark) || 1024 * 512,
|
47
|
-
});
|
45
|
+
const stream = new PassThrough({ highWaterMark: options?.highWaterMark || 1024 * 512 });
|
48
46
|
stream._destroy = () => {
|
49
47
|
stream.destroyed = true;
|
50
48
|
};
|
@@ -105,7 +103,11 @@ const downloadFromInfoCallback = (stream, info, options) => {
|
|
105
103
|
localAddress: utils.getRandomIPv6(options.IPv6Block),
|
106
104
|
});
|
107
105
|
}
|
106
|
+
|
108
107
|
if (options.agent) {
|
108
|
+
// Set agent on both the miniget and m3u8stream requests
|
109
|
+
options.requestOptions.agent = options.agent.agent;
|
110
|
+
|
109
111
|
if (options.agent.jar) {
|
110
112
|
utils.setPropInsensitive(
|
111
113
|
options.requestOptions.headers,
|
@@ -129,6 +131,7 @@ const downloadFromInfoCallback = (stream, info, options) => {
|
|
129
131
|
chunkReadahead: +info.live_chunk_readahead,
|
130
132
|
begin: options.begin || (format.isLive && Date.now()),
|
131
133
|
liveBuffer: options.liveBuffer,
|
134
|
+
// Now we have passed not only custom "dispatcher" with undici ProxyAgent, but also "agent" field which is compatible for node http
|
132
135
|
requestOptions: options.requestOptions,
|
133
136
|
parser: format.isDashMPD ? "dash-mpd" : "m3u8",
|
134
137
|
id: format.itag,
|
@@ -148,9 +151,9 @@ const downloadFromInfoCallback = (stream, info, options) => {
|
|
148
151
|
let shouldBeChunked = dlChunkSize !== 0 && (!format.hasAudio || !format.hasVideo);
|
149
152
|
|
150
153
|
if (shouldBeChunked) {
|
151
|
-
let start =
|
154
|
+
let start = options.range?.start || 0;
|
152
155
|
let end = start + dlChunkSize;
|
153
|
-
const rangeEnd = options.range
|
156
|
+
const rangeEnd = options.range?.end;
|
154
157
|
|
155
158
|
contentLength = options.range
|
156
159
|
? (rangeEnd ? rangeEnd + 1 : parseInt(format.contentLength)) - start
|
@@ -183,7 +186,7 @@ const downloadFromInfoCallback = (stream, info, options) => {
|
|
183
186
|
if (options.begin) {
|
184
187
|
format.url += `&begin=${parseTimestamp(options.begin)}`;
|
185
188
|
}
|
186
|
-
if (options.range
|
189
|
+
if (options.range?.start || options.range?.end) {
|
187
190
|
requestOptions.headers = Object.assign({}, requestOptions.headers, {
|
188
191
|
Range: `bytes=${options.range.start || "0"}-${options.range.end || ""}`,
|
189
192
|
});
|
package/lib/info-extras.js
CHANGED
@@ -7,7 +7,7 @@ const TITLE_TO_CATEGORY = {
|
|
7
7
|
song: { name: "Music", url: "https://music.youtube.com/" },
|
8
8
|
};
|
9
9
|
|
10
|
-
const getText = obj =>
|
10
|
+
const getText = obj => obj?.runs?.[0]?.text ?? obj?.simpleText;
|
11
11
|
|
12
12
|
/**
|
13
13
|
* Get video media.
|
@@ -38,7 +38,7 @@ exports.getMedia = info => {
|
|
38
38
|
let contents = row.metadataRowRenderer.contents[0];
|
39
39
|
media[title] = getText(contents);
|
40
40
|
let runs = contents.runs;
|
41
|
-
if (runs
|
41
|
+
if (runs?.[0]?.navigationEndpoint) {
|
42
42
|
media[`${title}_url`] = new URL(
|
43
43
|
runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url,
|
44
44
|
BASE_URL,
|
@@ -76,7 +76,7 @@ exports.getMedia = info => {
|
|
76
76
|
return media;
|
77
77
|
};
|
78
78
|
|
79
|
-
const isVerified = badges => !!
|
79
|
+
const isVerified = badges => !!badges?.find(b => b.metadataBadgeRenderer.tooltip === "Verified");
|
80
80
|
|
81
81
|
/**
|
82
82
|
* Get video author.
|
@@ -91,12 +91,7 @@ exports.getAuthor = info => {
|
|
91
91
|
verified = false;
|
92
92
|
try {
|
93
93
|
let results = info.response.contents.twoColumnWatchNextResults.results.results.contents;
|
94
|
-
let v = results.find(
|
95
|
-
v2 =>
|
96
|
-
v2.videoSecondaryInfoRenderer &&
|
97
|
-
v2.videoSecondaryInfoRenderer.owner &&
|
98
|
-
v2.videoSecondaryInfoRenderer.owner.videoOwnerRenderer,
|
99
|
-
);
|
94
|
+
let v = results.find(v2 => v2?.videoSecondaryInfoRenderer?.owner?.videoOwnerRenderer);
|
100
95
|
let videoOwnerRenderer = v.videoSecondaryInfoRenderer.owner.videoOwnerRenderer;
|
101
96
|
channelId = videoOwnerRenderer.navigationEndpoint.browseEndpoint.browseId;
|
102
97
|
thumbnails = videoOwnerRenderer.thumbnail.thumbnails.map(thumbnail => {
|
@@ -109,12 +104,12 @@ exports.getAuthor = info => {
|
|
109
104
|
// Do nothing.
|
110
105
|
}
|
111
106
|
try {
|
112
|
-
let videoDetails = info.player_response.microformat
|
113
|
-
let id =
|
107
|
+
let videoDetails = info.player_response.microformat?.playerMicroformatRenderer;
|
108
|
+
let id = videoDetails?.channelId || channelId || info.player_response.videoDetails.channelId;
|
114
109
|
let author = {
|
115
110
|
id: id,
|
116
|
-
name: videoDetails
|
117
|
-
user: videoDetails
|
111
|
+
name: videoDetails?.ownerChannelName ?? info.player_response.videoDetails.author,
|
112
|
+
user: videoDetails?.ownerProfileUrl.split("/").slice(-1)[0] ?? null,
|
118
113
|
channel_url: `https://www.youtube.com/channel/${id}`,
|
119
114
|
external_channel_url: videoDetails ? `https://www.youtube.com/channel/${videoDetails.externalChannelId}` : "",
|
120
115
|
user_url: videoDetails ? new URL(videoDetails.ownerProfileUrl, BASE_URL).toString() : "",
|
@@ -138,7 +133,7 @@ const parseRelatedVideo = (details, rvsParams) => {
|
|
138
133
|
let shortViewCount = getText(details.shortViewCountText);
|
139
134
|
let rvsDetails = rvsParams.find(elem => elem.id === details.videoId);
|
140
135
|
if (!/^\d/.test(shortViewCount)) {
|
141
|
-
shortViewCount =
|
136
|
+
shortViewCount = rvsDetails?.short_view_count_text || "";
|
142
137
|
}
|
143
138
|
viewCount = (/^\d/.test(viewCount) ? viewCount : shortViewCount).split(" ")[0];
|
144
139
|
let browseEndpoint = details.shortBylineText.runs[0].navigationEndpoint.browseEndpoint;
|
@@ -173,12 +168,14 @@ const parseRelatedVideo = (details, rvsParams) => {
|
|
173
168
|
view_count: viewCount.replace(/,/g, ""),
|
174
169
|
length_seconds: details.lengthText
|
175
170
|
? Math.floor(parseTimestamp(getText(details.lengthText)) / 1000)
|
176
|
-
: rvsParams
|
171
|
+
: rvsParams
|
172
|
+
? `${rvsParams.length_seconds}`
|
173
|
+
: undefined,
|
177
174
|
thumbnails: details.thumbnail.thumbnails,
|
178
175
|
richThumbnails: details.richThumbnail
|
179
176
|
? details.richThumbnail.movingThumbnailRenderer.movingThumbnailDetails.thumbnails
|
180
177
|
: [],
|
181
|
-
isLive: !!
|
178
|
+
isLive: !!details.badges?.find(b => b.metadataBadgeRenderer.label === "LIVE NOW"),
|
182
179
|
};
|
183
180
|
|
184
181
|
utils.deprecate(
|
@@ -288,7 +285,7 @@ exports.cleanVideoDetails = (videoDetails, info) => {
|
|
288
285
|
|
289
286
|
// Use more reliable `lengthSeconds` from `playerMicroformatRenderer`.
|
290
287
|
videoDetails.lengthSeconds =
|
291
|
-
|
288
|
+
info.player_response.microformat?.playerMicroformatRenderer?.lengthSeconds ||
|
292
289
|
info.player_response.videoDetails.lengthSeconds;
|
293
290
|
return videoDetails;
|
294
291
|
};
|
@@ -300,11 +297,7 @@ exports.cleanVideoDetails = (videoDetails, info) => {
|
|
300
297
|
* @returns {Array.<Object>}
|
301
298
|
*/
|
302
299
|
exports.getStoryboards = info => {
|
303
|
-
const parts =
|
304
|
-
info.player_response.storyboards &&
|
305
|
-
info.player_response.storyboards.playerStoryboardSpecRenderer &&
|
306
|
-
info.player_response.storyboards.playerStoryboardSpecRenderer.spec &&
|
307
|
-
info.player_response.storyboards.playerStoryboardSpecRenderer.spec.split("|");
|
300
|
+
const parts = info.player_response?.storyboards?.playerStoryboardSpecRenderer?.spec?.split("|");
|
308
301
|
|
309
302
|
if (!parts) return [];
|
310
303
|
|
@@ -342,16 +335,10 @@ exports.getStoryboards = info => {
|
|
342
335
|
* @returns {Array.<Object>}
|
343
336
|
*/
|
344
337
|
exports.getChapters = info => {
|
345
|
-
const playerOverlayRenderer =
|
346
|
-
|
347
|
-
const
|
348
|
-
|
349
|
-
playerOverlayRenderer.decoratedPlayerBarRenderer &&
|
350
|
-
playerOverlayRenderer.decoratedPlayerBarRenderer.decoratedPlayerBarRenderer &&
|
351
|
-
playerOverlayRenderer.decoratedPlayerBarRenderer.decoratedPlayerBarRenderer.playerBar;
|
352
|
-
const markersMap =
|
353
|
-
playerBar && playerBar.multiMarkersPlayerBarRenderer && playerBar.multiMarkersPlayerBarRenderer.markersMap;
|
354
|
-
const marker = Array.isArray(markersMap) && markersMap.find(m => m.value && Array.isArray(m.value.chapters));
|
338
|
+
const playerOverlayRenderer = info.response?.playerOverlays?.playerOverlayRenderer;
|
339
|
+
const playerBar = playerOverlayRenderer?.decoratedPlayerBarRenderer?.decoratedPlayerBarRenderer?.playerBar;
|
340
|
+
const markersMap = playerBar?.multiMarkersPlayerBarRenderer?.markersMap;
|
341
|
+
const marker = Array.isArray(markersMap) && markersMap.find(m => Array.isArray(m.value?.chapters));
|
355
342
|
if (!marker) return [];
|
356
343
|
const chapters = marker.value.chapters;
|
357
344
|
|