QOL: Move source code under the src directory. (#1318)

This commit is contained in:
Angelos Bouklis
2023-10-15 15:52:48 +03:00
committed by GitHub
parent 30c8dcf730
commit 7625a3aa52
159 changed files with 102 additions and 71 deletions

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"useTabs": false,
"singleQuote": true
}

View File

@ -18,7 +18,7 @@ export default defineConfig({
nodeResolvePlugin({ nodeResolvePlugin({
browser: false, browser: false,
preferBuiltins: true, preferBuiltins: true,
exportConditions: ['node', 'default', 'module', 'import'] , exportConditions: ['node', 'default', 'module', 'import'],
}), }),
commonjs({ commonjs({
ignoreDynamicRequires: true, ignoreDynamicRequires: true,
@ -34,7 +34,7 @@ export default defineConfig({
css(), css(),
copy({ copy({
targets: [ targets: [
{ src: 'error.html', dest: 'dist/' }, { src: 'src/error.html', dest: 'dist/' },
{ src: 'assets', dest: 'dist/' }, { src: 'assets', dest: 'dist/' },
], ],
}), }),
@ -47,18 +47,14 @@ export default defineConfig({
setTimeout(() => process.exit(0)); setTimeout(() => process.exit(0));
} }
}, },
name: 'force-close' name: 'force-close',
}, },
], ],
input: './index.ts', input: './src/index.ts',
output: { output: {
format: 'cjs', format: 'cjs',
name: '[name].js', name: '[name].js',
dir: './dist', dir: './dist',
}, },
external: [ external: ['electron', 'custom-electron-prompt', ...builtinModules],
'electron',
'custom-electron-prompt',
...builtinModules,
],
}); });

View File

@ -41,18 +41,14 @@ export default defineConfig({
setTimeout(() => process.exit(0)); setTimeout(() => process.exit(0));
} }
}, },
name: 'force-close' name: 'force-close',
}, },
], ],
input: './preload.ts', input: './src/preload.ts',
output: { output: {
format: 'cjs', format: 'cjs',
name: '[name].js', name: '[name].js',
dir: './dist', dir: './dist',
}, },
external: [ external: ['electron', 'custom-electron-prompt', ...builtinModules],
'electron',
'custom-electron-prompt',
...builtinModules,
],
}); });

View File

