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

feat: added support for multiple audio stream selection in combine mode

This commit is contained in:
2026-01-19 20:36:50 +05:30
Verified
parent 4e5a4a1c73
commit 04590d892d
10 changed files with 261 additions and 87 deletions

View File

@@ -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

View 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";

View File

@@ -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 ? (
<>

View File

@@ -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) => {

View File

@@ -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) => {

View File

@@ -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>
)}

View File

@@ -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,

View File

@@ -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}
/>
)}

View File

@@ -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,

View File

@@ -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;