mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-17 13:12:07 +00:00
feat(downloader): Added support for audio format auto-detection (#1310)
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
import { blockers } from '../plugins/adblocker/blocker-types';
|
import { blockers } from '../plugins/adblocker/blocker-types';
|
||||||
|
|
||||||
|
import { DefaultPresetList } from '../plugins/downloader/types';
|
||||||
|
|
||||||
export interface WindowSizeConfig {
|
export interface WindowSizeConfig {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@ -111,9 +113,9 @@ const defaultConfig = {
|
|||||||
},
|
},
|
||||||
'downloader': {
|
'downloader': {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
ffmpegArgs: ['-b:a', '256k'], // E.g. ["-b:a", "192k"] for an audio bitrate of 192kb/s
|
|
||||||
downloadFolder: undefined as string | undefined, // Custom download folder (absolute path)
|
downloadFolder: undefined as string | undefined, // Custom download folder (absolute path)
|
||||||
preset: 'mp3',
|
selectedPreset: 'mp3 (256kbps)', // Selected preset
|
||||||
|
customPresetSetting: DefaultPresetList['mp3 (256kbps)'], // Presets
|
||||||
skipExisting: false,
|
skipExisting: false,
|
||||||
playlistMaxItems: undefined as number | undefined,
|
playlistMaxItems: undefined as number | undefined,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import is from 'electron-is';
|
|||||||
|
|
||||||
import defaults from './defaults';
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
import { DefaultPresetList, type Preset } from '../plugins/downloader/types';
|
||||||
|
|
||||||
const getDefaults = () => {
|
const getDefaults = () => {
|
||||||
if (is.windows()) {
|
if (is.windows()) {
|
||||||
defaults.plugins['in-app-menu'].enabled = true;
|
defaults.plugins['in-app-menu'].enabled = true;
|
||||||
@ -18,6 +20,26 @@ const setDefaultPluginOptions = (store: Conf<Record<string, unknown>>, plugin: k
|
|||||||
};
|
};
|
||||||
|
|
||||||
const migrations = {
|
const migrations = {
|
||||||
|
'>=2.1.0'(store: Conf<Record<string, unknown>>) {
|
||||||
|
const originalPreset = store.get('plugins.downloader.preset') as string | undefined;
|
||||||
|
if (originalPreset) {
|
||||||
|
if (originalPreset !== 'opus') {
|
||||||
|
store.set('plugins.downloader.selectedPreset', 'Custom');
|
||||||
|
store.set('plugins.downloader.customPresetSetting', {
|
||||||
|
extension: 'mp3',
|
||||||
|
ffmpegArgs: store.get('plugins.downloader.ffmpegArgs') as string[] ?? DefaultPresetList['mp3 (256kbps)'].ffmpegArgs,
|
||||||
|
} satisfies Preset);
|
||||||
|
} else {
|
||||||
|
store.set('plugins.downloader.selectedPreset', 'Source');
|
||||||
|
store.set('plugins.downloader.customPresetSetting', {
|
||||||
|
extension: null,
|
||||||
|
ffmpegArgs: store.get('plugins.downloader.ffmpegArgs') as string[] ?? [],
|
||||||
|
} satisfies Preset);
|
||||||
|
}
|
||||||
|
store.delete('plugins.downloader.preset');
|
||||||
|
store.delete('plugins.downloader.ffmpegArgs');
|
||||||
|
}
|
||||||
|
},
|
||||||
'>=1.20.0'(store: Conf<Record<string, unknown>>) {
|
'>=1.20.0'(store: Conf<Record<string, unknown>>) {
|
||||||
setDefaultPluginOptions(store, 'visualizer');
|
setDefaultPluginOptions(store, 'visualizer');
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,11 @@ 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, presets, 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 style from './style.css';
|
import style from './style.css';
|
||||||
|
|
||||||
@ -221,13 +220,32 @@ async function downloadSongUnsafe(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const preset = config.get('preset') ?? 'mp3';
|
const selectedPreset = config.get('selectedPreset') ?? 'mp3 (256kbps)';
|
||||||
let presetSetting: { extension: string; ffmpegArgs: string[] } | null = null;
|
let presetSetting: Preset;
|
||||||
if (preset === 'opus') {
|
if (selectedPreset === 'Custom') {
|
||||||
presetSetting = presets[preset];
|
presetSetting = config.get('customPresetSetting') ?? DefaultPresetList['Custom'];
|
||||||
|
} else if (selectedPreset === 'Source') {
|
||||||
|
presetSetting = DefaultPresetList['Source'];
|
||||||
|
} else {
|
||||||
|
presetSetting = DefaultPresetList['mp3 (256kbps)'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let filename = filenamify(`${name}.${presetSetting?.extension ?? 'mp3'}`, {
|
const downloadOptions: FormatOptions = {
|
||||||
|
type: 'audio', // Audio, video or video+audio
|
||||||
|
quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
||||||
|
format: 'any', // Media container format
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = info.chooseFormat(downloadOptions);
|
||||||
|
|
||||||
|
let targetFileExtension: string;
|
||||||
|
if (!presetSetting?.extension) {
|
||||||
|
targetFileExtension = YoutubeFormatList.find((it) => it.itag === format.itag)?.container ?? 'mp3';
|
||||||
|
} else {
|
||||||
|
targetFileExtension = presetSetting?.extension ?? 'mp3';
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename = filenamify(`${name}.${targetFileExtension}`, {
|
||||||
replacement: '_',
|
replacement: '_',
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
});
|
});
|
||||||
@ -241,13 +259,6 @@ async function downloadSongUnsafe(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadOptions: FormatOptions = {
|
|
||||||
type: 'audio', // Audio, video or video+audio
|
|
||||||
quality: 'best', // Best, bestefficiency, 144p, 240p, 480p, 720p and so on.
|
|
||||||
format: 'any', // Media container format
|
|
||||||
};
|
|
||||||
|
|
||||||
const format = info.chooseFormat(downloadOptions);
|
|
||||||
const stream = await info.download(downloadOptions);
|
const stream = await info.download(downloadOptions);
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
@ -260,39 +271,20 @@ async function downloadSongUnsafe(
|
|||||||
mkdirSync(dir);
|
mkdirSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ffmpegArgs = config.get('ffmpegArgs');
|
const fileBuffer = await iterableStreamToTargetFile(
|
||||||
|
iterableStream,
|
||||||
|
targetFileExtension,
|
||||||
|
metadata,
|
||||||
|
presetSetting?.ffmpegArgs ?? [],
|
||||||
|
format.content_length ?? 0,
|
||||||
|
sendFeedback,
|
||||||
|
increasePlaylistProgress,
|
||||||
|
);
|
||||||
|
|
||||||
if (presetSetting && presetSetting?.extension !== 'mp3') {
|
if (fileBuffer) {
|
||||||
const file = createWriteStream(filePath);
|
if (targetFileExtension !== 'mp3') {
|
||||||
let downloaded = 0;
|
createWriteStream(filePath).write(fileBuffer);
|
||||||
const total: number = format.content_length ?? 1;
|
} else {
|
||||||
|
|
||||||
for await (const chunk of iterableStream) {
|
|
||||||
downloaded += chunk.length;
|
|
||||||
const ratio = downloaded / total;
|
|
||||||
const progress = Math.floor(ratio * 100);
|
|
||||||
sendFeedback(`Download: ${progress}%`, ratio);
|
|
||||||
increasePlaylistProgress(ratio);
|
|
||||||
file.write(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
await ffmpegWriteTags(
|
|
||||||
filePath,
|
|
||||||
metadata,
|
|
||||||
presetSetting.ffmpegArgs,
|
|
||||||
ffmpegArgs,
|
|
||||||
);
|
|
||||||
sendFeedback(null, -1);
|
|
||||||
} else {
|
|
||||||
const fileBuffer = await iterableStreamToMP3(
|
|
||||||
iterableStream,
|
|
||||||
metadata,
|
|
||||||
ffmpegArgs,
|
|
||||||
format.content_length ?? 0,
|
|
||||||
sendFeedback,
|
|
||||||
increasePlaylistProgress,
|
|
||||||
);
|
|
||||||
if (fileBuffer) {
|
|
||||||
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);
|
||||||
@ -304,10 +296,11 @@ async function downloadSongUnsafe(
|
|||||||
console.info(`Done: "${filePath}"`);
|
console.info(`Done: "${filePath}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function iterableStreamToMP3(
|
async function iterableStreamToTargetFile(
|
||||||
stream: AsyncGenerator<Uint8Array, void>,
|
stream: AsyncGenerator<Uint8Array, void>,
|
||||||
|
extension: string,
|
||||||
metadata: CustomSongInfo,
|
metadata: CustomSongInfo,
|
||||||
ffmpegArgs: 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 = () => {
|
||||||
@ -347,13 +340,14 @@ async function iterableStreamToMP3(
|
|||||||
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
increasePlaylistProgress(0.15 + (ratio * 0.85));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const safeVideoNameWithExtension = `${safeVideoName}.${extension}`;
|
||||||
try {
|
try {
|
||||||
await ffmpeg.run(
|
await ffmpeg.run(
|
||||||
'-i',
|
'-i',
|
||||||
safeVideoName,
|
safeVideoName,
|
||||||
...ffmpegArgs,
|
...presetFfmpegArgs,
|
||||||
...getFFmpegMetadataArgs(metadata),
|
...getFFmpegMetadataArgs(metadata),
|
||||||
`${safeVideoName}.mp3`,
|
safeVideoNameWithExtension,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
ffmpeg.FS('unlink', safeVideoName);
|
ffmpeg.FS('unlink', safeVideoName);
|
||||||
@ -362,9 +356,9 @@ async function iterableStreamToMP3(
|
|||||||
sendFeedback('Saving…');
|
sendFeedback('Saving…');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return ffmpeg.FS('readFile', `${safeVideoName}.mp3`);
|
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
|
||||||
} finally {
|
} finally {
|
||||||
ffmpeg.FS('unlink', `${safeVideoName}.mp3`);
|
ffmpeg.FS('unlink', safeVideoNameWithExtension);
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
sendError(error as Error, safeVideoName);
|
sendError(error as Error, safeVideoName);
|
||||||
@ -544,29 +538,6 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ffmpegWriteTags(filePath: string, metadata: CustomSongInfo, presetFFmpegArgs: string[] = [], ffmpegArgs: string[] = []) {
|
|
||||||
const releaseFFmpegMutex = await ffmpegMutex.acquire();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!ffmpeg.isLoaded()) {
|
|
||||||
await ffmpeg.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
await ffmpeg.run(
|
|
||||||
'-i',
|
|
||||||
filePath,
|
|
||||||
...getFFmpegMetadataArgs(metadata),
|
|
||||||
...presetFFmpegArgs,
|
|
||||||
...ffmpegArgs,
|
|
||||||
filePath,
|
|
||||||
);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
sendError(error as Error);
|
|
||||||
} finally {
|
|
||||||
releaseFFmpegMutex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
|
function getFFmpegMetadataArgs(metadata: CustomSongInfo) {
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { dialog } from 'electron';
|
import { dialog } from 'electron';
|
||||||
|
|
||||||
import { downloadPlaylist } from './back';
|
import { downloadPlaylist } from './back';
|
||||||
import { defaultMenuDownloadLabel, getFolder, presets } from './utils';
|
import { defaultMenuDownloadLabel, getFolder } from './utils';
|
||||||
|
import { DefaultPresetList } from './types';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
|
||||||
import { MenuTemplate } from '../../menu';
|
import { MenuTemplate } from '../../menu';
|
||||||
@ -25,12 +26,12 @@ export default (): MenuTemplate => [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Presets',
|
label: 'Presets',
|
||||||
submenu: Object.keys(presets).map((preset) => ({
|
submenu: Object.keys(DefaultPresetList).map((preset) => ({
|
||||||
label: preset,
|
label: preset,
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
checked: config.get('preset') === preset,
|
checked: config.get('selectedPreset') === preset,
|
||||||
click() {
|
click() {
|
||||||
config.set('preset', preset);
|
config.set('selectedPreset', preset);
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
|||||||
116
plugins/downloader/types.ts
Normal file
116
plugins/downloader/types.ts
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
export interface Preset {
|
||||||
|
extension?: string | null;
|
||||||
|
ffmpegArgs: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presets for FFmpeg
|
||||||
|
export const DefaultPresetList: Record<string, Preset> = {
|
||||||
|
'mp3 (256kbps)': {
|
||||||
|
extension: 'mp3',
|
||||||
|
ffmpegArgs: ['-b:a', '256k'],
|
||||||
|
},
|
||||||
|
'Source': {
|
||||||
|
extension: undefined,
|
||||||
|
ffmpegArgs: ['-acodec', 'copy'],
|
||||||
|
},
|
||||||
|
'Custom': {
|
||||||
|
extension: null,
|
||||||
|
ffmpegArgs: [],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface YouTubeFormat {
|
||||||
|
itag: number;
|
||||||
|
container: string;
|
||||||
|
content: string;
|
||||||
|
resolution: string;
|
||||||
|
bitrate: string;
|
||||||
|
range: string;
|
||||||
|
vrOr3D: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// converted from https://gist.github.com/sidneys/7095afe4da4ae58694d128b1034e01e2#file-youtube_format_code_itag_list-md
|
||||||
|
export const YoutubeFormatList: YouTubeFormat[] = [
|
||||||
|
{ itag: 5, container: 'flv', content: 'audio/video', resolution: '240p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 6, container: 'flv', content: 'audio/video', resolution: '270p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 17, container: '3gp', content: 'audio/video', resolution: '144p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 18, container: 'mp4', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 22, container: 'mp4', content: 'audio/video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 34, container: 'flv', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 35, container: 'flv', content: 'audio/video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 36, container: '3gp', content: 'audio/video', resolution: '180p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 37, container: 'mp4', content: 'audio/video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 38, container: 'mp4', content: 'audio/video', resolution: '3072p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 43, container: 'webm', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 44, container: 'webm', content: 'audio/video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 45, container: 'webm', content: 'audio/video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 46, container: 'webm', content: 'audio/video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 82, container: 'mp4', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 83, container: 'mp4', content: 'audio/video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 84, container: 'mp4', content: 'audio/video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 85, container: 'mp4', content: 'audio/video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 91, container: 'hls', content: 'audio/video', resolution: '144p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 92, container: 'hls', content: 'audio/video', resolution: '240p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 93, container: 'hls', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 94, container: 'hls', content: 'audio/video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 95, container: 'hls', content: 'audio/video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 96, container: 'hls', content: 'audio/video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '-' },
|
||||||
|
{ itag: 100, container: 'webm', content: 'audio/video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 101, container: 'webm', content: 'audio/video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 102, container: 'webm', content: 'audio/video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '3D' },
|
||||||
|
{ itag: 132, container: 'hls', content: 'audio/video', resolution: '240p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 133, container: 'mp4', content: 'video', resolution: '240p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 134, container: 'mp4', content: 'video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 135, container: 'mp4', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 136, container: 'mp4', content: 'video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 137, container: 'mp4', content: 'video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 138, container: 'mp4', content: 'video', resolution: '2160p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 139, container: 'm4a', content: 'audio', resolution: '-', bitrate: '48k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 140, container: 'm4a', content: 'audio', resolution: '-', bitrate: '128k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 141, container: 'm4a', content: 'audio', resolution: '-', bitrate: '256k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 151, container: 'hls', content: 'audio/video', resolution: '72p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 160, container: 'mp4', content: 'video', resolution: '144p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 167, container: 'webm', content: 'video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 168, container: 'webm', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 169, container: 'webm', content: 'video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 171, container: 'webm', content: 'audio', resolution: '-', bitrate: '128k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 218, container: 'webm', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 219, container: 'webm', content: 'video', resolution: '144p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 242, container: 'webm', content: 'video', resolution: '240p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 243, container: 'webm', content: 'video', resolution: '360p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 244, container: 'webm', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 245, container: 'webm', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 246, container: 'webm', content: 'video', resolution: '480p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 247, container: 'webm', content: 'video', resolution: '720p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 248, container: 'webm', content: 'video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 249, container: 'webm', content: 'audio', resolution: '-', bitrate: '50k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 250, container: 'webm', content: 'audio', resolution: '-', bitrate: '70k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 251, container: 'webm', content: 'audio', resolution: '-', bitrate: '160k', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 264, container: 'mp4', content: 'video', resolution: '1440p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 266, container: 'mp4', content: 'video', resolution: '2160p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 271, container: 'webm', content: 'video', resolution: '1440p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 272, container: 'webm', content: 'video', resolution: '4320p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 278, container: 'webm', content: 'video', resolution: '144p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 298, container: 'mp4', content: 'video', resolution: '720p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 299, container: 'mp4', content: 'video', resolution: '1080p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 302, container: 'webm', content: 'video', resolution: '720p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 303, container: 'webm', content: 'video', resolution: '1080p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 308, container: 'webm', content: 'video', resolution: '1440p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 313, container: 'webm', content: 'video', resolution: '2160p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 315, container: 'webm', content: 'video', resolution: '2160p60', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 330, container: 'webm', content: 'video', resolution: '144p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 331, container: 'webm', content: 'video', resolution: '240p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 332, container: 'webm', content: 'video', resolution: '360p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 333, container: 'webm', content: 'video', resolution: '480p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 334, container: 'webm', content: 'video', resolution: '720p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 335, container: 'webm', content: 'video', resolution: '1080p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 336, container: 'webm', content: 'video', resolution: '1440p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 337, container: 'webm', content: 'video', resolution: '2160p60', bitrate: '-', range: 'hdr', vrOr3D: '' },
|
||||||
|
{ itag: 272, container: 'webm', content: 'video', resolution: '2880p/4320p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 399, container: 'mp4', content: 'video', resolution: '1080p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 400, container: 'mp4', content: 'video', resolution: '1440p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 401, container: 'mp4', content: 'video', resolution: '2160p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 402, container: 'mp4', content: 'video', resolution: '2880p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 571, container: 'mp4', content: 'video', resolution: '3840p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
{ itag: 702, container: 'mp4', content: 'video', resolution: '3840p', bitrate: '-', range: '-', vrOr3D: '' },
|
||||||
|
];
|
||||||
@ -10,7 +10,7 @@ export const sendFeedback = (win: BrowserWindow, message?: unknown) => {
|
|||||||
|
|
||||||
export const cropMaxWidth = (image: Electron.NativeImage) => {
|
export const cropMaxWidth = (image: Electron.NativeImage) => {
|
||||||
const imageSize = image.getSize();
|
const imageSize = image.getSize();
|
||||||
// Standart youtube artwork width with margins from both sides is 280 + 720 + 280
|
// Standart YouTube artwork width with margins from both sides is 280 + 720 + 280
|
||||||
if (imageSize.width === 1280 && imageSize.height === 720) {
|
if (imageSize.width === 1280 && imageSize.height === 720) {
|
||||||
return image.crop({
|
return image.crop({
|
||||||
x: 280,
|
x: 280,
|
||||||
@ -23,15 +23,6 @@ export const cropMaxWidth = (image: Electron.NativeImage) => {
|
|||||||
return image;
|
return image;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Presets for FFmpeg
|
|
||||||
export const presets = {
|
|
||||||
'None (defaults to mp3)': undefined,
|
|
||||||
'opus': {
|
|
||||||
extension: 'opus',
|
|
||||||
ffmpegArgs: ['-acodec', 'libopus'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setBadge = (n: number) => {
|
export const setBadge = (n: number) => {
|
||||||
if (is.linux() || is.macOS()) {
|
if (is.linux() || is.macOS()) {
|
||||||
app.setBadgeCount(n);
|
app.setBadgeCount(n);
|
||||||
|
|||||||
Reference in New Issue
Block a user