feat: added square crop thumbnail config

This commit is contained in:
2025-12-16 20:31:38 +05:30
parent 1f06b73238
commit c1c2384c78
16 changed files with 866 additions and 840 deletions

686
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,9 +39,9 @@
"@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8", "@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.90.8", "@tanstack/react-query": "^5.90.12",
"@tanstack/react-query-devtools": "^5.90.2", "@tanstack/react-query-devtools": "^5.91.1",
"@tauri-apps/api": "^2.9.0", "@tauri-apps/api": "^2.9.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2", "@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.4.2", "@tauri-apps/plugin-dialog": "^2.4.2",
"@tauri-apps/plugin-fs": "^2.4.4", "@tauri-apps/plugin-fs": "^2.4.4",
@@ -57,36 +57,36 @@
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"es-toolkit": "^1.41.0", "es-toolkit": "^1.43.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.553.0", "lucide-react": "^0.561.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.2.0", "react": "^19.2.3",
"react-day-picker": "^9.11.1", "react-day-picker": "^9.12.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.3",
"react-hook-form": "^7.66.0", "react-hook-form": "^7.68.0",
"react-resizable-panels": "^3.0.6", "react-resizable-panels": "^3.0.6",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.10.1",
"recharts": "^3.4.1", "recharts": "^3.6.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"ulid": "^3.0.1", "ulid": "^3.0.2",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^4.1.12", "zod": "^4.2.0",
"zustand": "^5.0.8" "zustand": "^5.0.9"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.17", "@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.18",
"@tauri-apps/cli": "^2.9.4", "@tauri-apps/cli": "^2.9.6",
"@types/node": "^24.10.1", "@types/node": "^25.0.2",
"@types/react": "^19.2.3", "@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1", "@vitejs/plugin-react": "^5.1.2",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.2.2" "vite": "^7.3.0"
} }
} }

