ytb / index.html
Youssouf ⚜️
feat: add YouTube to MP3 downloader with playlist support
ed98528
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube to MP3</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 30px;
}
input[type="url"] {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 15px;
box-sizing: border-box;
}
button {
background: #ff0000;
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
}
button:hover {
background: #cc0000;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
#status {
margin-top: 20px;
padding: 15px;
border-radius: 5px;
display: none;
}
.analyzing { background: #fff3cd; }
.downloading { background: #d1ecf1; }
.completed { background: #d4edda; }
.error { background: #f8d7da; }
#progress {
margin-top: 10px;
font-weight: bold;
}
.task-list {
margin-top: 30px;
}
.task-item {
background: #f9f9f9;
padding: 15px;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
}
.task-item:hover {
background: #eee;
}
.task-url {
font-size: 14px;
color: #666;
margin-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-status {
font-weight: bold;
margin-bottom: 5px;
}
.task-completed { color: #28a745; }
.task-downloading { color: #17a2b8; }
.task-error { color: #dc3545; }
</style>
</head>
<body>
<div class="container">
<h1>🎵 YouTube to MP3</h1>
<p>Entrez une URL YouTube (vidéo ou playlist)</p>
<input type="url" id="urlInput" placeholder="https://www.youtube.com/watch?v=..." />
<button onclick="startDownload()">Télécharger</button>
<div id="status"></div>
<div id="progress"></div>
<div class="task-list">
<h3>Tâches récentes</h3>
<div id="tasksList"></div>
</div>
</div>
<script>
let currentTaskId = null;
let tasks = JSON.parse(localStorage.getItem('tasks') || '[]');
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function renderTasks() {
const list = document.getElementById('tasksList');
list.innerHTML = '';
tasks.slice().reverse().forEach(task => {
const div = document.createElement('div');
div.className = 'task-item';
div.onclick = () => checkStatus(task.id);
let statusClass = '';
let statusText = '';
if (task.status === 'completed') {
statusClass = 'task-completed';
statusText = '✓ Terminé';
} else if (task.status === 'downloading' || task.status === 'analyzing') {
statusClass = 'task-downloading';
statusText = `⏳ ${task.progress}/${task.total}`;
} else if (task.status === 'error') {
statusClass = 'task-error';
statusText = '✗ Erreur';
}
div.innerHTML = `
<div class="task-status ${statusClass}">${statusText}</div>
<div class="task-url">${task.url}</div>
`;
list.appendChild(div);
});
}
async function startDownload() {
const url = document.getElementById('urlInput').value;
if (!url) {
alert('Veuillez entrer une URL');
return;
}
const response = await fetch('/download', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
});
const data = await response.json();
if (data.error) {
alert(data.error);
return;
}
currentTaskId = data.task_id;
tasks.push({ id: currentTaskId, url: url, status: 'analyzing', progress: 0, total: 0 });
saveTasks();
renderTasks();
checkStatus(currentTaskId);
}
async function checkStatus(taskId) {
currentTaskId = taskId;
const response = await fetch(`/status/${taskId}`);
const data = await response.json();
const statusDiv = document.getElementById('status');
const progressDiv = document.getElementById('progress');
statusDiv.style.display = 'block';
statusDiv.className = data.status;
if (data.status === 'analyzing') {
statusDiv.textContent = '📊 Analyse de la playlist...';
} else if (data.status === 'downloading') {
statusDiv.textContent = '📥 Téléchargement en cours...';
progressDiv.textContent = `${data.progress}/${data.total} vidéos`;
} else if (data.status === 'completed') {
statusDiv.textContent = '✅ Téléchargement terminé!';
progressDiv.innerHTML = `<a href="/download/${taskId}" style="color: #007bff;">📦 Télécharger le ZIP</a>`;
} else if (data.status === 'error') {
statusDiv.textContent = '❌ Erreur: ' + data.error;
progressDiv.textContent = '';
}
const taskIndex = tasks.findIndex(t => t.id === taskId);
if (taskIndex !== -1) {
tasks[taskIndex] = { ...tasks[taskIndex], status: data.status, progress: data.progress, total: data.total };
saveTasks();
renderTasks();
}
if (data.status === 'downloading' || data.status === 'analyzing') {
setTimeout(() => checkStatus(taskId), 2000);
}
}
renderTasks();
</script>
</body>
</html>