Skip to content

Conversation

digitalnomad91
Copy link

  • 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
Comment on lines +83 to +85
const formattedMessage = args.length > 0
? `${message} ${args.join(' ')}`
: message;
Copy link
Contributor

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('·')}``

Suggested change
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, "\\'")}');`
Copy link
Contributor

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 ,

Suggested change
`console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');`
`console.${level}('[Downloader] ${formattedMessage.replace(/'/g, "\\'")}');`,

Comment on lines +147 to +150
const macCandidates = [
'/usr/local/bin/yt-dlp',
'/opt/homebrew/bin/yt-dlp',
];
Copy link
Contributor

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'

Suggested change
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');
Copy link
Contributor

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 ⏎···

Suggested change
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');
Copy link
Contributor

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')⏎··········

Suggested change
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]));
Copy link
Contributor

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]),⏎··········

Suggested change
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));
Copy link
Contributor

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),⏎······

Suggested change
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(' ')}`
Copy link
Contributor

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('·')},

Suggested change
`\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(() => {});
Copy link
Contributor

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 ⏎······()·=>·{},⏎····

Suggested change
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 : '';
Copy link
Contributor

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⏎···········

Suggested change
const ytDlpPathValue = typeof config.advanced?.ytDlpPath === 'string' ? config.advanced.ytDlpPath : '';
const ytDlpPathValue =
typeof config.advanced?.ytDlpPath === 'string'
? config.advanced.ytDlpPath
: '';

@JellyBrick
Copy link
Collaborator

Did you use AI tools something like Claude Code or Gemini Cli, Cursor, etc?

  1. path is hardcoded.
  2. there's a lot of unnecessary code.

@scratchusernamemrtbts
Copy link
Contributor

Did you use AI tools something like Claude Code or Gemini Cli, Cursor, etc?

1. path is hardcoded.

2. there's a lot of unnecessary code.

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?

@digitalnomad91
Copy link
Author

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).

Did you use AI tools something like Claude Code or Gemini Cli, Cursor, etc?

1. path is hardcoded.

2. there's a lot of unnecessary code.

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?

Comment on lines +444 to +459
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,
];
Copy link
Contributor

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

Comment on lines +379 to +394
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);
Copy link
Contributor

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

Comment on lines +120 to +124
const candidates = [
'C:/yt-dlp.exe',
path.join(homedir(), 'Downloads', 'yt-dlp.exe'),
'C:/utils/yt-dlp.exe',
];
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);
Copy link
Contributor

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

Comment on lines +753 to +758
if (chunk.includes('[youtube]') || chunk.includes('[download]')) {
if (chunk.includes('Downloading webpage')) {
logToFrontend('info', '🌐 Fetching track info...');
}
}
});
Copy link
Contributor

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)

Comment on lines +753 to +758
if (chunk.includes('[youtube]') || chunk.includes('[download]')) {
if (chunk.includes('Downloading webpage')) {
logToFrontend('info', '🌐 Fetching track info...');
}
}
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also use the [download] logs to update the overall progressbar
image

you can get the full playlist length from [youtube:tab] log
image

Comment on lines -759 to -763
const increaseProgress = (itemPercentage: number) => {
const currentProgress = (counter - 1) / (items.length ?? 1);
const newProgress = currentProgress + progressStep * itemPercentage;
win.setProgressBar(newProgress);
};
Copy link
Contributor

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

@scratchusernamemrtbts
Copy link
Contributor

yea i've tried building it on all platforms and it works fine if you set up the path correctly
need some tweaks before merging tho

@Technetium1
Copy link

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.

@oceanxxsounds
Copy link

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).

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!!!

@ArjixWasTaken
Copy link
Member

If there's enough interest

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

@oceanxxsounds
Copy link

If there's enough interest

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants