diff --git a/akarpov/music/services/external.py b/akarpov/music/services/external.py new file mode 100644 index 0000000..9f5047e --- /dev/null +++ b/akarpov/music/services/external.py @@ -0,0 +1,76 @@ +import requests +from typing import Optional, Dict, Any +import structlog +from django.conf import settings +from functools import wraps + +logger = structlog.get_logger(__name__) + + +class ExternalServiceClient: + def __init__(self): + self.base_url = getattr(settings, 'MUSIC_EXTERNAL_SERVICE_URL', None) + + def _make_request(self, endpoint: str, params: Dict = None, **kwargs) -> Optional[Dict]: + if not self.base_url: + return None + + url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}" + try: + response = requests.post(url, params=params, **kwargs) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + logger.error( + "External service request failed", + error=str(e), + endpoint=endpoint, + params=params + ) + return None + + def get_spotify_info(self, track_name: str) -> Optional[Dict[str, Any]]: + return self._make_request( + "/spotify/search", + params={"query": track_name} + ) + + def translate_text(self, text: str, source_lang: str = "auto", target_lang: str = "en") -> Optional[str]: + response = self._make_request( + "/translation/translate", + json={ + "text": text, + "source_lang": source_lang, + "target_lang": target_lang + } + ) + return response.get("translated_text") if response else None + + +def external_service_fallback(fallback_func): + """Decorator to try external service first, then fall back to local implementation""" + + @wraps(fallback_func) + def wrapper(*args, **kwargs): + if not hasattr(settings, 'MUSIC_EXTERNAL_SERVICE_URL') or not settings.MUSIC_EXTERNAL_SERVICE_URL: + return fallback_func(*args, **kwargs) + + client = ExternalServiceClient() + try: + if fallback_func.__name__ == "get_spotify_info": + result = client.get_spotify_info(args[1]) # args[1] is track_name + elif fallback_func.__name__ == "safe_translate": + result = client.translate_text(args[0]) # args[0] is text + + if result: + return result + except Exception as e: + logger.error( + "External service failed, falling back to local implementation", + error=str(e), + function=fallback_func.__name__ + ) + + return fallback_func(*args, **kwargs) + + return wrapper diff --git a/akarpov/music/services/info.py b/akarpov/music/services/info.py index 29736eb..cee6373 100644 --- a/akarpov/music/services/info.py +++ b/akarpov/music/services/info.py @@ -4,6 +4,11 @@ import requests import spotipy +from akarpov.music.services.external import ( + ExternalServiceClient, + external_service_fallback, +) + try: from deep_translator import GoogleTranslator except requests.exceptions.JSONDecodeError: @@ -76,6 +81,7 @@ def spotify_search(name: str, session: spotipy.Spotify, search_type="track"): return res +@external_service_fallback def get_spotify_info(name: str, session: spotipy.Spotify) -> dict: info = { "album_name": "", @@ -379,29 +385,43 @@ def save_author_image(author, image_path): print(f"Error saving author image: {str(e)}") -def safe_translate(text): +@external_service_fallback +def safe_translate(text: str) -> str: try: translated = GoogleTranslator(source="auto", target="en").translate(text) return slugify(translated) except Exception as e: - print(f"Error translating text: {str(e)}") + print(f"Translation failed: {str(e)}") return slugify(text) def search_all_platforms(track_name: str) -> dict: print(track_name) - # session = spotipy.Spotify( - # auth_manager=spotipy.SpotifyClientCredentials( - # client_id=settings.MUSIC_SPOTIFY_ID, - # client_secret=settings.MUSIC_SPOTIFY_SECRET, - # ) - # ) - # spotify_info = get_spotify_info(track_name, session) - spotify_info = {} # TODO: add proxy for info retrieve + + if settings.MUSIC_EXTERNAL_SERVICE_URL: + # Use external service if configured + client = ExternalServiceClient() + spotify_info = client.get_spotify_info(track_name) or {} + else: + # Local implementation fallback + try: + session = spotipy.Spotify( + auth_manager=SpotifyClientCredentials( + client_id=settings.MUSIC_SPOTIFY_ID, + client_secret=settings.MUSIC_SPOTIFY_SECRET, + ) + ) + spotify_info = get_spotify_info(track_name, session) + except Exception as e: + print("Local Spotify implementation failed", error=str(e)) + spotify_info = {} + yandex_info = search_yandex(track_name) + if "album_image_path" in spotify_info and "album_image_path" in yandex_info: os.remove(yandex_info["album_image_path"]) + # Combine artist information combined_artists = set() for artist in spotify_info.get("artists", []) + yandex_info.get("artists", []): normalized_artist = normalize_text(artist) @@ -410,12 +430,13 @@ def search_all_platforms(track_name: str) -> dict: for existing_artist in combined_artists ): combined_artists.add(normalized_artist) - genre = spotify_info.get("genre") or yandex_info.get("genre") - if type(genre) is list: - genre = sorted(genre, key=lambda x: len(x)) - genre = genre[0] - track_info = { + # Process genre information + genre = spotify_info.get("genre") or yandex_info.get("genre") + if isinstance(genre, list) and genre: + genre = sorted(genre, key=len)[0] + + return { "album_name": spotify_info.get("album_name") or yandex_info.get("album_name", ""), "release": spotify_info.get("release") or yandex_info.get("release", ""), @@ -425,5 +446,3 @@ def search_all_platforms(track_name: str) -> dict: "album_image": spotify_info.get("album_image_path") or yandex_info.get("album_image_path", None), } - - return track_info diff --git a/config/settings/base.py b/config/settings/base.py index 58aba7d..95d594e 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -715,6 +715,9 @@ LAST_FM_API_KEY = env("LAST_FM_API_KET", default="") LAST_FM_SECRET = env("LAST_FM_SECRET", default="") +# EXTERNAL +MUSIC_EXTERNAL_SERVICE_URL = env("MUSIC_EXTERNAL_SERVICE_URL", default="") + # ROBOTS # ------------------------------------------------------------------------------ ROBOTS_USE_SITEMAP = True