From 08578eef0b96e00179308907ab925efc42ca1e2f Mon Sep 17 00:00:00 2001 From: 0d0 <0d0acre@esiliati.org> Date: Sat, 19 Apr 2025 17:58:13 +0200 Subject: [PATCH] Update download method --- src/lib/client/downloader.ts | 21 +++++++ src/lib/components/DownloadManager.svelte | 68 ++++++++++++++++++++++- src/lib/server/ytdlp.ts | 15 +++-- src/routes/+page.svelte | 17 +++++- src/routes/download/+server.ts | 30 +++++----- 5 files changed, 128 insertions(+), 23 deletions(-) create mode 100644 src/lib/client/downloader.ts diff --git a/src/lib/client/downloader.ts b/src/lib/client/downloader.ts new file mode 100644 index 0000000..cb2d821 --- /dev/null +++ b/src/lib/client/downloader.ts @@ -0,0 +1,21 @@ +const createAnchorElement = (url: string, filename: string): HTMLAnchorElement => { + const anchor = document.createElement('a'); + anchor.href = url; + anchor.download = filename; + return anchor +} +export const download = async (url: string, filename: string) => { + const response = await fetch(url); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const blob = await response.blob(); + const objectURL = window.URL.createObjectURL(blob); + const anchor = createAnchorElement(url, filename) + document.body.appendChild(anchor); + anchor.click(); + anchor.remove(); + window.URL.revokeObjectURL(objectURL); +} diff --git a/src/lib/components/DownloadManager.svelte b/src/lib/components/DownloadManager.svelte index 2ca9b46..761f47f 100644 --- a/src/lib/components/DownloadManager.svelte +++ b/src/lib/components/DownloadManager.svelte @@ -1,3 +1,69 @@ + +{#if visible} +
+
+

+ Downloading {filename} +

+
+
+
+

{progress}%

+
+
+{/if} diff --git a/src/lib/server/ytdlp.ts b/src/lib/server/ytdlp.ts index a3758b6..631c969 100644 --- a/src/lib/server/ytdlp.ts +++ b/src/lib/server/ytdlp.ts @@ -5,7 +5,7 @@ import supportedFormats from '$lib/common/supportedFormats.json'; import { logger, mimeTypeMap } from '$lib/server/helpers'; const YTDLP_PATH: string = env.YTDLP_PATH as string; -const HTTPS_PROXY: string = env.v as string; +const HTTPS_PROXY: string = env.HTTPS_PROXY as string; export const ytdl = create(YTDLP_PATH); @@ -13,17 +13,22 @@ export const ytdl = create(YTDLP_PATH); * Fetch YouTube metadata (title, uploader/artist) */ export async function getYouTubeMetadata(link: string) { - return await ytdl(link, { + const options = { dumpSingleJson: true, noCheckCertificates: true, noWarnings: true, preferFreeFormats: true, - proxy: HTTPS_PROXY ? HTTPS_PROXY : '' - }); + } + + if (HTTPS_PROXY) { + options.proxy = HTTPS_PROXY; + } + + return await ytdl(link, options); } /** - * Streams the YouTube video/audio using yt-dlp + * Streams the YouTube video/audio using youtube-dl-exec */ export function streamYouTube(link: string, format: string): ReadableStream { logger.debug(`Starting to stream: ${link}`); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6827779..38902c4 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,7 +2,9 @@ import { PUBLIC_VERSION } from '$env/static/public'; import supportedFormats from '$lib/common/supportedFormats.json'; import Loader from '$lib/components/Loader.svelte'; - import { onMount } from 'svelte'; + import { download } from '$lib/client/downloader'; + import DownloadManager from '$lib/components/DownloadManager.svelte'; + import { mount, unmount } from 'svelte'; let source = $state('youtube'); let link = $state(''); @@ -12,6 +14,7 @@ let disabled = $state(true); let metadata = $state(false); let logs = $state(''); + let downloadManager: DownloadManager|null = null; const formats = Object.keys(supportedFormats).map((f) => { return { value: f, label: f.toUpperCase() }; @@ -25,7 +28,16 @@ showModal = !showModal; }; - const onClick = () => { + const dismiss = () => { + unmount(downloadManager) + } + + const onClick = async (evt) => { + evt.preventDefault(); + + const props = $state( { url: href }); + downloadManager = mount(DownloadManager, { target: document.body, props, events: { dismiss } }); + link = ''; }; @@ -166,7 +178,6 @@ id="btn-download" {href} onclick={onClick} - rel="external" class="{disabled ? 'pointer-events-none opacity-50' : ''} block w-full rounded-md border border-pink-400 bg-pink-600 px-4 py-3 text-center text-base font-bold text-black transition hover:bg-pink-500 active:border-yellow-400" diff --git a/src/routes/download/+server.ts b/src/routes/download/+server.ts index 540fed5..94a8c35 100644 --- a/src/routes/download/+server.ts +++ b/src/routes/download/+server.ts @@ -38,28 +38,30 @@ const validateRequest = (url: URL) => { }; export const GET: RequestHandler = async ({ url }) => { const { format, source, metadata, link } = validateRequest(url); - let filename = `noname.${format}`; + let filename = ''; + let contentLength = 0; - if (!!metadata) { - try { - logger.debug(`Fetching video data to set filename`); - // Fetch metadata for filename - const ytMetadata = await getYouTubeMetadata(link); - const { title, uploader } = ytMetadata; - const safeTitle = `${uploader} - ${title}`; - filename = `${safeTitle}.${format}`; - } catch (err) { - logger.error(err); - logger.error('Error fetching metadata:'); - throw error(500, 'Failed to fetch video metadata'); - } + try { + logger.debug(`Fetching video data to set filename`); + // Fetch metadata for filename + const ytMetadata = await getYouTubeMetadata(link); + const { title, uploader, filesize_approx } = ytMetadata; + contentLength = filesize_approx; + const safeTitle = `${uploader} - ${title}`; + filename = `${safeTitle}.${format}`; + } catch (err) { + logger.error(err); + logger.error('Error fetching metadata:'); + throw error(500, 'Failed to fetch video metadata'); } + try { // Stream video/audio return new Response(streamYouTube(link, format), { headers: { 'Content-Type': `${mimeTypeMap.get(format)}`, + 'Content-Length': contentLength, 'Content-Disposition': `attachment; filename="${filename}"` } });