mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-17 05:02:06 +00:00
rome lint
This commit is contained in:
@ -3,31 +3,31 @@ const {
|
|||||||
mkdirSync,
|
mkdirSync,
|
||||||
createWriteStream,
|
createWriteStream,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
} = require("fs");
|
} = require('fs');
|
||||||
const { join } = require("path");
|
const { join } = require('path');
|
||||||
|
|
||||||
const { fetchFromGenius } = require("../lyrics-genius/back");
|
const { fetchFromGenius } = require('../lyrics-genius/back');
|
||||||
const { isEnabled } = require("../../config/plugins");
|
const { isEnabled } = require('../../config/plugins');
|
||||||
const { getImage } = require("../../providers/song-info");
|
const { getImage } = require('../../providers/song-info');
|
||||||
const { injectCSS } = require("../utils");
|
const { injectCSS } = require('../utils');
|
||||||
const {
|
const {
|
||||||
presets,
|
presets,
|
||||||
cropMaxWidth,
|
cropMaxWidth,
|
||||||
getFolder,
|
getFolder,
|
||||||
setBadge,
|
setBadge,
|
||||||
sendFeedback: sendFeedback_,
|
sendFeedback: sendFeedback_,
|
||||||
} = require("./utils");
|
} = require('./utils');
|
||||||
|
|
||||||
const { ipcMain, app, dialog } = require("electron");
|
const { ipcMain, app, dialog } = require('electron');
|
||||||
const is = require("electron-is");
|
const is = require('electron-is');
|
||||||
const { Innertube, UniversalCache, Utils } = require("youtubei.js");
|
const { Innertube, UniversalCache, Utils } = require('youtubei.js');
|
||||||
const ytpl = require("ytpl"); // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
|
const ytpl = require('ytpl'); // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
|
||||||
|
|
||||||
const filenamify = require("filenamify");
|
const filenamify = require('filenamify');
|
||||||
const ID3Writer = require("browser-id3-writer");
|
const ID3Writer = require('browser-id3-writer');
|
||||||
const { randomBytes } = require("crypto");
|
const { randomBytes } = require('crypto');
|
||||||
const Mutex = require("async-mutex").Mutex;
|
const Mutex = require('async-mutex').Mutex;
|
||||||
const ffmpeg = require("@ffmpeg/ffmpeg").createFFmpeg({
|
const ffmpeg = require('@ffmpeg/ffmpeg').createFFmpeg({
|
||||||
log: false,
|
log: false,
|
||||||
logger: () => {}, // console.log,
|
logger: () => {}, // console.log,
|
||||||
progress: () => {}, // console.log,
|
progress: () => {}, // console.log,
|
||||||
@ -41,7 +41,7 @@ const cache = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = require("./config");
|
const config = require('./config');
|
||||||
|
|
||||||
/** @type {Innertube} */
|
/** @type {Innertube} */
|
||||||
let yt;
|
let yt;
|
||||||
@ -55,29 +55,31 @@ const sendError = (error) => {
|
|||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: "info",
|
type: 'info',
|
||||||
buttons: ["OK"],
|
buttons: ['OK'],
|
||||||
title: "Error in download!",
|
title: 'Error in download!',
|
||||||
message: "Argh! Apologies, download failed…",
|
message: 'Argh! Apologies, download failed…',
|
||||||
detail: `${error.toString()} ${error.cause ? `\n\n${error.cause.toString()}` : ''}`,
|
detail: `${error.toString()} ${
|
||||||
|
error.cause ? `\n\n${error.cause.toString()}` : ''
|
||||||
|
}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = async (win_, options) => {
|
module.exports = async (win_, options) => {
|
||||||
win = win_;
|
win = win_;
|
||||||
config.init(options);
|
config.init(options);
|
||||||
injectCSS(win.webContents, join(__dirname, "style.css"));
|
injectCSS(win.webContents, join(__dirname, 'style.css'));
|
||||||
|
|
||||||
yt = await Innertube.create({
|
yt = await Innertube.create({
|
||||||
cache: new UniversalCache(false),
|
cache: new UniversalCache(false),
|
||||||
generate_session_locally: true,
|
generate_session_locally: true,
|
||||||
});
|
});
|
||||||
ipcMain.on("download-song", (_, url) => downloadSong(url));
|
ipcMain.on('download-song', (_, url) => downloadSong(url));
|
||||||
ipcMain.on("video-src-changed", async (_, data) => {
|
ipcMain.on('video-src-changed', async (_, data) => {
|
||||||
playingUrl =
|
playingUrl =
|
||||||
JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical;
|
JSON.parse(data)?.microformat?.microformatDataRenderer?.urlCanonical;
|
||||||
});
|
});
|
||||||
ipcMain.on("download-playlist-request", async (_event, url) =>
|
ipcMain.on('download-playlist-request', async (_event, url) =>
|
||||||
downloadPlaylist(url),
|
downloadPlaylist(url),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -91,7 +93,12 @@ async function downloadSong(
|
|||||||
increasePlaylistProgress = () => {},
|
increasePlaylistProgress = () => {},
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await downloadSongUnsafe(url, playlistFolder, trackId, increasePlaylistProgress);
|
await downloadSongUnsafe(
|
||||||
|
url,
|
||||||
|
playlistFolder,
|
||||||
|
trackId,
|
||||||
|
increasePlaylistProgress,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendError(error);
|
sendError(error);
|
||||||
}
|
}
|
||||||
@ -112,38 +119,38 @@ async function downloadSongUnsafe(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sendFeedback("Downloading...", 2);
|
sendFeedback('Downloading...', 2);
|
||||||
|
|
||||||
const id = getVideoId(url);
|
const id = getVideoId(url);
|
||||||
const info = await yt.music.getInfo(id);
|
const info = await yt.music.getInfo(id);
|
||||||
|
|
||||||
const metadata = getMetadata(info);
|
const metadata = getMetadata(info);
|
||||||
if (metadata.album === "N/A") metadata.album = "";
|
if (metadata.album === 'N/A') metadata.album = '';
|
||||||
metadata.trackId = trackId;
|
metadata.trackId = trackId;
|
||||||
|
|
||||||
const dir =
|
const dir =
|
||||||
playlistFolder || config.get("downloadFolder") || app.getPath("downloads");
|
playlistFolder || config.get('downloadFolder') || app.getPath('downloads');
|
||||||
const name = `${metadata.artist ? `${metadata.artist} - ` : ""}${
|
const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
|
||||||
metadata.title
|
metadata.title
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const extension = presets[config.get("preset")]?.extension || "mp3";
|
const extension = presets[config.get('preset')]?.extension || 'mp3';
|
||||||
|
|
||||||
const filename = filenamify(`${name}.${extension}`, {
|
const filename = filenamify(`${name}.${extension}`, {
|
||||||
replacement: "_",
|
replacement: '_',
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
});
|
});
|
||||||
const filePath = join(dir, filename);
|
const filePath = join(dir, filename);
|
||||||
|
|
||||||
if (config.get("skipExisting") && existsSync(filePath)) {
|
if (config.get('skipExisting') && existsSync(filePath)) {
|
||||||
sendFeedback(null, -1);
|
sendFeedback(null, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const download_options = {
|
const download_options = {
|
||||||
type: "audio", // audio, video or video+audio
|
type: 'audio', // audio, video or video+audio
|
||||||
quality: "best", // best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
quality: 'best', // best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
||||||
format: "any", // media container format
|
format: 'any', // media container format
|
||||||
};
|
};
|
||||||
|
|
||||||
const format = info.chooseFormat(download_options);
|
const format = info.chooseFormat(download_options);
|
||||||
@ -159,7 +166,7 @@ async function downloadSongUnsafe(
|
|||||||
mkdirSync(dir);
|
mkdirSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!presets[config.get("preset")]) {
|
if (!presets[config.get('preset')]) {
|
||||||
const fileBuffer = await iterableStreamToMP3(
|
const fileBuffer = await iterableStreamToMP3(
|
||||||
iterableStream,
|
iterableStream,
|
||||||
metadata,
|
metadata,
|
||||||
@ -184,7 +191,7 @@ async function downloadSongUnsafe(
|
|||||||
await ffmpegWriteTags(
|
await ffmpegWriteTags(
|
||||||
filePath,
|
filePath,
|
||||||
metadata,
|
metadata,
|
||||||
presets[config.get("preset")]?.ffmpegArgs,
|
presets[config.get('preset')]?.ffmpegArgs,
|
||||||
);
|
);
|
||||||
sendFeedback(null, -1);
|
sendFeedback(null, -1);
|
||||||
}
|
}
|
||||||
@ -213,10 +220,10 @@ async function iterableStreamToMP3(
|
|||||||
// This is a very rough estimate, trying to make the progress bar look nice
|
// This is a very rough estimate, trying to make the progress bar look nice
|
||||||
increasePlaylistProgress(ratio * 0.15);
|
increasePlaylistProgress(ratio * 0.15);
|
||||||
}
|
}
|
||||||
sendFeedback("Loading…", 2); // indefinite progress bar after download
|
sendFeedback('Loading…', 2); // indefinite progress bar after download
|
||||||
|
|
||||||
const buffer = Buffer.concat(chunks);
|
const buffer = Buffer.concat(chunks);
|
||||||
const safeVideoName = randomBytes(32).toString("hex");
|
const safeVideoName = randomBytes(32).toString('hex');
|
||||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -224,10 +231,10 @@ async function iterableStreamToMP3(
|
|||||||
await ffmpeg.load();
|
await ffmpeg.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFeedback("Preparing file…");
|
sendFeedback('Preparing file…');
|
||||||
ffmpeg.FS("writeFile", safeVideoName, buffer);
|
ffmpeg.FS('writeFile', safeVideoName, buffer);
|
||||||
|
|
||||||
sendFeedback("Converting…");
|
sendFeedback('Converting…');
|
||||||
|
|
||||||
ffmpeg.setProgress(({ ratio }) => {
|
ffmpeg.setProgress(({ ratio }) => {
|
||||||
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
|
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
|
||||||
@ -235,15 +242,15 @@ async function iterableStreamToMP3(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await ffmpeg.run(
|
await ffmpeg.run(
|
||||||
"-i",
|
'-i',
|
||||||
safeVideoName,
|
safeVideoName,
|
||||||
...getFFmpegMetadataArgs(metadata),
|
...getFFmpegMetadataArgs(metadata),
|
||||||
`${safeVideoName}.mp3`,
|
`${safeVideoName}.mp3`,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendFeedback("Saving…");
|
sendFeedback('Saving…');
|
||||||
|
|
||||||
return ffmpeg.FS("readFile", `${safeVideoName}.mp3`);
|
return ffmpeg.FS('readFile', `${safeVideoName}.mp3`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendError(e);
|
sendError(e);
|
||||||
} finally {
|
} finally {
|
||||||
@ -267,35 +274,35 @@ async function getCoverBuffer(url) {
|
|||||||
|
|
||||||
async function writeID3(buffer, metadata, sendFeedback) {
|
async function writeID3(buffer, metadata, sendFeedback) {
|
||||||
try {
|
try {
|
||||||
sendFeedback("Writing ID3 tags...");
|
sendFeedback('Writing ID3 tags...');
|
||||||
|
|
||||||
const coverBuffer = await getCoverBuffer(metadata.image);
|
const coverBuffer = await getCoverBuffer(metadata.image);
|
||||||
|
|
||||||
const writer = new ID3Writer(buffer);
|
const writer = new ID3Writer(buffer);
|
||||||
|
|
||||||
// Create the metadata tags
|
// Create the metadata tags
|
||||||
writer.setFrame("TIT2", metadata.title).setFrame("TPE1", [metadata.artist]);
|
writer.setFrame('TIT2', metadata.title).setFrame('TPE1', [metadata.artist]);
|
||||||
if (metadata.album) {
|
if (metadata.album) {
|
||||||
writer.setFrame("TALB", metadata.album);
|
writer.setFrame('TALB', metadata.album);
|
||||||
}
|
}
|
||||||
if (coverBuffer) {
|
if (coverBuffer) {
|
||||||
writer.setFrame("APIC", {
|
writer.setFrame('APIC', {
|
||||||
type: 3,
|
type: 3,
|
||||||
data: coverBuffer,
|
data: coverBuffer,
|
||||||
description: "",
|
description: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isEnabled("lyrics-genius")) {
|
if (isEnabled('lyrics-genius')) {
|
||||||
const lyrics = await fetchFromGenius(metadata);
|
const lyrics = await fetchFromGenius(metadata);
|
||||||
if (lyrics) {
|
if (lyrics) {
|
||||||
writer.setFrame("USLT", {
|
writer.setFrame('USLT', {
|
||||||
description: "",
|
description: '',
|
||||||
lyrics: lyrics,
|
lyrics: lyrics,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (metadata.trackId) {
|
if (metadata.trackId) {
|
||||||
writer.setFrame("TRCK", metadata.trackId);
|
writer.setFrame('TRCK', metadata.trackId);
|
||||||
}
|
}
|
||||||
writer.addTag();
|
writer.addTag();
|
||||||
return Buffer.from(writer.arrayBuffer);
|
return Buffer.from(writer.arrayBuffer);
|
||||||
@ -318,18 +325,18 @@ async function downloadPlaylist(givenUrl) {
|
|||||||
getPlaylistID(new URL(playingUrl));
|
getPlaylistID(new URL(playingUrl));
|
||||||
|
|
||||||
if (!playlistId) {
|
if (!playlistId) {
|
||||||
sendError(new Error("No playlist ID found"));
|
sendError(new Error('No playlist ID found'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendFeedback = (message) => sendFeedback_(win, message);
|
const sendFeedback = (message) => sendFeedback_(win, message);
|
||||||
|
|
||||||
console.log(`trying to get playlist ID: '${playlistId}'`);
|
console.log(`trying to get playlist ID: '${playlistId}'`);
|
||||||
sendFeedback("Getting playlist info…");
|
sendFeedback('Getting playlist info…');
|
||||||
let playlist;
|
let playlist;
|
||||||
try {
|
try {
|
||||||
playlist = await ytpl(playlistId, {
|
playlist = await ytpl(playlistId, {
|
||||||
limit: config.get("playlistMaxItems") || Infinity,
|
limit: config.get('playlistMaxItems') || Infinity,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendError(
|
sendError(
|
||||||
@ -337,22 +344,22 @@ async function downloadPlaylist(givenUrl) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (playlist.items.length === 0) sendError(new Error("Playlist is empty"));
|
if (playlist.items.length === 0) sendError(new Error('Playlist is empty'));
|
||||||
if (playlist.items.length === 1) {
|
if (playlist.items.length === 1) {
|
||||||
sendFeedback("Playlist has only one item, downloading it directly");
|
sendFeedback('Playlist has only one item, downloading it directly');
|
||||||
await downloadSong(playlist.items[0].url);
|
await downloadSong(playlist.items[0].url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isAlbum = playlist.title.startsWith("Album - ");
|
const isAlbum = playlist.title.startsWith('Album - ');
|
||||||
if (isAlbum) {
|
if (isAlbum) {
|
||||||
playlist.title = playlist.title.slice(8);
|
playlist.title = playlist.title.slice(8);
|
||||||
}
|
}
|
||||||
const safePlaylistTitle = filenamify(playlist.title, { replacement: " " });
|
const safePlaylistTitle = filenamify(playlist.title, { replacement: ' ' });
|
||||||
|
|
||||||
const folder = getFolder(config.get("downloadFolder"));
|
const folder = getFolder(config.get('downloadFolder'));
|
||||||
const playlistFolder = join(folder, safePlaylistTitle);
|
const playlistFolder = join(folder, safePlaylistTitle);
|
||||||
if (existsSync(playlistFolder)) {
|
if (existsSync(playlistFolder)) {
|
||||||
if (!config.get("skipExisting")) {
|
if (!config.get('skipExisting')) {
|
||||||
sendError(new Error(`The folder ${playlistFolder} already exists`));
|
sendError(new Error(`The folder ${playlistFolder} already exists`));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -361,9 +368,9 @@ async function downloadPlaylist(givenUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
type: "info",
|
type: 'info',
|
||||||
buttons: ["OK"],
|
buttons: ['OK'],
|
||||||
title: "Started Download",
|
title: 'Started Download',
|
||||||
message: `Downloading Playlist "${playlist.title}"`,
|
message: `Downloading Playlist "${playlist.title}"`,
|
||||||
detail: `(${playlist.items.length} songs)`,
|
detail: `(${playlist.items.length} songs)`,
|
||||||
});
|
});
|
||||||
@ -425,7 +432,7 @@ async function ffmpegWriteTags(filePath, metadata, ffmpegArgs = []) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ffmpeg.run(
|
await ffmpeg.run(
|
||||||
"-i",
|
'-i',
|
||||||
filePath,
|
filePath,
|
||||||
...getFFmpegMetadataArgs(metadata),
|
...getFFmpegMetadataArgs(metadata),
|
||||||
...ffmpegArgs,
|
...ffmpegArgs,
|
||||||
@ -444,19 +451,19 @@ function getFFmpegMetadataArgs(metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(metadata.title ? ["-metadata", `title=${metadata.title}`] : []),
|
...(metadata.title ? ['-metadata', `title=${metadata.title}`] : []),
|
||||||
...(metadata.artist ? ["-metadata", `artist=${metadata.artist}`] : []),
|
...(metadata.artist ? ['-metadata', `artist=${metadata.artist}`] : []),
|
||||||
...(metadata.album ? ["-metadata", `album=${metadata.album}`] : []),
|
...(metadata.album ? ['-metadata', `album=${metadata.album}`] : []),
|
||||||
...(metadata.trackId ? ["-metadata", `track=${metadata.trackId}`] : []),
|
...(metadata.trackId ? ['-metadata', `track=${metadata.trackId}`] : []),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playlist radio modifier needs to be cut from playlist ID
|
// Playlist radio modifier needs to be cut from playlist ID
|
||||||
const INVALID_PLAYLIST_MODIFIER = "RDAMPL";
|
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
|
||||||
|
|
||||||
const getPlaylistID = (aURL) => {
|
const getPlaylistID = (aURL) => {
|
||||||
const result =
|
const result =
|
||||||
aURL?.searchParams.get("list") || aURL?.searchParams.get("playlist");
|
aURL?.searchParams.get('list') || aURL?.searchParams.get('playlist');
|
||||||
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
|
if (result?.startsWith(INVALID_PLAYLIST_MODIFIER)) {
|
||||||
return result.slice(6);
|
return result.slice(6);
|
||||||
}
|
}
|
||||||
@ -464,10 +471,10 @@ const getPlaylistID = (aURL) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getVideoId = (url) => {
|
const getVideoId = (url) => {
|
||||||
if (typeof url === "string") {
|
if (typeof url === 'string') {
|
||||||
url = new URL(url);
|
url = new URL(url);
|
||||||
}
|
}
|
||||||
return url.searchParams.get("v");
|
return url.searchParams.get('v');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMetadata = (info) => ({
|
const getMetadata = (info) => ({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
const { setOptions, setMenuOptions } = require("../../config/plugins");
|
const { setOptions, setMenuOptions } = require('../../config/plugins');
|
||||||
const defaultConfig = require("../../config/defaults");
|
const defaultConfig = require('../../config/defaults');
|
||||||
|
|
||||||
let config = defaultConfig.plugins["downloader"];
|
let config = defaultConfig.plugins['downloader'];
|
||||||
|
|
||||||
module.exports.init = (options) => {
|
module.exports.init = (options) => {
|
||||||
config = { ...config, ...options };
|
config = { ...config, ...options };
|
||||||
@ -9,15 +9,15 @@ module.exports.init = (options) => {
|
|||||||
|
|
||||||
module.exports.setAndMaybeRestart = (option, value) => {
|
module.exports.setAndMaybeRestart = (option, value) => {
|
||||||
config[option] = value;
|
config[option] = value;
|
||||||
setMenuOptions("downloader", config);
|
setMenuOptions('downloader', config);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.set = (option, value) => {
|
module.exports.set = (option, value) => {
|
||||||
config[option] = value;
|
config[option] = value;
|
||||||
setOptions("downloader", config);
|
setOptions('downloader', config);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.get = (option) => {
|
module.exports.get = (option) => {
|
||||||
let res = config[option];
|
const res = config[option];
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user