Files
yt2mp3/app/pages/index.vue
2026-01-21 20:48:45 +00:00

189 lines
5.3 KiB
Vue

<script setup lang="ts">
import * as z from 'zod'
import urlJoin from 'url-join';
import type { FormSubmitEvent } from '@nuxt/ui'
const { loggedIn } = useUserSession()
const conf = useRuntimeConfig()
const toast = useToast()
const video = ref(0);
const url = ref("");
const in_url = ref("");
const dwn_link = ref("");
const file_dwnl = ref("");
const music_params_schema = z.object({
song: z.string("Song name required"),
artist: z.string().optional(),
album: z.string().optional()
})
type musicParamsSchema = z.output<typeof music_params_schema>
const music_params = reactive<Partial<musicParamsSchema>>({
song: undefined,
artist: undefined,
album: undefined
})
function get_yt_vid() {
url.value = in_url.value
if (in_url.value !== "") video.value = 1
else video.value = 0
}
async function start_conversion(event: FormSubmitEvent<musicParamsSchema>) {
return $fetch("/convert", {
method: "POST",
body: {
url: url.value,
song: music_params.song,
artist: music_params.artist,
album: music_params.album
}
}).then(r => { // r resolves to { file: "my_audio.mp3" }
const file = r.file; // in this case r["file"] --> "my_audio.mp3"
const a = document.createElement("a");
a.href = useBasedPath(`/download/${file}`)
a.download = file; // sets suggested filename
file_dwnl.value = a.href; // this ref activates the Download button
document.body.appendChild(a); // must append first
a.click(); // triggers download
a.remove();
}).catch(r => {
// response is NOT ok (401, 500, etc...)
const res = r.data;
console.log(r.data)
if(res.statusCode == 401) {
navigateTo(useBasedPath("/login"),{external: true})
}
else toast.add({
title: "Error",
description: "Returned error. Check your YT link, if this happens too often, try again in a couple of minutes",
color: "error"
})
})
}
</script>
<template>
<MainHeader :logged_in="loggedIn"/>
<div class="justify-center flex flex-col max-sm:mt-5 max-md:mt-7 max-lg:mt-10 lg:mt-15 mb-4 w-full">
<h1 class="inter font-bold text-center mt-3 mb-3 text-white white-glow max-sm:text-5xl sm:text-6xl md:text-7xl xl:text-8xl rgb-text">Youtube to MP3</h1>
<div class="w-full flex self-center justify-center">
<UInput
class="self-center geist-mono mt-5 mb-5 max-sm:w-[92%] max-md:w-4/5 max-lg:w-4/5 lg:w-2/3 max-w-300"
:ui="{ base: 'rounded-2xl h-15 text-toned', trailing: 'p-0' }"
placeholder="Search for a YouTube video..."
v-model="in_url" >
<template #trailing>
<UButton class="self-end rounded-r-2xl h-15 px-3 hover:cursor-pointer" icon="lucide:search" @click="get_yt_vid" />
</template>
</UInput>
</div>
</div>
<div class="w-full max-w-md mx-auto mt-8 mb-5 p-7 light:bg-zinc-50 dark:bg-zinc-950 rounded-2xl" v-if="video">
<div class="video-container">
<LazyYoutube :src="url" />
</div>
<UForm :schema="music_params_schema" :state="music_params" @submit="start_conversion">
<UFormField name="song" class="w-full mt-4 mb-3" label="Track Name:">
<UInput type="text" v-model="music_params.song" class="w-full" />
</UFormField>
<UFormField name="artist" class="w-full mt-4 mb-3" label="Artist Name: (optional)">
<UInput type="text" v-model="music_params.artist"class="w-full" />
</UFormField>
<UFormField name="album" class="w-full mt-4 mb-3" label="Album Name: (optional)">
<UInput type="text" v-model="music_params.album" class="w-full" />
</UFormField>
<div class="text-center">
<UButton class="mt-4 rounded-2xl px-9 w-2/3 py-3 justify-center text-center " loading-auto type="submit">Convert to MP3</UButton>
</div>
<div class="text-center" v-if="dwn_link != ''">
<UButton class="mt-4 rounded-2xl px-9 w-2/3 py-3 justify-center text-center" icon="lucide:cloud-download" color="success" >Download</UButton>
</div>
</UForm>
</div>
</template>
<style>
.rgb-text {
background: linear-gradient(
90deg,
var(--color-red-500),
var(--color-yellow-500),
var(--color-lime-500),
var(--color-cyan-500),
var(--color-blue-500),
var(--color-pink-500),
var(--color-red-500)
);
background-size: 400% 100%;
animation: rgb 15s linear infinite;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
@keyframes rgb {
to {
background-position: 400% 0;
}
}
.white-glow {
filter: drop-shadow(0 0 10px rgba(255,255,255,.2));
}
/*.rgb-input {
position: relative;
padding: 2px;
background: #111;
color: white;
border: none;
outline: none;
z-index: 0;
@apply: rounded-lg;
}
.rgb-input::before {
content: "";
position: absolute;
inset: 2px;
border-radius: inherit;
background: linear-gradient(
90deg,
red,
yellow,
lime,
cyan,
blue,
magenta,
red
);
background-size: 400%;
animation: rgb 8s linear infinite;
filter: blur(2px);
z-index: -1;
@apply: rounded-lg;
}*/
</style>