269 lines
11 KiB
HTML
269 lines
11 KiB
HTML
<!doctype html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Descargador de YouTube</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<style>
|
|
.fade-out {
|
|
opacity: 1;
|
|
transition: opacity 1s ease-out;
|
|
}
|
|
.fade-out.hide {
|
|
opacity: 0;
|
|
}
|
|
/* Estilos para el spinner */
|
|
.spinner-container {
|
|
display: none; /* Oculto por defecto */
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
}
|
|
.spinner-border {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-light">
|
|
<div class="container mt-5">
|
|
<h1 class="mb-4 text-center">Descargador de YouTube</h1>
|
|
|
|
<ul class="nav nav-tabs" id="downloadTab" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="single-tab" data-bs-toggle="tab" data-bs-target="#single" type="button" role="tab">Vídeo / MP3 individual</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="playlist-tab" data-bs-toggle="tab" data-bs-target="#playlist" type="button" role="tab">Lista de reproducción</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="tab-content pt-4" id="downloadTabContent">
|
|
<div class="tab-pane fade show active" id="single" role="tabpanel">
|
|
<form method="POST" id="singleDownloadForm">
|
|
<input type="hidden" name="type" value="single">
|
|
<div class="mb-3">
|
|
<label for="url1" class="form-label">URL del vídeo</label>
|
|
<input type="url" class="form-control" name="url" id="url1" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Formato</label><br>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="mode" id="video1" value="video" checked>
|
|
<label class="form-check-label" for="video1">Vídeo</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="mode" id="audio1" value="audio">
|
|
<label class="form-check-label" for="audio1">MP3</label>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" id="singleDownloadButton">Descargar</button>
|
|
<div class="spinner-container" id="singleLoadingSpinner">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Cargando...</span>
|
|
</div>
|
|
<p class="mt-2">Descargando archivo...</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="playlist" role="tabpanel">
|
|
<form method="POST" id="playlistDownloadForm">
|
|
<input type="hidden" name="type" value="playlist">
|
|
<div class="mb-3">
|
|
<label for="url2" class="form-label">URL de la lista</label>
|
|
<input type="url" class="form-control" name="url" id="url2" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Formato</label><br>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="mode" id="video2" value="video" checked>
|
|
<label class="form-check-label" for="video2">Vídeo</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="mode" id="audio2" value="audio">
|
|
<label class="form-check-label" for="audio2">MP3</label>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary" id="playlistDownloadButton">Descargar Lista</button>
|
|
<div class="spinner-container" id="playlistLoadingSpinner">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Cargando...</span>
|
|
</div>
|
|
<p class="mt-2">Descargando archivos...</p>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="feedbackModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content bg-light">
|
|
<div class="modal-body">
|
|
{% if success %}
|
|
<div class="alert alert-success fade-out" role="alert">{{ success }}</div>
|
|
{% elif error %}
|
|
<div class="alert alert-danger fade-out" role="alert">{{ error }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const modal = new bootstrap.Modal(document.getElementById('feedbackModal'), {});
|
|
|
|
{% if success or error %}
|
|
window.addEventListener("load", () => {
|
|
modal.show();
|
|
setTimeout(() => {
|
|
modal.hide();
|
|
}, 3000);
|
|
});
|
|
{% endif %}
|
|
|
|
const singleDownloadForm = document.getElementById('singleDownloadForm');
|
|
const singleDownloadButton = document.getElementById('singleDownloadButton');
|
|
const singleLoadingSpinner = document.getElementById('singleLoadingSpinner');
|
|
|
|
const playlistDownloadForm = document.getElementById('playlistDownloadForm');
|
|
const playlistDownloadButton = document.getElementById('playlistDownloadButton');
|
|
const playlistLoadingSpinner = document.getElementById('playlistLoadingSpinner');
|
|
|
|
function showLoadingState(button, spinner) {
|
|
button.disabled = true;
|
|
spinner.style.display = 'block';
|
|
}
|
|
|
|
function hideLoadingState(button, spinner) {
|
|
button.disabled = false;
|
|
spinner.style.display = 'none';
|
|
}
|
|
|
|
// Single video/MP3 download
|
|
singleDownloadForm.addEventListener('submit', function(event) {
|
|
// Prevent default form submission to handle it with Fetch API
|
|
event.preventDefault();
|
|
showLoadingState(singleDownloadButton, singleLoadingSpinner);
|
|
|
|
const formData = new FormData(this); // 'this' refers to the form element
|
|
|
|
fetch('/', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
// Check if the response is a file (e.g., status 200 and content-disposition header)
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
if (response.ok && contentDisposition && contentDisposition.includes('attachment')) {
|
|
return response.blob().then(blob => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
const filename = contentDisposition.split('filename=')[1] ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'download';
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
// Hide loading state after file download starts
|
|
hideLoadingState(singleDownloadButton, singleLoadingSpinner);
|
|
// Show success message if needed, or clear form
|
|
showModal('Descarga iniciada correctamente.', 'success');
|
|
singleDownloadForm.reset(); // Clear the form
|
|
});
|
|
} else {
|
|
// If not a file, it's likely an error message from the server
|
|
return response.text().then(text => {
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(text, 'text/html');
|
|
const errorMessage = doc.querySelector('.alert-danger')?.textContent || 'Error desconocido';
|
|
hideLoadingState(singleDownloadButton, singleLoadingSpinner);
|
|
showModal(errorMessage, 'error');
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
hideLoadingState(singleDownloadButton, singleLoadingSpinner);
|
|
showModal('Error al conectar con el servidor.', 'error');
|
|
});
|
|
});
|
|
|
|
// Playlist download
|
|
playlistDownloadForm.addEventListener('submit', function(event) {
|
|
// Prevent default form submission to handle it with Fetch API
|
|
event.preventDefault();
|
|
showLoadingState(playlistDownloadButton, playlistLoadingSpinner);
|
|
|
|
const formData = new FormData(this); // 'this' refers to the form element
|
|
|
|
fetch('/', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
if (response.ok && contentDisposition && contentDisposition.includes('attachment')) {
|
|
return response.blob().then(blob => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
const filename = contentDisposition.split('filename=')[1] ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'download.zip';
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
// Hide loading state after file download starts
|
|
hideLoadingState(playlistDownloadButton, playlistLoadingSpinner);
|
|
// Show success message or clear form
|
|
showModal('Descarga de la lista iniciada correctamente.', 'success');
|
|
playlistDownloadForm.reset(); // Clear the form
|
|
});
|
|
} else {
|
|
return response.text().then(text => {
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(text, 'text/html');
|
|
const errorMessage = doc.querySelector('.alert-danger')?.textContent || 'Error desconocido';
|
|
hideLoadingState(playlistDownloadButton, playlistLoadingSpinner);
|
|
showModal(errorMessage, 'error');
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
hideLoadingState(playlistDownloadButton, playlistLoadingSpinner);
|
|
showModal('Error al conectar con el servidor.', 'error');
|
|
});
|
|
});
|
|
|
|
// Function to show the modal with messages
|
|
function showModal(message, type) {
|
|
const feedbackModalBody = document.querySelector('#feedbackModal .modal-body');
|
|
feedbackModalBody.innerHTML = `
|
|
<div class="alert alert-${type} fade-out" role="alert">${message}</div>
|
|
`;
|
|
modal.show();
|
|
setTimeout(() => {
|
|
modal.hide();
|
|
}, 3000);
|
|
}
|
|
|
|
// Set initial state for tabs (important for refreshing page and keeping correct tab active)
|
|
document.addEventListener('DOMContentLoaded', (event) => {
|
|
const currentTab = localStorage.getItem('activeTab');
|
|
if (currentTab) {
|
|
new bootstrap.Tab(document.getElementById(currentTab + '-tab')).show();
|
|
}
|
|
|
|
document.querySelectorAll('.nav-link').forEach(tabLink => {
|
|
tabLink.addEventListener('shown.bs.tab', function (e) {
|
|
localStorage.setItem('activeTab', e.target.id.replace('-tab', ''));
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |