Merge branch 'master' into download-playlist-badge-count

This commit is contained in:
th-ch
2022-01-16 17:23:43 +01:00
committed by GitHub
6 changed files with 125 additions and 93 deletions

View File

@ -95,7 +95,7 @@
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"node-fetch": "^2.6.6", "node-fetch": "^2.6.6",
"node-notifier": "^9.0.1", "node-notifier": "^9.0.1",
"ytdl-core": "^4.9.2", "ytdl-core": "^4.10.0",
"ytpl": "^2.2.3" "ytpl": "^2.2.3"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,4 +1,4 @@
const { contextBridge } = require("electron"); const { ipcRenderer } = require("electron");
const { defaultConfig } = require("../../config"); const { defaultConfig } = require("../../config");
const { getSongMenu } = require("../../providers/dom-elements"); const { getSongMenu } = require("../../providers/dom-elements");
@ -13,15 +13,17 @@ const downloadButton = ElementFromFile(
); );
let pluginOptions = {}; let pluginOptions = {};
const observer = new MutationObserver((mutations, observer) => { const observer = new MutationObserver(() => {
if (!menu) { if (!menu) {
menu = getSongMenu(); menu = getSongMenu();
if (!menu) return;
} }
if (menu.contains(downloadButton)) return;
const menuUrl = document.querySelector('tp-yt-paper-listbox [tabindex="0"] #navigation-endpoint')?.href;
if (menuUrl && !menuUrl.includes('watch?')) return;
if (menu && !menu.contains(downloadButton)) { menu.prepend(downloadButton);
menu.prepend(downloadButton); progress = document.querySelector("#ytmcustom-download");
progress = document.querySelector("#ytmcustom-download");
}
}); });
const reinit = () => { const reinit = () => {
@ -43,10 +45,16 @@ global.download = () => {
let metadata; let metadata;
let videoUrl = getSongMenu() let videoUrl = getSongMenu()
// selector of first button which is always "Start Radio" // selector of first button which is always "Start Radio"
?.querySelector('ytmusic-menu-navigation-item-renderer.iron-selected[tabindex="0"] #navigation-endpoint') ?.querySelector('ytmusic-menu-navigation-item-renderer[tabindex="0"] #navigation-endpoint')
?.getAttribute("href"); ?.getAttribute("href");
if (videoUrl) { if (videoUrl) {
videoUrl = baseUrl + "/" + videoUrl; if (videoUrl.startsWith('watch?')) {
videoUrl = baseUrl + "/" + videoUrl;
}
if (videoUrl.includes('?playlist=')) {
ipcRenderer.send('download-playlist-request', videoUrl);
return;
}
metadata = null; metadata = null;
} else { } else {
metadata = global.songInfo; metadata = global.songInfo;
@ -78,10 +86,13 @@ global.download = () => {
function observeMenu(options) { function observeMenu(options) {
pluginOptions = { ...pluginOptions, ...options }; pluginOptions = { ...pluginOptions, ...options };
observer.observe(document, {
childList: true, document.addEventListener('apiLoaded', () => {
subtree: true, observer.observe(document.querySelector('ytmusic-popup-container'), {
}); childList: true,
subtree: true,
});
}, { once: true, passive: true })
} }
module.exports = observeMenu; module.exports = observeMenu;

View File

@ -11,92 +11,33 @@ const { sendError } = require("./back");
const { defaultMenuDownloadLabel, getFolder, presets, setBadge } = require("./utils"); const { defaultMenuDownloadLabel, getFolder, presets, setBadge } = require("./utils");
let downloadLabel = defaultMenuDownloadLabel; let downloadLabel = defaultMenuDownloadLabel;
let playingPlaylistId = undefined; let playingUrl = undefined;
let callbackIsRegistered = false; let callbackIsRegistered = false;
// Playlist radio modifier needs to be cut from playlist ID
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = aURL => {
const result = aURL?.searchParams.get("list") || aURL?.searchParams.get("playlist");
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
return result.slice(6)
}
return result;
};
module.exports = (win, options) => { module.exports = (win, options) => {
if (!callbackIsRegistered) { if (!callbackIsRegistered) {
ipcMain.on("video-src-changed", async (_, data) => { ipcMain.on("video-src-changed", async (_, data) => {
playingPlaylistId = JSON.parse(data)?.videoDetails?.playlistId; playingUrl = JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical;
}); });
ipcMain.on("download-playlist-request", async (_event, url) => downloadPlaylist(url, win, options));
callbackIsRegistered = true; callbackIsRegistered = true;
} }
return [ return [
{ {
label: downloadLabel, label: downloadLabel,
click: async () => { click: () => downloadPlaylist(undefined, win, options),
const currentPagePlaylistId = new URL(win.webContents.getURL()).searchParams.get("list");
const playlistId = currentPagePlaylistId || playingPlaylistId;
if (!playlistId) {
sendError(win, new Error("No playlist ID found"));
return;
}
console.log(`trying to get playlist ID: '${playlistId}'`);
let playlist;
try {
playlist = await ytpl(playlistId, {
limit: options.playlistMaxItems || Infinity,
});
} catch (e) {
sendError(win, e);
return;
}
const playlistTitle = playlist.title;
const folder = getFolder(options.downloadFolder);
const playlistFolder = join(folder, playlistTitle);
if (existsSync(playlistFolder)) {
sendError(
win,
new Error(`The folder ${playlistFolder} already exists`)
);
return;
}
mkdirSync(playlistFolder, { recursive: true });
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Started Download",
message: `Downloading Playlist "${playlistTitle}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlistTitle}" (${playlist.items.length} songs)`
);
}
win.setProgressBar(2); // starts with indefinite bar
let downloadCount = 0;
setBadge(playlist.items.length);
let dirWatcher = chokidar.watch(playlistFolder);
dirWatcher.on('add', () => {
downloadCount++;
if (downloadCount >= playlist.items.length) {
win.setProgressBar(-1); // close progress bar
setBadge(0); // close badge counter
dirWatcher.close().then(() => dirWatcher = null);
} else {
win.setProgressBar(downloadCount / playlist.items.length);
setBadge(playlist.items.length - downloadCount);
}
});
playlist.items.forEach((song) => {
win.webContents.send(
"downloader-download-playlist",
song.url,
playlistTitle,
options
);
});
},
}, },
{ {
label: "Choose download folder", label: "Choose download folder",
@ -125,3 +66,83 @@ module.exports = (win, options) => {
}, },
]; ];
}; };
async function downloadPlaylist(givenUrl, win, options) {
if (givenUrl) {
try {
givenUrl = new URL(givenUrl);
} catch {
givenUrl = undefined;
};
}
const playlistId = getPlaylistID(givenUrl)
|| getPlaylistID(new URL(win.webContents.getURL()))
|| getPlaylistID(new URL(playingUrl));
if (!playlistId) {
sendError(win, new Error("No playlist ID found"));
return;
}
console.log(`trying to get playlist ID: '${playlistId}'`);
let playlist;
try {
playlist = await ytpl(playlistId, {
limit: options.playlistMaxItems || Infinity,
});
} catch (e) {
sendError(win, e);
return;
}
const playlistTitle = playlist.title;
const folder = getFolder(options.downloadFolder);
const playlistFolder = join(folder, playlistTitle);
if (existsSync(playlistFolder)) {
sendError(
win,
new Error(`The folder ${playlistFolder} already exists`)
);
return;
}
mkdirSync(playlistFolder, { recursive: true });
dialog.showMessageBox({
type: "info",
buttons: ["OK"],
title: "Started Download",
message: `Downloading Playlist "${playlistTitle}"`,
detail: `(${playlist.items.length} songs)`,
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlistTitle}" - ${playlist.items.length} songs (${playlistId})`
);
}
const steps = 1 / playlist.items.length;
let progress = 0;
win.setProgressBar(2); // starts with indefinite bar
let dirWatcher = chokidar.watch(playlistFolder);
dirWatcher.on('add', () => {
progress += steps;
if (progress >= 0.9999) {
win.setProgressBar(-1); // close progress bar
dirWatcher.close().then(() => dirWatcher = null);
} else {
win.setProgressBar(progress);
}
});
playlist.items.forEach((song) => {
win.webContents.send(
"downloader-download-playlist",
song.url,
playlistTitle,
options
);
});
}

View File

@ -46,7 +46,7 @@ const downloadVideoToMP3 = async (
cleanupName(videoDetails?.author?.name) || cleanupName(videoDetails?.author?.name) ||
"", "",
title: videoDetails?.media?.song || videoDetails?.title || "", title: videoDetails?.media?.song || videoDetails?.title || "",
imageSrcYTPL: thumbnails ? imageSrcYTPL: thumbnails ?
urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId) urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId)
: "" : ""
} }