@ -1,16 +1,32 @@
import { createWriteStream, existsSync, mkdirSync, writeFileSync, } from 'node:fs'; import {
createWriteStream,
existsSync,
mkdirSync,
writeFileSync,
} from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { randomBytes } from 'node:crypto'; import { randomBytes } from 'node:crypto';
import { app, BrowserWindow, dialog, ipcMain, net } from 'electron'; import { app, BrowserWindow, dialog, ipcMain, net } from 'electron';
import { ClientType, Innertube, UniversalCache, Utils, YTNodes } from 'youtubei.js'; import {
ClientType,
Innertube,
UniversalCache,
Utils,
YTNodes,
} from 'youtubei.js';
import is from 'electron-is'; import is from 'electron-is';
import filenamify from 'filenamify'; import filenamify from 'filenamify';
import { Mutex } from 'async-mutex'; import { Mutex } from 'async-mutex';
import { createFFmpeg } from '@ffmpeg.wasm/main'; import { createFFmpeg } from '@ffmpeg.wasm/main';
import NodeID3, { TagConstants } from 'node-id3'; import NodeID3, { TagConstants } from 'node-id3';
import { cropMaxWidth, getFolder, sendFeedback as sendFeedback_, setBadge } from './utils'; import {
cropMaxWidth,
getFolder,
sendFeedback as sendFeedback_,
setBadge,
} from './utils';
import config from './config'; import config from './config';
import { YoutubeFormatList, type Preset, DefaultPresetList } from './types'; import { YoutubeFormatList, type Preset, DefaultPresetList } from './types';
@ -34,10 +50,8 @@ type CustomSongInfo = SongInfo & { trackId?: string };
const ffmpeg = createFFmpeg({ const ffmpeg = createFFmpeg({
log: false, log: false,
logger() { logger() {}, // Console.log,
}, // Console.log, progress() {}, // Console.log,
progress() {
}, // Console.log,
}); });
const ffmpegMutex = new Mutex(); const ffmpegMutex = new Mutex();
@ -65,9 +79,13 @@ const sendError = (error: Error, source?: string) => {
}; };
export const getCookieFromWindow = async (win: BrowserWindow) => { export const getCookieFromWindow = async (win: BrowserWindow) => {
return (await win.webContents.session.cookies.get({ url: 'https://music.youtube.com' })).map((it) => return (
it.name + '=' + it.value + ';' await win.webContents.session.cookies.get({
).join(''); url: 'https://music.youtube.com',
})
)
.map((it) => it.name + '=' + it.value + ';')
.join('');
}; };
export default async (win_: BrowserWindow) => { export default async (win_: BrowserWindow) => {
@ -78,12 +96,13 @@ export default async (win_: BrowserWindow) => {
cache: new UniversalCache(false), cache: new UniversalCache(false),
cookie: await getCookieFromWindow(win), cookie: await getCookieFromWindow(win),
generate_session_locally: true, generate_session_locally: true,
fetch: async (input: RequestInfo | URL, init?: RequestInit) => { fetch: (async (input: RequestInfo | URL, init?: RequestInit) => {
const url = const url =
typeof input === 'string' ? typeof input === 'string'
new URL(input) : ? new URL(input)
input instanceof URL ? : input instanceof URL
input : new URL(input.url); ? input
: new URL(input.url);
if (init?.body && !init.method) { if (init?.body && !init.method) {
init.method = 'POST'; init.method = 'POST';
@ -95,7 +114,7 @@ export default async (win_: BrowserWindow) => {
); );
return net.fetch(request, init); return net.fetch(request, init);
} }) as typeof fetch,
}); });
ipcMain.on('download-song', (_, url: string) => downloadSong(url)); ipcMain.on('download-song', (_, url: string) => downloadSong(url));
ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => { ipcMain.on('video-src-changed', (_, data: GetPlayerResponse) => {
@ -110,15 +129,14 @@ export async function downloadSong(
url: string, url: string,
playlistFolder: string | undefined = undefined, playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined, trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
let resolvedName; let resolvedName;
try { try {
await downloadSongUnsafe( await downloadSongUnsafe(
false, false,
url, url,
(name: string) => resolvedName = name, (name: string) => (resolvedName = name),
playlistFolder, playlistFolder,
trackId, trackId,
increasePlaylistProgress, increasePlaylistProgress,
@ -132,15 +150,14 @@ export async function downloadSongFromId(
id: string, id: string,
playlistFolder: string | undefined = undefined, playlistFolder: string | undefined = undefined,
trackId: string | undefined = undefined, trackId: string | undefined = undefined,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
let resolvedName; let resolvedName;
try { try {
await downloadSongUnsafe( await downloadSongUnsafe(
true, true,
id, id,
(name: string) => resolvedName = name, (name: string) => (resolvedName = name),
playlistFolder, playlistFolder,
trackId, trackId,
increasePlaylistProgress, increasePlaylistProgress,
@ -190,8 +207,8 @@ async function downloadSongUnsafe(
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
}`; }`;
@ -214,7 +231,8 @@ async function downloadSongUnsafe(
} }
if (playabilityStatus.status === 'UNPLAYABLE') { if (playabilityStatus.status === 'UNPLAYABLE') {
const errorScreen = playabilityStatus.error_screen as PlayerErrorMessage | null; const errorScreen =
playabilityStatus.error_screen as PlayerErrorMessage | null;
throw new Error( throw new Error(
`[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`, `[${playabilityStatus.status}] ${errorScreen?.reason.text}: ${errorScreen?.subreason.text}`,
); );
@ -223,7 +241,8 @@ async function downloadSongUnsafe(
const selectedPreset = config.get('selectedPreset') ?? 'mp3 (256kbps)'; const selectedPreset = config.get('selectedPreset') ?? 'mp3 (256kbps)';
let presetSetting: Preset; let presetSetting: Preset;
if (selectedPreset === 'Custom') { if (selectedPreset === 'Custom') {
presetSetting = config.get('customPresetSetting') ?? DefaultPresetList['Custom']; presetSetting =
config.get('customPresetSetting') ?? DefaultPresetList['Custom'];
} else if (selectedPreset === 'Source') { } else if (selectedPreset === 'Source') {
presetSetting = DefaultPresetList['Source']; presetSetting = DefaultPresetList['Source'];
} else { } else {
@ -240,7 +259,9 @@ async function downloadSongUnsafe(
let targetFileExtension: string; let targetFileExtension: string;
if (!presetSetting?.extension) { if (!presetSetting?.extension) {
targetFileExtension = YoutubeFormatList.find((it) => it.itag === format.itag)?.container ?? 'mp3'; targetFileExtension =
YoutubeFormatList.find((it) => it.itag === format.itag)?.container ??
'mp3';
} else { } else {
targetFileExtension = presetSetting?.extension ?? 'mp3'; targetFileExtension = presetSetting?.extension ?? 'mp3';
} }
@ -285,7 +306,11 @@ async function downloadSongUnsafe(
if (targetFileExtension !== 'mp3') { if (targetFileExtension !== 'mp3') {
createWriteStream(filePath).write(fileBuffer); createWriteStream(filePath).write(fileBuffer);
} else { } else {
const buffer = await writeID3(Buffer.from(fileBuffer), metadata, sendFeedback); const buffer = await writeID3(
Buffer.from(fileBuffer),
metadata,
sendFeedback,
);
if (buffer) { if (buffer) {
writeFileSync(filePath, buffer); writeFileSync(filePath, buffer);
} }
@ -303,8 +328,7 @@ async function iterableStreamToTargetFile(
presetFfmpegArgs: string[], presetFfmpegArgs: string[],
contentLength: number, contentLength: number,
sendFeedback: (str: string, value?: number) => void, sendFeedback: (str: string, value?: number) => void,
increasePlaylistProgress: (value: number) => void = () => { increasePlaylistProgress: (value: number) => void = () => {},
},
) { ) {
const chunks = []; const chunks = [];
let downloaded = 0; let downloaded = 0;
@ -337,7 +361,7 @@ async function iterableStreamToTargetFile(
ffmpeg.setProgress(({ ratio }) => { ffmpeg.setProgress(({ ratio }) => {
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio); sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
increasePlaylistProgress(0.15 + (ratio * 0.85)); increasePlaylistProgress(0.15 + ratio * 0.85);
}); });
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`; const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
@ -372,7 +396,11 @@ const getCoverBuffer = cache(async (url: string) => {
return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null; return nativeImage && !nativeImage.isEmpty() ? nativeImage.toPNG() : null;
}); });
async function writeID3(buffer: Buffer, metadata: CustomSongInfo, sendFeedback: (str: string, value?: number) => void) { async function writeID3(
buffer: Buffer,
metadata: CustomSongInfo,
sendFeedback: (str: string, value?: number) => void,
) {
try { try {
sendFeedback('Writing ID3 tags...'); sendFeedback('Writing ID3 tags...');
const tags: NodeID3.Tags = {}; const tags: NodeID3.Tags = {};
@ -425,10 +453,10 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
return; return;
} }
const playlistId const playlistId =
= getPlaylistID(givenUrl) getPlaylistID(givenUrl) ||
|| getPlaylistID(new URL(win.webContents.getURL())) getPlaylistID(new URL(win.webContents.getURL())) ||
|| 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'));
@ -444,7 +472,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
playlist = await yt.music.getPlaylist(playlistId); playlist = await yt.music.getPlaylist(playlistId);
} catch (error: unknown) { } catch (error: unknown) {
sendError( sendError(
Error(`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(error)}`), Error(
`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(
error,
)}`,
),
); );
return; return;
} }
@ -461,15 +493,12 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
} }
const normalPlaylistTitle = playlist.header?.title?.text; const normalPlaylistTitle = playlist.header?.title?.text;
const playlistTitle = normalPlaylistTitle ?? const playlistTitle =
playlist normalPlaylistTitle ??
.page playlist.page.contents_memo
.contents_memo
?.get('MusicResponsiveListItemFlexColumn') ?.get('MusicResponsiveListItemFlexColumn')
?.at(2) ?.at(2)
?.as(YTNodes.MusicResponsiveListItemFlexColumn) ?.as(YTNodes.MusicResponsiveListItemFlexColumn)?.title?.text ??
?.title
?.text ??
'NO_TITLE'; 'NO_TITLE';
const isAlbum = !normalPlaylistTitle; const isAlbum = !normalPlaylistTitle;
@ -513,7 +542,7 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
const increaseProgress = (itemPercentage: number) => { const increaseProgress = (itemPercentage: number) => {
const currentProgress = (counter - 1) / (items.length ?? 1); const currentProgress = (counter - 1) / (items.length ?? 1);
const newProgress = currentProgress + (progressStep * itemPercentage); const newProgress = currentProgress + progressStep * itemPercentage;
win.setProgressBar(newProgress); win.setProgressBar(newProgress);
}; };
@ -528,7 +557,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
increaseProgress, increaseProgress,
).catch((error) => ).catch((error) =>
sendError( sendError(
new Error(`Error downloading "${song.author!.name} - ${song.title!}":\n ${error}`) new Error(
`Error downloading "${
song.author!.name
} - ${song.title!}":\n ${error}`,
),
), ),
); );
@ -562,8 +595,8 @@ function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
const INVALID_PLAYLIST_MODIFIER = 'RDAMPL'; const INVALID_PLAYLIST_MODIFIER = 'RDAMPL';
const getPlaylistID = (aURL: URL) => { const getPlaylistID = (aURL: URL) => {
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(INVALID_PLAYLIST_MODIFIER.length); return result.slice(INVALID_PLAYLIST_MODIFIER.length);
} }
@ -572,15 +605,18 @@ const getPlaylistID = (aURL: URL) => {
}; };
const getVideoId = (url: URL | string): string | null => { const getVideoId = (url: URL | string): string | null => {
return (new URL(url)).searchParams.get('v'); return new URL(url).searchParams.get('v');
}; };
const getMetadata = (info: TrackInfo): CustomSongInfo => ({ const getMetadata = (info: TrackInfo): CustomSongInfo => ({
videoId: info.basic_info.id!, videoId: info.basic_info.id!,
title: cleanupName(info.basic_info.title!), title: cleanupName(info.basic_info.title!),
artist: cleanupName(info.basic_info.author!), artist: cleanupName(info.basic_info.author!),
album: info.player_overlays?.browser_media_session?.as(YTNodes.BrowserMediaSession).album?.text, album: info.player_overlays?.browser_media_session?.as(
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))?.url, YTNodes.BrowserMediaSession,
).album?.text,
imageSrc: info.basic_info.thumbnail?.find((t) => !t.url.endsWith('.webp'))
?.url,
views: info.basic_info.view_count!, views: info.basic_info.view_count!,
songDuration: info.basic_info.duration!, songDuration: info.basic_info.duration!,
}); });

View File

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 392 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

View File

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 338 B

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

View File

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 546 B

Some files were not shown because too many files have changed in this diff Show More