mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-20 01:59:33 +05:30
(feat): introduced combine download mode and other tweaks and improvements
This commit is contained in:
@@ -55,7 +55,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const MAX_PARALLEL_DOWNLOADS = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
|
const MAX_PARALLEL_DOWNLOADS = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
|
||||||
const DOWNLOAD_DIR = useSettingsPageStatesStore(state => state.settings.download_dir);
|
const DOWNLOAD_DIR = useSettingsPageStatesStore(state => state.settings.download_dir);
|
||||||
const PREFER_VIDEO_OVER_PLAYLIST = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist);
|
const PREFER_VIDEO_OVER_PLAYLIST = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist);
|
||||||
const SHOW_DOWNLOADABLE_STREAMS_ONLY = useSettingsPageStatesStore(state => state.settings.show_downloadable_streams_only);
|
const STRICT_DOWNLOADABILITY_CHECK = useSettingsPageStatesStore(state => state.settings.strict_downloadablity_check);
|
||||||
const USE_PROXY = useSettingsPageStatesStore(state => state.settings.use_proxy);
|
const USE_PROXY = useSettingsPageStatesStore(state => state.settings.use_proxy);
|
||||||
const PROXY_URL = useSettingsPageStatesStore(state => state.settings.proxy_url);
|
const PROXY_URL = useSettingsPageStatesStore(state => state.settings.proxy_url);
|
||||||
const VIDEO_FORMAT = useSettingsPageStatesStore(state => state.settings.video_format);
|
const VIDEO_FORMAT = useSettingsPageStatesStore(state => state.settings.video_format);
|
||||||
@@ -92,11 +92,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string): Promise<RawVideoInfo | null> => {
|
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string): Promise<RawVideoInfo | null> => {
|
||||||
try {
|
try {
|
||||||
const args = [url, '--dump-single-json'];
|
const args = [url, '--dump-single-json', '--no-warnings'];
|
||||||
if (formatId) args.push('-f', formatId);
|
if (formatId) args.push('-f', formatId);
|
||||||
if (playlistIndex) args.push('--playlist-items', playlistIndex);
|
if (playlistIndex) args.push('--playlist-items', playlistIndex);
|
||||||
if (PREFER_VIDEO_OVER_PLAYLIST) args.push('--no-playlist');
|
if (PREFER_VIDEO_OVER_PLAYLIST) args.push('--no-playlist');
|
||||||
if (SHOW_DOWNLOADABLE_STREAMS_ONLY) args.push('--check-all-formats');
|
if (STRICT_DOWNLOADABILITY_CHECK && !formatId) args.push('--check-all-formats');
|
||||||
|
if (STRICT_DOWNLOADABILITY_CHECK && formatId) args.push('--check-formats');
|
||||||
if (USE_PROXY && PROXY_URL) args.push('--proxy', PROXY_URL);
|
if (USE_PROXY && PROXY_URL) args.push('--proxy', PROXY_URL);
|
||||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||||
|
|
||||||
@@ -178,6 +179,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
'-f',
|
'-f',
|
||||||
selectedFormat,
|
selectedFormat,
|
||||||
'--no-mtime',
|
'--no-mtime',
|
||||||
|
'--no-warnings',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (selectedSubtitles) {
|
if (selectedSubtitles) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useToast } from "@/hooks/use-toast";
|
|||||||
import { useAppContext } from "@/providers/appContextProvider";
|
import { useAppContext } from "@/providers/appContextProvider";
|
||||||
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||||
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
|
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
|
||||||
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch } from "lucide-react";
|
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon } from "lucide-react";
|
||||||
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
|
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
||||||
@@ -21,6 +21,8 @@ import { useForm } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form";
|
||||||
import { config } from "@/config";
|
import { config } from "@/config";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
|
||||||
const searchFormSchema = z.object({
|
const searchFormSchema = z.object({
|
||||||
url: z.string().min(1, { message: "URL is required" })
|
url: z.string().min(1, { message: "URL is required" })
|
||||||
@@ -42,12 +44,18 @@ export default function DownloaderPage() {
|
|||||||
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
|
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
|
||||||
const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch);
|
const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch);
|
||||||
|
|
||||||
|
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
|
||||||
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
|
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
|
||||||
const selctedDownloadFormat = useDownloaderPageStatesStore((state) => state.selctedDownloadFormat);
|
const selectedDownloadFormat = useDownloaderPageStatesStore((state) => state.selectedDownloadFormat);
|
||||||
|
const selectedCombinableVideoFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableVideoFormat);
|
||||||
|
const selectedCombinableAudioFormat = useDownloaderPageStatesStore((state) => state.selectedCombinableAudioFormat);
|
||||||
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
|
const selectedSubtitles = useDownloaderPageStatesStore((state) => state.selectedSubtitles);
|
||||||
const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex);
|
const selectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.selectedPlaylistVideoIndex);
|
||||||
|
const setActiveDownloadModeTab = useDownloaderPageStatesStore((state) => state.setActiveDownloadModeTab);
|
||||||
const setIsStartingDownload = useDownloaderPageStatesStore((state) => state.setIsStartingDownload);
|
const setIsStartingDownload = useDownloaderPageStatesStore((state) => state.setIsStartingDownload);
|
||||||
const setSelctedDownloadFormat = useDownloaderPageStatesStore((state) => state.setSelctedDownloadFormat);
|
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 setSelectedSubtitles = useDownloaderPageStatesStore((state) => state.setSelectedSubtitles);
|
||||||
const setSelectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideoIndex);
|
const setSelectedPlaylistVideoIndex = useDownloaderPageStatesStore((state) => state.setSelectedPlaylistVideoIndex);
|
||||||
|
|
||||||
@@ -82,22 +90,44 @@ export default function DownloaderPage() {
|
|||||||
const allFilteredFormats = [...(audioOnlyFormats || []), ...(videoOnlyFormats || []), ...(combinedFormats || []), ...(qualityPresetFormats || [])];
|
const allFilteredFormats = [...(audioOnlyFormats || []), ...(videoOnlyFormats || []), ...(combinedFormats || []), ...(qualityPresetFormats || [])];
|
||||||
const selectedFormat = (() => {
|
const selectedFormat = (() => {
|
||||||
if (videoMetadata?._type === 'video') {
|
if (videoMetadata?._type === 'video') {
|
||||||
if (selctedDownloadFormat === 'best') {
|
if (selectedDownloadFormat === 'best') {
|
||||||
return videoMetadata?.requested_downloads[0];
|
return videoMetadata?.requested_downloads[0];
|
||||||
}
|
}
|
||||||
return allFilteredFormats.find(
|
return allFilteredFormats.find(
|
||||||
(format) => format.format_id === selctedDownloadFormat
|
(format) => format.format_id === selectedDownloadFormat
|
||||||
);
|
);
|
||||||
} else if (videoMetadata?._type === 'playlist') {
|
} else if (videoMetadata?._type === 'playlist') {
|
||||||
if (selctedDownloadFormat === 'best') {
|
if (selectedDownloadFormat === 'best') {
|
||||||
return videoMetadata?.entries[Number(selectedPlaylistVideoIndex) - 1].requested_downloads[0];
|
return videoMetadata?.entries[Number(selectedPlaylistVideoIndex) - 1].requested_downloads[0];
|
||||||
}
|
}
|
||||||
return allFilteredFormats.find(
|
return allFilteredFormats.find(
|
||||||
(format) => format.format_id === selctedDownloadFormat
|
(format) => format.format_id === selectedDownloadFormat
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
const selectedFormatFileType = determineFileType(selectedFormat?.vcodec, selectedFormat?.acodec);
|
const selectedFormatFileType = determineFileType(selectedFormat?.vcodec, selectedFormat?.acodec);
|
||||||
|
const selectedVideoFormat = (() => {
|
||||||
|
if (videoMetadata?._type === 'video') {
|
||||||
|
return allFilteredFormats.find(
|
||||||
|
(format) => format.format_id === selectedCombinableVideoFormat
|
||||||
|
);
|
||||||
|
} else if (videoMetadata?._type === 'playlist') {
|
||||||
|
return allFilteredFormats.find(
|
||||||
|
(format) => format.format_id === selectedCombinableVideoFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const selectedAudioFormat = (() => {
|
||||||
|
if (videoMetadata?._type === 'video') {
|
||||||
|
return allFilteredFormats.find(
|
||||||
|
(format) => format.format_id === selectedCombinableAudioFormat
|
||||||
|
);
|
||||||
|
} else if (videoMetadata?._type === 'playlist') {
|
||||||
|
return allFilteredFormats.find(
|
||||||
|
(format) => format.format_id === selectedCombinableAudioFormat
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
const subtitles = videoMetadata?._type === 'video' ? (videoMetadata?.subtitles || {}) : videoMetadata?._type === 'playlist' ? (videoMetadata?.entries[Number(selectedPlaylistVideoIndex) - 1].subtitles || {}) : {};
|
const subtitles = videoMetadata?._type === 'video' ? (videoMetadata?.subtitles || {}) : videoMetadata?._type === 'playlist' ? (videoMetadata?.entries[Number(selectedPlaylistVideoIndex) - 1].subtitles || {}) : {};
|
||||||
const subtitleLanguages = Object.keys(subtitles).map(langCode => ({
|
const subtitleLanguages = Object.keys(subtitles).map(langCode => ({
|
||||||
@@ -109,7 +139,16 @@ export default function DownloaderPage() {
|
|||||||
const bottomBarRef = useRef<HTMLDivElement>(null);
|
const bottomBarRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
let selectedFormatExtensionMsg = 'Auto - unknown';
|
let selectedFormatExtensionMsg = 'Auto - unknown';
|
||||||
if (selectedFormat?.ext) {
|
if (activeDownloadModeTab === 'combine') {
|
||||||
|
if (videoFormat !== 'auto') {
|
||||||
|
selectedFormatExtensionMsg = `Combined - ${videoFormat.toUpperCase()}`;
|
||||||
|
}
|
||||||
|
else if (selectedAudioFormat?.ext && selectedVideoFormat?.ext) {
|
||||||
|
selectedFormatExtensionMsg = `Combined - ${selectedVideoFormat.ext.toUpperCase()} + ${selectedAudioFormat.ext.toUpperCase()}`;
|
||||||
|
} else {
|
||||||
|
selectedFormatExtensionMsg = `Combined - unknown`;
|
||||||
|
}
|
||||||
|
} else if (selectedFormat?.ext) {
|
||||||
if ((selectedFormatFileType === 'video+audio' || selectedFormatFileType === 'video') && videoFormat !== 'auto') {
|
if ((selectedFormatFileType === 'video+audio' || selectedFormatFileType === 'video') && videoFormat !== 'auto') {
|
||||||
selectedFormatExtensionMsg = `Forced - ${videoFormat.toUpperCase()}`;
|
selectedFormatExtensionMsg = `Forced - ${videoFormat.toUpperCase()}`;
|
||||||
} else if (selectedFormatFileType === 'audio' && audioFormat !== 'auto') {
|
} else if (selectedFormatFileType === 'audio' && audioFormat !== 'auto') {
|
||||||
@@ -119,6 +158,42 @@ export default function DownloaderPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selectedFormatResolutionMsg = 'unknown';
|
||||||
|
if (activeDownloadModeTab === 'combine') {
|
||||||
|
selectedFormatResolutionMsg = `${selectedVideoFormat?.resolution ?? 'unknown'} + ${selectedAudioFormat?.tbr ? formatBitrate(selectedAudioFormat.tbr) : 'unknown'}`;
|
||||||
|
} else if (selectedFormat?.resolution) {
|
||||||
|
selectedFormatResolutionMsg = selectedFormat.resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedFormatDynamicRangeMsg = '';
|
||||||
|
if (activeDownloadModeTab === 'combine') {
|
||||||
|
selectedFormatDynamicRangeMsg = selectedVideoFormat?.dynamic_range && selectedVideoFormat.dynamic_range !== 'SDR' ? selectedVideoFormat.dynamic_range : '';
|
||||||
|
} else if (selectedFormat?.dynamic_range && selectedFormat.dynamic_range !== 'SDR') {
|
||||||
|
selectedFormatDynamicRangeMsg = selectedFormat.dynamic_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedFormatFileSizeMsg = 'unknown filesize';
|
||||||
|
if (activeDownloadModeTab === 'combine') {
|
||||||
|
selectedFormatFileSizeMsg = selectedVideoFormat?.filesize_approx && selectedAudioFormat?.filesize_approx ? formatFileSize(selectedVideoFormat.filesize_approx + selectedAudioFormat.filesize_approx) : 'unknown filesize';
|
||||||
|
} else if (selectedFormat?.filesize_approx) {
|
||||||
|
selectedFormatFileSizeMsg = formatFileSize(selectedFormat.filesize_approx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedFormatFinalMsg = '';
|
||||||
|
if (activeDownloadModeTab === 'combine') {
|
||||||
|
if (selectedCombinableVideoFormat && selectedCombinableAudioFormat) {
|
||||||
|
selectedFormatFinalMsg = `${selectedFormatExtensionMsg} (${selectedFormatResolutionMsg}) ${selectedFormatDynamicRangeMsg} ${selectedSubtitles.length > 0 ? `• ESUB` : ''} • ${selectedFormatFileSizeMsg}`;
|
||||||
|
} else {
|
||||||
|
selectedFormatFinalMsg = `Choose a video and audio stream to combine`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedFormat) {
|
||||||
|
selectedFormatFinalMsg = `${selectedFormatExtensionMsg} (${selectedFormatResolutionMsg}) ${selectedFormatDynamicRangeMsg} ${selectedSubtitles.length > 0 ? `• ESUB` : ''} • ${selectedFormatFileSizeMsg}`;
|
||||||
|
} else {
|
||||||
|
selectedFormatFinalMsg = `Choose a stream to download`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const searchForm = useForm<z.infer<typeof searchFormSchema>>({
|
const searchForm = useForm<z.infer<typeof searchFormSchema>>({
|
||||||
resolver: zodResolver(searchFormSchema),
|
resolver: zodResolver(searchFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -131,7 +206,9 @@ export default function DownloaderPage() {
|
|||||||
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
|
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
|
||||||
setVideoMetadata(null);
|
setVideoMetadata(null);
|
||||||
setIsMetadataLoading(true);
|
setIsMetadataLoading(true);
|
||||||
setSelctedDownloadFormat('best');
|
setSelectedDownloadFormat('best');
|
||||||
|
setSelectedCombinableVideoFormat('');
|
||||||
|
setSelectedCombinableAudioFormat('');
|
||||||
setSelectedSubtitles([]);
|
setSelectedSubtitles([]);
|
||||||
setSelectedPlaylistVideoIndex('1');
|
setSelectedPlaylistVideoIndex('1');
|
||||||
fetchVideoMetadata(values.url).then((metadata) => {
|
fetchVideoMetadata(values.url).then((metadata) => {
|
||||||
@@ -281,11 +358,11 @@ export default function DownloaderPage() {
|
|||||||
{!isMetadataLoading && videoMetadata && videoMetadata._type === 'video' && ( // === Single Video ===
|
{!isMetadataLoading && videoMetadata && videoMetadata._type === 'video' && ( // === Single Video ===
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex flex-col w-[55%] border-r border-border pr-4">
|
<div className="flex flex-col w-[55%] border-r border-border pr-4">
|
||||||
<h3 className="text-sm mb-4 flex items-center gap-2">
|
<h3 className="text-sm mb-4 mt-2 flex items-center gap-2">
|
||||||
<Info className="w-4 h-4" />
|
<Info className="w-4 h-4" />
|
||||||
<span>Metadata</span>
|
<span>Metadata</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col overflow-y-scroll max-h-[53vh] no-scrollbar">
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
<AspectRatio ratio={16 / 9} className={clsx("w-full rounded-lg overflow-hidden mb-2 border border-border", videoMetadata.aspect_ratio && videoMetadata.aspect_ratio === 0.56 && "relative")}>
|
<AspectRatio ratio={16 / 9} className={clsx("w-full rounded-lg overflow-hidden mb-2 border border-border", videoMetadata.aspect_ratio && videoMetadata.aspect_ratio === 0.56 && "relative")}>
|
||||||
<ProxyImage src={videoMetadata.thumbnail} alt="thumbnail" className={clsx(videoMetadata.aspect_ratio && videoMetadata.aspect_ratio === 0.56 && "absolute h-full w-auto top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2")} />
|
<ProxyImage src={videoMetadata.thumbnail} alt="thumbnail" className={clsx(videoMetadata.aspect_ratio && videoMetadata.aspect_ratio === 0.56 && "absolute h-full w-auto top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2")} />
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
@@ -323,15 +400,27 @@ export default function DownloaderPage() {
|
|||||||
<Info className="w-3 h-3 mr-2" />
|
<Info className="w-3 h-3 mr-2" />
|
||||||
<span className="text-xs">Extracted from {videoMetadata.extractor ? videoMetadata.extractor.charAt(0).toUpperCase() + videoMetadata.extractor.slice(1) : 'Unknown'}</span>
|
<span className="text-xs">Extracted from {videoMetadata.extractor ? videoMetadata.extractor.charAt(0).toUpperCase() + videoMetadata.extractor.slice(1) : 'Unknown'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="spacer mb-14"></div>
|
<div className="spacer mb-10"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pl-4">
|
<div className="flex flex-col w-full pl-4">
|
||||||
<h3 className="text-sm mb-4 flex items-center gap-2">
|
<Tabs
|
||||||
|
className=""
|
||||||
|
value={activeDownloadModeTab}
|
||||||
|
onValueChange={(tab) => setActiveDownloadModeTab(tab)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm flex items-center gap-2">
|
||||||
<DownloadCloud className="w-4 h-4" />
|
<DownloadCloud className="w-4 h-4" />
|
||||||
<span>Download Options</span>
|
<span>Download Options</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col overflow-y-scroll max-h-[53vh] no-scrollbar">
|
<TabsList>
|
||||||
|
<TabsTrigger value="selective">Selective</TabsTrigger>
|
||||||
|
<TabsTrigger value="combine">Combine</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
<TabsContent value="selective">
|
||||||
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
{subtitles && !isObjEmpty(subtitles) && (
|
{subtitles && !isObjEmpty(subtitles) && (
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
type="multiple"
|
type="multiple"
|
||||||
@@ -357,9 +446,9 @@ export default function DownloaderPage() {
|
|||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
)}
|
)}
|
||||||
<FormatSelectionGroup
|
<FormatSelectionGroup
|
||||||
value={selctedDownloadFormat}
|
value={selectedDownloadFormat}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelctedDownloadFormat(value);
|
setSelectedDownloadFormat(value);
|
||||||
const currentlySelectedFormat = value === 'best' ? videoMetadata?.requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
|
const currentlySelectedFormat = value === 'best' ? videoMetadata?.requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
|
||||||
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
|
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
|
||||||
setSelectedSubtitles([]);
|
setSelectedSubtitles([]);
|
||||||
@@ -431,19 +520,102 @@ export default function DownloaderPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</FormatSelectionGroup>
|
</FormatSelectionGroup>
|
||||||
<div className="spacer mb-14"></div>
|
<div className="spacer mb-10"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="combine">
|
||||||
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
|
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && subtitles && !isObjEmpty(subtitles) && (
|
||||||
|
<ToggleGroup
|
||||||
|
type="multiple"
|
||||||
|
variant="outline"
|
||||||
|
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">
|
||||||
|
{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-muted/70 hover:bg-muted/70"
|
||||||
|
value={lang.code}
|
||||||
|
size="sm"
|
||||||
|
aria-label={lang.lang}
|
||||||
|
key={lang.code}>
|
||||||
|
{lang.lang}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ToggleGroup>
|
||||||
|
)}
|
||||||
|
<FormatSelectionGroup
|
||||||
|
className="mb-2"
|
||||||
|
value={selectedCombinableAudioFormat}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedCombinableAudioFormat(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormatSelectionGroup>
|
||||||
|
<FormatSelectionGroup
|
||||||
|
value={selectedCombinableVideoFormat}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedCombinableVideoFormat(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && (
|
||||||
|
<>
|
||||||
|
<p className="text-xs">Video</p>
|
||||||
|
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||||
|
{videoOnlyFormats.map((format) => (
|
||||||
|
<FormatSelectionGroupItem
|
||||||
|
key={format.format_id}
|
||||||
|
value={format.format_id}
|
||||||
|
format={format}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormatSelectionGroup>
|
||||||
|
{(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && (
|
||||||
|
<Alert>
|
||||||
|
<AlertCircleIcon />
|
||||||
|
<AlertTitle>Unable to use Combine Mode!</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<div className="spacer mb-10"></div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isMetadataLoading && videoMetadata && videoMetadata._type === 'playlist' && ( // === Playlists ===
|
{!isMetadataLoading && videoMetadata && videoMetadata._type === 'playlist' && ( // === Playlists ===
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex flex-col w-[55%] border-r border-border pr-4">
|
<div className="flex flex-col w-[55%] border-r border-border pr-4">
|
||||||
<h3 className="text-sm mb-4 flex items-center gap-2">
|
<h3 className="text-sm mb-4 mt-2 flex items-center gap-2">
|
||||||
<ListVideo className="w-4 h-4" />
|
<ListVideo className="w-4 h-4" />
|
||||||
<span>Playlist ({videoMetadata.entries[0].n_entries})</span>
|
<span>Playlist ({videoMetadata.entries[0].n_entries})</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col overflow-y-scroll max-h-[53vh] no-scrollbar">
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
<h2 className="mb-1">{videoMetadata.entries[0].playlist_title ? videoMetadata.entries[0].playlist_title : 'UNTITLED'}</h2>
|
<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_channel || videoMetadata.entries[0].playlist_uploader || 'unknown'}</p>
|
<p className="text-muted-foreground text-xs mb-4">{videoMetadata.entries[0].playlist_channel || videoMetadata.entries[0].playlist_uploader || 'unknown'}</p>
|
||||||
{/* <PlaylistToggleGroup
|
{/* <PlaylistToggleGroup
|
||||||
@@ -465,8 +637,10 @@ export default function DownloaderPage() {
|
|||||||
value={selectedPlaylistVideoIndex}
|
value={selectedPlaylistVideoIndex}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelectedPlaylistVideoIndex(value);
|
setSelectedPlaylistVideoIndex(value);
|
||||||
setSelctedDownloadFormat('best');
|
setSelectedDownloadFormat('best');
|
||||||
setSelectedSubtitles([]);
|
setSelectedSubtitles([]);
|
||||||
|
setSelectedCombinableVideoFormat('');
|
||||||
|
setSelectedCombinableAudioFormat('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoMetadata.entries.map((entry) => entry ? (
|
{videoMetadata.entries.map((entry) => entry ? (
|
||||||
@@ -481,15 +655,27 @@ export default function DownloaderPage() {
|
|||||||
<Info className="w-3 h-3 mr-2" />
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div className="spacer mb-14"></div>
|
<div className="spacer mb-10"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full pl-4">
|
<div className="flex flex-col w-full pl-4">
|
||||||
<h3 className="text-sm mb-4 flex items-center gap-2">
|
<Tabs
|
||||||
|
className=""
|
||||||
|
value={activeDownloadModeTab}
|
||||||
|
onValueChange={(tab) => setActiveDownloadModeTab(tab)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm flex items-center gap-2">
|
||||||
<DownloadCloud className="w-4 h-4" />
|
<DownloadCloud className="w-4 h-4" />
|
||||||
<span>Download Options</span>
|
<span>Download Options</span>
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col overflow-y-scroll max-h-[53vh] no-scrollbar">
|
<TabsList>
|
||||||
|
<TabsTrigger value="selective">Selective</TabsTrigger>
|
||||||
|
<TabsTrigger value="combine">Combine</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
<TabsContent value="selective">
|
||||||
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
{subtitles && !isObjEmpty(subtitles) && (
|
{subtitles && !isObjEmpty(subtitles) && (
|
||||||
<ToggleGroup
|
<ToggleGroup
|
||||||
type="multiple"
|
type="multiple"
|
||||||
@@ -515,9 +701,9 @@ export default function DownloaderPage() {
|
|||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
)}
|
)}
|
||||||
<FormatSelectionGroup
|
<FormatSelectionGroup
|
||||||
value={selctedDownloadFormat}
|
value={selectedDownloadFormat}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelctedDownloadFormat(value);
|
setSelectedDownloadFormat(value);
|
||||||
const currentlySelectedFormat = value === 'best' ? videoMetadata?.entries[Number(value) - 1].requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
|
const currentlySelectedFormat = value === 'best' ? videoMetadata?.entries[Number(value) - 1].requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
|
||||||
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
|
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
|
||||||
setSelectedSubtitles([]);
|
setSelectedSubtitles([]);
|
||||||
@@ -589,12 +775,95 @@ export default function DownloaderPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</FormatSelectionGroup>
|
</FormatSelectionGroup>
|
||||||
<div className="spacer mb-14"></div>
|
<div className="spacer mb-10"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="combine">
|
||||||
|
<div className="flex flex-col overflow-y-scroll max-h-[50vh] no-scrollbar">
|
||||||
|
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && subtitles && !isObjEmpty(subtitles) && (
|
||||||
|
<ToggleGroup
|
||||||
|
type="multiple"
|
||||||
|
variant="outline"
|
||||||
|
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">
|
||||||
|
{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-muted/70 hover:bg-muted/70"
|
||||||
|
value={lang.code}
|
||||||
|
size="sm"
|
||||||
|
aria-label={lang.lang}
|
||||||
|
key={lang.code}>
|
||||||
|
{lang.lang}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ToggleGroup>
|
||||||
|
)}
|
||||||
|
<FormatSelectionGroup
|
||||||
|
className="mb-2"
|
||||||
|
value={selectedCombinableAudioFormat}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedCombinableAudioFormat(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormatSelectionGroup>
|
||||||
|
<FormatSelectionGroup
|
||||||
|
value={selectedCombinableVideoFormat}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedCombinableVideoFormat(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{audioOnlyFormats && audioOnlyFormats.length > 0 && videoOnlyFormats && videoOnlyFormats.length > 0 && (
|
||||||
|
<>
|
||||||
|
<p className="text-xs">Video</p>
|
||||||
|
<div className="grid grid-cols-2 xl:grid-cols-3 gap-2">
|
||||||
|
{videoOnlyFormats.map((format) => (
|
||||||
|
<FormatSelectionGroupItem
|
||||||
|
key={format.format_id}
|
||||||
|
value={format.format_id}
|
||||||
|
format={format}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</FormatSelectionGroup>
|
||||||
|
{(!videoOnlyFormats || videoOnlyFormats.length === 0 || !audioOnlyFormats || audioOnlyFormats.length === 0) && (
|
||||||
|
<Alert>
|
||||||
|
<AlertCircleIcon />
|
||||||
|
<AlertTitle>Unable to use Combine Mode!</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Cannot use combine mode for this video as it does not have both audio and video formats available. Use Selective Mode or try another video.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
<div className="spacer mb-10"></div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isMetadataLoading && videoMetadata && selctedDownloadFormat && ( // === Bottom Bar ===
|
{!isMetadataLoading && videoMetadata && selectedDownloadFormat && ( // === Bottom Bar ===
|
||||||
<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 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 items-center gap-4">
|
||||||
<div className="flex justify-center items-center p-3 rounded-md border border-border">
|
<div className="flex justify-center items-center p-3 rounded-md border border-border">
|
||||||
@@ -610,7 +879,7 @@ export default function DownloaderPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-sm text-nowrap max-w-[30rem] xl:max-w-[50rem] overflow-hidden text-ellipsis">{videoMetadata._type === 'video' ? videoMetadata.title : videoMetadata._type === 'playlist' ? videoMetadata.entries[Number(selectedPlaylistVideoIndex) - 1].title : 'Unknown' }</span>
|
<span className="text-sm text-nowrap max-w-[30rem] xl:max-w-[50rem] overflow-hidden text-ellipsis">{videoMetadata._type === 'video' ? videoMetadata.title : videoMetadata._type === 'playlist' ? videoMetadata.entries[Number(selectedPlaylistVideoIndex) - 1].title : 'Unknown' }</span>
|
||||||
<span className="text-xs text-muted-foreground">{selectedFormatExtensionMsg} ({selectedFormat?.resolution ? selectedFormat.resolution : 'unknown'}) {selectedFormat?.dynamic_range && selectedFormat.dynamic_range !== 'SDR' ? selectedFormat.dynamic_range : null } {selectedSubtitles.length > 0 ? `• ESUB` : null} • {selectedFormat?.filesize_approx ? formatFileSize(selectedFormat?.filesize_approx) : 'unknown filesize'}</span>
|
<span className="text-xs text-muted-foreground">{selectedFormatFinalMsg}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -620,7 +889,7 @@ export default function DownloaderPage() {
|
|||||||
if (videoMetadata._type === 'playlist') {
|
if (videoMetadata._type === 'playlist') {
|
||||||
await startDownload(
|
await startDownload(
|
||||||
videoMetadata.original_url,
|
videoMetadata.original_url,
|
||||||
selctedDownloadFormat === 'best' ? videoMetadata.entries[Number(selectedPlaylistVideoIndex) - 1].requested_downloads[0].format_id : selctedDownloadFormat,
|
activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormat}` : selectedDownloadFormat === 'best' ? videoMetadata.entries[Number(selectedPlaylistVideoIndex) - 1].requested_downloads[0].format_id : selectedDownloadFormat,
|
||||||
selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null,
|
selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null,
|
||||||
undefined,
|
undefined,
|
||||||
selectedPlaylistVideoIndex
|
selectedPlaylistVideoIndex
|
||||||
@@ -628,7 +897,7 @@ export default function DownloaderPage() {
|
|||||||
} else if (videoMetadata._type === 'video') {
|
} else if (videoMetadata._type === 'video') {
|
||||||
await startDownload(
|
await startDownload(
|
||||||
videoMetadata.webpage_url,
|
videoMetadata.webpage_url,
|
||||||
selctedDownloadFormat === 'best' ? videoMetadata.requested_downloads[0].format_id : selctedDownloadFormat,
|
activeDownloadModeTab === 'combine' ? `${selectedCombinableVideoFormat}+${selectedCombinableAudioFormat}` : selectedDownloadFormat === 'best' ? videoMetadata.requested_downloads[0].format_id : selectedDownloadFormat,
|
||||||
selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null
|
selectedSubtitles.length > 0 ? selectedSubtitles.join(',') : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -647,12 +916,12 @@ export default function DownloaderPage() {
|
|||||||
setIsStartingDownload(false);
|
setIsStartingDownload(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={isStartingDownload}
|
disabled={isStartingDownload || !selectedDownloadFormat || (activeDownloadModeTab === 'combine' && (!selectedCombinableVideoFormat || !selectedCombinableAudioFormat))}
|
||||||
>
|
>
|
||||||
{isStartingDownload ? (
|
{isStartingDownload ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
Starting
|
Starting Download
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Start Download'
|
'Start Download'
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function SettingsPage() {
|
|||||||
const appTheme = useSettingsPageStatesStore(state => state.settings.theme);
|
const appTheme = useSettingsPageStatesStore(state => state.settings.theme);
|
||||||
const maxParallelDownloads = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
|
const maxParallelDownloads = useSettingsPageStatesStore(state => state.settings.max_parallel_downloads);
|
||||||
const preferVideoOverPlaylist = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist);
|
const preferVideoOverPlaylist = useSettingsPageStatesStore(state => state.settings.prefer_video_over_playlist);
|
||||||
const showDownloadableStreamsOnly = useSettingsPageStatesStore(state => state.settings.show_downloadable_streams_only);
|
const strictDownloadabilityCheck = useSettingsPageStatesStore(state => state.settings.strict_downloadablity_check);
|
||||||
const useProxy = useSettingsPageStatesStore(state => state.settings.use_proxy);
|
const useProxy = useSettingsPageStatesStore(state => state.settings.use_proxy);
|
||||||
const proxyUrl = useSettingsPageStatesStore(state => state.settings.proxy_url);
|
const proxyUrl = useSettingsPageStatesStore(state => state.settings.proxy_url);
|
||||||
const videoFormat = useSettingsPageStatesStore(state => state.settings.video_format);
|
const videoFormat = useSettingsPageStatesStore(state => state.settings.video_format);
|
||||||
@@ -281,7 +281,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Tabs
|
<Tabs
|
||||||
className="w-full flex flex-row items-start gap-4 mt-10"
|
className="w-full flex flex-row items-start gap-4 mt-7"
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
value={activeSubAppTab}
|
value={activeSubAppTab}
|
||||||
onValueChange={setActiveSubAppTab}
|
onValueChange={setActiveSubAppTab}
|
||||||
@@ -342,13 +342,13 @@ export default function SettingsPage() {
|
|||||||
onCheckedChange={(checked) => saveSettingsKey('prefer_video_over_playlist', checked)}
|
onCheckedChange={(checked) => saveSettingsKey('prefer_video_over_playlist', checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="show-downloadable-streams-only">
|
<div className="strict-downloadability-check">
|
||||||
<h3 className="font-semibold">Show Downloadable Streams Only (Strict)</h3>
|
<h3 className="font-semibold">Strict Downloadablity Check</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Check, filter-out and show the streams that are actualy downloadable (high quality results, takes longer time to search, start a download)</p>
|
<p className="text-xs text-muted-foreground mb-3">Only show streams that are actualy downloadable, also check formats before downloading (high quality results, takes longer time to search)</p>
|
||||||
<Switch
|
<Switch
|
||||||
id="show-downloadable-streams-only"
|
id="strict-downloadablity-check"
|
||||||
checked={showDownloadableStreamsOnly}
|
checked={strictDownloadabilityCheck}
|
||||||
onCheckedChange={(checked) => saveSettingsKey('show_downloadable_streams_only', checked)}
|
onCheckedChange={(checked) => saveSettingsKey('strict_downloadablity_check', checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@@ -507,7 +507,7 @@ export default function SettingsPage() {
|
|||||||
<TabsContent key="network" value="network" className="flex flex-col gap-4 min-h-[235px]">
|
<TabsContent key="network" value="network" className="flex flex-col gap-4 min-h-[235px]">
|
||||||
<div className="proxy">
|
<div className="proxy">
|
||||||
<h3 className="font-semibold">Proxy</h3>
|
<h3 className="font-semibold">Proxy</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Use proxy for downloads, Unblocks blocked sites in your region (Download speed may affect, Some sites may not work)</p>
|
<p className="text-xs text-muted-foreground mb-3">Use proxy for downloads, Unblocks blocked sites in your region (download speed may affect, some sites may not work)</p>
|
||||||
<div className="flex items-center space-x-2 mb-4">
|
<div className="flex items-center space-x-2 mb-4">
|
||||||
<Switch
|
<Switch
|
||||||
id="use-proxy"
|
id="use-proxy"
|
||||||
@@ -602,7 +602,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Tabs
|
<Tabs
|
||||||
className="w-full flex flex-row items-start gap-4 mt-10"
|
className="w-full flex flex-row items-start gap-4 mt-7"
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
value={activeSubExtTab}
|
value={activeSubExtTab}
|
||||||
onValueChange={setActiveSubExtTab}
|
onValueChange={setActiveSubExtTab}
|
||||||
@@ -667,7 +667,7 @@ export default function SettingsPage() {
|
|||||||
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'arc')}>Arc</Button>
|
<Button variant="outline" onClick={() => openLink('https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf', 'arc')}>Arc</Button>
|
||||||
<Button variant="outline" onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'zen')}>Zen</Button>
|
<Button variant="outline" onClick={() => openLink('https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus', 'zen')}>Zen</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground mb-2">* These links opens with coresponding browsers only. Make sure the browser is installed befor clicking the link</p>
|
<p className="text-xs text-muted-foreground mb-2">* These links opens with coresponding browsers only. Make sure the browser is installed before clicking the link</p>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="port" value="port" className="flex flex-col gap-4 min-h-[150px] max-w-[70%]">
|
<TabsContent key="port" value="port" className="flex flex-col gap-4 min-h-[150px] max-w-[70%]">
|
||||||
|
|||||||
@@ -42,12 +42,18 @@ export const useCurrentVideoMetadataStore = create<CurrentVideoMetadataStore>((s
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({
|
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({
|
||||||
|
activeDownloadModeTab: 'selective',
|
||||||
isStartingDownload: false,
|
isStartingDownload: false,
|
||||||
selctedDownloadFormat: 'best',
|
selectedDownloadFormat: 'best',
|
||||||
|
selectedCombinableVideoFormat: '',
|
||||||
|
selectedCombinableAudioFormat: '',
|
||||||
selectedSubtitles: [],
|
selectedSubtitles: [],
|
||||||
selectedPlaylistVideoIndex: '1',
|
selectedPlaylistVideoIndex: '1',
|
||||||
|
setActiveDownloadModeTab: (tab) => set(() => ({ activeDownloadModeTab: tab })),
|
||||||
setIsStartingDownload: (isStarting) => set(() => ({ isStartingDownload: isStarting })),
|
setIsStartingDownload: (isStarting) => set(() => ({ isStartingDownload: isStarting })),
|
||||||
setSelctedDownloadFormat: (format) => set(() => ({ selctedDownloadFormat: format })),
|
setSelectedDownloadFormat: (format) => set(() => ({ selectedDownloadFormat: format })),
|
||||||
|
setSelectedCombinableVideoFormat: (format) => set(() => ({ selectedCombinableVideoFormat: format })),
|
||||||
|
setSelectedCombinableAudioFormat: (format) => set(() => ({ selectedCombinableAudioFormat: format })),
|
||||||
setSelectedSubtitles: (subtitles) => set(() => ({ selectedSubtitles: subtitles })),
|
setSelectedSubtitles: (subtitles) => set(() => ({ selectedSubtitles: subtitles })),
|
||||||
setSelectedPlaylistVideoIndex: (index) => set(() => ({ selectedPlaylistVideoIndex: index }))
|
setSelectedPlaylistVideoIndex: (index) => set(() => ({ selectedPlaylistVideoIndex: index }))
|
||||||
}));
|
}));
|
||||||
@@ -107,7 +113,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
|||||||
theme: 'system',
|
theme: 'system',
|
||||||
download_dir: '',
|
download_dir: '',
|
||||||
prefer_video_over_playlist: true,
|
prefer_video_over_playlist: true,
|
||||||
show_downloadable_streams_only: false,
|
strict_downloadablity_check: false,
|
||||||
max_parallel_downloads: 2,
|
max_parallel_downloads: 2,
|
||||||
use_proxy: false,
|
use_proxy: false,
|
||||||
proxy_url: '',
|
proxy_url: '',
|
||||||
@@ -148,7 +154,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
|||||||
theme: 'system',
|
theme: 'system',
|
||||||
download_dir: '',
|
download_dir: '',
|
||||||
prefer_video_over_playlist: true,
|
prefer_video_over_playlist: true,
|
||||||
show_downloadable_streams_only: false,
|
strict_downloadablity_check: false,
|
||||||
max_parallel_downloads: 2,
|
max_parallel_downloads: 2,
|
||||||
use_proxy: false,
|
use_proxy: false,
|
||||||
proxy_url: '',
|
proxy_url: '',
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface Settings {
|
|||||||
download_dir: string;
|
download_dir: string;
|
||||||
max_parallel_downloads: number;
|
max_parallel_downloads: number;
|
||||||
prefer_video_over_playlist: boolean;
|
prefer_video_over_playlist: boolean;
|
||||||
show_downloadable_streams_only: boolean;
|
strict_downloadablity_check: boolean;
|
||||||
use_proxy: boolean;
|
use_proxy: boolean;
|
||||||
proxy_url: string;
|
proxy_url: string;
|
||||||
video_format: string;
|
video_format: string;
|
||||||
|
|||||||
@@ -31,12 +31,18 @@ export interface CurrentVideoMetadataStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloaderPageStatesStore {
|
export interface DownloaderPageStatesStore {
|
||||||
|
activeDownloadModeTab: string;
|
||||||
isStartingDownload: boolean;
|
isStartingDownload: boolean;
|
||||||
selctedDownloadFormat: string;
|
selectedDownloadFormat: string;
|
||||||
|
selectedCombinableVideoFormat: string;
|
||||||
|
selectedCombinableAudioFormat: string;
|
||||||
selectedSubtitles: string[];
|
selectedSubtitles: string[];
|
||||||
selectedPlaylistVideoIndex: string;
|
selectedPlaylistVideoIndex: string;
|
||||||
|
setActiveDownloadModeTab: (tab: string) => void;
|
||||||
setIsStartingDownload: (isStarting: boolean) => void;
|
setIsStartingDownload: (isStarting: boolean) => void;
|
||||||
setSelctedDownloadFormat: (format: string) => void;
|
setSelectedDownloadFormat: (format: string) => void;
|
||||||
|
setSelectedCombinableVideoFormat: (format: string) => void;
|
||||||
|
setSelectedCombinableAudioFormat: (format: string) => void;
|
||||||
setSelectedSubtitles: (subtitles: string[]) => void;
|
setSelectedSubtitles: (subtitles: string[]) => void;
|
||||||
setSelectedPlaylistVideoIndex: (index: string) => void;
|
setSelectedPlaylistVideoIndex: (index: string) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user