Remove Shikimori API, use AnimeThemes only, switch to WebM format
- Remove ShikimoriService, use AnimeThemes API for search - Replace shikimori_id with animethemes_slug as primary identifier - Remove FFmpeg MP3 conversion, download WebM directly - Add .webm support in storage and upload endpoints - Update frontend to use animethemes_slug 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ..db_models import Anime, AnimeTheme, ThemeType
|
||||
from ..schemas import AnimeSearchResult
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,41 +32,100 @@ class AnimeThemesService:
|
||||
def __init__(self):
|
||||
self.client = _get_animethemes_client()
|
||||
|
||||
async def _find_anime_slug(self, anime: Anime) -> Optional[str]:
|
||||
"""Find AnimeThemes slug by searching anime title."""
|
||||
async def search(self, query: str, limit: int = 20) -> List[AnimeSearchResult]:
|
||||
"""Search anime by query using AnimeThemes API."""
|
||||
try:
|
||||
response = await self.client.get(
|
||||
"/anime",
|
||||
params={
|
||||
"q": query,
|
||||
"include": "images",
|
||||
"page[size]": limit,
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Try different title variations
|
||||
search_terms = [
|
||||
anime.title_english,
|
||||
anime.title_russian,
|
||||
anime.title_japanese,
|
||||
]
|
||||
results = []
|
||||
for anime in data.get("anime", []):
|
||||
# Get poster URL from images
|
||||
poster_url = None
|
||||
images = anime.get("images", [])
|
||||
if images:
|
||||
# Prefer large_cover or first available
|
||||
for img in images:
|
||||
if img.get("facet") == "Large Cover":
|
||||
poster_url = img.get("link")
|
||||
break
|
||||
if not poster_url and images:
|
||||
poster_url = images[0].get("link")
|
||||
|
||||
for term in search_terms:
|
||||
if not term:
|
||||
continue
|
||||
results.append(AnimeSearchResult(
|
||||
animethemes_slug=anime.get("slug"),
|
||||
title_english=anime.get("name"),
|
||||
year=anime.get("year"),
|
||||
poster_url=poster_url,
|
||||
))
|
||||
|
||||
try:
|
||||
response = await self.client.get(
|
||||
"/anime",
|
||||
params={
|
||||
"q": term,
|
||||
"include": "animethemes.animethemeentries.videos.audio",
|
||||
},
|
||||
)
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to search AnimeThemes: {e}")
|
||||
return []
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
animes = data.get("anime", [])
|
||||
if animes:
|
||||
slug = animes[0].get("slug")
|
||||
logger.info(f"Found AnimeThemes slug '{slug}' for '{term}'")
|
||||
return slug
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to search AnimeThemes for '{term}': {e}")
|
||||
continue
|
||||
async def get_or_create_anime(self, db: AsyncSession, slug: str) -> Optional[Anime]:
|
||||
"""Get anime from DB or fetch from AnimeThemes and create."""
|
||||
|
||||
return None
|
||||
# Check if exists (with themes eagerly loaded)
|
||||
query = (
|
||||
select(Anime)
|
||||
.where(Anime.animethemes_slug == slug)
|
||||
.options(selectinload(Anime.themes))
|
||||
)
|
||||
result = await db.execute(query)
|
||||
anime = result.scalar_one_or_none()
|
||||
|
||||
if anime:
|
||||
return anime
|
||||
|
||||
# Fetch from AnimeThemes
|
||||
try:
|
||||
response = await self.client.get(
|
||||
f"/anime/{slug}",
|
||||
params={"include": "images"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch anime from AnimeThemes: {e}")
|
||||
return None
|
||||
|
||||
anime_data = data.get("anime", {})
|
||||
if not anime_data:
|
||||
return None
|
||||
|
||||
# Get poster URL
|
||||
poster_url = None
|
||||
images = anime_data.get("images", [])
|
||||
if images:
|
||||
for img in images:
|
||||
if img.get("facet") == "Large Cover":
|
||||
poster_url = img.get("link")
|
||||
break
|
||||
if not poster_url and images:
|
||||
poster_url = images[0].get("link")
|
||||
|
||||
anime = Anime(
|
||||
animethemes_slug=slug,
|
||||
title_english=anime_data.get("name"),
|
||||
year=anime_data.get("year"),
|
||||
poster_url=poster_url,
|
||||
)
|
||||
|
||||
db.add(anime)
|
||||
await db.commit()
|
||||
await db.refresh(anime)
|
||||
|
||||
return anime
|
||||
|
||||
async def fetch_themes(self, db: AsyncSession, anime: Anime) -> List[AnimeTheme]:
|
||||
"""Fetch themes from AnimeThemes API and sync to DB."""
|
||||
@@ -79,17 +139,8 @@ class AnimeThemesService:
|
||||
anime = result.scalar_one()
|
||||
current_themes = anime.themes or []
|
||||
|
||||
# Find slug if not cached
|
||||
if not anime.animethemes_slug:
|
||||
logger.info(f"Searching AnimeThemes slug for: {anime.title_english or anime.title_russian}")
|
||||
slug = await self._find_anime_slug(anime)
|
||||
logger.info(f"Found slug: {slug}")
|
||||
if slug:
|
||||
anime.animethemes_slug = slug
|
||||
await db.commit()
|
||||
|
||||
if not anime.animethemes_slug:
|
||||
logger.warning(f"No AnimeThemes slug found for anime {anime.id}: {anime.title_english or anime.title_russian}")
|
||||
logger.warning(f"No AnimeThemes slug for anime {anime.id}")
|
||||
return current_themes
|
||||
|
||||
# Fetch themes from AnimeThemes API
|
||||
|
||||
Reference in New Issue
Block a user