From c60a655e76f87da5d7c8ce7cd2473a62c4405563 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Fri, 7 Nov 2025 21:00:00 +1100 Subject: [PATCH] Update logic of artist avatar fetching --- backend/app/api/artist.py | 51 +++++++++++++++++++ frontend/src/components/MusicLibrary.tsx | 38 +++++++++----- .../components/player/FullScreenPlayer.tsx | 20 +++++--- frontend/src/components/player/Player.tsx | 39 +++++++------- frontend/src/lib/utils.ts | 24 +++++++++ 5 files changed, 133 insertions(+), 39 deletions(-) diff --git a/backend/app/api/artist.py b/backend/app/api/artist.py index c0da306..d1a1d59 100644 --- a/backend/app/api/artist.py +++ b/backend/app/api/artist.py @@ -482,6 +482,57 @@ async def get_artist_all_songs( ) +@router.get("/{artist_name}/image") +async def get_artist_image(artist_name: str, db: AsyncSession = Depends(get_db)): + """Get artist image (returns image file or 404) + + Priority: + 1. Cached artist image from online APIs + 2. Thumbnail from artist's first song + 3. 404 (frontend will show default icon) + """ + from fastapi.responses import FileResponse, Response + from fastapi import HTTPException + + # 1. Try to get from artist info cache + cached_info = get_cached_artist_info(artist_name) + if cached_info and cached_info.image: + # Check if it's a local cache path + if not cached_info.image.startswith('http'): + # It's a relative path like "cache/artist_images/artist_name.jpg" + image_path = os.path.join(settings.BASE_DIR, "data", cached_info.image) + if os.path.exists(image_path): + return FileResponse(image_path, media_type="image/jpeg") + + # If it's an HTTP URL (song thumbnail), redirect to it + if cached_info.image.startswith('http'): + return Response(status_code=307, headers={"Location": cached_info.image}) + + # 2. If not in cache, try to get from artist's songs + result = await db.execute( + select(Music) + .where(Music.artist == artist_name) + .where(Music.thumbnail.isnot(None)) + .where(Music.thumbnail != "") + .limit(1) + ) + song_with_thumbnail = result.scalar_one_or_none() + + if song_with_thumbnail and song_with_thumbnail.thumbnail: + # Check if it's a local thumbnail + if not song_with_thumbnail.thumbnail.startswith('http'): + thumbnail_path = os.path.join(settings.MUSIC_DIR, song_with_thumbnail.thumbnail) + if os.path.exists(thumbnail_path): + return FileResponse(thumbnail_path, media_type="image/jpeg") + + # If it's an HTTP URL, redirect + if song_with_thumbnail.thumbnail.startswith('http'): + return Response(status_code=307, headers={"Location": song_with_thumbnail.thumbnail}) + + # 3. No image found - return 404 + raise HTTPException(status_code=404, detail="Artist image not found") + + @router.get("/{artist_name}", response_model=List[MusicSchema]) async def get_artist_songs( artist_name: str, diff --git a/frontend/src/components/MusicLibrary.tsx b/frontend/src/components/MusicLibrary.tsx index de98263..4d19b74 100644 --- a/frontend/src/components/MusicLibrary.tsx +++ b/frontend/src/components/MusicLibrary.tsx @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Play, AlertCircle, Info, Search, LayoutList, LayoutGrid, ArrowUpDown, Loader2 } from 'lucide-react' import MusicDetailModal from './music/MusicDetailModal' +import { getMusicThumbnail } from '@/lib/utils' import { Select, SelectContent, @@ -187,17 +188,22 @@ export default function MusicLibrary({ onPlayMusic, currentMusic, onPlayNext }: className="flex items-center gap-2 md:gap-4 p-2 md:p-3 rounded-lg hover:bg-accent transition-colors cursor-pointer" onClick={() => music.file_exists && onPlayMusic(music, filteredMusic)} > - {music.thumbnail ? ( + {getMusicThumbnail(music) ? ( {music.title} { + // Fallback to default icon if image fails to load + const target = e.target as HTMLImageElement + target.style.display = 'none' + target.nextElementSibling?.classList.remove('hidden') + }} /> - ) : ( -
- -
- )} + ) : null} +
+ +
@@ -243,17 +249,21 @@ export default function MusicLibrary({ onPlayMusic, currentMusic, onPlayNext }: className="group relative flex flex-col gap-2 p-3 rounded-lg hover:bg-accent transition-colors" >
- {music.thumbnail ? ( + {getMusicThumbnail(music) ? ( {music.title} { + const target = e.target as HTMLImageElement + target.style.display = 'none' + target.nextElementSibling?.classList.remove('hidden') + }} /> - ) : ( -
- -
- )} + ) : null} +
+ +