feat: Add anime metadata extraction and fix episode selection bug
Features: - Added rich metadata extraction for all anime providers (Anime-Sama, Neko-Sama, Anime-Ultime, Vostfree) - New AnimeMetadata model with synopsis, genres, rating, release year, studio, poster/banner images, episode count, and status - New /api/anime/metadata endpoint for fetching metadata of specific anime - Enhanced /api/anime/search endpoint with optional include_metadata parameter - Updated web interface with metadata display (expandable synopsis, genres, rating, year) - Added metadata toggle checkbox in search UI (disabled by default for performance) Bug Fixes: - Fixed episode selection bug where select would reset to default after any change - Removed onchange event from select element that was causing unwanted reloads - Fixed download button disappearing after episode download - Episodes can now be downloaded multiple times without page refresh Enhancements: - Metadata displayed with icons (📅 year, ⭐ rating, 🏷️ genres, 📺 episodes, 📡 status) - Expandable synopsis section for detailed descriptions - Better visual organization of anime information - Maintains backward compatibility (metadata is optional) 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:
@@ -42,7 +42,7 @@ async def root():
|
||||
return {
|
||||
"message": "Ohm Stream Downloader API",
|
||||
"status": "running",
|
||||
"version": "2.0",
|
||||
"version": "2.1",
|
||||
"endpoints": {
|
||||
"POST /api/download": "Start a new download",
|
||||
"GET /api/downloads": "List all downloads",
|
||||
@@ -51,6 +51,10 @@ async def root():
|
||||
"POST /api/download/{task_id}/resume": "Resume a download",
|
||||
"DELETE /api/download/{task_id}": "Cancel a download",
|
||||
"GET /api/providers": "List all supported providers",
|
||||
"GET /api/anime/search": "Search anime across all providers",
|
||||
"GET /api/anime/metadata": "Get detailed anime metadata (synopsis, genres, rating, etc.)",
|
||||
"GET /api/anime/episodes": "Get episode list for an anime",
|
||||
"POST /api/anime/download-season": "Download all episodes of a season",
|
||||
"GET /web": "Web interface"
|
||||
}
|
||||
}
|
||||
@@ -156,14 +160,21 @@ async def download_file(task_id: str):
|
||||
|
||||
# Unified Anime Search endpoints
|
||||
@app.get("/api/anime/search")
|
||||
async def search_anime_unified(q: str, lang: str = "vostfr"):
|
||||
"""Search across all anime providers"""
|
||||
async def search_anime_unified(q: str, lang: str = "vostfr", include_metadata: bool = False):
|
||||
"""
|
||||
Search across all anime providers
|
||||
|
||||
Args:
|
||||
q: Search query
|
||||
lang: Language preference (vostfr, vf)
|
||||
include_metadata: Whether to fetch full metadata (slower but more detailed)
|
||||
"""
|
||||
import time
|
||||
import asyncio
|
||||
from app.providers import get_anime_providers
|
||||
from app.downloaders import AnimeSamaDownloader, AnimeUltimeDownloader, NekoSamaDownloader, VostfreeDownloader
|
||||
|
||||
print(f"\n[SEARCH] Starting search for '{q}' in {lang}")
|
||||
print(f"\n[SEARCH] Starting search for '{q}' in {lang} (metadata={include_metadata})")
|
||||
start_time = time.time()
|
||||
|
||||
results = {}
|
||||
@@ -184,7 +195,7 @@ async def search_anime_unified(q: str, lang: str = "vostfr"):
|
||||
if provider_id in downloaders:
|
||||
downloader = downloaders[provider_id]
|
||||
print(f"[SEARCH] Queueing search on {provider_id}...")
|
||||
search_tasks.append(downloader.search_anime(q, lang))
|
||||
search_tasks.append(downloader.search_anime(q, lang, include_metadata=include_metadata))
|
||||
provider_ids.append(provider_id)
|
||||
|
||||
# Wait for all searches to complete with a timeout per provider
|
||||
@@ -207,10 +218,41 @@ async def search_anime_unified(q: str, lang: str = "vostfr"):
|
||||
return {
|
||||
"query": q,
|
||||
"lang": lang,
|
||||
"include_metadata": include_metadata,
|
||||
"results": results
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/anime/metadata")
|
||||
async def get_anime_metadata(url: str):
|
||||
"""
|
||||
Get detailed metadata for a specific anime
|
||||
|
||||
Args:
|
||||
url: The anime page URL
|
||||
"""
|
||||
from app.downloaders import get_downloader
|
||||
|
||||
try:
|
||||
downloader = get_downloader(url)
|
||||
|
||||
# Check if the downloader has metadata support
|
||||
if hasattr(downloader, 'get_anime_metadata'):
|
||||
metadata = await downloader.get_anime_metadata(url)
|
||||
return {
|
||||
"url": url,
|
||||
"metadata": metadata
|
||||
}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Downloader for {url} does not support metadata extraction"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/api/anime/episodes")
|
||||
async def get_anime_episodes(url: str, lang: str = "vostfr"):
|
||||
"""Get list of episodes for an anime"""
|
||||
|
||||
Reference in New Issue
Block a user