1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-02-04 15:22:23 +05:30

feat: added square crop thumbnail config

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

View File

@@ -14,6 +14,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox";
interface DownloadConfigDialogProps {
selectedFormatFileType: "video+audio" | "video" | "audio" | "unknown";
@@ -221,6 +222,15 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
disabled={useCustomCommands}
/>
<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>
</TabsContent>

View File

@@ -19,16 +19,13 @@ interface SelectivePlaylistDownloadProps {
videoOnlyFormats: VideoFormat[] | undefined;
combinedFormats: VideoFormat[] | undefined;
qualityPresetFormats: VideoFormat[] | undefined;
allFilteredFormats: VideoFormat[];
subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
}
interface CombinedPlaylistDownloadProps {
audioOnlyFormats: VideoFormat[] | undefined;
videoOnlyFormats: VideoFormat[] | undefined;
subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
}
interface PlaylistDownloaderProps {
@@ -37,9 +34,7 @@ interface PlaylistDownloaderProps {
videoOnlyFormats: VideoFormat[] | undefined;
combinedFormats: VideoFormat[] | undefined;
qualityPresetFormats: VideoFormat[] | undefined;
allFilteredFormats: VideoFormat[];
subtitleLanguages: { code: string; lang: string }[];
selectedFormat: VideoFormat | undefined;
}
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 selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex);
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return (
<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"
value={selectedSubtitles}
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>
<div className="flex gap-2 flex-wrap items-center">
@@ -141,10 +137,11 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
value={selectedDownloadFormat}
onValueChange={(value) => {
setSelectedDownloadFormat(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') {
setSelectedSubtitles([]);
}
// 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') {
// setSelectedSubtitles([]);
// }
resetDownloadConfiguration();
}}
>
<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 selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return (
<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"
value={selectedSubtitles}
onValueChange={(value) => setSelectedSubtitles(value)}
disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'}
>
<p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center">
@@ -256,6 +253,7 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
value={selectedCombinableAudioFormat}
onValueChange={(value) => {
setSelectedCombinableAudioFormat(value);
resetDownloadConfiguration();
}}
>
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
@@ -277,6 +275,7 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
value={selectedCombinableVideoFormat}
onValueChange={(value) => {
setSelectedCombinableVideoFormat(value);
resetDownloadConfiguration();
}}
>
{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 setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab);
const playlistPanelSizes = useDownloaderPageStatesStore((state) => state.playlistPanelSizes);
const setPlaylistPanelSizes = useDownloaderPageStatesStore((state) => state.setPlaylistPanelSizes);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return (
<div className="flex">
@@ -334,7 +334,10 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
<Tabs
className=""
value={activeDownloadModeTab}
onValueChange={(tab) => setActiveDownloadModeTab(tab)}
onValueChange={(tab) => {
setActiveDownloadModeTab(tab);
resetDownloadConfiguration();
}}
>
<div className="flex items-center justify-between">
<h3 className="text-sm flex items-center gap-2">
@@ -353,9 +356,7 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
videoOnlyFormats={videoOnlyFormats}
combinedFormats={combinedFormats}
qualityPresetFormats={qualityPresetFormats}
allFilteredFormats={allFilteredFormats}
subtitleLanguages={subtitleLanguages}
selectedFormat={selectedFormat}
/>
</TabsContent>
<TabsContent value="combine">
@@ -363,7 +364,6 @@ export function PlaylistDownloader({ videoMetadata, audioOnlyFormats, videoOnlyF
audioOnlyFormats={audioOnlyFormats}
videoOnlyFormats={videoOnlyFormats}
subtitleLanguages={subtitleLanguages}
selectedFormat={selectedFormat}
/>
</TabsContent>
</Tabs>

View File

@@ -97,7 +97,7 @@ function SelectiveVideoDownload({ videoMetadata, audioOnlyFormats, videoOnlyForm
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return (
<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') {
// setSelectedSubtitles([]);
// }
setDownloadConfigurationKey('output_format', null);
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
resetDownloadConfiguration();
}}
>
<p className="text-xs">Suggested</p>
@@ -216,7 +213,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
return (
<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}
onValueChange={(value) => {
setSelectedCombinableAudioFormat(value);
setDownloadConfigurationKey('output_format', null);
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
resetDownloadConfiguration();
}}
>
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
@@ -273,10 +267,7 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
value={selectedCombinableVideoFormat}
onValueChange={(value) => {
setSelectedCombinableVideoFormat(value);
setDownloadConfigurationKey('output_format', null);
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
resetDownloadConfiguration();
}}
>
{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) {
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
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 setVideoPanelSizes = useDownloaderPageStatesStore((state) => state.setVideoPanelSizes);
@@ -336,11 +327,8 @@ export function VideoDownloader({ videoMetadata, audioOnlyFormats, videoOnlyForm
className=""
value={activeDownloadModeTab}
onValueChange={(tab) => {
setActiveDownloadModeTab(tab)
setDownloadConfigurationKey('output_format', null);
setDownloadConfigurationKey('embed_metadata', null);
setDownloadConfigurationKey('embed_thumbnail', null);
setDownloadConfigurationKey('sponsorblock', null);
setActiveDownloadModeTab(tab);
resetDownloadConfiguration();
}}
>
<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' },
];
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 (
<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">
<h4 className="font-semibold flex items-center gap-2">
<a href={dep.url} target="_blank" className="hover:underline">
@@ -1379,7 +1379,7 @@ function AppInfoSettings() {
<span className="flex items-center gap-4 flex-wrap">
<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" >
<Mail className="size-4" /> Write an Email
<Mail className="size-4" /> Write Us an Email
</a>
</Button>
<Button className="px-4" size="sm" asChild>
@@ -1415,16 +1415,16 @@ function AppInfoSettings() {
</DialogHeader>
<div className="flex flex-col gap-4 max-h-[45vh] overflow-y-auto">
<h4 className="text-sm font-semibold">External Binaries</h4>
{binDepsList.map((dep) => (
<DependencyItem {...dep} />
{binDepsList.map(({key, ...dep}) => (
<DependencyItem key={key} {...dep} />
))}
<h4 className="text-sm font-semibold">Languages, Frameworks & Tooling</h4>
{langDepsList.map((dep) => (
<DependencyItem {...dep} />
{langDepsList.map(({key, ...dep}) => (
<DependencyItem key={key} {...dep} />
))}
<h4 className="text-sm font-semibold">Notable Libraries</h4>
{libDepsList.map((dep) => (
<DependencyItem {...dep} />
{libDepsList.map(({key, ...dep}) => (
<DependencyItem key={key} {...dep} />
))}
</div>
</DialogContent>

View File

@@ -1,28 +1,31 @@
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
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",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("grid place-content-center text-current")}
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"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
)}
{...props}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

View File

@@ -1,42 +1,44 @@
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { Circle } from "lucide-react"
import { CircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
}
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
return (
<RadioGroupPrimitive.Item
ref={ref}
data-slot="radio-group-item"
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
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="h-3.5 w-3.5 fill-primary" />
<RadioGroupPrimitive.Indicator
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.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
}
export { RadioGroup, RadioGroupItem }

View File

@@ -340,14 +340,14 @@ export default function useDownloader() {
}
// Handle audio only
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
else if (fileType === 'unknown' && format) {
if (['mkv', 'mp4', 'webm'].includes(format)) {
args.push(recodeOrRemux, formatToUse);
} 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 squareCropThumbnail = 0;
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 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) {
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,
embed_metadata: embedMetadata,
embed_thumbnail: embedThumbnail,
square_crop_thumbnail: squareCropThumbnail,
sponsorblock_remove: sponsorblockRemove,
sponsorblock_mark: sponsorblockMark,
use_aria2: useAria2,
@@ -630,6 +637,7 @@ export default function useDownloader() {
output_format: resumeState?.output_format || null,
embed_metadata: resumeState?.embed_metadata || 0,
embed_thumbnail: resumeState?.embed_thumbnail || 0,
square_crop_thumbnail: resumeState?.square_crop_thumbnail || 0,
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
sponsorblock_mark: resumeState?.sponsorblock_mark || null,
use_aria2: resumeState?.use_aria2 || 0,

View File

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

View File

@@ -101,12 +101,13 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
output_format,
embed_metadata,
embed_thumbnail,
square_crop_thumbnail,
sponsorblock_remove,
sponsorblock_mark,
use_aria2,
custom_command,
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
download_status = $2,
video_id = $3,
@@ -135,11 +136,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
output_format = $26,
embed_metadata = $27,
embed_thumbnail = $28,
sponsorblock_remove = $29,
sponsorblock_mark = $30,
use_aria2 = $31,
custom_command = $32,
queue_config = $33`,
square_crop_thumbnail = $29,
sponsorblock_remove = $30,
sponsorblock_mark = $31,
use_aria2 = $32,
custom_command = $33,
queue_config = $34`,
[
downloadState.download_id,
downloadState.download_status,
@@ -169,6 +171,7 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
downloadState.output_format,
downloadState.embed_metadata,
downloadState.embed_thumbnail,
downloadState.square_crop_thumbnail,
downloadState.sponsorblock_remove,
downloadState.sponsorblock_mark,
downloadState.use_aria2,

View File

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

View File

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

View File

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