Fix: Include backend/audio Django app in repository
This commit is contained in:
parent
d04e726373
commit
644cfab298
37 changed files with 6632 additions and 4 deletions
294
backend/audio/fanart_client.py
Normal file
294
backend/audio/fanart_client.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
"""Fanart.tv API client for fetching artist and album artwork"""
|
||||
import requests
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
from django.conf import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FanartClient:
|
||||
"""Client for Fanart.tv API"""
|
||||
|
||||
# Register for API key at: https://fanart.tv/get-an-api-key/
|
||||
API_KEY = getattr(settings, 'FANART_API_KEY', '')
|
||||
BASE_URL = 'http://webservice.fanart.tv/v3'
|
||||
|
||||
def __init__(self, api_key: str = None):
|
||||
self.api_key = api_key or self.API_KEY
|
||||
if not self.api_key:
|
||||
logger.warning("Fanart.tv API key not configured")
|
||||
|
||||
def get_artist_images(self, musicbrainz_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get artist images by MusicBrainz ID
|
||||
|
||||
Args:
|
||||
musicbrainz_id: MusicBrainz artist ID
|
||||
|
||||
Returns:
|
||||
Dictionary with artist images organized by type
|
||||
"""
|
||||
if not self.api_key:
|
||||
return None
|
||||
|
||||
try:
|
||||
url = f"{self.BASE_URL}/music/{musicbrainz_id}"
|
||||
params = {'api_key': self.api_key}
|
||||
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Organize images by type
|
||||
images = {
|
||||
'backgrounds': [],
|
||||
'thumbnails': [],
|
||||
'logos': [],
|
||||
'logos_hd': [],
|
||||
'banners': [],
|
||||
'album_covers': []
|
||||
}
|
||||
|
||||
# Artist backgrounds
|
||||
if 'artistbackground' in data:
|
||||
for img in data['artistbackground']:
|
||||
images['backgrounds'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Artist thumbnails
|
||||
if 'artistthumb' in data:
|
||||
for img in data['artistthumb']:
|
||||
images['thumbnails'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Music logos
|
||||
if 'musiclogo' in data:
|
||||
for img in data['musiclogo']:
|
||||
images['logos'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# HD Music logos
|
||||
if 'hdmusiclogo' in data:
|
||||
for img in data['hdmusiclogo']:
|
||||
images['logos_hd'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Music banners
|
||||
if 'musicbanner' in data:
|
||||
for img in data['musicbanner']:
|
||||
images['banners'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Album covers
|
||||
if 'albums' in data:
|
||||
for album_id, album_data in data['albums'].items():
|
||||
if 'albumcover' in album_data:
|
||||
for img in album_data['albumcover']:
|
||||
images['album_covers'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'album_id': album_id,
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Sort by likes (descending)
|
||||
for category in images:
|
||||
images[category].sort(key=lambda x: int(x['likes']), reverse=True)
|
||||
|
||||
return images
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.warning(f"Fanart.tv artist not found: {musicbrainz_id}")
|
||||
else:
|
||||
logger.error(f"HTTP error fetching artist from Fanart.tv: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching artist from Fanart.tv: {e}")
|
||||
return None
|
||||
|
||||
def get_album_images(self, musicbrainz_release_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get album images by MusicBrainz release ID
|
||||
|
||||
Args:
|
||||
musicbrainz_release_id: MusicBrainz release ID
|
||||
|
||||
Returns:
|
||||
Dictionary with album images
|
||||
"""
|
||||
if not self.api_key:
|
||||
return None
|
||||
|
||||
try:
|
||||
url = f"{self.BASE_URL}/music/albums/{musicbrainz_release_id}"
|
||||
params = {'api_key': self.api_key}
|
||||
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
images = {
|
||||
'covers': [],
|
||||
'discs': []
|
||||
}
|
||||
|
||||
# Album covers
|
||||
if 'albums' in data:
|
||||
for album_id, album_data in data['albums'].items():
|
||||
if 'albumcover' in album_data:
|
||||
for img in album_data['albumcover']:
|
||||
images['covers'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# CD art
|
||||
if 'cdart' in album_data:
|
||||
for img in album_data['cdart']:
|
||||
images['discs'].append({
|
||||
'id': img['id'],
|
||||
'url': img['url'],
|
||||
'disc': img.get('disc', '1'),
|
||||
'likes': img.get('likes', '0')
|
||||
})
|
||||
|
||||
# Sort by likes
|
||||
images['covers'].sort(key=lambda x: int(x['likes']), reverse=True)
|
||||
images['discs'].sort(key=lambda x: int(x['likes']), reverse=True)
|
||||
|
||||
return images
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 404:
|
||||
logger.warning(f"Fanart.tv album not found: {musicbrainz_release_id}")
|
||||
else:
|
||||
logger.error(f"HTTP error fetching album from Fanart.tv: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching album from Fanart.tv: {e}")
|
||||
return None
|
||||
|
||||
def get_best_artist_image(self, musicbrainz_id: str, image_type: str = 'thumbnail') -> Optional[str]:
|
||||
"""
|
||||
Get best (most liked) artist image of specific type
|
||||
|
||||
Args:
|
||||
musicbrainz_id: MusicBrainz artist ID
|
||||
image_type: Type of image ('thumbnail', 'background', 'logo', 'logo_hd', 'banner')
|
||||
|
||||
Returns:
|
||||
URL of the best image or None
|
||||
"""
|
||||
images = self.get_artist_images(musicbrainz_id)
|
||||
if not images:
|
||||
return None
|
||||
|
||||
# Map to correct key
|
||||
type_map = {
|
||||
'thumbnail': 'thumbnails',
|
||||
'background': 'backgrounds',
|
||||
'logo': 'logos',
|
||||
'logo_hd': 'logos_hd',
|
||||
'banner': 'banners'
|
||||
}
|
||||
|
||||
key = type_map.get(image_type, image_type)
|
||||
if key in images and images[key]:
|
||||
return images[key][0]['url'] # First item is most liked
|
||||
|
||||
return None
|
||||
|
||||
def get_best_album_cover(self, musicbrainz_release_id: str) -> Optional[str]:
|
||||
"""
|
||||
Get best (most liked) album cover
|
||||
|
||||
Args:
|
||||
musicbrainz_release_id: MusicBrainz release ID
|
||||
|
||||
Returns:
|
||||
URL of the best cover or None
|
||||
"""
|
||||
images = self.get_album_images(musicbrainz_release_id)
|
||||
if not images or not images.get('covers'):
|
||||
return None
|
||||
|
||||
return images['covers'][0]['url'] # First item is most liked
|
||||
|
||||
def download_image(self, url: str, output_path: str) -> bool:
|
||||
"""
|
||||
Download image from URL
|
||||
|
||||
Args:
|
||||
url: Image URL
|
||||
output_path: Local path to save image
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url, timeout=30, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(output_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
|
||||
logger.info(f"Downloaded image to {output_path}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading image from {url}: {e}")
|
||||
return False
|
||||
|
||||
def search_by_artist_name(self, artist_name: str) -> Optional[str]:
|
||||
"""
|
||||
Search for MusicBrainz ID by artist name
|
||||
Note: Fanart.tv doesn't have a search endpoint, so this uses MusicBrainz API
|
||||
|
||||
Args:
|
||||
artist_name: Artist name to search for
|
||||
|
||||
Returns:
|
||||
MusicBrainz artist ID or None
|
||||
"""
|
||||
try:
|
||||
# Use MusicBrainz API for search
|
||||
url = 'https://musicbrainz.org/ws/2/artist/'
|
||||
params = {
|
||||
'query': f'artist:{artist_name}',
|
||||
'fmt': 'json',
|
||||
'limit': 1
|
||||
}
|
||||
headers = {
|
||||
'User-Agent': 'SoundWave/1.0 (https://github.com/tubearchivist/tubearchivist)'
|
||||
}
|
||||
|
||||
response = requests.get(url, params=params, headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
if data.get('artists') and len(data['artists']) > 0:
|
||||
return data['artists'][0]['id']
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error searching for artist on MusicBrainz: {e}")
|
||||
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue