212 lines
7.8 KiB
Python
212 lines
7.8 KiB
Python
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 |