-
Notifications
You must be signed in to change notification settings - Fork 1.8k
youtube.js yt-dlp switch #4225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
youtube.js yt-dlp switch #4225
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,10 @@ | ||||||||||||||||||
| import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; | ||||||||||||||||||
| import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs'; | ||||||||||||||||||
| import { join } from 'node:path'; | ||||||||||||||||||
| import { randomBytes } from 'node:crypto'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { app, type BrowserWindow, dialog, ipcMain } from 'electron'; | ||||||||||||||||||
| import { spawn, spawnSync } from 'node:child_process'; | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <importPlugin/order> reported by reviewdog 🐶
Suggested change
Comment on lines
5
to
+6
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <importPlugin/order> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| import { which } from 'which'; | ||||||||||||||||||
| import { | ||||||||||||||||||
| Innertube, | ||||||||||||||||||
| UniversalCache, | ||||||||||||||||||
|
|
@@ -441,6 +443,171 @@ async function downloadSongUnsafe( | |||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // If configured to use yt-dlp, delegate downloading to the external binary | ||||||||||||||||||
| if ((config.engine ?? 'youtube.js') === 'yt-dlp') { | ||||||||||||||||||
| const findYtdlpExecutable = async (preferred?: string) => { | ||||||||||||||||||
| const candidates: (string | undefined)[] = []; | ||||||||||||||||||
| if (preferred) candidates.push(preferred); | ||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| // Try to find yt-dlp using the which command | ||||||||||||||||||
| try { | ||||||||||||||||||
| const foundPath = await which('yt-dlp', { nothrow: true }); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-assignment> reported by reviewdog 🐶
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-call> reported by reviewdog 🐶 |
||||||||||||||||||
| if (foundPath) return foundPath; | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-return> reported by reviewdog 🐶 |
||||||||||||||||||
| } catch (_) { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||
| // ignore if which fails | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for (const c of candidates) { | ||||||||||||||||||
| if (!c) continue; | ||||||||||||||||||
| try { | ||||||||||||||||||
| // Try running `--version` to check it's executable | ||||||||||||||||||
| const res = spawnSync(c, ['--version'], { encoding: 'utf8', stdio: 'ignore' }); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| if (res && res.status === 0) return c; | ||||||||||||||||||
| } catch (_) { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||
| // ignore and try next | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return null; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const findFfmpegExecutable = async (preferred?: string) => { | ||||||||||||||||||
| const candidates: (string | undefined)[] = []; | ||||||||||||||||||
| if (preferred) candidates.push(preferred); | ||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| // Try to find ffmpeg using the which command | ||||||||||||||||||
| try { | ||||||||||||||||||
| const foundPath = await which('ffmpeg', { nothrow: true }); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-assignment> reported by reviewdog 🐶
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-call> reported by reviewdog 🐶 |
||||||||||||||||||
| if (foundPath) return foundPath; | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-return> reported by reviewdog 🐶 |
||||||||||||||||||
| } catch (_) { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||
| // ignore if which fails | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| return null; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| const ytdlpExecutable = await findYtdlpExecutable(config.ytdlpPath ?? undefined); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-assignment> reported by reviewdog 🐶
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| const ffmpegPath = await findFfmpegExecutable(config.ytdlpFfmpegPath ?? undefined); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-assignment> reported by reviewdog 🐶
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| // Check if both yt-dlp and ffmpeg are available | ||||||||||||||||||
| if (!ytdlpExecutable) { | ||||||||||||||||||
| dialog.showMessageBox(win, { | ||||||||||||||||||
| type: 'error', | ||||||||||||||||||
| buttons: ['OK'], | ||||||||||||||||||
| title: t('plugins.downloader.backend.dialog.ytdlp-not-found.title') || 'yt-dlp introuvable', | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| message: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-not-found.message') || | ||||||||||||||||||
| "yt-dlp n'a pas été trouvé. Veuillez installer yt-dlp ou indiquer le chemin complet dans le menu du plugin.", | ||||||||||||||||||
| }); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| if (!ffmpegPath) { | ||||||||||||||||||
| dialog.showMessageBox(win, { | ||||||||||||||||||
| type: 'error', | ||||||||||||||||||
| buttons: ['OK'], | ||||||||||||||||||
| title: t('plugins.downloader.backend.dialog.ffmpeg-not-found.title') || 'ffmpeg introuvable', | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| message: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ffmpeg-not-found.message') || | ||||||||||||||||||
| "ffmpeg n'a pas été trouvé. Veuillez installer ffmpeg ou indiquer le chemin complet dans le menu du plugin.", | ||||||||||||||||||
| }); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| const baseName = filename.replace(new RegExp(`\\.${targetFileExtension}$`), ''); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| const outputTemplate = | ||||||||||||||||||
| targetFileExtension === 'mp3' | ||||||||||||||||||
| ? join(dir, `${baseName}.mp3`) | ||||||||||||||||||
| : join(dir, `${baseName}.%(ext)s`); | ||||||||||||||||||
|
|
||||||||||||||||||
| const urlToDownload = isId ? `https://www.youtube.com/watch?v=${id}` : idOrUrl; | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| const args: string[] = ['--no-playlist', '--add-metadata', '--embed-thumbnail', '--output', outputTemplate]; | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| if (targetFileExtension === 'mp3') { | ||||||||||||||||||
| args.unshift('--extract-audio', '--audio-format', 'mp3', '--audio-quality', '0'); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| } else { | ||||||||||||||||||
| args.unshift('-f', 'best'); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Pass ffmpeg location to yt-dlp | ||||||||||||||||||
| args.push('--ffmpeg-location', ffmpegPath); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-argument> reported by reviewdog 🐶 |
||||||||||||||||||
|
|
||||||||||||||||||
| sendFeedback(t('plugins.downloader.backend.feedback.downloading'), 2); | ||||||||||||||||||
|
|
||||||||||||||||||
| await new Promise<void>((resolve, reject) => { | ||||||||||||||||||
| try { | ||||||||||||||||||
| const proc = spawn(ytdlpExecutable, [...args, urlToDownload], { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <@typescript-eslint/no-unsafe-argument> reported by reviewdog 🐶 |
||||||||||||||||||
| stdio: ['ignore', 'pipe', 'pipe'], | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| const outChunks: string[] = []; | ||||||||||||||||||
| const errChunks: string[] = []; | ||||||||||||||||||
|
|
||||||||||||||||||
| proc.stdout.on('data', (d) => { | ||||||||||||||||||
| const s = String(d); | ||||||||||||||||||
| outChunks.push(s); | ||||||||||||||||||
| sendFeedback(s); | ||||||||||||||||||
| }); | ||||||||||||||||||
| proc.stderr.on('data', (d) => { | ||||||||||||||||||
| const s = String(d); | ||||||||||||||||||
| errChunks.push(s); | ||||||||||||||||||
| sendFeedback(s); | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| proc.on('close', (code) => { | ||||||||||||||||||
| const out = outChunks.join(''); | ||||||||||||||||||
| const err = errChunks.join(''); | ||||||||||||||||||
| if (code === 0) { | ||||||||||||||||||
| sendFeedback(null, -1); | ||||||||||||||||||
| resolve(); | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const tail = (str: string, max = 2000) => | ||||||||||||||||||
| str.length > max ? '...\n' + str.slice(-max) : str; | ||||||||||||||||||
|
|
||||||||||||||||||
| dialog.showMessageBox(win, { | ||||||||||||||||||
| type: 'error', | ||||||||||||||||||
| buttons: ['OK'], | ||||||||||||||||||
| defaultId: 0, | ||||||||||||||||||
| title: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-error.title') || | ||||||||||||||||||
| `yt-dlp erreur`, | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <stylistic/quotes> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| message: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-error.message') || | ||||||||||||||||||
| `yt-dlp a échoué avec le code ${code}`, | ||||||||||||||||||
| detail: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-error.detail') || | ||||||||||||||||||
| `Derniers messages d'erreur:\n${tail(err)}\n\nSortie:\n${tail(out)}`, | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| reject(new Error(`yt-dlp exited with code ${code}: ${tail(err, 500)}`)); | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Suggested change
|
||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| proc.on('error', (err: NodeJS.ErrnoException) => { | ||||||||||||||||||
| if (err.code === 'EACCES') { | ||||||||||||||||||
| dialog.showMessageBox(win, { | ||||||||||||||||||
| type: 'error', | ||||||||||||||||||
| buttons: ['OK'], | ||||||||||||||||||
| title: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-permission.title') || | ||||||||||||||||||
| 'Permission refusée', | ||||||||||||||||||
| message: | ||||||||||||||||||
| t('plugins.downloader.backend.dialog.ytdlp-permission.message') || | ||||||||||||||||||
| `Impossible d'exécuter ${ytdlpExecutable} (EACCES). Rendre le fichier exécutable : chmod +x ${ytdlpExecutable} ou choisissez un autre chemin dans le menu du plugin.`, | ||||||||||||||||||
| }); | ||||||||||||||||||
| } | ||||||||||||||||||
| reject(err); | ||||||||||||||||||
| }); | ||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| reject(err); | ||||||||||||||||||
| } | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
| // ytdlp already wrote the file to disk using the output template. We're done. | ||||||||||||||||||
| return; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const stream = await info.download(downloadOptions); | ||||||||||||||||||
|
|
||||||||||||||||||
| console.info( | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'readdirSync' is defined but never used.