From be4f7e10241512517948b8742a3afd98187daabd Mon Sep 17 00:00:00 2001 From: luciano Date: Tue, 14 Jul 2026 16:30:49 +0200 Subject: [PATCH] bridge: 4 metodi Api per feature Beatport (fetch + download riusa start_tracks_download) --- api/bridge.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/api/bridge.py b/api/bridge.py index 27f0631..018e75c 100644 --- a/api/bridge.py +++ b/api/bridge.py @@ -8,9 +8,12 @@ import re import threading import time import webbrowser +from dataclasses import asdict +from pathlib import Path from typing import Any, Optional from core.config import load_config, save_config, VERSION, LICENSE_API_URL +from core import beatport from core import license as license_mod from core.downloader import ( download_playlist, @@ -903,3 +906,91 @@ class Api: }) self._emit("video:done", {"ok": True}) + + # ================================================================ + # Beatport charts + # ================================================================ + def beatport_genres(self) -> list: + """Lista dei generi disponibili per il dropdown UI.""" + return beatport.list_genres() + + def beatport_fetch_chart(self, slug: str, force_refresh: bool = False) -> dict: + """Fetches la Top 100 per il genere. Salva anche l'ultimo genere in config. + Ritorna {ok: True, tracks: [...]} oppure {ok: False, error, message}.""" + # Memoria: ricorda l'ultimo genere usato + try: + cfg = load_config() + cfg["beatport_last_genre"] = slug + save_config(cfg) + except Exception: + pass + + try: + tracks = beatport.fetch_top100(slug, force_refresh=force_refresh) + except ValueError as e: + return {"ok": False, "error": "invalid_genre", "message": str(e)} + except beatport.BeatportUnreachableError as e: + return {"ok": False, "error": "unreachable", "message": str(e)} + except beatport.BeatportParseError as e: + return {"ok": False, "error": "parse", "message": str(e)} + + return {"ok": True, "tracks": [asdict(t) for t in tracks]} + + def beatport_check_existing(self, tracks: list, genre_name: str) -> list: + """Per ogni track ritorna True se il file esiste gia' in output_dir/Beatport//. + Match euristico: nel filename (senza extension) devono comparire sia il titolo + che il primo artista (case-insensitive).""" + cfg = load_config() + out_root = (cfg.get("output_dir") or "").strip() + if not out_root: + return [False] * len(tracks) + + safe_name = genre_name.replace("/", "_").replace("\\", "_").strip() + out_dir = Path(out_root) / "Beatport" / safe_name + if not out_dir.exists(): + return [False] * len(tracks) + + existing_stems = [p.stem.lower() for p in out_dir.glob("*.mp3")] + result = [] + for t in tracks: + title = (t.get("title") or "").lower().strip() + artists = (t.get("artists") or "") + first_artist = artists.split(",")[0].split("&")[0].strip().lower() + if not title or not first_artist: + result.append(False) + continue + found = any( + (title in stem and first_artist in stem) + for stem in existing_stems + ) + result.append(found) + return result + + def beatport_download_selected(self, tracks: list, genre_name: str) -> dict: + """Converte i BeatportTrack in tracklist compatibile con start_tracks_download + e lancia il download riusando l'infrastruttura esistente (license gate, + skip file gia' scaricati, log su canale 'download', ecc.).""" + cfg = load_config() + out_root = (cfg.get("output_dir") or "").strip() + if not out_root: + return {"ok": False, "error": "Cartella output non impostata"} + + safe_genre = genre_name.replace("/", "_").replace("\\", "_").strip() + subfolder = f"Beatport/{safe_genre}" if safe_genre else "Beatport" + + converted = [] + for t in tracks: + title = (t.get("title") or "").strip() + artists = (t.get("artists") or "").strip() + if not title: + continue + converted.append({"name": title, "artist": artists}) + + if not converted: + return {"ok": False, "error": "Nessun brano valido"} + + return self.start_tracks_download({ + "tracks": converted, + "output_dir": out_root, + "subfolder": subfolder, + })