|
|
<!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> |