Skip to content

Commit a35abc6

Browse files
committed
Add yt-dlp download
1 parent 926f4ee commit a35abc6

8 files changed

Lines changed: 951 additions & 3417 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ node_modules
1414
!*-extra-files/ThirdPartyApps/ffmpeg/bin
1515
*-extra-files/ThirdPartyApps/ffmpeg/bin/**
1616
!*-extra-files/ThirdPartyApps/ffmpeg/bin/.gitkeep
17+
*-extra-files/ThirdPartyApps/yt-dlp/**
18+
!*-extra-files/ThirdPartyApps/yt-dlp/.gitkeep
1719
*-extra-files/Models/**
1820
!*-extra-files/Models/.gitkeep

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ npm i -D
2424

2525
### Linux (Not officially supported)
2626

27-
Install `ffmpeg` globally using your preferred package manager, and install `demucs` globally with `pip`.
27+
Install `ffmpeg` globally using your preferred package manager, and install `demucs` and `yt-dlp` globally with `pip`.
2828
If you get "Couldn't find appropriate backend" errors, try installing `libsox-dev`.
2929

3030
## Run in Development Mode

download-third-party-apps.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,21 @@ async function main() {
125125
]
126126

127127
if (process.platform === 'win32') {
128-
downloads.push([
129-
'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip',
130-
path.join(
131-
`${winOrMac}-extra-files`,
132-
'ThirdPartyApps',
133-
'ffmpeg',
134-
'ffmpeg-release-essentials.zip'
135-
),
136-
])
128+
downloads.push(
129+
[
130+
'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip',
131+
path.join(
132+
`${winOrMac}-extra-files`,
133+
'ThirdPartyApps',
134+
'ffmpeg',
135+
'ffmpeg-release-essentials.zip'
136+
),
137+
],
138+
[
139+
'https://github.com/yt-dlp/yt-dlp/releases/download/2025.09.26/yt-dlp_win.zip',
140+
path.join(`${winOrMac}-extra-files`, 'ThirdPartyApps', 'yt-dlp', 'yt-dlp_win.zip'),
141+
]
142+
)
137143
} else if (process.platform === 'darwin') {
138144
downloads.push(
139145
[
@@ -143,6 +149,10 @@ async function main() {
143149
[
144150
'https://evermeet.cx/ffmpeg/getrelease/ffprobe/zip',
145151
path.join(`${winOrMac}-extra-files`, 'ThirdPartyApps', 'ffmpeg', 'ffprobe-release.zip'),
152+
],
153+
[
154+
'https://github.com/yt-dlp/yt-dlp/releases/download/2025.09.26/yt-dlp_macos.zip',
155+
path.join(`${winOrMac}-extra-files`, 'ThirdPartyApps', 'yt-dlp', 'yt-dlp_macos.zip'),
146156
]
147157
)
148158
}

mac-extra-files/ThirdPartyApps/yt-dlp/.gitkeep

Whitespace-only changes.

main-src/fetchYtStream.js

Lines changed: 5 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,6 @@
1-
// This file is partially copied from: https://github.com/LuanRT/BgUtils/blob/main/examples/node/index.ts
1+
import { Innertube } from 'youtubei.js'
22

3-
import fs from 'fs/promises'
4-
import path from 'path'
5-
import os from 'os'
6-
import { Innertube, ProtoUtils, Utils, UniversalCache, Log } from 'youtubei.js'
7-
import { BG, USER_AGENT } from 'bgutils-js'
8-
import { JSDOM } from 'jsdom'
9-
10-
let cacheDir = null
113
let searchInnertube = null
12-
let downloadInnertube = null
13-
let downloadPoToken = null
14-
15-
const createYtCacheDir = async () => {
16-
if (cacheDir) {
17-
return
18-
}
19-
20-
cacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'StemRoller-cache-'))
21-
}
22-
23-
export const deleteYtCacheDir = async () => {
24-
if (!cacheDir) {
25-
return
26-
}
27-
28-
try {
29-
await fs.rm(cacheDir, {
30-
recursive: true,
31-
maxRetries: 5,
32-
retryDelay: 1000,
33-
})
34-
console.log(`Deleted cache folder "${cacheDir}"`)
35-
} catch (error) {
36-
console.trace(error)
37-
}
38-
}
394

405
const setupSearchInnertube = async () => {
416
if (searchInnertube) {
@@ -45,46 +10,6 @@ const setupSearchInnertube = async () => {
4510
searchInnertube = await Innertube.create({ retrieve_player: false })
4611
}
4712

48-
const setupDownloadInnertube = async () => {
49-
if (downloadInnertube) {
50-
return
51-
}
52-
53-
await setupSearchInnertube()
54-
55-
if (process.env.NODE_ENV === 'dev') {
56-
Log.setLevel(Log.Level.INFO)
57-
} else {
58-
Log.setLevel(Log.Level.WARNING)
59-
}
60-
61-
const visitorData = ProtoUtils.encodeVisitorData(
62-
Utils.generateRandomString(11),
63-
Math.floor(Date.now() / 1000)
64-
)
65-
downloadPoToken = await getPo(visitorData)
66-
67-
await createYtCacheDir()
68-
69-
downloadInnertube = await Innertube.create({
70-
po_token: downloadPoToken,
71-
visitor_data: visitorData,
72-
cache: new UniversalCache(true, cacheDir),
73-
generate_session_locally: true,
74-
player_id: '0004de42', // https://github.com/LuanRT/YouTube.js/issues/1043#issuecomment-3328154175
75-
})
76-
}
77-
78-
export const fetchYtStream = async (videoId) => {
79-
await setupDownloadInnertube()
80-
81-
const ytStream = await downloadInnertube.download(videoId, {
82-
type: 'audio',
83-
})
84-
85-
return ytStream
86-
}
87-
8813
export const searchYt = async (query) => {
8914
await setupSearchInnertube()
9015

@@ -121,54 +46,8 @@ export const searchYt = async (query) => {
12146
return plainResults
12247
}
12348

124-
const getPo = async (identifier) => {
125-
const dom = new JSDOM(
126-
'<!DOCTYPE html><html lang="en"><head><title></title></head><body></body></html>',
127-
{
128-
url: 'https://www.youtube.com/',
129-
referrer: 'https://www.youtube.com/',
130-
userAgent: USER_AGENT,
131-
}
132-
)
133-
Object.assign(globalThis, {
134-
window: dom.window,
135-
document: dom.window.document,
136-
location: dom.window.location,
137-
origin: dom.window.origin,
138-
})
139-
if (!Reflect.has(globalThis, 'navigator')) {
140-
Object.defineProperty(globalThis, 'navigator', { value: dom.window.navigator })
141-
}
142-
143-
const requestKey = 'O43z0dpjhgX20SCx4KAo'
144-
145-
const bgConfig = {
146-
fetch,
147-
globalObj: globalThis,
148-
requestKey,
149-
identifier,
150-
}
151-
152-
const bgChallenge = await BG.Challenge.create(bgConfig)
153-
154-
if (!bgChallenge) {
155-
throw new Error('Could not get challenge')
156-
}
157-
158-
const interpreterJavascript =
159-
bgChallenge.interpreterJavascript.privateDoNotAccessOrElseSafeScriptWrappedValue
160-
161-
if (interpreterJavascript) {
162-
new Function(interpreterJavascript)()
163-
} else {
164-
throw new Error('Could not load VM')
165-
}
166-
167-
const poTokenResult = await BG.PoToken.generate({
168-
program: bgChallenge.program,
169-
globalName: bgChallenge.globalName,
170-
bgConfig,
171-
})
172-
173-
return poTokenResult.poToken
49+
export const fetchYtStream = async (videoId) => {
50+
//
51+
// TODO
52+
//
17453
}

0 commit comments

Comments
 (0)