-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat: replace YouTube.js with yt-dlp for audio downloads #3915
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?
feat: replace YouTube.js with yt-dlp for audio downloads #3915
Conversation
digitalnomad91
commented
Sep 19, 2025
- Complete rewrite of downloader plugin to use yt-dlp external tool
- Improved error handling and user feedback with enhanced logging
- Better progress tracking during downloads and conversions
- Added file existence checking with skipExisting option
- Enhanced metadata embedding (thumbnail, ID3 tags, artist/title)
- Streamlined playlist download functionality
- Added configurable yt-dlp path in advanced settings
- Higher quality audio downloads (320K bitrate)
- More robust download process with better error reporting
- Complete rewrite of downloader plugin to use yt-dlp external tool - Improved error handling and user feedback with enhanced logging - Better progress tracking during downloads and conversions - Added file existence checking with skipExisting option - Enhanced metadata embedding (thumbnail, ID3 tags, artist/title) - Streamlined playlist download functionality - Added configurable yt-dlp path in advanced settings - Higher quality audio downloads (320K bitrate) - More robust download process with better error reporting
- Add new feedback messages for yt-dlp download process - Include file existence check messages - Update error handling text for better user experience
- Add chalk dependency for colored terminal output in yt-dlp downloader - Include TypeScript types for chalk - Update pnpm-lock.yaml with new dependencies
const formattedMessage = args.length > 0 | ||
? `${message} ${args.join(' ')}` | ||
: message; |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ·args.length·>·0⏎····?·
${message}·${args.join('·')}⏎···
with ⏎····args.length·>·0·?·
${message}·${args.join('·')}``
const formattedMessage = args.length > 0 | |
? `${message} ${args.join(' ')}` | |
: message; | |
const formattedMessage = | |
args.length > 0 ? `${message} ${args.join(' ')}` : message; |
// Also log to frontend for production debugging | ||
try { | ||
win?.webContents?.executeJavaScript( | ||
`console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');` |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Insert ,
`console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');` | |
`console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');`, |
const macCandidates = [ | ||
'/usr/local/bin/yt-dlp', | ||
'/opt/homebrew/bin/yt-dlp', | ||
]; |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ⏎······'/usr/local/bin/yt-dlp',⏎······'/opt/homebrew/bin/yt-dlp',⏎····
with '/usr/local/bin/yt-dlp',·'/opt/homebrew/bin/yt-dlp'
const macCandidates = [ | |
'/usr/local/bin/yt-dlp', | |
'/opt/homebrew/bin/yt-dlp', | |
]; | |
const macCandidates = ['/usr/local/bin/yt-dlp', '/opt/homebrew/bin/yt-dlp']; |
} | ||
|
||
info = bypassedResult; | ||
const dir = playlistFolder || config.downloadFolder || app.getPath('downloads'); |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Insert ⏎···
const dir = playlistFolder || config.downloadFolder || app.getPath('downloads'); | |
const dir = | |
playlistFolder || config.downloadFolder || app.getPath('downloads'); |
increasePlaylistProgress, | ||
); | ||
if (infoExitCode === 0) { | ||
const expectedFilename = infoOutput.trim().replace('.webm', '.mp3').replace('.m4a', '.mp3'); |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace .trim().replace('.webm',·'.mp3')
with ⏎··········.trim()⏎··········.replace('.webm',·'.mp3')⏎··········
const expectedFilename = infoOutput.trim().replace('.webm', '.mp3').replace('.m4a', '.mp3'); | |
const expectedFilename = infoOutput | |
.trim() | |
.replace('.webm', '.mp3') | |
.replace('.m4a', '.mp3'); |
if (chunk.includes('[ExtractAudio] Destination:')) { | ||
const filename = chunk.match(/\[ExtractAudio\] Destination: (.+)/); | ||
if (filename) { | ||
logToFrontend('info', '✅ Track completed:', path.basename(filename[1])); |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace 'info',·'✅·Track·completed:',·path.basename(filename[1])
with ⏎············'info',⏎············'✅·Track·completed:',⏎············path.basename(filename[1]),⏎··········
logToFrontend('info', '✅ Track completed:', path.basename(filename[1])); | |
logToFrontend( | |
'info', | |
'✅ Track completed:', | |
path.basename(filename[1]), | |
); |
ytDlpProcess.stderr?.on('data', (data: Buffer) => { | ||
const errorChunk = data.toString(); | ||
errorOutput += errorChunk; | ||
logToFrontend('warn', '⚠️ yt-dlp stderr:', errorChunk.trim().substring(0, 200)); |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace 'warn',·'⚠️·yt-dlp·stderr:',·errorChunk.trim().substring(0,·200)
with ⏎········'warn',⏎········'⚠️·yt-dlp·stderr:',⏎········errorChunk.trim().substring(0,·200),⏎······
logToFrontend('warn', '⚠️ yt-dlp stderr:', errorChunk.trim().substring(0, 200)); | |
logToFrontend( | |
'warn', | |
'⚠️ yt-dlp stderr:', | |
errorChunk.trim().substring(0, 200), | |
); |
logToFrontend('error', '📄 Error output:', errorOutput); | ||
throw new Error( | ||
`yt-dlp failed with exit code ${exitCode}: ${errorOutput}` + | ||
`\nCommand: ${ytDlpPath} ${args.join(' ')}` |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace \nCommand:·${ytDlpPath}·${args.join('·')}
with ··
\nCommand:·${ytDlpPath}·${args.join('·')},
`\nCommand: ${ytDlpPath} ${args.join(' ')}` | |
`\nCommand: ${ytDlpPath} ${args.join(' ')}`, |
|
||
logToFrontend('info', '🎉 Playlist download completed successfully!'); | ||
sendFeedback('Download complete!', -1); | ||
sendOsNotification('Download complete!', `Saved to: ${dir}`).catch(() => {}); |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ()·=>·{}
with ⏎······()·=>·{},⏎····
sendOsNotification('Download complete!', `Saved to: ${dir}`).catch(() => {}); | |
sendOsNotification('Download complete!', `Saved to: ${dir}`).catch( | |
() => {}, | |
); |
{ | ||
label: t('plugins.downloader.menu.yt-dlp-location-nice'), | ||
click: async () => { | ||
const ytDlpPathValue = typeof config.advanced?.ytDlpPath === 'string' ? config.advanced.ytDlpPath : ''; |
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.
🚫 [eslint] <prettier/prettier> reported by reviewdog 🐶
Replace ·typeof·config.advanced?.ytDlpPath·===·'string'·?·config.advanced.ytDlpPath
with ⏎··········typeof·config.advanced?.ytDlpPath·===·'string'⏎············?·config.advanced.ytDlpPath⏎···········
const ytDlpPathValue = typeof config.advanced?.ytDlpPath === 'string' ? config.advanced.ytDlpPath : ''; | |
const ytDlpPathValue = | |
typeof config.advanced?.ytDlpPath === 'string' | |
? config.advanced.ytDlpPath | |
: ''; |
Did you use AI tools something like Claude Code or Gemini Cli, Cursor, etc?
|
i'd say the emoji spam and comments are a clear sign of AI tools being used also looks like most translation strings don't actually exist the chalk package was also committed after everything else and seems to be unused? |
Yep, just wanted to get something working and I didn't spend a ton of time on this beyond that. If there's enough interest perhaps I'll take some time to clean things up (and get rid of the emoji debugging stuff lol). It's set up where you can set your own path in the GUI itself btw (in the plugin downloader menu).
|
const args = [ | ||
'-x', | ||
'--audio-format', | ||
'mp3', | ||
'--audio-quality', | ||
'320K', // Higher bitrate | ||
'--embed-thumbnail', // Embed album art into the MP3 | ||
'--embed-metadata', // Embed ID3 tags | ||
'--add-metadata', // Additional metadata | ||
'--convert-thumbnails', | ||
'jpg', // Convert to standard format (for embedded only, no separate files) | ||
|
||
'-o', | ||
outTemplate, | ||
url, | ||
]; |
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.
this completesly ignores the preset configuration
const allPaths = [ | ||
config.advanced?.ytDlpPath | ||
? `[custom] ${config.advanced.ytDlpPath}` | ||
: null, | ||
...(is.windows() | ||
? [ | ||
'C:/yt-dlp.exe', | ||
path.join(homedir(), 'Downloads', 'yt-dlp.exe'), | ||
'C:/utils/yt-dlp.exe', | ||
] | ||
: is.linux() | ||
? ['/usr/bin/yt-dlp'] | ||
: is.macOS() | ||
? ['/usr/local/bin/yt-dlp', '/opt/homebrew/bin/yt-dlp'] | ||
: []), | ||
].filter(Boolean); |
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.
probably better for getYtDlpPath to return the path checked instead of reconstructing it
const candidates = [ | ||
'C:/yt-dlp.exe', | ||
path.join(homedir(), 'Downloads', 'yt-dlp.exe'), | ||
'C:/utils/yt-dlp.exe', | ||
]; |
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.
pretty sure on windows directories are seperated by a backslash
), | ||
}); | ||
logToFrontend('info', '🚀 Starting yt-dlp process...'); | ||
win.setProgressBar(0.1); // Start with indefinite bar |
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.
win.setProgressBar(0.1); // Start with indefinite bar | |
win.setProgressBar(2); // Start with indefinite bar |
if (!isNaN(progress) && progress > 0 && progress !== lastProgressUpdate) { | ||
lastProgressUpdate = progress; | ||
logToFrontend('info', `📊 Playlist progress: ${Math.floor(progress * 100)}%`); | ||
sendFeedback(`Downloading... ${Math.floor(progress * 100)}%`, progress); |
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.
we shouldn't update the window progress bar since this is the progress of the current song not the whole playlist
use the old increaseProgress
function instead
if (chunk.includes('[youtube]') || chunk.includes('[download]')) { | ||
if (chunk.includes('Downloading webpage')) { | ||
logToFrontend('info', '🌐 Fetching track 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.
should probably also send feedback here (sendFeedback('fetching track info')) (replace with translation key ofc)
if (chunk.includes('[youtube]') || chunk.includes('[download]')) { | ||
if (chunk.includes('Downloading webpage')) { | ||
logToFrontend('info', '🌐 Fetching track 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.
const increaseProgress = (itemPercentage: number) => { | ||
const currentProgress = (counter - 1) / (items.length ?? 1); | ||
const newProgress = currentProgress + progressStep * itemPercentage; | ||
win.setProgressBar(newProgress); | ||
}; |
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.
you could reimplement this back if you want the current song download progress to have an effect on the window progress bar
yea i've tried building it on all platforms and it works fine if you set up the path correctly |
I think there should be a hard block on this until the next announcement related to yt-dlp/yt-dlp#14404 especially regarding the fact that available qualities may be changed, and PO tokens could be required. Why is it forcefully set to 320k, when that may not (and often won't) be available? This is too wrong... I agree with the above review. |
Yes PLEASE someone get this to work somehow for yt-dlp because that's literally the one and only feature that I've been waiting for with this app because I have to manually run a .BAT file for yt-dlp locally on my Windows 10 PC outside of the app just to download songs & playlists to WAV format in it's highest quality at 44K 320Kbps so we need this feature in the download section badly!!! |
Personally, the only way I would agree to having this merged, is if it only functions as a fallback for the current download method. yt-dlp takes a youtube music link and converts it to a normal youtube link, making it impossible to download some songs that are not available in normal youtube |
If you could just please do it as the option that you said then that would be amazing for us Music Production folks! I highly doubt that it would gather interest because the normal consumer doesn't need WAV files because you can't add Metadata Info to them even though they're higher quality than MP3 files. This would just speed things up tremendously for our workflows. |