# lastfm_cover_downloader.py import os import requests import urllib.parse from pathlib import Path import logging # Configuración básica de logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Obtener la ruta de la clave de API de Last.fm desde una variable de entorno API_KEY_FILE = os.environ.get( 'LASTFM_API_KEY_PATH', os.path.expandvars('$HOME/apikeys/lastfm')) def get_lastfm_api_key(): """Leer la clave de API de Last.fm desde un archivo.""" try: with open(API_KEY_FILE, 'r') as file: api_key = file.readline().strip() logger.info(f"Successfully read Last.fm API key from { API_KEY_FILE}") return api_key except FileNotFoundError: logger.error(f"API key file '{API_KEY_FILE}' not found.") raise except Exception as e: logger.error(f"Error reading API key: {e}") raise # Obtener la clave de API de Last.fm LASTFM_API_KEY = get_lastfm_api_key() if not LASTFM_API_KEY: raise RuntimeError("Last.fm API key could not be loaded.") def get_largest_cover_image(data): """Seleccionar la imagen de portada más grande disponible en la respuesta de Last.fm.""" if 'album' in data and 'image' in data['album']: # Definir el orden de prioridad de tamaños size_priority = ["mega", "extralarge", "large", "medium", "small"] for size in size_priority: for img in data['album']['image']: if img["size"] == size and img["#text"]: logger.info(f"Using {size} cover: {img['#text']}") return img["#text"] return None def download_image(cover_url, output_path): """Descargar la imagen de portada desde una URL y guardarla en la ruta especificada.""" try: logger.info(f"Downloading image from {cover_url} to {output_path}") response = requests.get(cover_url, stream=True) if response.status_code == 200: with open(output_path, 'wb') as f: for chunk in response.iter_content(1024): f.write(chunk) logger.info(f"Image downloaded to {output_path}") return output_path else: logger.error(f"Failed to download image. HTTP Status: { response.status_code}") raise Exception(f"HTTP Status {response.status_code}") except Exception as e: logger.error(f"Error downloading image: {e}") raise def download_cover_from_lastfm(artist, album, album_dir): """Descargar la portada del álbum desde Last.fm y guardarla en el directorio del álbum.""" try: logger.info(f"Fetching cover for '{album}' by { artist} from Last.fm...") # URL de la API de Last.fm url = f"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key={LASTFM_API_KEY}&artist={ urllib.parse.quote(artist)}&album={urllib.parse.quote(album)}&format=json" response = requests.get(url) if response.status_code == 200: data = response.json() cover_url = get_largest_cover_image(data) if cover_url: # Asegurar que el directorio existe album_dir.mkdir(parents=True, exist_ok=True) local_cover_path = album_dir / "cover.jpg" # Guardar como 'cover.jpg' download_image(cover_url, local_cover_path) logger.info(f"Downloaded cover to {local_cover_path}") return local_cover_path logger.warning(f"No cover found for '{album}' by {artist} on Last.fm.") raise Exception(f"No cover found for '{album}' by {artist}.") except Exception as e: logger.error(f"Error fetching cover from Last.fm: {e}") raise def cover_exists(album_dir): """Verificar si 'cover.jpg' o 'Cover.jpg' existe en el directorio del álbum.""" cover_path_lower = album_dir / "cover.jpg" cover_path_upper = album_dir / "Cover.jpg" exists = cover_path_lower.exists() or cover_path_upper.exists() logger.info(f"Checking cover existence in '{album_dir}': { 'Found' if exists else 'Not found'}") return exists def get_local_album_dir(xesam_url, music_base_path=os.environ.get('MUSIC_LIBRARY_PATH', os.path.expandvars('$HOME/media/all'))): """Extraer el directorio del álbum desde la ruta `xesam:url`.""" if not xesam_url: logger.warning( "xesam:url is empty or missing. Skipping cover detection.") return None try: logger.info(f"Raw xesam:url value: {xesam_url}") # Convertir la URL xesam en una ruta de archivo local local_path = urllib.parse.unquote(xesam_url).replace("file://", "") logger.info(f"Converted local path: {local_path}") # Eliminar '/trackXXXX' si es una ruta de archivo CUE if "/track" in local_path and ".cue" in local_path: local_path = local_path.split("/track")[0] logger.info(f"Stripped CUE path: {local_path}") # Si la ruta no es absoluta, agregar el directorio base de música track_path = Path(local_path) if not track_path.is_absolute(): track_path = Path(music_base_path) / track_path logger.info(f"Updated to absolute path: {track_path}") # Verificar si la ruta resultante existe if not track_path.exists(): logger.warning(f"The path '{track_path}' does not exist.") return None # Manejar archivos CUE: Subir al directorio del álbum real if track_path.suffix == ".cue": return track_path.parent # Manejo estándar de rutas if track_path.is_file(): return track_path.parent else: logger.warning(f"Path is not a valid file: {track_path}") except Exception as e: logger.error(f"Error extracting local album directory: {e}") return None