11import os from 'os'
22import fs from 'fs/promises'
3- import { createWriteStream } from 'fs'
4- import { pipeline } from 'stream/promises'
53import path from 'path'
64import childProcess from 'child_process'
75import treeKill from 'tree-kill'
86import sanitizeFilename from 'sanitize-filename'
97import { app , BrowserWindow , powerSaveBlocker } from 'electron'
10- import { fetchYtStream } from './fetchYtStream.js'
118
129let statusUpdateCallback = null ,
1310 donateUpdateCallback = null
11+ let ytCacheDir = null
1412let curItems = [ ] ,
15- curChildProcess = null ,
16- curYtdlAbortController = null
13+ curChildProcess = null
1714let curProgressFtStemIdx = null
1815
1916function getPathToThirdPartyApps ( ) {
@@ -58,16 +55,24 @@ const PATH_TO_DEMUCS = PATH_TO_THIRD_PARTY_APPS
5855const 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
6161const DEMUCS_EXE_NAME = PATH_TO_THIRD_PARTY_APPS ? 'demucs-cxfreeze' : 'demucs'
6262const FFMPEG_EXE_NAME = 'ffmpeg'
63+ const YT_DLP_EXE_NAME = process . platform === 'darwin' ? 'yt-dlp_macos' : 'yt-dlp'
6364const 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}
6768if ( 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}
7277const TMP_PREFIX = 'StemRoller-'
7378
@@ -79,12 +84,6 @@ function getJobCount() {
7984}
8085
8186function 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
182202async 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
0 commit comments