yt-dlp downloads
This commit is contained in:
@@ -18,9 +18,12 @@ import {
|
||||
Eye,
|
||||
EyeOff,
|
||||
Upload,
|
||||
Download,
|
||||
} from 'lucide-react';
|
||||
import { getIcon } from 'material-file-icons';
|
||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { useFiles, type DirEntry, useTasks, type TaskSummary, Breadcrumb, Toolbar } from 'apps/FileBrowser';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useUserState } from '@/state/useUserState';
|
||||
@@ -61,6 +64,9 @@ export const Files = ({ basePath = '/' }: FilesProps) => {
|
||||
const [cloning, setCloning] = useState(false);
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [runningTask, setRunningTask] = useState<{ task: TaskSummary; entry: DirEntry } | null>(null);
|
||||
const [showVideoDownload, setShowVideoDownload] = useState(false);
|
||||
const [videoUrl, setVideoUrl] = useState('');
|
||||
const [audioOnly, setAudioOnly] = useState(false);
|
||||
const dragCounter = useRef(0);
|
||||
const { getMatchingTasks } = useTasks();
|
||||
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -366,6 +372,22 @@ export const Files = ({ basePath = '/' }: FilesProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleVideoDownload = async () => {
|
||||
const url = videoUrl.trim();
|
||||
if (!url) return;
|
||||
const toastId = toast.loading(audioOnly ? 'Extracting audio...' : 'Downloading video...');
|
||||
try {
|
||||
await files.downloadVideo(url, currentPath, audioOnly);
|
||||
toast.success('Download complete', { id: toastId });
|
||||
setShowVideoDownload(false);
|
||||
setVideoUrl('');
|
||||
setAudioOnly(false);
|
||||
await refresh();
|
||||
} catch {
|
||||
toast.error('Download failed', { id: toastId });
|
||||
}
|
||||
};
|
||||
|
||||
const handleCut = () => {
|
||||
const paths = selected.size > 0 ? selectedPaths() : [];
|
||||
if (paths.length === 0) return;
|
||||
@@ -908,6 +930,10 @@ export const Files = ({ basePath = '/' }: FilesProps) => {
|
||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||
Create Workspace here
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={() => setShowVideoDownload(true)} className="cursor-pointer">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download video
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)}
|
||||
@@ -929,6 +955,63 @@ export const Files = ({ basePath = '/' }: FilesProps) => {
|
||||
cwd={{ root: homeRoot, path: currentPath.replace(/^\//, '') }}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
open={showVideoDownload}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setShowVideoDownload(false);
|
||||
setVideoUrl('');
|
||||
setAudioOnly(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Download video</DialogTitle>
|
||||
<DialogDescription>Download a video from a URL using yt-dlp</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
onSubmit={(ev) => {
|
||||
ev.preventDefault();
|
||||
handleVideoDownload();
|
||||
}}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<input
|
||||
autoFocus
|
||||
value={videoUrl}
|
||||
onChange={(ev) => setVideoUrl(ev.target.value)}
|
||||
placeholder="https://www.youtube.com/watch?v=..."
|
||||
className="h-10 w-full text-sm rounded-md border border-duck-dark/20 bg-background/60 text-duck-dark placeholder:text-duck-dark/40 outline-none focus:border-duck-teal/50 px-3"
|
||||
/>
|
||||
<label className="flex items-center gap-2 cursor-pointer text-sm text-duck-dark/70">
|
||||
<Checkbox checked={audioOnly} onCheckedChange={(v) => setAudioOnly(v === true)} />
|
||||
Extract audio only (mp3)
|
||||
</label>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowVideoDownload(false);
|
||||
setVideoUrl('');
|
||||
setAudioOnly(false);
|
||||
}}
|
||||
className="px-4 py-2 text-sm rounded-md text-duck-dark/70 hover:bg-duck-dark/5 cursor-pointer transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!videoUrl.trim()}
|
||||
className="px-4 py-2 text-sm rounded-md bg-duck-teal text-duck-yellow hover:bg-duck-teal/90 cursor-pointer transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user