155 lines
5.9 KiB
Python
Executable File
155 lines
5.9 KiB
Python
Executable File
# 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
|
|
|