mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2026-02-04 08:22:23 +05:30
feat: added support for multiple audio stream selection in combine mode
This commit is contained in:
@@ -58,7 +58,7 @@ const FormatSelectionGroupItem = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative w-full rounded-lg border-2 border-border bg-background px-3 py-2 shadow-sm transition-all",
|
||||
"data-[state=checked]:border-primary data-[state=checked]:border-2 data-[state=checked]:bg-primary/10",
|
||||
"data-[state=checked]:border-primary data-[state=checked]:bg-primary/10",
|
||||
"hover:bg-muted/70",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
|
||||
114
src/components/custom/formatToggleGroup.tsx
Normal file
114
src/components/custom/formatToggleGroup.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import * as React from "react";
|
||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
||||
import { type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { toggleVariants } from "@/components/ui/toggle";
|
||||
import { VideoFormat } from "@/types/video";
|
||||
import { determineFileType, formatBitrate, formatCodec, formatFileSize } from "@/utils";
|
||||
import { Music, Video, File } from "lucide-react";
|
||||
|
||||
const FormatToggleGroupContext = React.createContext<
|
||||
VariantProps<typeof toggleVariants> & { toggleType?: "single" | "multiple" }
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
toggleType: "multiple",
|
||||
});
|
||||
|
||||
type FormatToggleGroupProps =
|
||||
| (Omit<React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>, "type"> &
|
||||
VariantProps<typeof toggleVariants> & { type: "single", value?: string, onValueChange?: (value: string) => void })
|
||||
| (Omit<React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root>, "type"> &
|
||||
VariantProps<typeof toggleVariants> & { type: "multiple", value?: string[], onValueChange?: (value: string[]) => void });
|
||||
|
||||
export const FormatToggleGroup = React.forwardRef<
|
||||
React.ComponentRef<typeof ToggleGroupPrimitive.Root>,
|
||||
FormatToggleGroupProps
|
||||
>(({ className, variant, size, children, type = "multiple", ...props }, ref) => {
|
||||
if (type === "single") {
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
type="single"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...(props as any)}
|
||||
>
|
||||
<FormatToggleGroupContext.Provider value={{ variant, size, toggleType: "single" }}>
|
||||
{children}
|
||||
</FormatToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Root
|
||||
ref={ref}
|
||||
type="multiple"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...(props as any)}
|
||||
>
|
||||
<FormatToggleGroupContext.Provider value={{ variant, size, toggleType: "multiple" }}>
|
||||
{children}
|
||||
</FormatToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
);
|
||||
});
|
||||
FormatToggleGroup.displayName = "FormatToggleGroup";
|
||||
|
||||
export const FormatToggleGroupItem = React.forwardRef<
|
||||
React.ComponentRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants> & {
|
||||
format: VideoFormat
|
||||
}
|
||||
>(({ className, children, variant, size, format, value, ...props }, ref) => {
|
||||
const determineFileTypeIcon = (format: VideoFormat) => {
|
||||
const fileFormat = determineFileType(/*format.video_ext, format.audio_ext,*/ format.vcodec, format.acodec)
|
||||
switch (fileFormat) {
|
||||
case 'video+audio':
|
||||
return (
|
||||
<span className="absolute flex items-center right-2 bottom-2">
|
||||
<Video className="w-3 h-3 mr-2" />
|
||||
<Music className="w-3 h-3" />
|
||||
</span>
|
||||
)
|
||||
case 'video':
|
||||
return (
|
||||
<Video className="w-3 h-3 absolute right-2 bottom-2" />
|
||||
)
|
||||
case 'audio':
|
||||
return (
|
||||
<Music className="w-3 h-3 absolute right-2 bottom-2" />
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<File className="w-3 h-3 absolute right-2 bottom-2" />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full p-2 rounded-lg border-2 border-border bg-background px-3 py-2 shadow-sm transition-all",
|
||||
"hover:bg-muted/70 data-[state=on]:bg-primary/10",
|
||||
"data-[state=on]:border-primary",
|
||||
className
|
||||
)}
|
||||
value={value}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex flex-col items-start text-start gap-1">
|
||||
<h5 className="text-sm">{format.format}</h5>
|
||||
<p className="text-muted-foreground text-xs">{format.filesize_approx ? formatFileSize(format.filesize_approx) : 'unknown'} {format.tbr ? formatBitrate(format.tbr) : 'unknown'}</p>
|
||||
<p className="text-muted-foreground text-xs">{format.ext ? format.ext.toUpperCase() : 'unknown'} {
|
||||
((format.vcodec && format.vcodec !== 'none') || (format.acodec && format.acodec !== 'none')) && (
|
||||
`(${format.vcodec && format.vcodec !== 'none' ? formatCodec(format.vcodec) : ''}${format.vcodec && format.vcodec !== 'none' && format.acodec && format.acodec !== 'none' ? ' ' : ''}${format.acodec && format.acodec !== 'none' ? formatCodec(format.acodec) : ''})`
|
||||
)}</p>
|
||||
{determineFileTypeIcon(format)}
|
||||
</div>
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
FormatToggleGroupItem.displayName = "FormatToggleGroupItem";
|
||||
@@ -24,7 +24,7 @@ interface BottomBarProps {
|
||||
selectedFormat: VideoFormat | undefined;
|
||||
selectedFormatFileType: "video+audio" | "video" | "audio" | "unknown";
|
||||
selectedVideoFormat: VideoFormat | undefined;
|
||||
selectedAudioFormat: VideoFormat | undefined;
|
||||
selectedAudioFormats: VideoFormat[] | undefined;
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
|
||||
const activeDownloadConfigurationTab = useDownloaderPageStatesStore((state) => state.activeDownloadConfigurationTab);
|
||||
const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat);
|
||||
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||
const selectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormats);
|
||||
const downloadConfiguration = useDownloaderPageStatesStore((state) => state.downloadConfiguration);
|
||||
const setActiveDownloadConfigurationTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadConfigurationTab);
|
||||
const setDownloadConfigurationKey = useDownloaderPageStatesStore((state) => state.setDownloadConfigurationKey);
|
||||
@@ -45,6 +45,8 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
|
||||
const useCustomCommands = useSettingsPageStatesStore(state => state.settings.use_custom_commands);
|
||||
const customCommands = useSettingsPageStatesStore(state => state.settings.custom_commands);
|
||||
|
||||
const isCombineableAudioSelected = selectedCombinableAudioFormats && selectedCombinableAudioFormats.length > 0;
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Tooltip>
|
||||
@@ -53,7 +55,7 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={!selectedDownloadFormat || (activeDownloadModeTab === 'combine' && (!selectedCombinableVideoFormat || !selectedCombinableAudioFormat))}
|
||||
disabled={!selectedDownloadFormat || (activeDownloadModeTab === 'combine' && (!selectedCombinableVideoFormat || !isCombineableAudioSelected))}
|
||||
>
|
||||
<Settings2 className="size-4" />
|
||||
</Button>
|
||||
@@ -272,14 +274,14 @@ function DownloadConfigDialog({ selectedFormatFileType }: DownloadConfigDialogPr
|
||||
);
|
||||
}
|
||||
|
||||
export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileType, selectedVideoFormat, selectedAudioFormat, containerRef }: BottomBarProps) {
|
||||
export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileType, selectedVideoFormat, selectedAudioFormats, containerRef }: BottomBarProps) {
|
||||
const { startDownload } = useAppContext();
|
||||
|
||||
console.log(selectedAudioFormats);
|
||||
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
|
||||
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
|
||||
const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat);
|
||||
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||
const selectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormats);
|
||||
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
|
||||
const selectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideos);
|
||||
const downloadConfiguration = useDownloaderPageStatesStore((state) => state.downloadConfiguration);
|
||||
@@ -295,17 +297,21 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
|
||||
const isPlaylist = videoMetadata._type === 'playlist';
|
||||
const isMultiplePlaylistItems = isPlaylist && selectedPlaylistVideos.length > 1;
|
||||
const isCombineableAudioSelected = selectedCombinableAudioFormats && selectedCombinableAudioFormats.length > 0 && selectedAudioFormats && selectedAudioFormats.length > 0;
|
||||
const isMultipleCombineableAudioSelected = selectedCombinableAudioFormats && selectedCombinableAudioFormats.length > 1 && selectedAudioFormats && selectedAudioFormats.length > 1;
|
||||
|
||||
let selectedFormatExtensionMsg = 'Auto - unknown';
|
||||
if (activeDownloadModeTab === 'combine') {
|
||||
if (downloadConfiguration.output_format && downloadConfiguration.output_format !== 'auto') {
|
||||
selectedFormatExtensionMsg = `Combined - ${downloadConfiguration.output_format.toUpperCase()}`;
|
||||
}
|
||||
else if (videoFormat !== 'auto') {
|
||||
} else if (videoFormat !== 'auto') {
|
||||
selectedFormatExtensionMsg = `Combined - ${videoFormat.toUpperCase()}`;
|
||||
}
|
||||
else if (selectedAudioFormat?.ext && selectedVideoFormat?.ext) {
|
||||
selectedFormatExtensionMsg = `Combined - ${selectedVideoFormat.ext.toUpperCase()} + ${selectedAudioFormat.ext.toUpperCase()}`;
|
||||
} else if (isCombineableAudioSelected && selectedVideoFormat?.ext) {
|
||||
if (isMultipleCombineableAudioSelected) {
|
||||
selectedFormatExtensionMsg = `Combined - ${selectedVideoFormat.ext.toUpperCase()} + ${selectedAudioFormats.length} Audio`;
|
||||
} else {
|
||||
selectedFormatExtensionMsg = `Combined - ${selectedVideoFormat.ext.toUpperCase()} + ${selectedAudioFormats[0].ext.toUpperCase()}`;
|
||||
}
|
||||
} else {
|
||||
selectedFormatExtensionMsg = `Combined - unknown`;
|
||||
}
|
||||
@@ -322,9 +328,23 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
}
|
||||
|
||||
let selectedFormatResolutionMsg = 'unknown';
|
||||
let totalTbr = 0;
|
||||
if (activeDownloadModeTab === 'combine') {
|
||||
selectedFormatResolutionMsg = `${selectedVideoFormat?.resolution ?? 'unknown'} + ${selectedAudioFormat?.tbr ? formatBitrate(selectedAudioFormat.tbr) : 'unknown'}`;
|
||||
if (isCombineableAudioSelected) {
|
||||
if (isMultipleCombineableAudioSelected) {
|
||||
const totalAudioTbr = selectedAudioFormats.reduce((acc, format) => acc + (format.tbr ?? 0), 0);
|
||||
totalTbr = (selectedVideoFormat?.tbr ?? 0) + totalAudioTbr;
|
||||
selectedFormatResolutionMsg = `${selectedVideoFormat?.resolution ?? 'unknown'} + ${formatBitrate(totalAudioTbr)}`;
|
||||
} else {
|
||||
totalTbr = (selectedVideoFormat?.tbr ?? 0) + (selectedAudioFormats && selectedAudioFormats[0].tbr ? selectedAudioFormats[0].tbr : 0);
|
||||
selectedFormatResolutionMsg = `${selectedVideoFormat?.resolution ?? 'unknown'} + ${selectedAudioFormats && selectedAudioFormats[0].tbr ? formatBitrate(selectedAudioFormats[0].tbr) : 'unknown'}`;
|
||||
}
|
||||
} else {
|
||||
totalTbr = selectedVideoFormat?.tbr ?? 0;
|
||||
selectedFormatResolutionMsg = `${selectedVideoFormat?.resolution ?? 'unknown'} + unknown`;
|
||||
}
|
||||
} else if (selectedFormat?.resolution) {
|
||||
totalTbr = selectedFormat.tbr ?? 0;
|
||||
selectedFormatResolutionMsg = selectedFormat.resolution;
|
||||
}
|
||||
|
||||
@@ -336,24 +356,37 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
}
|
||||
|
||||
let selectedFormatFileSizeMsg = 'unknown filesize';
|
||||
let totalFilesize = 0;
|
||||
if (activeDownloadModeTab === 'combine') {
|
||||
selectedFormatFileSizeMsg = selectedVideoFormat?.filesize_approx && selectedAudioFormat?.filesize_approx ? formatFileSize(selectedVideoFormat.filesize_approx + selectedAudioFormat.filesize_approx) : 'unknown filesize';
|
||||
if (isCombineableAudioSelected) {
|
||||
if (isMultipleCombineableAudioSelected) {
|
||||
totalFilesize = (selectedVideoFormat?.filesize_approx ?? 0) + selectedAudioFormats.reduce((acc, format) => acc + (format.filesize_approx ?? 0), 0);
|
||||
selectedFormatFileSizeMsg = totalFilesize > 0 ? formatFileSize(totalFilesize) : 'unknown filesize';
|
||||
} else {
|
||||
totalFilesize = (selectedVideoFormat?.filesize_approx ?? 0) + (selectedAudioFormats && selectedAudioFormats[0].filesize_approx ? selectedAudioFormats[0].filesize_approx : 0);
|
||||
selectedFormatFileSizeMsg = (selectedVideoFormat?.filesize_approx && selectedAudioFormats && selectedAudioFormats[0].filesize_approx) ? formatFileSize(selectedVideoFormat.filesize_approx + selectedAudioFormats[0].filesize_approx) : 'unknown filesize';
|
||||
}
|
||||
} else {
|
||||
totalFilesize = selectedVideoFormat?.filesize_approx ?? 0;
|
||||
selectedFormatFileSizeMsg = selectedVideoFormat?.filesize_approx ? formatFileSize(selectedVideoFormat.filesize_approx) : 'unknown filesize';
|
||||
}
|
||||
} else if (selectedFormat?.filesize_approx) {
|
||||
totalFilesize = selectedFormat.filesize_approx;
|
||||
selectedFormatFileSizeMsg = formatFileSize(selectedFormat.filesize_approx);
|
||||
}
|
||||
|
||||
let selectedFormatFinalMsg = '';
|
||||
if (activeDownloadModeTab === 'combine') {
|
||||
if (selectedCombinableVideoFormat && selectedCombinableAudioFormat) {
|
||||
if (selectedCombinableVideoFormat && selectedCombinableAudioFormats.length > 0) {
|
||||
selectedFormatFinalMsg = `${selectedFormatExtensionMsg} (${selectedFormatResolutionMsg}) ${selectedFormatDynamicRangeMsg} ${selectedSubtitles.length > 0 ? `• ESUB` : ''} ${useSponsorblock || (downloadConfiguration.sponsorblock && downloadConfiguration.sponsorblock !== 'auto') ? `• SPBLOCK` : ''} • ${selectedFormatFileSizeMsg}`;
|
||||
} else {
|
||||
selectedFormatFinalMsg = `Choose a video and audio stream to combine`;
|
||||
selectedFormatFinalMsg = `Choose a video and audio streams to combine`;
|
||||
}
|
||||
} else {
|
||||
if (selectedFormat) {
|
||||
selectedFormatFinalMsg = `${selectedFormatExtensionMsg} (${selectedFormatResolutionMsg}) ${selectedFormatDynamicRangeMsg} ${selectedSubtitles.length > 0 ? `• ESUB` : ''} ${useSponsorblock || (downloadConfiguration.sponsorblock && downloadConfiguration.sponsorblock !== 'auto') ? `• SPBLOCK` : ''} • ${selectedFormatFileSizeMsg}`;
|
||||
} else {
|
||||
selectedFormatFinalMsg = `Choose a stream to download`;
|
||||
selectedFormatFinalMsg = `Select a stream to download`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,13 +422,16 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
<div className="flex justify-between items-center gap-2 fixed bottom-0 right-0 p-4 w-full bg-background rounded-t-lg border-t border-border z-20" ref={bottomBarRef}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex justify-center items-center p-3 rounded-md border border-border">
|
||||
{selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio') && (
|
||||
{activeDownloadModeTab === 'combine' && (
|
||||
<Video className="w-4 h-4 stroke-primary" />
|
||||
)}
|
||||
{selectedFormatFileType && selectedFormatFileType === 'audio' && (
|
||||
{activeDownloadModeTab !== 'combine' && selectedFormatFileType && (selectedFormatFileType === 'video' || selectedFormatFileType === 'video+audio') && (
|
||||
<Video className="w-4 h-4 stroke-primary" />
|
||||
)}
|
||||
{activeDownloadModeTab !== 'combine' && selectedFormatFileType && selectedFormatFileType === 'audio' && (
|
||||
<Music className="w-4 h-4 stroke-primary" />
|
||||
)}
|
||||
{(!selectedFormatFileType) || (selectedFormatFileType && selectedFormatFileType !== 'video' && selectedFormatFileType !== 'audio' && selectedFormatFileType !== 'video+audio') && (
|
||||
{activeDownloadModeTab !== 'combine' && ((!selectedFormatFileType) || (selectedFormatFileType && selectedFormatFileType !== 'video' && selectedFormatFileType !== 'audio' && selectedFormatFileType !== 'video+audio')) && (
|
||||
<File className="w-4 h-4 stroke-primary" />
|
||||
)}
|
||||
</div>
|
||||
@@ -413,21 +449,28 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
if (videoMetadata._type === 'playlist') {
|
||||
await startDownload({
|
||||
url: videoMetadata.original_url,
|
||||
selectedFormat: activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormat}` : selectedDownloadFormat,
|
||||
selectedFormat: activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormats.join('+')}` : selectedDownloadFormat,
|
||||
downloadConfig: downloadConfiguration,
|
||||
selectedSubtitles: selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null,
|
||||
playlistItems: selectedPlaylistVideos.sort((a, b) => Number(a) - Number(b)).join(','),
|
||||
overrideOptions: isMultiplePlaylistItems ? {
|
||||
filesize: activeDownloadModeTab === 'combine' ? (selectedVideoFormat?.filesize_approx && selectedAudioFormat?.filesize_approx ? selectedVideoFormat.filesize_approx + selectedAudioFormat.filesize_approx : undefined) : selectedFormat?.filesize_approx ? selectedFormat.filesize_approx : undefined,
|
||||
tbr: activeDownloadModeTab === 'combine' ? (selectedVideoFormat?.tbr && selectedAudioFormat?.tbr ? selectedVideoFormat.tbr + selectedAudioFormat.tbr : undefined) : selectedFormat?.tbr ? selectedFormat.tbr : undefined,
|
||||
filesize: totalFilesize > 0 ? totalFilesize : undefined,
|
||||
tbr: totalTbr > 0 ? totalTbr : undefined,
|
||||
} : isMultipleCombineableAudioSelected ? {
|
||||
filesize: totalFilesize > 0 ? totalFilesize : undefined,
|
||||
tbr: totalTbr > 0 ? totalTbr : undefined,
|
||||
} : undefined
|
||||
});
|
||||
} else if (videoMetadata._type === 'video') {
|
||||
await startDownload({
|
||||
url: videoMetadata.webpage_url,
|
||||
selectedFormat: activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormat}` : selectedDownloadFormat === 'best' ? videoMetadata.requested_downloads[0].format_id : selectedDownloadFormat,
|
||||
selectedFormat: activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormats.join('+')}` : selectedDownloadFormat === 'best' ? videoMetadata.requested_downloads[0].format_id : selectedDownloadFormat,
|
||||
downloadConfig: downloadConfiguration,
|
||||
selectedSubtitles: selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null
|
||||
selectedSubtitles: selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null,
|
||||
overrideOptions: isMultipleCombineableAudioSelected ? {
|
||||
filesize: totalFilesize > 0 ? totalFilesize : undefined,
|
||||
tbr: totalTbr > 0 ? totalTbr : undefined,
|
||||
} : undefined
|
||||
});
|
||||
}
|
||||
// toast({
|
||||
@@ -443,7 +486,7 @@ export function BottomBar({ videoMetadata, selectedFormat, selectedFormatFileTyp
|
||||
setIsStartingDownload(false);
|
||||
}
|
||||
}}
|
||||
disabled={isStartingDownload || !selectedDownloadFormat || (activeDownloadModeTab === 'combine' && (!selectedCombinableVideoFormat || !selectedCombinableAudioFormat)) || (useCustomCommands && !downloadConfiguration.custom_command)}
|
||||
disabled={isStartingDownload || !selectedDownloadFormat || (activeDownloadModeTab === 'combine' && (!selectedCombinableVideoFormat || !isCombineableAudioSelected)) || (useCustomCommands && !downloadConfiguration.custom_command)}
|
||||
>
|
||||
{isStartingDownload ? (
|
||||
<>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen
|
||||
import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup";
|
||||
import { getMergedBestFormat } from "@/utils";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { FormatToggleGroup, FormatToggleGroupItem } from "@/components/custom/formatToggleGroup";
|
||||
|
||||
interface PlaylistPreviewSelectionProps {
|
||||
videoMetadata: RawVideoInfo;
|
||||
@@ -42,7 +43,7 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
|
||||
const selectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideos);
|
||||
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
|
||||
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
|
||||
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
|
||||
const setSelectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormats);
|
||||
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
|
||||
const setSelectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideos);
|
||||
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
|
||||
@@ -70,7 +71,7 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
|
||||
setSelectedDownloadFormat('best');
|
||||
setSelectedSubtitles([]);
|
||||
setSelectedCombinableVideoFormat('');
|
||||
setSelectedCombinableAudioFormat('');
|
||||
setSelectedCombinableAudioFormats([]);
|
||||
resetDownloadConfiguration();
|
||||
}}
|
||||
disabled={totalVideos <= 1}
|
||||
@@ -90,7 +91,7 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
|
||||
setSelectedDownloadFormat('best');
|
||||
setSelectedSubtitles([]);
|
||||
setSelectedCombinableVideoFormat('');
|
||||
setSelectedCombinableAudioFormat('');
|
||||
setSelectedCombinableAudioFormats([]);
|
||||
resetDownloadConfiguration();
|
||||
}
|
||||
}}
|
||||
@@ -237,10 +238,10 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
|
||||
|
||||
function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLanguages }: CombinedPlaylistDownloadProps) {
|
||||
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||
const selectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormats);
|
||||
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
|
||||
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
|
||||
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
|
||||
const setSelectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormats);
|
||||
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
|
||||
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
|
||||
|
||||
@@ -276,29 +277,31 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
|
||||
</div>
|
||||
</ToggleGroup>
|
||||
)}
|
||||
<FormatSelectionGroup
|
||||
<FormatToggleGroup
|
||||
type="multiple"
|
||||
variant="outline"
|
||||
className="mb-2"
|
||||
value={selectedCombinableAudioFormat}
|
||||
onValueChange={(value) => {
|
||||
setSelectedCombinableAudioFormat(value);
|
||||
value={selectedCombinableAudioFormats}
|
||||
onValueChange={(value: string[]) => {
|
||||
setSelectedCombinableAudioFormats(value);
|
||||
resetDownloadConfiguration();
|
||||
}}
|
||||
>
|
||||
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
|
||||
<>
|
||||
<p className="text-xs">Audio</p>
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
{audioOnlyFormats.map((format) => (
|
||||
<FormatSelectionGroupItem
|
||||
key={format.format_id}
|
||||
value={format.format_id}
|
||||
format={format}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
<p className="text-xs">Audio</p>
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
{audioOnlyFormats.map((format) => (
|
||||
<FormatToggleGroupItem
|
||||
key={format.format_id}
|
||||
value={format.format_id}
|
||||
format={format}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</FormatSelectionGroup>
|
||||
</FormatToggleGroup>
|
||||
<FormatSelectionGroup
|
||||
value={selectedCombinableVideoFormat}
|
||||
onValueChange={(value) => {
|
||||
|
||||
@@ -8,10 +8,10 @@ import { Calendar, Clock, DownloadCloud, Eye, Info, ThumbsUp, AlertCircleIcon }
|
||||
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { RawVideoInfo, VideoFormat } from "@/types/video";
|
||||
// import { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||
import { FormatToggleGroup, FormatToggleGroupItem } from "@/components/custom/formatToggleGroup";
|
||||
|
||||
interface VideoPreviewProps {
|
||||
videoMetadata: RawVideoInfo;
|
||||
@@ -215,10 +215,10 @@ function SelectiveVideoDownload({ videoMetadata, audioOnlyFormats, videoOnlyForm
|
||||
|
||||
function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLanguages }: CombinedVideoDownloadProps) {
|
||||
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||
const selectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormats);
|
||||
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
|
||||
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
|
||||
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
|
||||
const setSelectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormats);
|
||||
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
|
||||
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
|
||||
|
||||
@@ -254,29 +254,31 @@ function CombinedVideoDownload({ audioOnlyFormats, videoOnlyFormats, subtitleLan
|
||||
</div>
|
||||
</ToggleGroup>
|
||||
)}
|
||||
<FormatSelectionGroup
|
||||
<FormatToggleGroup
|
||||
type="multiple"
|
||||
variant="outline"
|
||||
className="mb-2"
|
||||
value={selectedCombinableAudioFormat}
|
||||
onValueChange={(value) => {
|
||||
setSelectedCombinableAudioFormat(value);
|
||||
value={selectedCombinableAudioFormats}
|
||||
onValueChange={(value: string[]) => {
|
||||
setSelectedCombinableAudioFormats(value);
|
||||
resetDownloadConfiguration();
|
||||
}}
|
||||
>
|
||||
{videoOnlyFormats && videoOnlyFormats.length > 0 && audioOnlyFormats && audioOnlyFormats.length > 0 && (
|
||||
<>
|
||||
<p className="text-xs">Audio</p>
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
{audioOnlyFormats.map((format) => (
|
||||
<FormatSelectionGroupItem
|
||||
key={format.format_id}
|
||||
value={format.format_id}
|
||||
format={format}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
<>
|
||||
<p className="text-xs">Audio</p>
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||
{audioOnlyFormats.map((format) => (
|
||||
<FormatToggleGroupItem
|
||||
key={format.format_id}
|
||||
value={format.format_id}
|
||||
format={format}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</FormatSelectionGroup>
|
||||
</FormatToggleGroup>
|
||||
<FormatSelectionGroup
|
||||
value={selectedCombinableVideoFormat}
|
||||
onValueChange={(value) => {
|
||||
|
||||
@@ -145,6 +145,7 @@ export function CompletedDownload({ state }: CompletedDownloadProps) {
|
||||
|
||||
const isPlaylist = state.playlist_id !== null && state.playlist_indices !== null;
|
||||
const isMultiplePlaylistItems = isPlaylist && state.playlist_indices && state.playlist_indices.includes(',');
|
||||
const isMultipleAudioFormatSelected = state.format_id ? state.format_id.split('+').length > 2 : false;
|
||||
|
||||
return (
|
||||
<div className="p-4 border border-border rounded-lg flex gap-4" key={state.download_id}>
|
||||
@@ -238,6 +239,9 @@ export function CompletedDownload({ state }: CompletedDownloadProps) {
|
||||
{state.acodec && !isMultiplePlaylistItems && (
|
||||
<span className="border border-border py-1 px-2 rounded">{formatCodec(state.acodec)}</span>
|
||||
)}
|
||||
{isMultipleAudioFormatSelected && (
|
||||
<span className="border border-border py-1 px-2 rounded">MULTIAUDIO</span>
|
||||
)}
|
||||
{state.dynamic_range && state.dynamic_range !== 'SDR' && !isMultiplePlaylistItems && (
|
||||
<span className="border border-border py-1 px-2 rounded">{state.dynamic_range}</span>
|
||||
)}
|
||||
|
||||
@@ -104,7 +104,11 @@ export default function useDownloader() {
|
||||
const { url, formatId, playlistIndices, selectedSubtitles, resumeState, downloadConfig } = params;
|
||||
try {
|
||||
const args = [url, '--dump-single-json', '--no-warnings'];
|
||||
if (formatId) args.push('--format', formatId);
|
||||
if (formatId) {
|
||||
const isMultipleAudioFormatSelected = formatId.split('+').length > 2;
|
||||
args.push('--format', formatId);
|
||||
if (isMultipleAudioFormatSelected) args.push('--audio-multistreams');
|
||||
}
|
||||
if (selectedSubtitles) {
|
||||
const isAutoSub = selectedSubtitles.split(',').some(lang => lang.endsWith('-orig'));
|
||||
if (isAutoSub) args.push('--write-auto-sub');
|
||||
@@ -218,7 +222,7 @@ export default function useDownloader() {
|
||||
const { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems, overrideOptions } = params;
|
||||
LOG.info('NEODLP', `Initiating yt-dlp download for URL: ${url}`);
|
||||
|
||||
console.log('Starting download:', { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems });
|
||||
console.log('Starting download:', { url, selectedFormat, downloadConfig, selectedSubtitles, resumeState, playlistItems, overrideOptions });
|
||||
if (!ffmpegPath || !tempDownloadDirPath || !downloadDirPath) {
|
||||
console.error('FFmpeg or download paths not found');
|
||||
return;
|
||||
@@ -227,6 +231,7 @@ export default function useDownloader() {
|
||||
const isPlaylist = (playlistItems && typeof playlistItems === 'string') || (resumeState?.playlist_id && resumeState?.playlist_indices) ? true : false;
|
||||
const playlistIndices = isPlaylist ? (resumeState?.playlist_indices || playlistItems) : null;
|
||||
const isMultiplePlaylistItems = isPlaylist && playlistIndices && typeof playlistIndices === 'string' && playlistIndices.includes(',');
|
||||
const isMultipleAudioFormatSelected = selectedFormat.split('+').length > 2;
|
||||
let videoMetadata = await fetchVideoMetadata({
|
||||
url,
|
||||
formatId: (!isPlaylist || (isPlaylist && selectedFormat !== 'best')) ? selectedFormat : undefined,
|
||||
@@ -245,7 +250,7 @@ export default function useDownloader() {
|
||||
console.log('Video Metadata:', videoMetadata);
|
||||
videoMetadata = isPlaylist ? videoMetadata.entries[0] : videoMetadata;
|
||||
|
||||
const fileType = determineFileType(videoMetadata.vcodec, videoMetadata.acodec);
|
||||
const fileType = isMultipleAudioFormatSelected ? 'video+audio' : determineFileType(videoMetadata.vcodec, videoMetadata.acodec);
|
||||
|
||||
if (fileType !== 'unknown' && (VIDEO_FORMAT !== 'auto' || AUDIO_FORMAT !== 'auto')) {
|
||||
if (VIDEO_FORMAT !== 'auto' && (fileType === 'video+audio' || fileType === 'video')) videoMetadata.ext = VIDEO_FORMAT;
|
||||
@@ -313,6 +318,9 @@ export default function useDownloader() {
|
||||
|
||||
if (!isPlaylist || (isPlaylist && selectedFormat !== 'best')) {
|
||||
args.push('--format', selectedFormat);
|
||||
if (isMultipleAudioFormatSelected) {
|
||||
args.push('--audio-multistreams');
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_MODE && LOG_VERBOSE) {
|
||||
@@ -529,7 +537,7 @@ export default function useDownloader() {
|
||||
speed: currentProgress.speed || null,
|
||||
eta: currentProgress.eta || null,
|
||||
filepath: downloadFilePath,
|
||||
filetype: determineFileType(videoMetadata.vcodec, videoMetadata.acodec) || null,
|
||||
filetype: fileType || null,
|
||||
filesize: resumeState?.filesize || overrideOptions?.filesize || videoMetadata.filesize_approx || null,
|
||||
output_format: outputFormat,
|
||||
embed_metadata: embedMetadata,
|
||||
|
||||
@@ -48,11 +48,11 @@ export default function DownloaderPage() {
|
||||
|
||||
const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat);
|
||||
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||
const selectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormats);
|
||||
const selectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideos);
|
||||
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
|
||||
const setSelectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableVideoFormat);
|
||||
const setSelectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormat);
|
||||
const setSelectedCombinableAudioFormats = useDownloaderPageStatesStore((state) => state.setSelectedCombinableAudioFormats);
|
||||
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
|
||||
const setSelectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideos);
|
||||
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
|
||||
@@ -126,14 +126,14 @@ export default function DownloaderPage() {
|
||||
);
|
||||
}
|
||||
})();
|
||||
const selectedAudioFormat = (() => {
|
||||
const selectedAudioFormats = (() => {
|
||||
if (videoMetadata?._type === 'video') {
|
||||
return allFilteredFormats.find(
|
||||
(format) => format.format_id === selectedCombinableAudioFormat
|
||||
return allFilteredFormats.filter(
|
||||
(format) => selectedCombinableAudioFormats.includes(format.format_id)
|
||||
);
|
||||
} else if (videoMetadata?._type === 'playlist') {
|
||||
return allFilteredFormats.find(
|
||||
(format) => format.format_id === selectedCombinableAudioFormat
|
||||
return allFilteredFormats.filter(
|
||||
(format) => selectedCombinableAudioFormats.includes(format.format_id)
|
||||
);
|
||||
}
|
||||
})();
|
||||
@@ -186,7 +186,7 @@ export default function DownloaderPage() {
|
||||
setIsMetadataLoading(true);
|
||||
setSelectedDownloadFormat('best');
|
||||
setSelectedCombinableVideoFormat('');
|
||||
setSelectedCombinableAudioFormat('');
|
||||
setSelectedCombinableAudioFormats([]);
|
||||
setSelectedSubtitles([]);
|
||||
setSelectedPlaylistVideos(["1"]);
|
||||
resetDownloadConfiguration();
|
||||
@@ -391,7 +391,7 @@ export default function DownloaderPage() {
|
||||
selectedFormat={selectedFormat}
|
||||
selectedFormatFileType={selectedFormatFileType}
|
||||
selectedVideoFormat={selectedVideoFormat}
|
||||
selectedAudioFormat={selectedAudioFormat}
|
||||
selectedAudioFormats={selectedAudioFormats}
|
||||
containerRef={containerRef}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
||||
isStartingDownload: false,
|
||||
selectedDownloadFormat: 'best',
|
||||
selectedCombinableVideoFormat: '',
|
||||
selectedCombinableAudioFormat: '',
|
||||
selectedCombinableAudioFormats: [],
|
||||
selectedSubtitles: [],
|
||||
selectedPlaylistVideos: ["1"],
|
||||
downloadConfiguration: {
|
||||
@@ -71,7 +71,7 @@ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((s
|
||||
setIsStartingDownload: (isStarting) => set(() => ({ isStartingDownload: isStarting })),
|
||||
setSelectedDownloadFormat: (format) => set(() => ({ selectedDownloadFormat: format })),
|
||||
setSelectedCombinableVideoFormat: (format) => set(() => ({ selectedCombinableVideoFormat: format })),
|
||||
setSelectedCombinableAudioFormat: (format) => set(() => ({ selectedCombinableAudioFormat: format })),
|
||||
setSelectedCombinableAudioFormats: (formats) => set(() => ({ selectedCombinableAudioFormats: formats })),
|
||||
setSelectedSubtitles: (subtitles) => set(() => ({ selectedSubtitles: subtitles })),
|
||||
setSelectedPlaylistVideos: (indices) => set(() => ({ selectedPlaylistVideos: indices })),
|
||||
setDownloadConfigurationKey: (key, value) => set((state) => ({
|
||||
@@ -204,7 +204,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
||||
force_internet_protocol: 'ipv4',
|
||||
use_custom_commands: false,
|
||||
custom_commands: [],
|
||||
filename_template: '%(title)s_%(resolution|unknown)s',
|
||||
filename_template: '%(title|Unknown)s_%(resolution|unknown)s',
|
||||
debug_mode: false,
|
||||
log_verbose: true,
|
||||
log_progress: false,
|
||||
@@ -275,7 +275,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
||||
force_internet_protocol: 'ipv4',
|
||||
use_custom_commands: false,
|
||||
custom_commands: [],
|
||||
filename_template: '%(title)s_%(resolution|unknown)s',
|
||||
filename_template: '%(title|Unknown)s_%(resolution|unknown)s',
|
||||
debug_mode: false,
|
||||
log_verbose: true,
|
||||
log_progress: false,
|
||||
|
||||
@@ -41,7 +41,7 @@ export interface DownloaderPageStatesStore {
|
||||
isStartingDownload: boolean;
|
||||
selectedDownloadFormat: string;
|
||||
selectedCombinableVideoFormat: string;
|
||||
selectedCombinableAudioFormat: string;
|
||||
selectedCombinableAudioFormats: string[];
|
||||
selectedSubtitles: string[];
|
||||
selectedPlaylistVideos: string[];
|
||||
downloadConfiguration: DownloadConfiguration;
|
||||
@@ -54,7 +54,7 @@ export interface DownloaderPageStatesStore {
|
||||
setIsStartingDownload: (isStarting: boolean) => void;
|
||||
setSelectedDownloadFormat: (format: string) => void;
|
||||
setSelectedCombinableVideoFormat: (format: string) => void;
|
||||
setSelectedCombinableAudioFormat: (format: string) => void;
|
||||
setSelectedCombinableAudioFormats: (formats: string[]) => void;
|
||||
setSelectedSubtitles: (subtitles: string[]) => void;
|
||||
setSelectedPlaylistVideos: (indices: string[]) => void;
|
||||
setDownloadConfigurationKey: (key: string, value: unknown) => void;
|
||||
|
||||
Reference in New Issue
Block a user