View File

@ -30,7 +30,7 @@ const observePopupContainer = () => {
menu = getSongMenu(); menu = getSongMenu();
} }
if (menu && !menu.contains(slider)) { if (menu && menu.lastElementChild.lastElementChild.innerText.startsWith('Stats') && !menu.contains(slider)) {
menu.prepend(slider); menu.prepend(slider);
if (!observingSlider) { if (!observingSlider) {
setupSliderListener(); setupSliderListener();

View File

@ -8361,10 +8361,10 @@ yocto-queue@^1.0.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
ytdl-core@^4.9.2: ytdl-core@^4.10.0:
version "4.9.2" version "4.10.0"
resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.9.2.tgz#c2d1ec44ee3cabff35e5843c6831755e69ffacf0" resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-4.10.0.tgz#0835cb411677684539fac2bcc10553f6f58db3e1"
integrity sha512-aTlsvsN++03MuOtyVD4DRF9Z/9UAeeuiNbjs+LjQBAiw4Hrdp48T3U9vAmRPyvREzupraY8pqRoBfKGqpq+eHA== integrity sha512-RCCoSVTmMeBPH5NFR1fh3nkDU9okvWM0ZdN6plw6I5+vBBZVUEpOt8vjbSgprLRMmGUsmrQZJhvG1CHOat4mLA==
dependencies: dependencies:
m3u8stream "^0.8.4" m3u8stream "^0.8.4"
miniget "^4.0.0" miniget "^4.0.0"