688
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -151,8 +151,83 @@ pub fn get_migrations() -> Vec<Migration> {
}, },
Migration { Migration {
version: 3, version: 3,
description: "add_performance_indexes", description: "add_more_columns_and_indexes_to_downloads",
sql: " sql: "
-- Create temporary table with all new columns
CREATE TABLE downloads_temp (
id INTEGER PRIMARY KEY NOT NULL,
download_id TEXT UNIQUE NOT NULL,
download_status TEXT NOT NULL,
video_id TEXT NOT NULL,
format_id TEXT NOT NULL,
subtitle_id TEXT,
queue_index INTEGER,
playlist_id TEXT,
playlist_index INTEGER,
resolution TEXT,
ext TEXT,
abr REAL,
vbr REAL,
acodec TEXT,
vcodec TEXT,
dynamic_range TEXT,
process_id INTEGER,
status TEXT,
progress REAL,
total INTEGER,
downloaded INTEGER,
speed REAL,
eta INTEGER,
filepath TEXT,
filetype TEXT,
filesize INTEGER,
output_format TEXT,
embed_metadata INTEGER NOT NULL DEFAULT 0,
embed_thumbnail INTEGER NOT NULL DEFAULT 0,
square_crop_thumbnail INTEGER NOT NULL DEFAULT 0,
sponsorblock_remove TEXT,
sponsorblock_mark TEXT,
use_aria2 INTEGER NOT NULL DEFAULT 0,
custom_command TEXT,
queue_config TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
);
-- Copy all data from original table to temporary table with default values for new columns
INSERT INTO downloads_temp SELECT
id, download_id, download_status, video_id, format_id, subtitle_id,
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
acodec, vcodec, dynamic_range, process_id, status, progress, total,
downloaded, speed, eta, filepath, filetype, filesize,
output_format,
embed_metadata,
embed_thumbnail,
0, -- square_crop_thumbnail
sponsorblock_remove, sponsorblock_mark, use_aria2,
custom_command, queue_config, created_at, updated_at
FROM downloads;
-- Remove existing triggers
DROP TRIGGER IF EXISTS update_downloads_updated_at;
-- Drop the original table
DROP TABLE downloads;
-- Rename temporary table to original name
ALTER TABLE downloads_temp RENAME TO downloads;
-- Create trigger for updating updated_at timestamp
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
AFTER UPDATE ON downloads
FOR EACH ROW
BEGIN
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;
-- Add indexes to improve query performance
CREATE INDEX IF NOT EXISTS idx_downloads_video_id ON downloads(video_id); CREATE INDEX IF NOT EXISTS idx_downloads_video_id ON downloads(video_id);
CREATE INDEX IF NOT EXISTS idx_downloads_playlist_id ON downloads(playlist_id); CREATE INDEX IF NOT EXISTS idx_downloads_playlist_id ON downloads(playlist_id);
CREATE INDEX IF NOT EXISTS idx_downloads_status_updated ON downloads(download_status, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_downloads_status_updated ON downloads(download_status, updated_at DESC);

View File

@@ -14,6 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox";
interface DownloadConfigDialogProps { interface DownloadConfigDialogProps {
selectedFormatFileType: "video+audio" | "video" | "audio" | "unknown"; selectedFormatFileType: "video+audio" | "video" | "audio" | "unknown";
@@ -221,6 +222,15 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
disabled={useCustomCommands} disabled={useCustomCommands}
/> />
<Label htmlFor="embed-thumbnail">Embed Thumbnail</Label> <Label htmlFor="embed-thumbnail">Embed Thumbnail</Label>
<div className="flex items-center gap-3 ml-4">
<Checkbox
id="square-crop-thumbnail"
checked={downloadConfiguration.square_crop_thumbnail !== null ? downloadConfiguration.square_crop_thumbnail : false}
onCheckedChange={(checked) => setDownloadConfigurationKey('square_crop_thumbnail', checked)}
disabled={useCustomCommands || !(downloadConfiguration.embed_thumbnail !== null ? downloadConfiguration.embed_thumbnail : (selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio')) || activeDownloadModeTab === 'combine' ? embedVideoThumbnail : selectedFormatFileType && selectedFormatFileType === 'audio' ? embedAudioThumbnail : false)}
/>
<Label htmlFor="square-crop-thumbnail">Square Crop</Label>
</div>
</div> </div>
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -19,16 +19,13 @@ interface SelectivePlaylistDownloadProps {
videoOnlyFormats: VideoFormat[] | undefined; videoOnlyFormats: VideoFormat[] | undefined;
combinedFormats: VideoFormat[] | undefined; combinedFormats: VideoFormat[] | undefined;
qualityPresetFormats: VideoFormat[] | undefined; qualityPresetFormats: VideoFormat[] | undefined;
allFilteredFormats: VideoFormat[];
subtitleLanguages: { code: string; lang: string }[]; subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
} }
interface CombinedPlaylistDownloadProps { interface CombinedPlaylistDownloadProps {
audioOnlyFormats: VideoFormat[] | undefined; audioOnlyFormats: VideoFormat[] | undefined;
videoOnlyFormats: VideoFormat[] | undefined; videoOnlyFormats: VideoFormat[] | undefined;
subtitleLanguages: { code: string; lang: string }[]; subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
} }
interface PlaylistDownloaderProps { interface PlaylistDownloaderProps {
@@ -37,9 +34,7 @@ interface PlaylistDownloaderProps {
videoOnlyFormats: VideoFormat[] | undefined; videoOnlyFormats: VideoFormat[] | undefined;
combinedFormats: VideoFormat[] | undefined; combinedFormats: VideoFormat[] | undefined;
qualityPresetFormats: VideoFormat[] | undefined; qualityPresetFormats: VideoFormat[] | undefined;
allFilteredFormats: VideoFormat[];
subtitleLanguages: { code: string; lang: string }[]; subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
} }
function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionProps) { function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionProps) {
@@ -104,12 +99,13 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
); );
} }
function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, allFilteredFormats, subtitleLanguages, selectedFormat }: SelectivePlaylistDownloadProps) { function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, subtitleLanguages }: SelectivePlaylistDownloadProps) {
const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat); const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat);
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles); const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex); const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex);
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat); const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles); const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return ( return (
<div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar"> <div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar">
@@ -120,7 +116,7 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
className="flex flex-col items-start gap-2 mb-2" className="flex flex-col items-start gap-2 mb-2"
value={selectedSubtitles} value={selectedSubtitles}
onValueChange={(value) => setSelectedSubtitles(value)} onValueChange={(value) => setSelectedSubtitles(value)}
disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'} // disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'}
> >
<p className="text-xs">Subtitle Languages</p> <p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
@@ -141,10 +137,11 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
value={selectedDownloadFormat} value={selectedDownloadFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedDownloadFormat(value); setSelectedDownloadFormat(value);
const currentlySelectedFormat = value === 'best' ? videoMetadata?.entries[Number(value) - 1].requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value); // const currentlySelectedFormat = value === 'best' ? videoMetadata?.entries[Number(value) - 1].requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') { // if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
setSelectedSubtitles([]); // setSelectedSubtitles([]);
} // }
resetDownloadConfiguration();
}} }}
> >
<p className="text-xs">Suggested</p> <p className="text-xs">Suggested</p>
@@ -217,13 +214,14 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
); );
} }
function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLanguages, selectedFormat }: CombinedPlaylistDownloadProps) { function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLanguages }: CombinedPlaylistDownloadProps) {
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat); const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat); const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles); const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat); const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat); const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles); const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return ( return (
<div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar"> <div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar">
@@ -234,7 +232,6 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
className="flex flex-col items-start gap-2 mb-2" className="flex flex-col items-start gap-2 mb-2"
value={selectedSubtitles} value={selectedSubtitles}
onValueChange={(value) => setSelectedSubtitles(value)} onValueChange={(value) => setSelectedSubtitles(value)}
disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'}
> >
<p className="text-xs">Subtitle Languages</p> <p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
@@ -256,6 +253,7 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
value={selectedCombinableAudioFormat} value={selectedCombinableAudioFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedCombinableAudioFormat(value); setSelectedCombinableAudioFormat(value);
resetDownloadConfiguration();
}} }}
> >
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && ( {videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
@@ -277,6 +275,7 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
value={selectedCombinableVideoFormat} value={selectedCombinableVideoFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedCombinableVideoFormat(value); setSelectedCombinableVideoFormat(value);
resetDownloadConfiguration();
}} }}
> >
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && ( {audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && (
@@ -308,11 +307,12 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
); );
} }
export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, allFilteredFormats, subtitleLanguages, selectedFormat }: PlaylistDownloaderProps) { export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, subtitleLanguages }: PlaylistDownloaderProps) {
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab); const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
const setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab); const setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab);
const playlistPanelSizes = useDownloaderPageStatesStore((state) => state.playlistPanelSizes); const playlistPanelSizes = useDownloaderPageStatesStore((state) => state.playlistPanelSizes);
const setPlaylistPanelSizes = useDownloaderPageStatesStore((state) => state.setPlaylistPanelSizes); const setPlaylistPanelSizes = useDownloaderPageStatesStore((state) => state.setPlaylistPanelSizes);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return ( return (
<div className="flex"> <div className="flex">
@@ -334,7 +334,10 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
<Tabs <Tabs
className="" className=""
value={activeDownloadModeTab} value={activeDownloadModeTab}
onValueChange={(tab) => setActiveDownloadModeTab(tab)} onValueChange={(tab) => {
setActiveDownloadModeTab(tab);
resetDownloadConfiguration();
}}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="text-sm flex items-center gap-2"> <h3 className="text-sm flex items-center gap-2">
@@ -353,9 +356,7 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
videoOnlyFormats={videoOnlyFormats} videoOnlyFormats={videoOnlyFormats}
combinedFormats={combinedFormats} combinedFormats={combinedFormats}
qualityPresetFormats={qualityPresetFormats} qualityPresetFormats={qualityPresetFormats}
allFilteredFormats={allFilteredFormats}
subtitleLanguages={subtitleLanguages} subtitleLanguages={subtitleLanguages}
selectedFormat={selectedFormat}
/> />
</TabsContent> </TabsContent>
<TabsContent value="combine"> <TabsContent value="combine">
@@ -363,7 +364,6 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
audioOnlyFormats={audioOnlyFormats} audioOnlyFormats={audioOnlyFormats}
videoOnlyFormats={videoOnlyFormats} videoOnlyFormats={videoOnlyFormats}
subtitleLanguages={subtitleLanguages} subtitleLanguages={subtitleLanguages}
selectedFormat={selectedFormat}
/> />
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@@ -97,7 +97,7 @@ function SelectiveVideoDownload({ videoMetadata, audioOnlyFormats, videoOnlyForm
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles); const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat); const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles); const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey); const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return ( return (
<div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar"> <div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar">
@@ -133,10 +133,7 @@ function SelectiveVideoDownload({ videoMetadata, audioOnlyFormats, videoOnlyForm
// if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') { // if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
// setSelectedSubtitles([]); // setSelectedSubtitles([]);
// } // }
setDownloadConfigurationKey('output_format', null); resetDownloadConfiguration();
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
}} }}
> >
<p className="text-xs">Suggested</p> <p className="text-xs">Suggested</p>
@@ -216,7 +213,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat); const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat); const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles); const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey); const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return ( return (
<div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar"> <div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar">
@@ -248,10 +245,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
value={selectedCombinableAudioFormat} value={selectedCombinableAudioFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedCombinableAudioFormat(value); setSelectedCombinableAudioFormat(value);
setDownloadConfigurationKey('output_format', null); resetDownloadConfiguration();
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
}} }}
> >
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && ( {videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
@@ -273,10 +267,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
value={selectedCombinableVideoFormat} value={selectedCombinableVideoFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedCombinableVideoFormat(value); setSelectedCombinableVideoFormat(value);
setDownloadConfigurationKey('output_format', null); resetDownloadConfiguration();
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
}} }}
> >
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && ( {audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && (
@@ -311,7 +302,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
export function VideoDownloader({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, subtitleLanguages }: VideoDownloaderProps) { export function VideoDownloader({ videoMetadata, audioOnlyFormats, videoOnlyFormats, combinedFormats, qualityPresetFormats, subtitleLanguages }: VideoDownloaderProps) {
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab); const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
const setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab); const setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab);
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey); const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
const videoPanelSizes = useDownloaderPageStatesStore((state) => state.videoPanelSizes); const videoPanelSizes = useDownloaderPageStatesStore((state) => state.videoPanelSizes);
const setVideoPanelSizes = useDownloaderPageStatesStore((state) => state.setVideoPanelSizes); const setVideoPanelSizes = useDownloaderPageStatesStore((state) => state.setVideoPanelSizes);
@@ -336,11 +327,8 @@ export function VideoDownloader({ videoMetadata, audioOnlyFormats, videoOnlyForm
className="" className=""
value={activeDownloadModeTab} value={activeDownloadModeTab}
onValueChange={(tab) => { onValueChange={(tab) => {
setActiveDownloadModeTab(tab) setActiveDownloadModeTab(tab);
setDownloadConfigurationKey('output_format', null); resetDownloadConfiguration();
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
}} }}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">

View File

@@ -1295,9 +1295,9 @@ function AppInfoSettings() {
{ key: 'directories', name: 'Directories', desc: 'A Rust library for platform-specific standard locations', url: 'https://crates.io/crates/directories', license: 'MIT, Apache-2.0', licenseUrl: 'https://codeberg.org/dirs/directories-rs/src/branch/main/LICENSE-APACHE' }, { key: 'directories', name: 'Directories', desc: 'A Rust library for platform-specific standard locations', url: 'https://crates.io/crates/directories', license: 'MIT, Apache-2.0', licenseUrl: 'https://codeberg.org/dirs/directories-rs/src/branch/main/LICENSE-APACHE' },
]; ];
function DependencyItem(dep: { key: string, name: string; desc: string; url: string; license: string; licenseUrl: string }) { function DependencyItem(dep: { name: string; desc: string; url: string; license: string; licenseUrl: string }) {
return ( return (
<div key={dep.key} className="p-4 border border-border rounded-md flex items-center justify-between gap-4"> <div className="p-4 border border-border rounded-md flex items-center justify-between gap-4">
<div className="flex flex-col"> <div className="flex flex-col">
<h4 className="font-semibold flex items-center gap-2"> <h4 className="font-semibold flex items-center gap-2">
<a href={dep.url} target="_blank" className="hover:underline"> <a href={dep.url} target="_blank" className="hover:underline">
@@ -1379,7 +1379,7 @@ function AppInfoSettings() {
<span className="flex items-center gap-4 flex-wrap"> <span className="flex items-center gap-4 flex-wrap">
<Button className="px-4" variant="outline" size="sm" asChild> <Button className="px-4" variant="outline" size="sm" asChild>
<a href={'mailto:' + config.appSupportEmail + '?subject=[BUG]%20Title%20Here&body=Describe%20The%20Bug%20Here.%20Follow%20this%20issue%20template%3A%20https%3A%2F%2Fgithub.com%2Fneosubhamoy%2Fneodlp%2Fissues%2Fnew%3Ftemplate%3Dbug_report.md'} target="_blank" > <a href={'mailto:' + config.appSupportEmail + '?subject=[BUG]%20Title%20Here&body=Describe%20The%20Bug%20Here.%20Follow%20this%20issue%20template%3A%20https%3A%2F%2Fgithub.com%2Fneosubhamoy%2Fneodlp%2Fissues%2Fnew%3Ftemplate%3Dbug_report.md'} target="_blank" >
<Mail className="size-4" /> Write an Email <Mail className="size-4" /> Write Us an Email
</a> </a>
</Button> </Button>
<Button className="px-4" size="sm" asChild> <Button className="px-4" size="sm" asChild>
@@ -1415,16 +1415,16 @@ function AppInfoSettings() {
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-4 max-h-[45vh] overflow-y-auto"> <div className="flex flex-col gap-4 max-h-[45vh] overflow-y-auto">
<h4 className="text-sm font-semibold">External Binaries</h4> <h4 className="text-sm font-semibold">External Binaries</h4>
{binDepsList.map((dep) => ( {binDepsList.map(({key, ...dep}) => (
<DependencyItem {...dep} /> <DependencyItem key={key} {...dep} />
))} ))}
<h4 className="text-sm font-semibold">Languages, Frameworks & Tooling</h4> <h4 className="text-sm font-semibold">Languages, Frameworks & Tooling</h4>
{langDepsList.map((dep) => ( {langDepsList.map(({key, ...dep}) => (
<DependencyItem {...dep} /> <DependencyItem key={key} {...dep} />
))} ))}
<h4 className="text-sm font-semibold">Notable Libraries</h4> <h4 className="text-sm font-semibold">Notable Libraries</h4>
{libDepsList.map((dep) => ( {libDepsList.map(({key, ...dep}) => (
<DependencyItem {...dep} /> <DependencyItem key={key} {...dep} />
))} ))}
</div> </div>
</DialogContent> </DialogContent>

View File

@@ -1,28 +1,31 @@
import * as React from "react" import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react" import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef< function Checkbox({
React.ElementRef<typeof CheckboxPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
ref={ref} data-slot="checkbox"
className={cn( className={cn(
"grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("grid place-content-center text-current")} data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
> >
<Check className="h-4 w-4" /> <CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
)) )
Checkbox.displayName = CheckboxPrimitive.Root.displayName }
export { Checkbox } export { Checkbox }

View File

@@ -1,42 +1,44 @@
import * as React from "react" import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react" import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef< function RadioGroup({
React.ElementRef<typeof RadioGroupPrimitive.Root>, className,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> ...props
>(({ className, ...props }, ref) => { }: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return ( return (
<RadioGroupPrimitive.Root <RadioGroupPrimitive.Root
className={cn("grid gap-2", className)} data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props} {...props}
ref={ref}
/> />
) )
}) }
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef< function RadioGroupItem({
React.ElementRef<typeof RadioGroupPrimitive.Item>, className,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> ...props
>(({ className, ...props }, ref) => { }: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return ( return (
<RadioGroupPrimitive.Item <RadioGroupPrimitive.Item
ref={ref} data-slot="radio-group-item"
className={cn( className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
> >
<RadioGroupPrimitive.Indicator className="flex items-center justify-center"> <RadioGroupPrimitive.Indicator
<Circle className="h-3.5 w-3.5 fill-primary" /> data-slot="radio-group-indicator"
className="relative flex items-center justify-center"
>
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
</RadioGroupPrimitive.Indicator> </RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item> </RadioGroupPrimitive.Item>
) )
}) }
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem } export { RadioGroup, RadioGroupItem }

View File

@@ -340,14 +340,14 @@ export default function useDownloader() {
} }
// Handle audio only // Handle audio only
else if (fileType === 'audio' && (AUDIO_FORMAT !== 'auto' || format)) { else if (fileType === 'audio' && (AUDIO_FORMAT !== 'auto' || format)) {
args.push('--extract-audio', '--audio-format', format || AUDIO_FORMAT); args.push('--extract-audio', '--audio-format', format || AUDIO_FORMAT, '--audio-quality', '0');
} }
// Handle unknown filetype // Handle unknown filetype
else if (fileType === 'unknown' && format) { else if (fileType === 'unknown' && format) {
if (['mkv', 'mp4', 'webm'].includes(format)) { if (['mkv', 'mp4', 'webm'].includes(format)) {
args.push(recodeOrRemux, formatToUse); args.push(recodeOrRemux, formatToUse);
} else if (['mp3', 'm4a', 'opus'].includes(format)) { } else if (['mp3', 'm4a', 'opus'].includes(format)) {
args.push('--extract-audio', '--audio-format', format); args.push('--extract-audio', '--audio-format', format, '--audio-quality', '0');
} }
} }
} }
@@ -365,6 +365,7 @@ export default function useDownloader() {
} }
let embedThumbnail = 0; let embedThumbnail = 0;
let squareCropThumbnail = 0;
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || EMBED_VIDEO_THUMBNAIL || EMBED_AUDIO_THUMBNAIL)) { if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || EMBED_VIDEO_THUMBNAIL || EMBED_AUDIO_THUMBNAIL)) {
const shouldEmbedThumbForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (EMBED_VIDEO_THUMBNAIL && downloadConfig.embed_thumbnail === null)); const shouldEmbedThumbForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (EMBED_VIDEO_THUMBNAIL && downloadConfig.embed_thumbnail === null));
const shouldEmbedThumbForAudio = fileType === 'audio' && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (EMBED_AUDIO_THUMBNAIL && downloadConfig.embed_thumbnail === null)); const shouldEmbedThumbForAudio = fileType === 'audio' && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (EMBED_AUDIO_THUMBNAIL && downloadConfig.embed_thumbnail === null));
@@ -372,7 +373,12 @@ export default function useDownloader() {
if (shouldEmbedThumbForUnknown || shouldEmbedThumbForVideo || shouldEmbedThumbForAudio) { if (shouldEmbedThumbForUnknown || shouldEmbedThumbForVideo || shouldEmbedThumbForAudio) {
embedThumbnail = 1; embedThumbnail = 1;
args.push('--embed-thumbnail'); args.push('--embed-thumbnail', '--convert-thumbnail', 'jpg');
if (downloadConfig.square_crop_thumbnail || resumeState?.square_crop_thumbnail) {
squareCropThumbnail = 1;
args.push('--postprocessor-args', 'ThumbnailsConvertor+FFmpeg_o:-c:v mjpeg -qmin 1 -qscale:v 1 -vf crop="\'min(iw,ih)\':\'min(iw,ih)\'"');
}
} }
} }
@@ -503,6 +509,7 @@ export default function useDownloader() {
output_format: outputFormat, output_format: outputFormat,
embed_metadata: embedMetadata, embed_metadata: embedMetadata,
embed_thumbnail: embedThumbnail, embed_thumbnail: embedThumbnail,
square_crop_thumbnail: squareCropThumbnail,
sponsorblock_remove: sponsorblockRemove, sponsorblock_remove: sponsorblockRemove,
sponsorblock_mark: sponsorblockMark, sponsorblock_mark: sponsorblockMark,
use_aria2: useAria2, use_aria2: useAria2,
@@ -630,6 +637,7 @@ export default function useDownloader() {
output_format: resumeState?.output_format || null, output_format: resumeState?.output_format || null,
embed_metadata: resumeState?.embed_metadata || 0, embed_metadata: resumeState?.embed_metadata || 0,
embed_thumbnail: resumeState?.embed_thumbnail || 0, embed_thumbnail: resumeState?.embed_thumbnail || 0,
square_crop_thumbnail: resumeState?.square_crop_thumbnail || 0,
sponsorblock_remove: resumeState?.sponsorblock_remove || null, sponsorblock_remove: resumeState?.sponsorblock_remove || null,
sponsorblock_mark: resumeState?.sponsorblock_mark || null, sponsorblock_mark: resumeState?.sponsorblock_mark || null,
use_aria2: resumeState?.use_aria2 || 0, use_aria2: resumeState?.use_aria2 || 0,

View File

@@ -347,10 +347,8 @@ export default function DownloaderPage() {
audioOnlyFormats={audioOnlyFormats} audioOnlyFormats={audioOnlyFormats}
videoOnlyFormats={videoOnlyFormats} videoOnlyFormats={videoOnlyFormats}
combinedFormats={combinedFormats} combinedFormats={combinedFormats}
allFilteredFormats={allFilteredFormats}
qualityPresetFormats={qualityPresetFormats} qualityPresetFormats={qualityPresetFormats}
subtitleLanguages={subtitleLanguages} subtitleLanguages={subtitleLanguages}
selectedFormat={selectedFormat}
/> />
)} )}
{!isMetadataLoading && videoMetadata && selectedDownloadFormat && ( {!isMetadataLoading && videoMetadata && selectedDownloadFormat && (

View File

@@ -101,12 +101,13 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
output_format, output_format,
embed_metadata, embed_metadata,
embed_thumbnail, embed_thumbnail,
square_crop_thumbnail,
sponsorblock_remove, sponsorblock_remove,
sponsorblock_mark, sponsorblock_mark,
use_aria2, use_aria2,
custom_command, custom_command,
queue_config queue_config
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33) ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34)
ON CONFLICT(download_id) DO UPDATE SET ON CONFLICT(download_id) DO UPDATE SET
download_status = $2, download_status = $2,
video_id = $3, video_id = $3,
@@ -135,11 +136,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
output_format = $26, output_format = $26,
embed_metadata = $27, embed_metadata = $27,
embed_thumbnail = $28, embed_thumbnail = $28,
sponsorblock_remove = $29, square_crop_thumbnail = $29,
sponsorblock_mark = $30, sponsorblock_remove = $30,
use_aria2 = $31, sponsorblock_mark = $31,
custom_command = $32, use_aria2 = $32,
queue_config = $33`, custom_command = $33,
queue_config = $34`,
[ [
downloadState.download_id, downloadState.download_id,
downloadState.download_status, downloadState.download_status,
@@ -169,6 +171,7 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
downloadState.output_format, downloadState.output_format,
downloadState.embed_metadata, downloadState.embed_metadata,
downloadState.embed_thumbnail, downloadState.embed_thumbnail,
downloadState.square_crop_thumbnail,
downloadState.sponsorblock_remove, downloadState.sponsorblock_remove,
downloadState.sponsorblock_mark, downloadState.sponsorblock_mark,
downloadState.use_aria2, downloadState.use_aria2,

View File

@@ -58,6 +58,7 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
output_format: null, output_format: null,
embed_metadata: null, embed_metadata: null,
embed_thumbnail: null, embed_thumbnail: null,
square_crop_thumbnail: null,
sponsorblock: null, sponsorblock: null,
custom_command: null custom_command: null
}, },
@@ -86,6 +87,7 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
output_format: null, output_format: null,
embed_metadata: null, embed_metadata: null,
embed_thumbnail: null, embed_thumbnail: null,
square_crop_thumbnail: null,
sponsorblock: null, sponsorblock: null,
custom_command: null custom_command: null
} }

