Initial commit
This commit is contained in:
212
app/lastfm_api.py
Normal file
212
app/lastfm_api.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import requests
|
||||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LastFmAPI:
|
||||
"""Classe pour interagir avec l'API Last.fm"""
|
||||
|
||||
def __init__(self, api_key=None):
|
||||
self.api_key = api_key
|
||||
self.base_url = "http://ws.audioscrobbler.com/2.0/"
|
||||
self.session = requests.Session()
|
||||
|
||||
def search_album(self, query):
|
||||
"""Recherche un album sur Last.fm"""
|
||||
try:
|
||||
clean_query = self._clean_music_title(query)
|
||||
artist, album = self._extract_artist_album(clean_query)
|
||||
|
||||
logger.info(f"🎵 Recherche Last.fm: Artiste='{artist}' Album='{album}'")
|
||||
|
||||
if not album:
|
||||
return None
|
||||
|
||||
params = {
|
||||
'method': 'album.search',
|
||||
'album': album,
|
||||
'api_key': self.api_key,
|
||||
'format': 'json',
|
||||
'limit': 1
|
||||
}
|
||||
|
||||
response = self.session.get(self.base_url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
results = data.get('results', {}).get('albummatches', {}).get('album', [])
|
||||
|
||||
if results:
|
||||
album_data = results[0]
|
||||
return self.get_album_info(album_data['artist'], album_data['name'])
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur recherche album Last.fm: {e}")
|
||||
return None
|
||||
|
||||
def get_album_info(self, artist, album):
|
||||
"""Récupère les infos complètes d'un album"""
|
||||
try:
|
||||
params = {
|
||||
'method': 'album.getinfo',
|
||||
'artist': artist,
|
||||
'album': album,
|
||||
'api_key': self.api_key,
|
||||
'format': 'json'
|
||||
}
|
||||
|
||||
response = self.session.get(self.base_url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Vérifier si erreur Last.fm
|
||||
if 'error' in data:
|
||||
logger.debug(f"Last.fm error: {data.get('message', 'Unknown error')}")
|
||||
return None
|
||||
|
||||
album_info = data.get('album')
|
||||
|
||||
# Vérifier que album_info est un dict et non une string
|
||||
if album_info and isinstance(album_info, dict):
|
||||
return self._format_album(album_info)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur info album: {e}")
|
||||
return None
|
||||
|
||||
def _format_album(self, album):
|
||||
"""Formate les données d'un album"""
|
||||
try:
|
||||
# Récupérer la plus grande image disponible
|
||||
images = album.get('image', [])
|
||||
cover_url = None
|
||||
|
||||
if isinstance(images, list):
|
||||
for img in reversed(images):
|
||||
if isinstance(img, dict) and img.get('size') in ['extralarge', 'large', 'medium']:
|
||||
cover_url = img.get('#text')
|
||||
if cover_url:
|
||||
break
|
||||
|
||||
# Récupérer les tags
|
||||
tags_data = album.get('tags', {})
|
||||
tags = []
|
||||
if isinstance(tags_data, dict):
|
||||
tag_list = tags_data.get('tag', [])
|
||||
if isinstance(tag_list, list):
|
||||
tags = [tag.get('name') for tag in tag_list[:5] if isinstance(tag, dict)]
|
||||
|
||||
# Récupérer le wiki
|
||||
wiki = album.get('wiki', {})
|
||||
summary = ''
|
||||
published = ''
|
||||
if isinstance(wiki, dict):
|
||||
summary = wiki.get('summary', '')
|
||||
published = wiki.get('published', '')
|
||||
|
||||
return {
|
||||
'artist': album.get('artist', ''),
|
||||
'album': album.get('name', ''),
|
||||
'cover_url': cover_url,
|
||||
'summary': summary,
|
||||
'published': published,
|
||||
'listeners': album.get('listeners', 0),
|
||||
'playcount': album.get('playcount', 0),
|
||||
'tags': tags,
|
||||
'url': album.get('url', ''),
|
||||
'type': 'album'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur formatage album: {e}")
|
||||
return None
|
||||
|
||||
def _clean_music_title(self, title):
|
||||
"""Nettoie un titre de torrent musical"""
|
||||
original = title
|
||||
|
||||
# Supprimer les préfixes comme [Request]
|
||||
title = re.sub(r'^\s*\[.*?\]\s*', '', title)
|
||||
|
||||
# Remplacer les points et underscores par des espaces SAUF le tiret
|
||||
title = title.replace('.', ' ').replace('_', ' ')
|
||||
|
||||
# Supprimer les tags de qualité
|
||||
title = re.sub(r'\b(FLAC|MP3|AAC|WAV|OGG|ALAC|DSD|WEB|CD|VINYL)\b', '', title, flags=re.IGNORECASE)
|
||||
title = re.sub(r'\b(320|256|192|128|24bit|16bit)\s*(kbps|khz)?\b', '', title, flags=re.IGNORECASE)
|
||||
title = re.sub(r'\b(CBR|VBR|Lossless)\b', '', title, flags=re.IGNORECASE)
|
||||
|
||||
# Supprimer les infos de format entre parenthèses ou crochets à la fin
|
||||
# mais garder le contenu principal
|
||||
title = re.sub(r'\s*\([^)]*(?:FLAC|MP3|Lossless|kbps|Vinyl|CD|WEB)[^)]*\)\s*', '', title, flags=re.IGNORECASE)
|
||||
title = re.sub(r'\s*\[[^\]]*(?:FLAC|MP3|Lossless|kbps|Vinyl|CD|WEB)[^\]]*\]\s*', '', title, flags=re.IGNORECASE)
|
||||
|
||||
# Supprimer les années entre parenthèses (2025) ou à la fin
|
||||
title = re.sub(r'\s*\(\s*(19|20)\d{2}\s*\)\s*', ' ', title)
|
||||
title = re.sub(r'\s+(19|20)\d{2}\s*$', '', title)
|
||||
|
||||
# Supprimer (EP), (Single), (Remaster), etc.
|
||||
title = re.sub(r'\s*\(\s*(EP|Single|Remaster|Remastered|Deluxe|Edition|Upconvert)\s*\)\s*', '', title, flags=re.IGNORECASE)
|
||||
|
||||
# Supprimer Discography, Anthology, etc.
|
||||
title = re.sub(r'\s*[-–]\s*Discography.*$', '', title, flags=re.IGNORECASE)
|
||||
title = re.sub(r'\s+Discography.*$', '', title, flags=re.IGNORECASE)
|
||||
|
||||
# Supprimer les groupes de release à la fin (-GROUPE)
|
||||
title = re.sub(r'\s*-\s*[A-Z0-9]{2,}$', '', title)
|
||||
|
||||
# Supprimer les parenthèses/crochets incomplets (ex: "(20" ou "[FLA")
|
||||
title = re.sub(r'\s*\([^)]*$', '', title) # Parenthèse ouvrante sans fermante
|
||||
title = re.sub(r'\s*\[[^\]]*$', '', title) # Crochet ouvrant sans fermant
|
||||
|
||||
# Supprimer les "..." à la fin
|
||||
title = re.sub(r'\s*\.{2,}\s*$', '', title)
|
||||
|
||||
# Nettoyer les espaces multiples
|
||||
title = re.sub(r'\s+', ' ', title).strip()
|
||||
|
||||
# Supprimer les tirets en début ou fin
|
||||
title = re.sub(r'^-+\s*|\s*-+$', '', title)
|
||||
|
||||
logger.debug(f"Music title cleaned: '{original[:50]}' → '{title}'")
|
||||
|
||||
return title
|
||||
|
||||
def _extract_artist_album(self, title):
|
||||
"""Extrait l'artiste et l'album du titre"""
|
||||
# Chercher le séparateur " - " (artiste - album)
|
||||
if ' - ' in title:
|
||||
parts = title.split(' - ', 1)
|
||||
artist = parts[0].strip()
|
||||
album = parts[1].strip()
|
||||
|
||||
# Si l'album est vide, utiliser le titre entier comme album
|
||||
if not album:
|
||||
return '', title.strip()
|
||||
|
||||
return artist, album
|
||||
|
||||
# Pas de séparateur trouvé - essayer de deviner
|
||||
# Parfois le format est "Artiste Album" sans séparateur
|
||||
return '', title.strip()
|
||||
|
||||
def enrich_torrent(self, torrent_title):
|
||||
"""Enrichit un torrent musical avec les données Last.fm"""
|
||||
try:
|
||||
album_data = self.search_album(torrent_title)
|
||||
|
||||
if album_data:
|
||||
logger.info(f"✅ Album trouvé: {album_data['artist']} - {album_data['album']}")
|
||||
return album_data
|
||||
else:
|
||||
logger.warning(f"❌ Album non trouvé: {torrent_title[:60]}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur enrichissement musique: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user