Skip to content

Commit 01c7d11

Browse files
committed
yt-dlp functions
1 parent a35abc6 commit 01c7d11

3 files changed

Lines changed: 92 additions & 78 deletions

File tree

main-src/main.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import xxhash from 'xxhash-wasm'
1010
import serve from 'electron-serve'
1111
import Store from 'electron-store'
1212
import * as processQueue from './processQueue.js'
13-
import { searchYt, deleteYtCacheDir } from './fetchYtStream.js'
13+
import { searchYt } from './searchYt.js'
1414

1515
let electronStore = null
1616

@@ -306,7 +306,7 @@ async function checkForMsvcRuntime() {
306306
}
307307

308308
async function main() {
309-
await deleteYtCacheDir()
309+
await processQueue.deleteYtCacheDir()
310310
await processQueue.deleteTmpFolders()
311311

312312
app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
@@ -364,7 +364,7 @@ async function main() {
364364
})
365365

366366
app.on('window-all-closed', async () => {
367-
await deleteYtCacheDir()
367+
await processQueue.deleteYtCacheDir()
368368
await processQueue.setItems([])
369369
await processQueue.deleteTmpFolders()
370370
app.quit()

main-src/processQueue.js

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import os from 'os'
22
import fs from 'fs/promises'
3-
import { createWriteStream } from 'fs'
4-
import { pipeline } from 'stream/promises'
53
import path from 'path'
64
import childProcess from 'child_process'
75
import treeKill from 'tree-kill'
86
import sanitizeFilename from 'sanitize-filename'
97
import { app, BrowserWindow, powerSaveBlocker } from 'electron'
10-
import { fetchYtStream } from './fetchYtStream.js'
118

129
let statusUpdateCallback = null,
1310
donateUpdateCallback = null
11+
let ytCacheDir = null
1412
let curItems = [],
15-
curChildProcess = null,
16-
curYtdlAbortController = null
13+
curChildProcess = null
1714
let curProgressFtStemIdx = null
1815

1916
function getPathToThirdPartyApps() {
@@ -58,16 +55,24 @@ const PATH_TO_DEMUCS = PATH_TO_THIRD_PARTY_APPS
5855
const PATH_TO_FFMPEG = PATH_TO_THIRD_PARTY_APPS
5956
? path.join(PATH_TO_THIRD_PARTY_APPS, 'ffmpeg', 'bin')
6057
: null
58+
const PATH_TO_YT_DLP = PATH_TO_THIRD_PARTY_APPS
59+
? path.join(PATH_TO_THIRD_PARTY_APPS, 'yt-dlp')
60+
: null
6161
const DEMUCS_EXE_NAME = PATH_TO_THIRD_PARTY_APPS ? 'demucs-cxfreeze' : 'demucs'
6262
const FFMPEG_EXE_NAME = 'ffmpeg'
63+
const YT_DLP_EXE_NAME = process.platform === 'darwin' ? 'yt-dlp_macos' : 'yt-dlp'
6364
const CHILD_PROCESS_ENV = {
6465
...process.env,
6566
LANG: null, // Will be set when ready to split, since we can only check system locale after `app` is ready
6667
}
6768
if (PATH_TO_THIRD_PARTY_APPS) {
6869
// Override the system's PATH with the path to our own bundled third-party apps
6970
CHILD_PROCESS_ENV.PATH =
70-
PATH_TO_DEMUCS + (process.platform === 'win32' ? ';' : ':') + PATH_TO_FFMPEG
71+
PATH_TO_DEMUCS +
72+
(process.platform === 'win32' ? ';' : ':') +
73+
PATH_TO_FFMPEG +
74+
(process.platform === 'win32' ? ';' : ':') +
75+
PATH_TO_YT_DLP
7176
}
7277
const TMP_PREFIX = 'StemRoller-'
7378

@@ -79,12 +84,6 @@ function getJobCount() {
7984
}
8085

8186
function killCurChildProcess() {
82-
if (curYtdlAbortController) {
83-
console.log('Aborting ytdl pipeline')
84-
curYtdlAbortController.abort()
85-
curYtdlAbortController = null
86-
}
87-
8887
if (curChildProcess) {
8988
try {
9089
console.trace(`treeKill process ${curChildProcess.pid}`)
@@ -130,7 +129,7 @@ function updateDemucsProgress(videoId, data) {
130129
}
131130
}
132131

133-
function spawnAndWait(videoId, cwd, command, args, isDemucs, isHybrid) {
132+
function spawnAndWait(videoId, cwd, command, args, isDemucs) {
134133
return new Promise((resolve, reject) => {
135134
killCurChildProcess()
136135

@@ -168,15 +167,36 @@ function spawnAndWait(videoId, cwd, command, args, isDemucs, isHybrid) {
168167
})
169168
}
170169

171-
async function asyncYtdl(videoId, downloadPath) {
172-
curYtdlAbortController = new AbortController()
170+
async function createYtCacheDir() {
171+
if (ytCacheDir) {
172+
return
173+
}
173174

174-
const ytdlStream = await fetchYtStream(videoId)
175-
const fileStream = createWriteStream(downloadPath)
176-
await pipeline(ytdlStream, fileStream, {
177-
signal: curYtdlAbortController.signal,
178-
})
179-
curYtdlAbortController = null
175+
ytCacheDir = await fs.mkdtemp(path.join(os.tmpdir(), 'StemRoller-cache-'))
176+
}
177+
178+
export async function deleteYtCacheDir() {
179+
if (!ytCacheDir) {
180+
return
181+
}
182+
183+
try {
184+
await fs.rm(ytCacheDir, {
185+
recursive: true,
186+
maxRetries: 5,
187+
retryDelay: 1000,
188+
})
189+
console.log(`Deleted cache folder "${ytCacheDir}"`)
190+
} catch (error) {
191+
console.trace(error)
192+
}
193+
}
194+
195+
async function downloadYoutube(videoId, downloadPath) {
196+
await createYtCacheDir()
197+
const videoUrl = `https://www.youtube.com/watch?v=${videoId}`
198+
const ytDlpExeArgs = ['-f', 'bestaudio', '--cache-dir', ytCacheDir, '-o', path.basename(downloadPath), '--newline', '--progress', videoUrl]
199+
await spawnAndWait(videoId, path.dirname(downloadPath), YT_DLP_EXE_NAME, ytDlpExeArgs, false)
180200
}
181201

182202
async function findDemucsOutputDir(basePath) {
@@ -268,7 +288,7 @@ async function _processVideo(video, tmpDir) {
268288
const ytFilename = 'yt-audio'
269289
const ytPath = path.join(tmpDir, ytFilename)
270290
console.log(`Downloading YouTube video "${video.videoId}"; storing in "${ytPath}"`)
271-
await asyncYtdl(video.videoId, ytPath)
291+
await downloadYoutube(video.videoId, ytPath)
272292
mediaPath = ytPath
273293
} else if (video.mediaSource === 'local') {
274294
mediaPath = video.localInputPath
Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,47 @@
1-
import { Innertube } from 'youtubei.js'
2-
3-
let searchInnertube = null
4-
5-
const setupSearchInnertube = async () => {
6-
if (searchInnertube) {
7-
return
8-
}
9-
10-
searchInnertube = await Innertube.create({ retrieve_player: false })
11-
}
12-
13-
export const searchYt = async (query) => {
14-
await setupSearchInnertube()
15-
16-
const innertubeResults = await searchInnertube.search(query, {
17-
type: 'video',
18-
})
19-
20-
const plainResults = []
21-
22-
for (const result of innertubeResults.results) {
23-
if (
24-
result.type !== 'Video' ||
25-
typeof result.video_id === 'undefined' ||
26-
typeof result.title === 'undefined' ||
27-
typeof result.title.text === 'undefined' ||
28-
typeof result.author === 'undefined' ||
29-
typeof result.author.name === 'undefined' ||
30-
typeof result.length_text === 'undefined' ||
31-
typeof result.length_text.text === 'undefined'
32-
) {
33-
continue
34-
}
35-
36-
plainResults.push({
37-
videoId: result.video_id,
38-
title: result.title.text,
39-
length_text: result.length_text.text,
40-
author: {
41-
name: result.author.name,
42-
},
43-
})
44-
}
45-
46-
return plainResults
47-
}
48-
49-
export const fetchYtStream = async (videoId) => {
50-
//
51-
// TODO
52-
//
53-
}
1+
import { Innertube } from 'youtubei.js'
2+
3+
let searchInnertube = null
4+
5+
const setupSearchInnertube = async () => {
6+
if (searchInnertube) {
7+
return
8+
}
9+
10+
searchInnertube = await Innertube.create({ retrieve_player: false })
11+
}
12+
13+
export const searchYt = async (query) => {
14+
await setupSearchInnertube()
15+
16+
const innertubeResults = await searchInnertube.search(query, {
17+
type: 'video',
18+
})
19+
20+
const plainResults = []
21+
22+
for (const result of innertubeResults.results) {
23+
if (
24+
result.type !== 'Video' ||
25+
typeof result.video_id === 'undefined' ||
26+
typeof result.title === 'undefined' ||
27+
typeof result.title.text === 'undefined' ||
28+
typeof result.author === 'undefined' ||
29+
typeof result.author.name === 'undefined' ||
30+
typeof result.length_text === 'undefined' ||
31+
typeof result.length_text.text === 'undefined'
32+
) {
33+
continue
34+
}
35+
36+
plainResults.push({
37+
videoId: result.video_id,
38+
title: result.title.text,
39+
length_text: result.length_text.text,
40+
author: {
41+
name: result.author.name,
42+
},
43+
})
44+
}
45+
46+
return plainResults
47+
}

0 commit comments

Comments
 (0)