View File

@@ -40,6 +40,7 @@ export interface DownloadState {
output_format: string | null; output_format: string | null;
embed_metadata: number; embed_metadata: number;
embed_thumbnail: number; embed_thumbnail: number;
square_crop_thumbnail: number;
sponsorblock_remove: string | null; sponsorblock_remove: string | null;
sponsorblock_mark: string | null; sponsorblock_mark: string | null;
use_aria2: number; use_aria2: number;
@@ -78,6 +79,7 @@ export interface Download {
output_format: string | null; output_format: string | null;
embed_metadata: number; embed_metadata: number;
embed_thumbnail: number; embed_thumbnail: number;
square_crop_thumbnail: number;
sponsorblock_remove: string | null; sponsorblock_remove: string | null;
sponsorblock_mark: string | null; sponsorblock_mark: string | null;
use_aria2: number; use_aria2: number;

View File

@@ -62,6 +62,7 @@ export interface DownloadConfiguration {
output_format: string | null; output_format: string | null;
embed_metadata: boolean | null; embed_metadata: boolean | null;
embed_thumbnail: boolean | null; embed_thumbnail: boolean | null;
square_crop_thumbnail: boolean | null;
sponsorblock: string | null; sponsorblock: string | null;
custom_command: string | null; custom_command: string | null;
} }