1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-02-05 08:42:21 +05:30

feat: added support for full-playlist/selective-batch downloading #9

This commit is contained in:
2026-01-15 15:22:43 +05:30
Verified
parent 2b7ab9def4
commit 1eb23eb035
18 changed files with 768 additions and 290 deletions

View File

@@ -3,11 +3,12 @@ import { DownloadCloud, Info, ListVideo, AlertCircleIcon } from "lucide-react";
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 { PlaylistSelectionGroup, PlaylistSelectionGroupItem } from "@/components/custom/playlistSelectionGroup";
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 { PlaylistToggleGroup, PlaylistToggleGroupItem } from "@/components/custom/playlistToggleGroup";
import { getMergedBestFormat } from "@/utils";
import { Switch } from "@/components/ui/switch";
interface PlaylistPreviewSelectionProps {
videoMetadata: RawVideoInfo;
@@ -38,28 +39,61 @@ interface PlaylistDownloaderProps {
}
function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionProps) {
const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex);
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 setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const setSelectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideoIndex);
const setSelectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideos);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
const totalVideos = videoMetadata.entries.filter((entry) => entry).length;
const allVideoIndices = videoMetadata.entries.filter((entry) => entry).map((entry) => entry.playlist_index.toString());
return (
<div className="flex flex-col w-full pr-4">
<h3 className="text-sm mb-4 mt-2 flex items-center gap-2">
<ListVideo className="w-4 h-4" />
<span>Playlist ({videoMetadata.entries[0].n_entries})</span>
</h3>
<div className="flex items-center justify-between mb-4 mt-2">
<h3 className="text-sm flex items-center gap-2">
<ListVideo className="w-4 h-4" />
<span>Playlist ({videoMetadata.entries[0].n_entries})</span>
</h3>
<div className="flex items-center space-x-2">
<Switch
id="select-all-videos"
checked={selectedPlaylistVideos.length === totalVideos && totalVideos > 0}
onCheckedChange={(checked) => {
if (checked) {
setSelectedPlaylistVideos(allVideoIndices);
} else {
setSelectedPlaylistVideos(["1"]);
}
setSelectedDownloadFormat('best');
setSelectedSubtitles([]);
setSelectedCombinableVideoFormat('');
setSelectedCombinableAudioFormat('');
resetDownloadConfiguration();
}}
disabled={totalVideos <= 1}
/>
</div>
</div>
<div className="flex flex-col overflow-y-scroll max-h-[50vh] xl:max-h-[60vh] no-scrollbar">
<h2 className="mb-1">{videoMetadata.entries[0].playlist_title ? videoMetadata.entries[0].playlist_title : 'UNTITLED'}</h2>
<p className="text-muted-foreground text-xs mb-4">{videoMetadata.entries[0].playlist_creator || videoMetadata.entries[0].playlist_channel || videoMetadata.entries[0].playlist_uploader || 'unknown'}</p>
{/* <PlaylistToggleGroup
<PlaylistToggleGroup
className="mb-2"
type="multiple"
value={selectedVideos}
onValueChange={setSelectedVideos}
value={selectedPlaylistVideos}
onValueChange={(value: string[]) => {
if (value.length > 0) {
setSelectedPlaylistVideos(value);
setSelectedDownloadFormat('best');
setSelectedSubtitles([]);
setSelectedCombinableVideoFormat('');
setSelectedCombinableAudioFormat('');
resetDownloadConfiguration();
}
}}
>
{videoMetadata.entries.map((entry) => entry ? (
<PlaylistToggleGroupItem
@@ -68,27 +102,7 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
video={entry}
/>
) : null)}
</PlaylistToggleGroup> */}
<PlaylistSelectionGroup
className="mb-2"
value={selectedPlaylistVideoIndex}
onValueChange={(value) => {
setSelectedPlaylistVideoIndex(value);
setSelectedDownloadFormat('best');
setSelectedSubtitles([]);
setSelectedCombinableVideoFormat('');
setSelectedCombinableAudioFormat('');
resetDownloadConfiguration();
}}
>
{videoMetadata.entries.map((entry) => entry ? (
<PlaylistSelectionGroupItem
key={entry.playlist_index}
value={entry.playlist_index.toString()}
video={entry}
/>
) : null)}
</PlaylistSelectionGroup>
</PlaylistToggleGroup>
<div className="flex items-center text-muted-foreground">
<Info className="w-3 h-3 mr-2" />
<span className="text-xs">Extracted from {videoMetadata.entries[0].extractor ? videoMetadata.entries[0].extractor.charAt(0).toUpperCase() + videoMetadata.entries[0].extractor.slice(1) : 'Unknown'}</span>
@@ -102,7 +116,7 @@ function PlaylistPreviewSelection({ videoMetadata }: PlaylistPreviewSelectionPro
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 selectedPlaylistVideos = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideos);
const setSelectedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelectedDownloadFormat);
const setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
const resetDownloadConfiguration = useDownloaderPageStatesStore((state) => state.resetDownloadConfiguration);
@@ -120,16 +134,23 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
>
<p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => (
<ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code}
size="sm"
aria-label={lang.lang}
key={lang.code}>
{lang.lang}
</ToggleGroupItem>
))}
{subtitleLanguages.map((lang) => {
const hasAutoSubSelected = selectedSubtitles.some(code => code.endsWith('-orig'));
const hasNormalSubSelected = selectedSubtitles.some(code => !code.endsWith('-orig'));
const isDisabled = (hasAutoSubSelected && !lang.code.endsWith('-orig')) || (hasNormalSubSelected && lang.code.endsWith('-orig'));
return (
<ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code}
size="sm"
aria-label={lang.lang}
key={lang.code}
disabled={isDisabled}>
{lang.lang}
</ToggleGroupItem>
);
})}
</div>
</ToggleGroup>
)}
@@ -149,7 +170,7 @@ function SelectivePlaylistDownload({ videoMetadata, audioOnlyFormats, videoOnlyF
<FormatSelectionGroupItem
key="best"
value="best"
format={videoMetadata.entries[Number(selectedPlaylistVideoIndex) - 1].requested_downloads[0]}
format={getMergedBestFormat(videoMetadata.entries, selectedPlaylistVideos) as VideoFormat}
/>
</div>
{qualityPresetFormats && qualityPresetFormats.length > 0 && (
@@ -235,16 +256,23 @@ function CombinedPlaylistDownload({ audioOnlyFormats, videoOnlyFormats, subtitle
>
<p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center">
{subtitleLanguages.map((lang) => (
<ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code}
size="sm"
aria-label={lang.lang}
key={lang.code}>
{lang.lang}
</ToggleGroupItem>
))}
{subtitleLanguages.map((lang) => {
const hasAutoSubSelected = selectedSubtitles.some(code => code.endsWith('-orig'));
const hasNormalSubSelected = selectedSubtitles.some(code => !code.endsWith('-orig'));
const isDisabled = (hasAutoSubSelected && !lang.code.endsWith('-orig')) || (hasNormalSubSelected && lang.code.endsWith('-orig'));
return (
<ToggleGroupItem
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-primary/10 hover:bg-muted/70"
value={lang.code}
size="sm"
aria-label={lang.lang}
key={lang.code}
disabled={isDisabled}>
{lang.lang}
</ToggleGroupItem>
);
})}
</div>
</ToggleGroup>
)}