-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.ts
77 lines (71 loc) · 2.09 KB
/
mod.ts
1
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
import { decipherFormatURLs } from "./decipher.ts";
import { Format, VideoInfo } from "./types.ts";
export const BASE_URL = "https://www.youtube.com/watch?v=";
const USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0";
export async function ytDownload(
id: string,
options: Options = { hasAudio: true, hasVideo: true }
) {
const formats = await getFormats(id, options);
if (formats.length === 0) {
throw "No matching formats found";
}
return getDataStream(formats[0]);
}
export async function getVideoInfo(id: string): Promise<undefined | VideoInfo> {
try {
const url = `${BASE_URL}${id}&hl=en`;
const resp = await fetch(url, {
headers: {
"User-Agent": USER_AGENT,
},
});
const html = await resp.text();
const responses = html.match(
/ytInitialPlayerResponse = ([\S\s]+)?(?=;var meta)/
);
const playerInfo = JSON.parse(responses![1]);
const playerURL = html.match(/"jsUrl":"([^"]+)"/)![1];
await decipherFormatURLs(playerInfo, playerURL);
return playerInfo;
} catch {
return undefined;
}
}
type Options = {
hasVideo?: boolean;
hasAudio?: boolean;
mimeType?: string;
};
export async function getFormats(
id: string,
{ hasAudio, hasVideo, mimeType }: Options = {}
) {
const info = await getVideoInfo(id);
if (info === undefined) {
throw "Video not found";
}
const result = [
...info.streamingData.adaptiveFormats,
...info.streamingData.formats,
];
result.sort((a, b) => b.bitrate - a.bitrate);
return result
.filter(
(format) =>
hasAudio === undefined ||
(hasAudio && format.audioQuality !== undefined) ||
(!hasAudio && format.audioQuality === undefined)
)
.filter(
(format) =>
hasVideo === undefined ||
(hasVideo && format.qualityLabel !== undefined) ||
(!hasVideo && format.qualityLabel === undefined)
)
.filter((format) => mimeType === undefined || mimeType === format.mimeType);
}
export async function getDataStream(format: Format) {
return (await fetch(format.url)).body!;
}