mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
download using youtubei,js instead of ytdl-core
This commit is contained in:
193
plugins/downloader/back-downloader.js
Normal file
193
plugins/downloader/back-downloader.js
Normal file
@ -0,0 +1,193 @@
|
||||
const { existsSync, mkdirSync, createWriteStream, writeFileSync } = require('fs');
|
||||
const { ipcMain, app } = require("electron");
|
||||
const { join } = require("path");
|
||||
|
||||
const { Innertube, UniversalCache, Utils } = require('youtubei.js');
|
||||
const filenamify = require("filenamify");
|
||||
const id3 = require('node-id3').Promise;
|
||||
|
||||
const { sendError } = require("./back");
|
||||
const { presets } = require('./utils');
|
||||
|
||||
ffmpegWriteTags
|
||||
/** @type {Innertube} */
|
||||
let yt;
|
||||
let options;
|
||||
|
||||
module.exports = async (options_) => {
|
||||
options = options_;
|
||||
yt = await Innertube.create({ cache: new UniversalCache(false), generate_session_locally: true });
|
||||
ipcMain.handle("download-song", (_, url) => downloadSong(url));
|
||||
};
|
||||
|
||||
async function downloadSong(url, playlistFolder = undefined) {
|
||||
const metadata = await getMetadata(url);
|
||||
|
||||
const stream = await yt.download(metadata.id, {
|
||||
type: 'audio', // audio, video or video+audio
|
||||
quality: 'best', // best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
||||
format: 'any' // media container format
|
||||
});
|
||||
|
||||
console.info(`Downloading ${metadata.artist} - ${metadata.title} {${metadata.id}}...`);
|
||||
|
||||
const iterableStream = Utils.streamToIterable(stream);
|
||||
|
||||
const dir = playlistFolder || options.downloadFolder || app.getPath("downloads");
|
||||
const name = `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}`;
|
||||
|
||||
const extension = presets[options.preset]?.extension || 'mp3';
|
||||
|
||||
const filename = filenamify(`${name}.${extension}`, {
|
||||
replacement: "_",
|
||||
maxLength: 255,
|
||||
});
|
||||
const filePath = join(dir, filename);
|
||||
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir);
|
||||
}
|
||||
|
||||
if (!presets[options.preset]) {
|
||||
await toMP3(iterableStream, filePath, metadata);
|
||||
console.info('writing id3 tags...'); // DELETE
|
||||
await writeID3(filePath, metadata).then(() => console.info('done writing id3 tags!')); // DELETE
|
||||
} else {
|
||||
const file = createWriteStream(filePath);
|
||||
//stream.pipeTo(file);
|
||||
for await (const chunk of iterableStream) {
|
||||
file.write(chunk);
|
||||
}
|
||||
ffmpegWriteTags(filePath, metadata, presets[options.preset]?.ffmpegArgs);
|
||||
}
|
||||
|
||||
console.info(`${filePath} - Done!`, '\n');
|
||||
}
|
||||
module.exports.downloadSong = downloadSong;
|
||||
|
||||
function getIdFromUrl(url) {
|
||||
const match = url.match(/v=([^&]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
async function getMetadata(url) {
|
||||
const id = getIdFromUrl(url);
|
||||
const info = await yt.music.getInfo(id);
|
||||
//console.log('got info:' + JSON.stringify(info, null, 2)); // DELETE
|
||||
|
||||
return {
|
||||
id: info.basic_info.id,
|
||||
title: info.basic_info.title,
|
||||
artist: info.basic_info.author,
|
||||
album: info.player_overlays?.browser_media_session?.album?.text,
|
||||
image: info.basic_info.thumbnail[0].url,
|
||||
};
|
||||
}
|
||||
|
||||
const { getImage } = require("../../providers/song-info");
|
||||
const { cropMaxWidth } = require("./utils");
|
||||
|
||||
async function writeID3(filePath, metadata) {
|
||||
const tags = {
|
||||
title: metadata.title,
|
||||
artist: metadata.artist,
|
||||
album: metadata.album,
|
||||
image: {
|
||||
mime: "image/png",
|
||||
type: {
|
||||
id: 3,
|
||||
name: "front cover"
|
||||
},
|
||||
description: "",
|
||||
imageBuffer: cropMaxWidth(await getImage(metadata.image))?.toPNG(),
|
||||
}
|
||||
// TODO: lyrics
|
||||
};
|
||||
|
||||
await id3.write(tags, filePath);
|
||||
}
|
||||
|
||||
const { randomBytes } = require("crypto");
|
||||
const Mutex = require("async-mutex").Mutex;
|
||||
const ffmpeg = require("@ffmpeg/ffmpeg").createFFmpeg({
|
||||
log: false,
|
||||
logger: () => { }, // console.log,
|
||||
progress: () => { }, // console.log,
|
||||
});
|
||||
|
||||
const ffmpegMutex = new Mutex();
|
||||
|
||||
async function toMP3(stream, filePath, metadata, extension = "mp3") {
|
||||
const chunks = [];
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
const buffer = Buffer.concat(chunks);
|
||||
const safeVideoName = randomBytes(32).toString("hex");
|
||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
||||
|
||||
try {
|
||||
if (!ffmpeg.isLoaded()) {
|
||||
// sendFeedback("Loading…", 2); // indefinite progress bar after download
|
||||
await ffmpeg.load();
|
||||
}
|
||||
|
||||
// sendFeedback("Preparing file…");
|
||||
ffmpeg.FS("writeFile", safeVideoName, buffer);
|
||||
|
||||
// sendFeedback("Converting…");
|
||||
|
||||
await ffmpeg.run(
|
||||
"-i",
|
||||
safeVideoName,
|
||||
...getFFmpegMetadataArgs(metadata),
|
||||
safeVideoName + "." + extension
|
||||
);
|
||||
|
||||
const fileBuffer = ffmpeg.FS("readFile", safeVideoName + "." + extension);
|
||||
|
||||
await writeID3(fileBuffer, metadata);
|
||||
|
||||
// sendFeedback("Saving…");
|
||||
|
||||
writeFileSync(filePath, fileBuffer);
|
||||
} catch (e) {
|
||||
sendError(e);
|
||||
} finally {
|
||||
releaseFFmpegMutex();
|
||||
}
|
||||
}
|
||||
|
||||
async function ffmpegWriteTags(filePath, metadata, ffmpegArgs = []) {
|
||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
||||
|
||||
try {
|
||||
if (!ffmpeg.isLoaded()) {
|
||||
await ffmpeg.load();
|
||||
}
|
||||
|
||||
await ffmpeg.run(
|
||||
"-i",
|
||||
filePath,
|
||||
...getFFmpegMetadataArgs(metadata),
|
||||
...ffmpegArgs,
|
||||
filePath
|
||||
);
|
||||
} catch (e) {
|
||||
sendError(e);
|
||||
} finally {
|
||||
releaseFFmpegMutex();
|
||||
}
|
||||
}
|
||||
|
||||
function getFFmpegMetadataArgs(metadata) {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
...(metadata.title ? ["-metadata", `title=${metadata.title}`] : []),
|
||||
...(metadata.artist ? ["-metadata", `artist=${metadata.artist}`] : []),
|
||||
...(metadata.album ? ["-metadata", `album=${metadata.album}`] : []),
|
||||
];
|
||||
};
|
||||
@ -12,7 +12,9 @@ const { isEnabled } = require("../../config/plugins");
|
||||
const { getImage } = require("../../providers/song-info");
|
||||
const { fetchFromGenius } = require("../lyrics-genius/back");
|
||||
|
||||
const sendError = (win, error) => {
|
||||
let win = {};
|
||||
|
||||
const sendError = (error) => {
|
||||
win.setProgressBar(-1); // close progress bar
|
||||
dialog.showMessageBox({
|
||||
type: "info",
|
||||
@ -25,8 +27,13 @@ const sendError = (win, error) => {
|
||||
|
||||
let nowPlayingMetadata = {};
|
||||
|
||||
function handle(win) {
|
||||
|
||||
function handle(win_, options) {
|
||||
win = win_;
|
||||
injectCSS(win.webContents, join(__dirname, "style.css"));
|
||||
|
||||
require("./back-downloader")(options);
|
||||
|
||||
registerCallback((info) => {
|
||||
nowPlayingMetadata = info;
|
||||
});
|
||||
@ -34,7 +41,7 @@ function handle(win) {
|
||||
listenAction(CHANNEL, (event, action, arg) => {
|
||||
switch (action) {
|
||||
case ACTIONS.ERROR: // arg = error
|
||||
sendError(win, arg);
|
||||
sendError(arg);
|
||||
break;
|
||||
case ACTIONS.METADATA:
|
||||
event.returnValue = JSON.stringify(nowPlayingMetadata);
|
||||
@ -46,52 +53,6 @@ function handle(win) {
|
||||
console.log("Unknown action: " + action);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.on("add-metadata", async (event, filePath, songBuffer, currentMetadata) => {
|
||||
let fileBuffer = songBuffer;
|
||||
const songMetadata = currentMetadata.imageSrcYTPL ? // This means metadata come from ytpl.getInfo();
|
||||
{
|
||||
...currentMetadata,
|
||||
image: cropMaxWidth(await getImage(currentMetadata.imageSrcYTPL))
|
||||
} :
|
||||
{ ...nowPlayingMetadata, ...currentMetadata };
|
||||
|
||||
try {
|
||||
const coverBuffer = songMetadata.image && !songMetadata.image.isEmpty() ?
|
||||
songMetadata.image.toPNG() : null;
|
||||
|
||||
const writer = new ID3Writer(songBuffer);
|
||||
|
||||
// Create the metadata tags
|
||||
writer
|
||||
.setFrame("TIT2", songMetadata.title)
|
||||
.setFrame("TPE1", [songMetadata.artist]);
|
||||
if (coverBuffer) {
|
||||
writer.setFrame("APIC", {
|
||||
type: 3,
|
||||
data: coverBuffer,
|
||||
description: ""
|
||||
});
|
||||
}
|
||||
if (isEnabled("lyrics-genius")) {
|
||||
const lyrics = await fetchFromGenius(songMetadata);
|
||||
if (lyrics) {
|
||||
writer.setFrame("USLT", {
|
||||
description: lyrics,
|
||||
lyrics: lyrics,
|
||||
});
|
||||
}
|
||||
}
|
||||
writer.addTag();
|
||||
fileBuffer = Buffer.from(writer.arrayBuffer);
|
||||
} catch (error) {
|
||||
sendError(win, error);
|
||||
}
|
||||
|
||||
writeFileSync(filePath, fileBuffer);
|
||||
// Notify the youtube-dl file
|
||||
event.reply("add-metadata-done");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = handle;
|
||||
|
||||
@ -4,7 +4,6 @@ const { defaultConfig } = require("../../config");
|
||||
const { getSongMenu } = require("../../providers/dom-elements");
|
||||
const { ElementFromFile, templatePath, triggerAction } = require("../utils");
|
||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||
const { downloadVideoToMP3 } = require("./youtube-dl");
|
||||
|
||||
let menu = null;
|
||||
let progress = null;
|
||||
@ -61,28 +60,9 @@ global.download = () => {
|
||||
videoUrl = metadata.url || window.location.href;
|
||||
}
|
||||
|
||||
downloadVideoToMP3(
|
||||
videoUrl,
|
||||
(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);
|
||||
reinit();
|
||||
},
|
||||
reinit,
|
||||
pluginOptions,
|
||||
metadata
|
||||
);
|
||||
ipcRenderer.invoke('download-song', videoUrl).finally(() => triggerAction(CHANNEL, ACTIONS.PROGRESS, -1));
|
||||
return;
|
||||
};
|
||||
// });
|
||||
|
||||
function observeMenu(options) {
|
||||
pluginOptions = { ...pluginOptions, ...options };
|
||||
|
||||
@ -9,6 +9,7 @@ const filenamify = require('filenamify');
|
||||
|
||||
const { setMenuOptions } = require("../../config/plugins");
|
||||
const { sendError } = require("./back");
|
||||
const { downloadSong } = require("./back-downloader");
|
||||
const { defaultMenuDownloadLabel, getFolder, presets, setBadge } = require("./utils");
|
||||
|
||||
let downloadLabel = defaultMenuDownloadLabel;
|
||||
@ -62,7 +63,7 @@ module.exports = (win, options) => {
|
||||
options.preset = preset;
|
||||
setMenuOptions("downloader", options);
|
||||
},
|
||||
checked: options.preset === preset || presets[preset] === undefined,
|
||||
checked: options.preset === preset,
|
||||
})),
|
||||
},
|
||||
];
|
||||
@ -81,7 +82,7 @@ async function downloadPlaylist(givenUrl, win, options) {
|
||||
|| getPlaylistID(new URL(playingUrl));
|
||||
|
||||
if (!playlistId) {
|
||||
sendError(win, new Error("No playlist ID found"));
|
||||
sendError(new Error("No playlist ID found"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -92,18 +93,15 @@ async function downloadPlaylist(givenUrl, win, options) {
|
||||
limit: options.playlistMaxItems || Infinity,
|
||||
});
|
||||
} catch (e) {
|
||||
sendError(win, e);
|
||||
sendError(e);
|
||||
return;
|
||||
}
|
||||
const safePlaylistTitle = filenamify(playlist.title, {replacement: ' '});
|
||||
const safePlaylistTitle = filenamify(playlist.title, { replacement: ' ' });
|
||||
|
||||
const folder = getFolder(options.downloadFolder);
|
||||
const playlistFolder = join(folder, safePlaylistTitle);
|
||||
if (existsSync(playlistFolder)) {
|
||||
sendError(
|
||||
win,
|
||||
new Error(`The folder ${playlistFolder} already exists`)
|
||||
);
|
||||
sendError(new Error(`The folder ${playlistFolder} already exists`));
|
||||
return;
|
||||
}
|
||||
mkdirSync(playlistFolder, { recursive: true });
|
||||
@ -128,24 +126,30 @@ async function downloadPlaylist(givenUrl, win, options) {
|
||||
setBadge(playlist.items.length);
|
||||
|
||||
let dirWatcher = chokidar.watch(playlistFolder);
|
||||
dirWatcher.on('add', () => {
|
||||
downloadCount += 1;
|
||||
if (downloadCount >= playlist.items.length) {
|
||||
const closeDirWatcher = () => {
|
||||
if (dirWatcher) {
|
||||
win.setProgressBar(-1); // close progress bar
|
||||
setBadge(0); // close badge counter
|
||||
dirWatcher.close().then(() => (dirWatcher = null));
|
||||
}
|
||||
};
|
||||
dirWatcher.on('add', () => {
|
||||
downloadCount += 1;
|
||||
if (downloadCount >= playlist.items.length) {
|
||||
closeDirWatcher();
|
||||
} else {
|
||||
win.setProgressBar(downloadCount / playlist.items.length);
|
||||
setBadge(playlist.items.length - downloadCount);
|
||||
}
|
||||
});
|
||||
|
||||
playlist.items.forEach((song) => {
|
||||
win.webContents.send(
|
||||
"downloader-download-playlist",
|
||||
song.url,
|
||||
safePlaylistTitle,
|
||||
options
|
||||
);
|
||||
});
|
||||
try {
|
||||
for (const song of playlist.items) {
|
||||
await downloadSong(song.url, playlistFolder).catch((e) => sendError(e));
|
||||
}
|
||||
} catch (e) {
|
||||
sendError(e);
|
||||
} finally {
|
||||
closeDirWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const electron = require("electron");
|
||||
const { app } = require("electron");
|
||||
const is = require('electron-is');
|
||||
|
||||
module.exports.getFolder = customFolder => customFolder || electron.app.getPath("downloads");
|
||||
module.exports.getFolder = customFolder => customFolder || app.getPath("downloads");
|
||||
module.exports.defaultMenuDownloadLabel = "Download playlist";
|
||||
|
||||
const orderedQualityList = ["maxresdefault", "hqdefault", "mqdefault", "sdddefault"];
|
||||
@ -41,6 +41,6 @@ module.exports.presets = {
|
||||
|
||||
module.exports.setBadge = n => {
|
||||
if (is.linux() || is.macOS()) {
|
||||
electron.app.setBadgeCount(n);
|
||||
app.setBadgeCount(n);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,206 +0,0 @@
|
||||
const { randomBytes } = require("crypto");
|
||||
const { join } = require("path");
|
||||
|
||||
const Mutex = require("async-mutex").Mutex;
|
||||
const { ipcRenderer } = require("electron");
|
||||
const is = require("electron-is");
|
||||
const filenamify = require("filenamify");
|
||||
|
||||
// Workaround for "Automatic publicPath is not supported in this browser"
|
||||
// See https://github.com/cypress-io/cypress/issues/18435#issuecomment-1048863509
|
||||
const script = document.createElement("script");
|
||||
document.body.appendChild(script);
|
||||
script.src = " "; // single space and not the empty string
|
||||
|
||||
// Browser version of FFmpeg (in renderer process) instead of loading @ffmpeg/ffmpeg
|
||||
// because --js-flags cannot be passed in the main process when the app is packaged
|
||||
// See https://github.com/electron/electron/issues/22705
|
||||
const FFmpeg = require("@ffmpeg/ffmpeg/dist/ffmpeg.min");
|
||||
const ytdl = require("ytdl-core");
|
||||
|
||||
const { triggerAction, triggerActionSync } = require("../utils");
|
||||
const { ACTIONS, CHANNEL } = require("./actions.js");
|
||||
const { presets, urlToJPG } = require("./utils");
|
||||
const { cleanupName } = require("../../providers/song-info");
|
||||
|
||||
const { createFFmpeg } = FFmpeg;
|
||||
const ffmpeg = createFFmpeg({
|
||||
log: false,
|
||||
logger: () => {}, // console.log,
|
||||
progress: () => {}, // console.log,
|
||||
});
|
||||
const ffmpegMutex = new Mutex();
|
||||
|
||||
const downloadVideoToMP3 = async (
|
||||
videoUrl,
|
||||
sendFeedback,
|
||||
sendError,
|
||||
reinit,
|
||||
options,
|
||||
metadata = undefined,
|
||||
subfolder = ""
|
||||
) => {
|
||||
sendFeedback("Downloading…");
|
||||
|
||||
if (metadata === null) {
|
||||
const { videoDetails } = await ytdl.getInfo(videoUrl);
|
||||
const thumbnails = videoDetails?.thumbnails;
|
||||
metadata = {
|
||||
artist:
|
||||
videoDetails?.media?.artist ||
|
||||
cleanupName(videoDetails?.author?.name) ||
|
||||
"",
|
||||
title: videoDetails?.media?.song || videoDetails?.title || "",
|
||||
imageSrcYTPL: thumbnails ?
|
||||
urlToJPG(thumbnails[thumbnails.length - 1].url, videoDetails?.videoId)
|
||||
: ""
|
||||
}
|
||||
}
|
||||
|
||||
let videoName = "YouTube Music - Unknown title";
|
||||
let videoReadableStream;
|
||||
try {
|
||||
videoReadableStream = ytdl(videoUrl, {
|
||||
filter: "audioonly",
|
||||
quality: "highestaudio",
|
||||
highWaterMark: 32 * 1024 * 1024, // 32 MB
|
||||
requestOptions: { maxRetries: 3 },
|
||||
});
|
||||
} catch (err) {
|
||||
sendError(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const chunks = [];
|
||||
videoReadableStream
|
||||
.on("data", (chunk) => {
|
||||
chunks.push(chunk);
|
||||
})
|
||||
.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.replaceAll("|", "").toString("ascii");
|
||||
if (is.dev()) {
|
||||
console.log(
|
||||
"Downloading video - name:",
|
||||
videoName,
|
||||
"- quality:",
|
||||
format.audioBitrate + "kbits/s"
|
||||
);
|
||||
}
|
||||
})
|
||||
.on("error", sendError)
|
||||
.on("end", async () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
await toMP3(
|
||||
videoName,
|
||||
buffer,
|
||||
sendFeedback,
|
||||
sendError,
|
||||
reinit,
|
||||
options,
|
||||
metadata,
|
||||
subfolder
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const toMP3 = async (
|
||||
videoName,
|
||||
buffer,
|
||||
sendFeedback,
|
||||
sendError,
|
||||
reinit,
|
||||
options,
|
||||
existingMetadata = undefined,
|
||||
subfolder = ""
|
||||
) => {
|
||||
const convertOptions = { ...presets[options.preset], ...options };
|
||||
const safeVideoName = randomBytes(32).toString("hex");
|
||||
const extension = convertOptions.extension || "mp3";
|
||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
||||
|
||||
try {
|
||||
if (!ffmpeg.isLoaded()) {
|
||||
sendFeedback("Loading…", 2); // indefinite progress bar after download
|
||||
await ffmpeg.load();
|
||||
}
|
||||
|
||||
sendFeedback("Preparing file…");
|
||||
ffmpeg.FS("writeFile", safeVideoName, buffer);
|
||||
|
||||
sendFeedback("Converting…");
|
||||
const metadata = existingMetadata || getMetadata();
|
||||
await ffmpeg.run(
|
||||
"-i",
|
||||
safeVideoName,
|
||||
...getFFmpegMetadataArgs(metadata),
|
||||
...(convertOptions.ffmpegArgs || []),
|
||||
safeVideoName + "." + extension
|
||||
);
|
||||
|
||||
const folder = options.downloadFolder || await ipcRenderer.invoke('getDownloadsFolder');
|
||||
const name = metadata.title
|
||||
? `${metadata.artist ? `${metadata.artist} - ` : ""}${metadata.title}`
|
||||
: videoName;
|
||||
const filename = filenamify(name + "." + extension, {
|
||||
replacement: "_",
|
||||
maxLength: 255,
|
||||
});
|
||||
|
||||
const filePath = join(folder, subfolder, filename);
|
||||
const fileBuffer = ffmpeg.FS("readFile", safeVideoName + "." + extension);
|
||||
|
||||
// Add the metadata
|
||||
sendFeedback("Adding metadata…");
|
||||
ipcRenderer.send("add-metadata", filePath, fileBuffer, {
|
||||
artist: metadata.artist,
|
||||
title: metadata.title,
|
||||
imageSrcYTPL: metadata.imageSrcYTPL
|
||||
});
|
||||
ipcRenderer.once("add-metadata-done", reinit);
|
||||
} catch (e) {
|
||||
sendError(e);
|
||||
} finally {
|
||||
releaseFFmpegMutex();
|
||||
}
|
||||
};
|
||||
|
||||
const getMetadata = () => {
|
||||
return JSON.parse(triggerActionSync(CHANNEL, ACTIONS.METADATA));
|
||||
};
|
||||
|
||||
const getFFmpegMetadataArgs = (metadata) => {
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
...(metadata.title ? ["-metadata", `title=${metadata.title}`] : []),
|
||||
...(metadata.artist ? ["-metadata", `artist=${metadata.artist}`] : []),
|
||||
];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
downloadVideoToMP3,
|
||||
};
|
||||
|
||||
ipcRenderer.on(
|
||||
"downloader-download-playlist",
|
||||
(_, url, playlistFolder, options) => {
|
||||
downloadVideoToMP3(
|
||||
url,
|
||||
() => {},
|
||||
(error) => {
|
||||
triggerAction(CHANNEL, ACTIONS.ERROR, error);
|
||||
},
|
||||
() => {},
|
||||
options,
|
||||
null,
|
||||
playlistFolder
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user