feat: Add batch download for entire anime seasons

Add ability to download all episodes of a season with one click.

Backend changes:
- New POST /api/anime/download-season endpoint
- Retrieves all episodes and creates download tasks for each
- Returns list of task IDs and total episode count

Frontend changes:
- Add "Toute la saison" button next to episode selector
- Button shown immediately when episodes are loaded
- Confirmation dialog before starting batch download
- Success message showing number of episodes queued

Features:
- Respects max_parallel limit (default: 3 concurrent downloads)
- Proper episode naming (e.g., "Hells Paradise - Episode 01.mp4")
- Works with all anime providers (Anime-Sama, Anime-Ultime, etc.)

Example usage:
- Click "Toute la saison" button on any anime card
- Confirm the dialog
- All episodes are queued and download automatically

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
root
2026-01-23 08:40:33 +00:00
parent c977306020
commit 40977438ff
2 changed files with 65 additions and 9 deletions
+30
View File
@@ -259,6 +259,36 @@ async def download_anime_episode(
return {"task_id": task.id, "task": task}
@app.post("/api/anime/download-season")
async def download_anime_season(
url: str,
background_tasks: BackgroundTasks,
lang: str = "vostfr"
):
"""Download all episodes of an anime season"""
from app.downloaders import get_downloader
downloader = get_downloader(url)
episodes = await downloader.get_episodes(url, lang)
if not episodes:
raise HTTPException(status_code=404, detail="No episodes found")
# Create download tasks for all episodes
task_ids = []
for episode in episodes:
request = DownloadRequest(url=episode['url'])
task = download_manager.create_task(request)
task_ids.append(task.id)
background_tasks.add_task(download_manager.start_download, task.id)
return {
"message": f"Started downloading {len(task_ids)} episodes",
"task_ids": task_ids,
"total_episodes": len(episodes)
}
# Video Streaming endpoints
@app.get("/video/{task_id}")
async def stream_video(task_id: str, request: Request):
+33 -7
View File
@@ -645,6 +645,12 @@
</svg>
Télécharger
</button>
<button class="btn-primary" style="background: linear-gradient(45deg, #ff6b6b, #ffa500);" onclick="downloadEntireSeason('${encodeURIComponent(anime.url)}', '${lang}')" title="Télécharger toute la saison">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:14px;height:14px;margin-right:4px;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
</svg>
Toute la saison
</button>
</div>
</div>
`;
@@ -690,15 +696,9 @@
selectElement.appendChild(option);
});
// Show download button when episode is selected
selectElement.onchange = () => {
// Show download buttons immediately (season button is always useful)
const actionsDiv = document.getElementById(actionsId);
if (selectElement.value) {
actionsDiv.style.display = 'flex';
} else {
actionsDiv.style.display = 'none';
}
};
// Store the selected episode URL for download
selectElement.dataset.animeUrl = url;
@@ -754,6 +754,32 @@
}
}
async function downloadEntireSeason(encodedUrl, lang) {
const url = decodeURIComponent(encodedUrl);
if (!confirm(`⚠️ Attention: Vous allez télécharger toute la saison. Cela peut prendre du temps et utiliser beaucoup d'espace disque.\n\nVoulez-vous continuer ?`)) {
return;
}
try {
const response = await fetch(`${API_BASE}/anime/download-season?url=${encodeURIComponent(url)}&lang=${lang}`, {
method: 'POST'
});
if (response.ok) {
const data = await response.json();
loadDownloads();
alert(`${data.message}\n\n${data.total_episodes} épisodes ont été ajoutés à la file de téléchargement!`);
} else {
const error = await response.json();
alert(`Erreur: ${error.detail || 'Impossible de démarrer le téléchargement de la saison'}`);
}
} catch (error) {
console.error('Error:', error);
alert('Erreur lors du démarrage du téléchargement de la saison');
}
}
async function getProvidersInfo() {
if (!searchResultsCache.providers) {
const response = await fetch(`${API_BASE}/providers`);