From a8ac2c3af988f299be85010e7fea541096b7e261 Mon Sep 17 00:00:00 2001 From: Araxeus Date: Sat, 8 May 2021 07:11:54 +0300 Subject: [PATCH] download progress bar on taskbar + Get the best possible artwork --- plugins/downloader/actions.js | 1 + plugins/downloader/back.js | 17 +++++++++++------ plugins/downloader/front.js | 7 ++++++- plugins/downloader/menu.js | 3 +-- plugins/downloader/utils.js | 30 ++++++++++++++++++++++++++++++ plugins/downloader/youtube-dl.js | 23 +++++++++++++---------- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/plugins/downloader/actions.js b/plugins/downloader/actions.js index da75f181..0d6c3426 100644 --- a/plugins/downloader/actions.js +++ b/plugins/downloader/actions.js @@ -2,6 +2,7 @@ const CHANNEL = "downloader"; const ACTIONS = { ERROR: "error", METADATA: "metadata", + PROGRESS: "progress", }; module.exports = { diff --git a/plugins/downloader/back.js b/plugins/downloader/back.js index ddf3dc46..4590525e 100644 --- a/plugins/downloader/back.js +++ b/plugins/downloader/back.js @@ -6,10 +6,11 @@ const { dialog, ipcMain } = require("electron"); const getSongInfo = require("../../providers/song-info"); const { injectCSS, listenAction } = require("../utils"); +const { cropMaxWidth } = require("./utils"); const { ACTIONS, CHANNEL } = require("./actions.js"); const { getImage } = require("../../providers/song-info"); -const sendError = (win, err) => { +const sendError = (err) => { const dialogOpts = { type: "info", buttons: ["OK"], @@ -17,6 +18,7 @@ const sendError = (win, err) => { message: "Argh! Apologies, download failed…", detail: err.toString(), }; + win.setProgressBar(-1); // close progress bar dialog.showMessageBox(dialogOpts); }; @@ -29,14 +31,17 @@ function handle(win) { metadata = info; }); - listenAction(CHANNEL, (event, action, error) => { + listenAction(CHANNEL, (event, action, arg) => { switch (action) { - case ACTIONS.ERROR: - sendError(win, error); + case ACTIONS.ERROR: //arg = error + sendError(arg); break; case ACTIONS.METADATA: event.returnValue = JSON.stringify(metadata); break; + case ACTIONS.PROGRESS: //arg = progress + win.setProgressBar(arg); + break; default: console.log("Unknown action: " + action); } @@ -46,7 +51,7 @@ function handle(win) { let fileBuffer = songBuffer; if (currentMetadata.imageSrc) { - currentMetadata.image = await getImage(currentMetadata.imageSrc); + currentMetadata.image = cropMaxWidth(await getImage(currentMetadata.imageSrc)); } const songMetadata = { ...metadata, ...currentMetadata }; @@ -69,7 +74,7 @@ function handle(win) { writer.addTag(); fileBuffer = Buffer.from(writer.arrayBuffer); } catch (error) { - sendError(win, error); + sendError(error); } writeFileSync(filePath, fileBuffer); diff --git a/plugins/downloader/front.js b/plugins/downloader/front.js index 23cd1448..f9a0e9b9 100644 --- a/plugins/downloader/front.js +++ b/plugins/downloader/front.js @@ -25,6 +25,7 @@ const observer = new MutationObserver((mutations, observer) => { }); const reinit = () => { + triggerAction(CHANNEL, ACTIONS.PROGRESS, -1); // closes progress bar if (!progress) { console.warn("Cannot update progress"); } else { @@ -38,6 +39,7 @@ const baseUrl = defaultConfig.url; // contextBridge.exposeInMainWorld("downloader", { // download: () => { global.download = () => { + triggerAction(CHANNEL, ACTIONS.PROGRESS, 2); // starts with indefinite progress bar let metadata; let videoUrl = getSongMenu() ?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"]') @@ -53,12 +55,15 @@ global.download = () => { downloadVideoToMP3( videoUrl, - (feedback) => { + (feedback, ratio = undefined) => { if (!progress) { console.warn("Cannot update progress"); } else { progress.innerHTML = feedback; } + if (ratio) { + triggerAction(CHANNEL, ACTIONS.PROGRESS, ratio); + } }, (error) => { triggerAction(CHANNEL, ACTIONS.ERROR, error); diff --git a/plugins/downloader/menu.js b/plugins/downloader/menu.js index f5b99d73..b2dd5f25 100644 --- a/plugins/downloader/menu.js +++ b/plugins/downloader/menu.js @@ -32,7 +32,7 @@ module.exports = (win, options) => { const currentURL = metadataURL || win.webContents.getURL(); const playlistID = new URL(currentURL).searchParams.get("list"); if (!playlistID) { - sendError(win, new Error("No playlist ID found")); + sendError(new Error("No playlist ID found")); return; } @@ -46,7 +46,6 @@ module.exports = (win, options) => { const playlistFolder = join(folder, playlistTitle); if (existsSync(playlistFolder)) { sendError( - win, new Error(`The folder ${playlistFolder} already exists`) ); return; diff --git a/plugins/downloader/utils.js b/plugins/downloader/utils.js index e2763541..7c5b9b96 100644 --- a/plugins/downloader/utils.js +++ b/plugins/downloader/utils.js @@ -3,3 +3,33 @@ const electron = require("electron"); module.exports.getFolder = (customFolder) => customFolder || (electron.app || electron.remote.app).getPath("downloads"); module.exports.defaultMenuDownloadLabel = "Download playlist"; + +module.exports.UrlToJPG = (imgUrl, videoId) => { + if (!imgUrl || imgUrl.includes(".jpg")) return imgUrl; + if (imgUrl.includes("maxresdefault")) { + return "https://img.youtube.com/vi/"+videoId+"/maxresdefault.jpg"; + } + if (imgUrl.includes("hqdefault")) { + return "https://img.youtube.com/vi/"+videoId+"/hqdefault.jpg"; + } //it will almost never get further than hq + if (imgUrl.includes("mqdefault")) { + return "https://img.youtube.com/vi/"+videoId+"/mqdefault.jpg"; + } + if (imgUrl.includes("sdddefault")) { + return "https://img.youtube.com/vi/"+videoId+"/sdddefault.jpg"; + } + return "https://img.youtube.com/vi/"+videoId+"/default.jpg"; +} + +module.exports.cropMaxWidth = (image) => { + const imageSize = image.getSize(); + if (imageSize.width === 1280 && imageSize.height === 720) { + return image.crop({ + x: 280, + y: 0, + width: 720, + height: 720 + }); + } + return image; +} diff --git a/plugins/downloader/youtube-dl.js b/plugins/downloader/youtube-dl.js index 814d9924..7edf8143 100644 --- a/plugins/downloader/youtube-dl.js +++ b/plugins/downloader/youtube-dl.js @@ -14,7 +14,7 @@ const ytdl = require("ytdl-core"); const { triggerAction, triggerActionSync } = require("../utils"); const { ACTIONS, CHANNEL } = require("./actions.js"); -const { getFolder } = require("./utils"); +const { getFolder, UrlToJPG } = require("./utils"); const { cleanupArtistName } = require("../../providers/song-info"); const { createFFmpeg } = FFmpeg; @@ -37,12 +37,14 @@ const downloadVideoToMP3 = async ( sendFeedback("Downloading…"); if (metadata === null) { - const info = await ytdl.getInfo(videoUrl); - const thumbnails = info.videoDetails?.author?.thumbnails; + const { videoDetails } = await ytdl.getInfo(videoUrl); + const thumbnails = videoDetails?.thumbnails; metadata = { - artist: info.videoDetails?.media?.artist || cleanupArtistName(info.videoDetails?.author?.name) || "", - title: info.videoDetails?.media?.song || info.videoDetails?.title || "", - imageSrc: thumbnails ? thumbnails[thumbnails.length - 1].url : "" + artist: videoDetails?.media?.artist || cleanupArtistName(videoDetails?.author?.name) || "", + title: videoDetails?.media?.song || videoDetails?.title || "", + imageSrc: thumbnails ? + UrlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId) + : "" } } @@ -65,9 +67,10 @@ const downloadVideoToMP3 = async ( .on("data", (chunk) => { chunks.push(chunk); }) - .on("progress", (chunkLength, downloaded, total) => { - const progress = Math.floor((downloaded / total) * 100); - sendFeedback("Download: " + progress + "%"); + .on("progress", (_chunkLength, downloaded, total) => { + const ratio = downloaded / total; + const progress = Math.floor(ratio * 100); + sendFeedback("Download: " + progress + "%", ratio); }) .on("info", (info, format) => { videoName = info.videoDetails.title.replace("|", "").toString("ascii"); @@ -112,7 +115,7 @@ const toMP3 = async ( try { if (!ffmpeg.isLoaded()) { - sendFeedback("Loading…"); + sendFeedback("Loading…", 2); // indefinite progress bar after download await ffmpeg.load(); }