Bump deps
This commit is contained in:
parent
6ca85079fa
commit
ebf8d8e67d
5 changed files with 217 additions and 210 deletions
|
@ -1,80 +0,0 @@
|
|||
import { DOWNLOAD_PATH } from '$env/static/private';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import youtubedl from 'youtube-dl-exec';
|
||||
|
||||
const isAudioFormat = (format: string) => {
|
||||
let isAudio = false;
|
||||
|
||||
switch (format) {
|
||||
case 'mp3':
|
||||
isAudio = true;
|
||||
break;
|
||||
default:
|
||||
isAudio = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return isAudio;
|
||||
};
|
||||
|
||||
const isVideoFormat = (format: string) => {
|
||||
let isVideo = false;
|
||||
|
||||
switch (format) {
|
||||
case 'mp4':
|
||||
isVideo = true;
|
||||
break;
|
||||
default:
|
||||
isVideo = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return isVideo;
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
download: async ({ request, cookies }) => {
|
||||
const data = await request.formData();
|
||||
|
||||
const obj = {};
|
||||
for (const element of data) {
|
||||
obj[element[0]] = element[1];
|
||||
}
|
||||
|
||||
const { format, source, link } = obj;
|
||||
|
||||
if (!(format && source && link)) {
|
||||
throw redirect(307, '/');
|
||||
}
|
||||
|
||||
console.info(`Asked ${source} download of ${link}`);
|
||||
|
||||
switch (source) {
|
||||
case 'youtube':
|
||||
const options = {
|
||||
output: `${DOWNLOAD_PATH}/%(artist)s-%(title)s.%(ext)s`,
|
||||
embedThumbnail: true
|
||||
}
|
||||
|
||||
if (isAudioFormat(format)) {
|
||||
options.extractAudio = true;
|
||||
options.audioFormat = format;
|
||||
}
|
||||
|
||||
isVideoFormat(format) ? options.format = format : ''
|
||||
|
||||
const output = await youtubedl(link);
|
||||
|
||||
console.log(output);
|
||||
console.info(`Downloaded ${link} to ${output}`);
|
||||
|
||||
break;
|
||||
case 'spotify':
|
||||
break;
|
||||
default:
|
||||
console.error('ops');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
|
@ -1,7 +1,4 @@
|
|||
<script>
|
||||
import { enhance } from '$app/forms';
|
||||
|
||||
let { form } = $props();
|
||||
let source = $state('youtube');
|
||||
let link = $state('');
|
||||
let format = $state('mp3');
|
||||
|
@ -13,29 +10,27 @@
|
|||
showModal = !showModal;
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
console.log({
|
||||
source,
|
||||
link,
|
||||
format
|
||||
});
|
||||
};
|
||||
|
||||
const onFormSubmit = ({ formElement, formData, action, cancel, submitter }) => {
|
||||
loading = true;
|
||||
// `formElement` is this `<form>` element
|
||||
// `formData` is its `FormData` object that's about to be submitted
|
||||
// `action` is the URL to which the form is posted
|
||||
// calling `cancel()` will prevent the submission
|
||||
// `submitter` is the `HTMLElement` that caused the form to be submitted
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.append('source', source);
|
||||
searchParams.append('link', link);
|
||||
searchParams.append('format', format);
|
||||
|
||||
return async ({ result, update }) => {
|
||||
loading = false;
|
||||
error = result.type !== 'success';
|
||||
// `result` is an `ActionResult` object
|
||||
// `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
|
||||
};
|
||||
const response = await fetch(`/download?${searchParams.toString()}`);
|
||||
const file = window.URL.createObjectURL(await response.blob());
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.setAttribute('download', true);
|
||||
anchor.href = file;
|
||||
anchor.click();
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
|
@ -66,7 +61,7 @@
|
|||
</button>
|
||||
|
||||
<h1 class="mb-6 text-center text-xl">🐙 Scaricatore 🐙</h1>
|
||||
<form method="POST" action="?/download" class="space-y-6" use:enhance={onFormSubmit}>
|
||||
<form class="space-y-6" onsubmit={handleSubmit}>
|
||||
<!-- Source Selection -->
|
||||
<fieldset class="space-y-4">
|
||||
<legend class="text-green-400">Choose Source:</legend>
|
||||
|
@ -125,8 +120,8 @@
|
|||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button
|
||||
type="submit"
|
||||
<a
|
||||
{href}
|
||||
class="w-full rounded-lg border-4 border-pink-700 bg-pink-500 px-4 py-3 text-black transition hover:bg-pink-600 active:border-yellow-500"
|
||||
>
|
||||
Start Download!
|
||||
|
|
92
src/routes/download/+server.ts
Normal file
92
src/routes/download/+server.ts
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { DOWNLOAD_PATH } from '$env/static/private';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import ytdl from 'youtube-dl-exec';
|
||||
|
||||
import { json, error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
|
||||
/**
|
||||
* Fetch YouTube metadata (title, uploader/artist)
|
||||
*/
|
||||
async function getYouTubeMetadata(link: string) {
|
||||
return await ytdl(link, {
|
||||
dumpSingleJson: true,
|
||||
noCheckCertificates: true,
|
||||
noWarnings: true,
|
||||
preferFreeFormats: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the YouTube video/audio using youtube-dl-exec
|
||||
*/
|
||||
function streamYouTube(link: string, format: string): ReadableStream<Uint8Array> {
|
||||
return new ReadableStream({
|
||||
start(controller) {
|
||||
const args = [
|
||||
'-o',
|
||||
'-',
|
||||
format === 'mp3' ? '--embed-metadata' : '',
|
||||
format === 'mp3' ? '--embed-thumbnail' : '',
|
||||
'--format',
|
||||
format === 'mp3' ? 'bestaudio' : 'best',
|
||||
'--audio-format',
|
||||
format === 'mp3' ? 'mp3' : '',
|
||||
'--no-playlist'
|
||||
].filter(Boolean);
|
||||
|
||||
const process = spawn('yt-dlp', [...args, link], { stdio: ['ignore', 'pipe', 'ignore'] });
|
||||
|
||||
process.stdout.on('data', (chunk) => controller.enqueue(chunk));
|
||||
process.stdout.on('end', () => controller.close());
|
||||
process.stdout.on('error', (err) => {
|
||||
console.error('Stream error:', err);
|
||||
controller.error(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Sanitize filename by removing unsafe characters
|
||||
*/
|
||||
function sanitizeFilename(name: string): string {
|
||||
return name.replace(/[\/:*?"<>|]/g, '').trim();
|
||||
}
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
// Get query params
|
||||
const link = url.searchParams.get('link');
|
||||
const format = url.searchParams.get('format'); // mp3, mp4
|
||||
const source = url.searchParams.get('source'); // youtube or spotify
|
||||
|
||||
// Validate input
|
||||
if (!link || !format || !source) {
|
||||
throw error(400, 'Missing required query parameters: link, format, or source');
|
||||
}
|
||||
|
||||
if (source !== 'youtube') {
|
||||
throw error(400, 'Currently, only YouTube is supported');
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch metadata for filename
|
||||
const metadata = await getYouTubeMetadata(link);
|
||||
const { title, uploader } = metadata;
|
||||
const safeTitle = sanitizeFilename(`${uploader} - ${title}`);
|
||||
const filename = `${safeTitle}.${format}`;
|
||||
|
||||
console.log(filename)
|
||||
// Stream video/audio
|
||||
return new Response(streamYouTube(link, format), {
|
||||
headers: {
|
||||
'Content-Type': format === 'mp3' ? 'audio/mpeg' : 'video/mp4',
|
||||
'Content-Disposition': `attachment; filename="${filename}"`
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching metadata:', err);
|
||||
throw error(500, 'Failed to fetch video metadata');
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue