Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(downloader): New option to download on finish #1964

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
112ba19
Create front.js
Johannes7k75 Sep 18, 2023
f617a98
Create scrollbutton.html
Johannes7k75 Sep 18, 2023
efe96b6
Update build.yml
Johannes7k75 Sep 18, 2023
0e5f696
Update build.yml
Johannes7k75 Sep 18, 2023
b0860fb
Add config option
Johannes7k75 Apr 11, 2024
a24e409
add menu option
Johannes7k75 Apr 11, 2024
3ce35cf
add menu label locales
Johannes7k75 Apr 11, 2024
a0cef36
fix wrong formatting
Johannes7k75 Apr 13, 2024
b81eb00
use the config from l:95 for downloadOnFinish
Johannes7k75 Apr 13, 2024
3812c98
add config for what is meant by "last seconds"
Johannes7k75 Apr 14, 2024
33b8dac
add new checks for "last seconds"
Johannes7k75 Apr 14, 2024
0de0174
add new locales
Johannes7k75 Apr 14, 2024
dcb598a
Merge branch 'th-ch:master' into downloader-download_finished_song
Johannes7k75 Apr 30, 2024
ffe4111
Merge branch 'downloader-download_finished_song' of https://github.co…
Johannes7k75 Apr 30, 2024
7288efd
Remove Workflow_dispatch
Johannes7k75 Apr 30, 2024
4b13717
Make downloadOnFinishDownload folder configureable
Johannes7k75 May 1, 2024
49e95ab
update paths for `downloadFolder` and `downloadOnFinishFolder` after …
Johannes7k75 May 1, 2024
d1777d9
remove unnecessary files
Johannes7k75 May 25, 2024
f984cae
Merge branch 'th-ch:master' into downloader-download_finished_song
Johannes7k75 Jun 2, 2024
880ceca
Merge remote-tracking branch 'origin/master' into downloader-download…
JellyBrick Jul 14, 2024
05b2d17
fix: apply fix from eslint
JellyBrick Jul 14, 2024
9acf7ad
fix: moduleResolution
JellyBrick Jul 14, 2024
5ddfa89
fix: menu
JellyBrick Jul 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/i18n/resources/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,22 @@
"choose-download-folder": "Downloadordner wählen",
"download-playlist": "Wiedergabeliste herunterladen",
"presets": "Voreinstellungen",
"skip-existing": "Vorhandene Dateien überspringen"
"skip-existing": "Vorhandene Dateien überspringen",
"download-finish-settings": {
"label": "Song am Ende runterladen",
"submenu": {
"enabled": "Aktiviert",
"mode": "Zeitmodus",
"seconds": "Sekunden",
"percent": "Prozent",
"advanced": "Erweitert"
},
"prompt": {
"title": "Konfiguriere wann runtergeladen werden soll",
"last-seconds": "Letzten x Sekunden",
"last-percent": "Nach x Prozent"
}
}
},
"name": "Downloader",
"renderer": {
Expand Down
17 changes: 16 additions & 1 deletion src/i18n/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,22 @@
"choose-download-folder": "Choose download folder",
"download-playlist": "Download playlist",
"presets": "Presets",
"skip-existing": "Skip existing files"
"skip-existing": "Skip existing files",
"download-finish-settings": {
"label": "Download on finish",
"submenu": {
"enabled": "Enabled",
"mode": "Time mode",
"seconds": "Seconds",
"percent": "Percent",
"advanced": "Advanced"
},
"prompt": {
"title": "Configure when to download",
"last-seconds": "Last x seconds",
"last-percent": "After x percent"
}
}
},
"name": "Downloader",
"renderer": {
Expand Down
14 changes: 14 additions & 0 deletions src/plugins/downloader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { t } from '@/i18n';
export type DownloaderPluginConfig = {
enabled: boolean;
downloadFolder?: string;
downloadOnFinish?: {
enabled: boolean;
seconds: number;
percent: number;
mode: 'percent' | 'seconds';
folder?: string;
};
selectedPreset: string;
customPresetSetting: Preset;
skipExisting: boolean;
Expand All @@ -20,6 +27,13 @@ export type DownloaderPluginConfig = {
export const defaultConfig: DownloaderPluginConfig = {
enabled: false,
downloadFolder: undefined,
downloadOnFinish: {
enabled: false,
seconds: 20,
percent: 10,
mode: 'seconds',
folder: undefined,
},
selectedPreset: 'mp3 (256kbps)', // Selected preset
customPresetSetting: DefaultPresetList['mp3 (256kbps)'], // Presets
skipExisting: false,
Expand Down
69 changes: 60 additions & 9 deletions src/plugins/downloader/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import {
existsSync,
mkdirSync,
writeFileSync,
} from 'node:fs';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { randomBytes } from 'node:crypto';

import { app, BrowserWindow, dialog } from 'electron';
import { app, BrowserWindow, dialog, ipcMain } from 'electron';
import {
ClientType,
Innertube,
Expand All @@ -29,7 +25,12 @@ import {

import { fetchFromGenius } from '@/plugins/lyrics-genius/main';
import { isEnabled } from '@/config/plugins';
import { cleanupName, getImage, MediaType, type SongInfo } from '@/providers/song-info';
import registerCallback, {
cleanupName,
getImage,
MediaType,
type SongInfo,
} from '@/providers/song-info';
import { getNetFetchAsFetch } from '@/plugins/utils/main';

import { t } from '@/i18n';
Expand Down Expand Up @@ -114,6 +115,8 @@ export const onMainLoad = async ({
ipc.handle('download-playlist-request', async (url: string) =>
downloadPlaylist(url),
);

downloadSongOnFinishSetup({ ipc, getConfig });
};

export const onConfigChange = (newConfig: DownloaderPluginConfig) => {
Expand Down Expand Up @@ -162,6 +165,48 @@ export async function downloadSongFromId(
}
}

function downloadSongOnFinishSetup({
ipc,
}: Pick<BackendContext<DownloaderPluginConfig>, 'ipc' | 'getConfig'>) {
let currentUrl: string | undefined;
let duration: number | undefined;
let time = 0;

registerCallback((songInfo: SongInfo) => {
if (
!songInfo.isPaused &&
songInfo.url !== currentUrl &&
config.downloadOnFinish?.enabled
) {
if (typeof currentUrl === 'string' && duration && duration > 0) {
if (
config.downloadOnFinish.mode === 'seconds' &&
duration - time <= config.downloadOnFinish.seconds
) {
downloadSong(currentUrl, config.downloadOnFinish.folder ?? config.downloadFolder);
} else if (
config.downloadOnFinish.mode === 'percent' &&
time >= duration * (config.downloadOnFinish.percent / 100)
) {
downloadSong(currentUrl, config.downloadOnFinish.folder ?? config.downloadFolder);
}
}

currentUrl = songInfo.url;
duration = songInfo.songDuration;
time = 0;
}
});

ipcMain.on('ytmd:player-api-loaded', () => {
ipc.send('ytmd:setup-time-changed-listener');
});

ipcMain.on('ytmd:time-changed', (_, t: number) => {
if (t > time) time = t;
});
}

async function downloadSongUnsafe(
isId: boolean,
idOrUrl: string,
Expand Down Expand Up @@ -375,7 +420,12 @@ async function iterableStreamToProcessedUint8Array(
'writeFile',
safeVideoName,
Buffer.concat(
await downloadChunks(stream, contentLength, sendFeedback, increasePlaylistProgress),
await downloadChunks(
stream,
contentLength,
sendFeedback,
increasePlaylistProgress,
),
),
);

Expand Down Expand Up @@ -516,10 +566,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
return;
}

if (!playlist || !playlist.items || playlist.items.length === 0) {
if (!playlist || !playlist.items || playlist.items.length === 0 || !playlist.header || !('title' in playlist.header)) {
sendError(
new Error(t('plugins.downloader.backend.feedback.playlist-is-empty')),
);
return;
}

const normalPlaylistTitle = playlist.header?.title?.text;
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/downloader/main/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { app, BrowserWindow } from 'electron';
import is from 'electron-is';

export const getFolder = (customFolder: string) =>
customFolder || app.getPath('downloads');
export const getFolder = (customFolder?: string) =>
customFolder ?? app.getPath('downloads');

export const sendFeedback = (win: BrowserWindow, message?: unknown) => {
win.webContents.send('downloader-feedback', message);
Expand Down
144 changes: 142 additions & 2 deletions src/plugins/downloader/menu.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,163 @@
import { dialog } from 'electron';
import prompt from 'custom-electron-prompt';
import { deepmerge } from 'deepmerge-ts';

import { downloadPlaylist } from './main';
import { getFolder } from './main/utils';
import { DefaultPresetList } from './types';

import { t } from '@/i18n';

import promptOptions from '@/providers/prompt-options';

import { type DownloaderPluginConfig, defaultConfig } from './index';

import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';

import type { DownloaderPluginConfig } from './index';

export const onMenu = async ({
getConfig,
setConfig,
}: MenuContext<DownloaderPluginConfig>): Promise<MenuTemplate> => {
const config = await getConfig();

return [
{
label: t('plugins.downloader.menu.download-finish-settings.label'),
type: 'submenu',
submenu: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.enabled',
),
type: 'checkbox',
checked: config.downloadOnFinish?.enabled ?? false,
click(item) {
setConfig({
downloadOnFinish: {
...deepmerge(defaultConfig.downloadOnFinish, config.downloadOnFinish)!,
enabled: item.checked,
},
});
},
},
{
type: 'separator',
},
{
label: t('plugins.downloader.menu.choose-download-folder'),
click() {
const result = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory'],
defaultPath: getFolder(config.downloadOnFinish?.folder ?? config.downloadFolder),
});
if (result) {
setConfig({
downloadOnFinish: {
...deepmerge(defaultConfig.downloadOnFinish, config.downloadOnFinish)!,
folder: result[0],
}
});
}
},
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.mode',
),
type: 'submenu',
submenu: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.seconds',
),
type: 'radio',
checked: config.downloadOnFinish?.mode === 'seconds',
click() {
setConfig({
downloadOnFinish: {
...deepmerge(defaultConfig.downloadOnFinish, config.downloadOnFinish)!,
mode: 'seconds',
},
});
},
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.percent',
),
type: 'radio',
checked: config.downloadOnFinish?.mode === 'percent',
click() {
setConfig({
downloadOnFinish: {
...deepmerge(defaultConfig.downloadOnFinish, config.downloadOnFinish)!,
mode: 'percent',
},
});
},
},
],
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.submenu.advanced',
),
async click() {
const res = await prompt({
title: t(
'plugins.downloader.menu.download-finish-settings.prompt.title',
),
type: 'multiInput',
multiInputOptions: [
{
label: t(
'plugins.downloader.menu.download-finish-settings.prompt.last-seconds',
),
inputAttrs: {
type: 'number',
required: true,
min: '0',
step: '1',
},
value: config.downloadOnFinish?.seconds ?? defaultConfig.downloadOnFinish!.seconds,
},
{
label: t(
'plugins.downloader.menu.download-finish-settings.prompt.last-percent',
),
inputAttrs: {
type: 'number',
required: true,
min: '1',
max: '100',
step: '1',
},
value: config.downloadOnFinish?.percent ?? defaultConfig.downloadOnFinish!.percent,
},
],
...promptOptions(),
height: 240,
resizable: true,
}).catch(console.error);

if (!res) {
return undefined;
}

setConfig({
downloadOnFinish: {
...deepmerge(defaultConfig.downloadOnFinish, config.downloadOnFinish)!,
seconds: Number(res[0]),
percent: Number(res[1]),
},
});
return;
},
},
],
},

{
label: t('plugins.downloader.menu.download-playlist'),
click: () => downloadPlaylist(),
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"moduleResolution": "node",
"jsx": "preserve",
"jsxImportSource": "solid-js",
"baseUrl": ".",
Expand Down
Loading