Initial clean commit
This commit is contained in:
@@ -0,0 +1,839 @@
|
||||
import requests
|
||||
import json
|
||||
import statistics
|
||||
import musicbrainzngs
|
||||
import openai
|
||||
from flask import current_app
|
||||
from collections import Counter
|
||||
import traceback # Added for detailed error tracking
|
||||
|
||||
def get_song_metadata_by_isrc(isrc, app=None):
|
||||
"""
|
||||
Get comprehensive song metadata by ISRC code from multiple sources.
|
||||
|
||||
Args:
|
||||
isrc (str): The ISRC code to look up
|
||||
app: Flask application context (optional)
|
||||
|
||||
Returns:
|
||||
dict: Standardized metadata with the following keys:
|
||||
- artist_name: Artist name(s)
|
||||
- title: Title of the song
|
||||
- year: Year the song was first released
|
||||
- genre: Primary genre
|
||||
- genres: All genres as an array
|
||||
- popularity: Popularity rating (0-100)
|
||||
- preview_url: Main preview URL (prioritized from sources)
|
||||
- sources: List of sources that provided data
|
||||
- spotify_id: Spotify track ID if available
|
||||
- deezer_id: Deezer track ID if available
|
||||
- And more provider-specific data
|
||||
"""
|
||||
# Initialize result dictionary
|
||||
metadata = {
|
||||
"artist_name": None,
|
||||
"title": None,
|
||||
"year": None,
|
||||
"genre": None,
|
||||
"genres": [], # New array to store all genres
|
||||
"popularity": None,
|
||||
"preview_url": None,
|
||||
"sources": [],
|
||||
"isrc": isrc,
|
||||
"spotify_id": None,
|
||||
"deezer_id": None,
|
||||
# Cover URLs from different sources
|
||||
"cover_url": None,
|
||||
"spotify_cover_url": None,
|
||||
"deezer_cover_url": None,
|
||||
"apple_cover_url": None,
|
||||
# Preview URLs from different sources
|
||||
"spotify_preview_url": None,
|
||||
"deezer_preview_url": None,
|
||||
"apple_preview_url": None,
|
||||
"youtube_preview_url": None
|
||||
}
|
||||
|
||||
# Store results from different sources to compare
|
||||
artist_names = []
|
||||
titles = []
|
||||
years = []
|
||||
genres = [] # This will collect all genres for final processing
|
||||
preview_urls = []
|
||||
|
||||
# Initialize logger if app context provided
|
||||
logger = app.logger if app else None
|
||||
if logger:
|
||||
logger.info(f"=== DEBUG: Starting metadata refresh for ISRC: {isrc} ===")
|
||||
|
||||
try:
|
||||
# 0. Query ACRCloud first (provides info from multiple platforms)
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Querying ACRCloud for ISRC: {isrc}")
|
||||
acrcloud_data = get_acrcloud_data(isrc, app)
|
||||
if acrcloud_data:
|
||||
metadata["sources"].append("acrcloud")
|
||||
if logger:
|
||||
logger.info(f"DEBUG: ACRCloud data received: {json.dumps(acrcloud_data, default=str)}")
|
||||
|
||||
if acrcloud_data.get("artist_name"):
|
||||
artist_names.append(acrcloud_data["artist_name"])
|
||||
if acrcloud_data.get("title"):
|
||||
titles.append(acrcloud_data["title"])
|
||||
if acrcloud_data.get("year"):
|
||||
years.append(acrcloud_data["year"])
|
||||
if acrcloud_data.get("genre"):
|
||||
# Debug the genre value
|
||||
if logger:
|
||||
logger.info(f"DEBUG: ACRCloud genre type: {type(acrcloud_data['genre']).__name__}")
|
||||
logger.info(f"DEBUG: ACRCloud genre value: {acrcloud_data['genre']}")
|
||||
|
||||
# Handle genre properly whether it's a string, list, or dict
|
||||
if isinstance(acrcloud_data["genre"], list):
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Processing genre as list: {acrcloud_data['genre']}")
|
||||
genres.extend(acrcloud_data["genre"]) # ACRCloud might return multiple genres
|
||||
elif isinstance(acrcloud_data["genre"], str):
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Processing genre as string: {acrcloud_data['genre']}")
|
||||
genres.append(acrcloud_data["genre"])
|
||||
elif isinstance(acrcloud_data["genre"], dict):
|
||||
# Debug the dict structure
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Processing genre as dict: {acrcloud_data['genre']}")
|
||||
|
||||
# Extract genre name from dict if available
|
||||
for genre_key, genre_value in acrcloud_data["genre"].items():
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Genre key: {genre_key}, value type: {type(genre_value).__name__}")
|
||||
|
||||
if isinstance(genre_value, str):
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Adding genre string: {genre_value}")
|
||||
genres.append(genre_value)
|
||||
elif isinstance(genre_value, list) and genre_value:
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Adding genres from list: {genre_value}")
|
||||
genres.extend([g for g in genre_value if isinstance(g, str)])
|
||||
else:
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Skipping genre value of type: {type(genre_value).__name__}")
|
||||
else:
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Unknown genre type: {type(acrcloud_data['genre']).__name__}")
|
||||
|
||||
# Store platform IDs
|
||||
if acrcloud_data.get("spotify_id"):
|
||||
metadata["spotify_id"] = acrcloud_data["spotify_id"]
|
||||
if acrcloud_data.get("deezer_id"):
|
||||
metadata["deezer_id"] = acrcloud_data["deezer_id"]
|
||||
|
||||
# Store preview URLs from different sources
|
||||
if acrcloud_data.get("spotify_preview_url"):
|
||||
metadata["spotify_preview_url"] = acrcloud_data["spotify_preview_url"]
|
||||
if acrcloud_data.get("deezer_preview_url"):
|
||||
metadata["deezer_preview_url"] = acrcloud_data["deezer_preview_url"]
|
||||
if acrcloud_data.get("apple_preview_url"):
|
||||
metadata["apple_preview_url"] = acrcloud_data["apple_preview_url"]
|
||||
if acrcloud_data.get("youtube_preview_url"):
|
||||
metadata["youtube_preview_url"] = acrcloud_data["youtube_preview_url"]
|
||||
|
||||
# Store cover URLs from different sources
|
||||
if acrcloud_data.get("spotify_cover_url"):
|
||||
metadata["spotify_cover_url"] = acrcloud_data["spotify_cover_url"]
|
||||
if acrcloud_data.get("deezer_cover_url"):
|
||||
metadata["deezer_cover_url"] = acrcloud_data["deezer_cover_url"]
|
||||
if acrcloud_data.get("apple_cover_url"):
|
||||
metadata["apple_cover_url"] = acrcloud_data["apple_cover_url"]
|
||||
|
||||
# Store album cover as main cover if available
|
||||
if acrcloud_data.get("album_cover"):
|
||||
metadata["cover_url"] = acrcloud_data["album_cover"]
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"ACRCloud error for ISRC {isrc}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
try:
|
||||
# 1. Query MusicBrainz (direct ISRC support)
|
||||
mb_data = get_musicbrainz_data(isrc, logger)
|
||||
if mb_data:
|
||||
metadata["sources"].append("musicbrainz")
|
||||
if mb_data.get("artist_name"):
|
||||
artist_names.append(mb_data["artist_name"])
|
||||
if mb_data.get("title"):
|
||||
titles.append(mb_data["title"])
|
||||
if mb_data.get("year"):
|
||||
years.append(mb_data["year"])
|
||||
if mb_data.get("genre"):
|
||||
genres.append(mb_data["genre"])
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"MusicBrainz error for ISRC {isrc}: {e}")
|
||||
|
||||
try:
|
||||
# 2. Query Spotify (direct ISRC support)
|
||||
spotify_data = get_spotify_data(isrc, app)
|
||||
if spotify_data:
|
||||
metadata["sources"].append("spotify")
|
||||
if spotify_data.get("artist_name"):
|
||||
artist_names.append(spotify_data["artist_name"])
|
||||
if spotify_data.get("title"):
|
||||
titles.append(spotify_data["title"])
|
||||
if spotify_data.get("year"):
|
||||
years.append(spotify_data["year"])
|
||||
if spotify_data.get("genre"):
|
||||
genres.append(spotify_data["genre"])
|
||||
if spotify_data.get("popularity") is not None:
|
||||
metadata["popularity"] = spotify_data["popularity"]
|
||||
if spotify_data.get("spotify_preview_url"):
|
||||
metadata["spotify_preview_url"] = spotify_data["spotify_preview_url"]
|
||||
if spotify_data.get("id"):
|
||||
metadata["spotify_id"] = spotify_data["id"]
|
||||
if spotify_data.get("spotify_cover_url"):
|
||||
metadata["spotify_cover_url"] = spotify_data["spotify_cover_url"]
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Spotify error for ISRC {isrc}: {e}")
|
||||
|
||||
try:
|
||||
# 3. Query Deezer (direct ISRC support in newer API)
|
||||
deezer_data = get_deezer_data(isrc, app)
|
||||
if deezer_data:
|
||||
metadata["sources"].append("deezer")
|
||||
if deezer_data.get("artist_name"):
|
||||
artist_names.append(deezer_data["artist_name"])
|
||||
if deezer_data.get("title"):
|
||||
titles.append(deezer_data["title"])
|
||||
if deezer_data.get("year"):
|
||||
years.append(deezer_data["year"])
|
||||
if deezer_data.get("genre"):
|
||||
genres.append(deezer_data["genre"])
|
||||
if deezer_data.get("deezer_preview_url"):
|
||||
metadata["deezer_preview_url"] = deezer_data["deezer_preview_url"]
|
||||
if deezer_data.get("id"):
|
||||
metadata["deezer_id"] = deezer_data["id"]
|
||||
if deezer_data.get("deezer_cover_url"):
|
||||
metadata["deezer_cover_url"] = deezer_data["deezer_cover_url"]
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Deezer error for ISRC {isrc}: {e}")
|
||||
|
||||
# Debug the collected data before processing
|
||||
if logger:
|
||||
logger.info(f"DEBUG: All collected artist names: {artist_names}")
|
||||
logger.info(f"DEBUG: All collected titles: {titles}")
|
||||
logger.info(f"DEBUG: All collected years: {years}")
|
||||
logger.info(f"DEBUG: All collected genres: {genres}")
|
||||
|
||||
# Determine most common values so far
|
||||
if artist_names and titles:
|
||||
try:
|
||||
# Use the most frequent values from collected data
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Computing most common artist from: {artist_names}")
|
||||
metadata["artist_name"] = Counter(artist_names).most_common(1)[0][0]
|
||||
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Computing most common title from: {titles}")
|
||||
metadata["title"] = Counter(titles).most_common(1)[0][0]
|
||||
|
||||
# With artist and title, we can query services that don't support ISRC
|
||||
try:
|
||||
# 4. Query Last.fm
|
||||
lastfm_data = get_lastfm_data(metadata["artist_name"], metadata["title"], app)
|
||||
if lastfm_data:
|
||||
metadata["sources"].append("lastfm")
|
||||
if lastfm_data.get("genre"):
|
||||
genres.append(lastfm_data["genre"])
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Last.fm error for {metadata['artist_name']} - {metadata['title']}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
try:
|
||||
# 5. Query OpenAI for additional verification
|
||||
openai_data = get_openai_data(metadata["artist_name"], metadata["title"], app)
|
||||
if openai_data:
|
||||
metadata["sources"].append("openai")
|
||||
if openai_data.get("year"):
|
||||
years.append(openai_data["year"])
|
||||
if openai_data.get("genre"):
|
||||
genres.append(openai_data["genre"])
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"OpenAI error for {metadata['artist_name']} - {metadata['title']}: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Error determining most common values: {e}")
|
||||
logger.error(f"Artist names: {artist_names}")
|
||||
logger.error(f"Titles: {titles}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
# Process collected data
|
||||
if years:
|
||||
try:
|
||||
# For year, take the earliest one as "first released"
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Processing years: {years}")
|
||||
|
||||
numeric_years = [int(y) for y in years if y and y.isdigit()]
|
||||
if numeric_years:
|
||||
metadata["year"] = str(min(numeric_years))
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Selected earliest year: {metadata['year']}")
|
||||
except Exception as e:
|
||||
# Fallback to most common if conversion fails
|
||||
if logger:
|
||||
logger.error(f"Year processing error: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
try:
|
||||
metadata["year"] = Counter(years).most_common(1)[0][0]
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Fallback to most common year: {metadata['year']}")
|
||||
except Exception as e2:
|
||||
if logger:
|
||||
logger.error(f"Year fallback error: {e2}")
|
||||
|
||||
# Process all genres and create a clean list for tagging
|
||||
if genres:
|
||||
try:
|
||||
# First, clean up genres for storage
|
||||
clean_genres = []
|
||||
for g in genres:
|
||||
if isinstance(g, str) and g.strip():
|
||||
# Translate specific genre names to English
|
||||
if g.strip().lower() == "vaihtoehtoinen":
|
||||
clean_genres.append("Alternative")
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Translated genre 'Vaihtoehtoinen' to 'Alternative'")
|
||||
else:
|
||||
clean_genres.append(g.strip())
|
||||
elif isinstance(g, list):
|
||||
# Flatten any nested lists and translate if needed
|
||||
for item in g:
|
||||
if isinstance(item, str) and item.strip():
|
||||
if item.strip().lower() == "vaihtoehtoinen":
|
||||
clean_genres.append("Alternative")
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Translated genre 'Vaihtoehtoinen' to 'Alternative'")
|
||||
else:
|
||||
clean_genres.append(item.strip())
|
||||
|
||||
# Store all unique genres in the metadata
|
||||
unique_genres = []
|
||||
for g in clean_genres:
|
||||
if g.lower() not in [existing.lower() for existing in unique_genres]:
|
||||
unique_genres.append(g)
|
||||
|
||||
metadata["genres"] = unique_genres
|
||||
|
||||
if logger:
|
||||
logger.info(f"DEBUG: All cleaned genres: {unique_genres}")
|
||||
|
||||
# For the main genre field, take the most common one
|
||||
if clean_genres:
|
||||
# Get a case-insensitive count by converting all to lowercase
|
||||
lowercase_genres = [g.lower() for g in clean_genres]
|
||||
genre_counter = Counter(lowercase_genres)
|
||||
most_common_genre_lower = genre_counter.most_common(1)[0][0]
|
||||
|
||||
# Find the original case version from our clean genres
|
||||
for g in clean_genres:
|
||||
if g.lower() == most_common_genre_lower:
|
||||
metadata["genre"] = g
|
||||
break
|
||||
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Selected most common genre as main: {metadata['genre']}")
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"Genre processing error: {e}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
# Process preview URLs with priority for Spotify, then Apple Music, then Deezer
|
||||
preview_sources = [
|
||||
metadata.get("spotify_preview_url"),
|
||||
metadata.get("apple_preview_url"),
|
||||
metadata.get("deezer_preview_url"),
|
||||
metadata.get("youtube_preview_url")
|
||||
]
|
||||
|
||||
# Select the first available preview URL as the main one
|
||||
for url in preview_sources:
|
||||
if url:
|
||||
metadata["preview_url"] = url
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Selected preview URL: {url}")
|
||||
break
|
||||
|
||||
# Process cover URLs with priority
|
||||
cover_sources = [
|
||||
metadata.get("spotify_cover_url"),
|
||||
metadata.get("apple_cover_url"),
|
||||
metadata.get("deezer_cover_url")
|
||||
]
|
||||
|
||||
# Select the first available cover URL as the main one if not already set
|
||||
if not metadata["cover_url"]:
|
||||
for url in cover_sources:
|
||||
if url:
|
||||
metadata["cover_url"] = url
|
||||
if logger:
|
||||
logger.info(f"DEBUG: Selected cover URL: {url}")
|
||||
break
|
||||
|
||||
if logger:
|
||||
logger.info(f"=== DEBUG: Completed metadata refresh for ISRC: {isrc} ===")
|
||||
logger.info(f"=== Final metadata: {json.dumps(metadata, default=str)} ===")
|
||||
|
||||
return metadata
|
||||
|
||||
def get_musicbrainz_data(isrc, logger=None):
|
||||
"""Query MusicBrainz API using ISRC"""
|
||||
result = {}
|
||||
|
||||
# Set user agent for MusicBrainz API
|
||||
musicbrainzngs.set_useragent("MusicRound", "0.1", "fret@fret.de")
|
||||
|
||||
try:
|
||||
# Search MusicBrainz by ISRC
|
||||
mb_results = musicbrainzngs.search_recordings(isrc=isrc, limit=1)
|
||||
if mb_results and mb_results.get('recording-list') and len(mb_results['recording-list']) > 0:
|
||||
recording = mb_results['recording-list'][0]
|
||||
|
||||
# Extract title
|
||||
result["title"] = recording.get('title')
|
||||
|
||||
# Extract artist name
|
||||
if recording.get('artist-credit'):
|
||||
artist_names = []
|
||||
for artist_credit in recording['artist-credit']:
|
||||
if isinstance(artist_credit, dict) and 'artist' in artist_credit:
|
||||
artist_names.append(artist_credit['artist']['name'])
|
||||
if artist_names:
|
||||
result["artist_name"] = ", ".join(artist_names)
|
||||
|
||||
# Extract genre tags
|
||||
if 'tag-list' in recording:
|
||||
tags = [tag['name'] for tag in recording['tag-list']]
|
||||
if tags:
|
||||
result["genre"] = tags[0]
|
||||
|
||||
# Get release year
|
||||
if 'release-list' in recording and recording['release-list']:
|
||||
release = recording['release-list'][0]
|
||||
if 'date' in release:
|
||||
result["year"] = release['date'][:4] # Extract year from date
|
||||
except Exception as e:
|
||||
if logger:
|
||||
logger.error(f"MusicBrainz API error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def get_spotify_data(isrc, app=None):
|
||||
"""Query Spotify API using ISRC"""
|
||||
result = {}
|
||||
|
||||
try:
|
||||
# Get Spotify client from app context
|
||||
sp = app.config.get('sp') if app else None
|
||||
if not sp:
|
||||
return result
|
||||
|
||||
# Search Spotify by ISRC
|
||||
query = f"isrc:{isrc}"
|
||||
spotify_result = sp.search(q=query, type='track')
|
||||
|
||||
if spotify_result and spotify_result.get('tracks') and spotify_result['tracks'].get('items'):
|
||||
track = spotify_result['tracks']['items'][0]
|
||||
|
||||
# Extract track title
|
||||
result["title"] = track.get('name')
|
||||
|
||||
# Extract artist names
|
||||
if track.get('artists'):
|
||||
result["artist_name"] = ", ".join([artist['name'] for artist in track['artists']])
|
||||
|
||||
# Extract popularity
|
||||
result["popularity"] = track.get('popularity')
|
||||
|
||||
# Extract preview URL
|
||||
result["spotify_preview_url"] = track.get('preview_url')
|
||||
|
||||
# Store main track ID
|
||||
result["id"] = track.get('id')
|
||||
|
||||
# Get album details to extract more info
|
||||
if track.get('album') and track['album'].get('id'):
|
||||
album = sp.album(track['album']['id'])
|
||||
|
||||
# Extract genre
|
||||
if album.get('genres') and len(album['genres']) > 0:
|
||||
result["genre"] = album['genres'][0]
|
||||
|
||||
# Extract release year
|
||||
if album.get('release_date'):
|
||||
result["year"] = album['release_date'][:4]
|
||||
|
||||
# Extract cover images
|
||||
if track['album'].get('images') and len(track['album']['images']) > 0:
|
||||
for img in track['album']['images']:
|
||||
if img.get('height') and img.get('width') and img.get('url'):
|
||||
if img['height'] > 600: # Consider this a large image
|
||||
result["spotify_cover_url"] = img['url']
|
||||
break
|
||||
# If we didn't find a large image, use the first one
|
||||
if not result.get("spotify_cover_url") and track['album']['images'][0].get('url'):
|
||||
result["spotify_cover_url"] = track['album']['images'][0]['url']
|
||||
except Exception as e:
|
||||
if app:
|
||||
app.logger.error(f"Spotify API error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def get_deezer_data(isrc, app=None):
|
||||
"""Query Deezer API using ISRC"""
|
||||
result = {}
|
||||
|
||||
try:
|
||||
# Get Deezer client from app context or create a basic one
|
||||
deezer_client = app.config.get('deezer') if app else None
|
||||
|
||||
if not deezer_client:
|
||||
# If no client in app context, make direct API call
|
||||
response = requests.get(f"https://api.deezer.com/track/isrc:{isrc}")
|
||||
if response.status_code == 200:
|
||||
track = response.json()
|
||||
else:
|
||||
return result
|
||||
else:
|
||||
# Try to use the ISRC search if available, or search by track if not
|
||||
try:
|
||||
track = deezer_client._make_request(f"track/isrc:{isrc}")
|
||||
except:
|
||||
# Deezer client might not have direct ISRC support, so try a workaround
|
||||
# (This would require having title and artist from another source)
|
||||
track = None
|
||||
|
||||
if track and not track.get('error'):
|
||||
# Extract title
|
||||
result["title"] = track.get('title')
|
||||
|
||||
# Extract artist name
|
||||
if track.get('artist'):
|
||||
result["artist_name"] = track['artist'].get('name')
|
||||
|
||||
# Extract preview URL
|
||||
result["deezer_preview_url"] = track.get('preview')
|
||||
|
||||
# Extract Deezer ID
|
||||
result["id"] = track.get('id')
|
||||
|
||||
# Get album details to extract more info
|
||||
if track.get('album') and track['album'].get('id'):
|
||||
album_id = track['album']['id']
|
||||
|
||||
if deezer_client:
|
||||
album = deezer_client.get_album(album_id)
|
||||
else:
|
||||
album_response = requests.get(f"https://api.deezer.com/album/{album_id}")
|
||||
album = album_response.json() if album_response.status_code == 200 else None
|
||||
|
||||
if album and not album.get('error'):
|
||||
# Extract genre
|
||||
if album.get('genres') and album['genres'].get('data') and len(album['genres']['data']) > 0:
|
||||
result["genre"] = album['genres']['data'][0].get('name')
|
||||
|
||||
# Extract release year
|
||||
if album.get('release_date'):
|
||||
result["year"] = album['release_date'][:4]
|
||||
|
||||
# Extract cover image
|
||||
if track['album'].get('cover'):
|
||||
result["deezer_cover_url"] = track['album']['cover']
|
||||
# Try the bigger version
|
||||
if track['album'].get('cover_xl'):
|
||||
result["deezer_cover_url"] = track['album']['cover_xl']
|
||||
elif track['album'].get('cover_big'):
|
||||
result["deezer_cover_url"] = track['album']['cover_big']
|
||||
except Exception as e:
|
||||
if app:
|
||||
app.logger.error(f"Deezer API error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def get_lastfm_data(artist_name, track_title, app=None):
|
||||
"""Query Last.fm API using artist name and track title"""
|
||||
result = {}
|
||||
|
||||
if not artist_name or not track_title:
|
||||
return result
|
||||
|
||||
try:
|
||||
# Get Last.fm API key from app context or environment
|
||||
lastfm_api_key = None
|
||||
if app:
|
||||
lastfm_api_key = app.config.get('LASTFM_API_KEY')
|
||||
|
||||
if not lastfm_api_key:
|
||||
return result
|
||||
|
||||
# Query Last.fm API
|
||||
url = 'http://ws.audioscrobbler.com/2.0/'
|
||||
params = {
|
||||
'method': 'track.getInfo',
|
||||
'api_key': lastfm_api_key,
|
||||
'artist': artist_name,
|
||||
'track': track_title,
|
||||
'format': 'json'
|
||||
}
|
||||
|
||||
response = requests.get(url=url, params=params)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
|
||||
# Extract genre from top tags
|
||||
if (data.get('track') and
|
||||
data['track'].get('toptags') and
|
||||
data['track']['toptags'].get('tag')):
|
||||
tags = data['track']['toptags']['tag']
|
||||
if tags and len(tags) > 0:
|
||||
result["genre"] = tags[0]['name']
|
||||
except Exception as e:
|
||||
if app:
|
||||
app.logger.error(f"Last.fm API error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def get_openai_data(artist_name, track_title, app=None):
|
||||
"""Query OpenAI API for additional metadata verification"""
|
||||
result = {}
|
||||
|
||||
if not artist_name or not track_title:
|
||||
return result
|
||||
|
||||
try:
|
||||
# Get OpenAI API details from app context
|
||||
if not app:
|
||||
return result
|
||||
|
||||
openai_api_key = app.config.get('OPENAI_API_KEY')
|
||||
openai_url = app.config.get('OPENAI_URL')
|
||||
openai_model = app.config.get('OPENAI_MODEL')
|
||||
|
||||
if not openai_api_key or not openai_model:
|
||||
return result
|
||||
|
||||
# Configure OpenAI API key
|
||||
openai.api_key = openai_api_key
|
||||
|
||||
# Create prompt
|
||||
prompt = f"Provide the genre and release year for the song '{track_title}' by {artist_name}. Return the data as a JSON object with keys 'genre' and 'year'. If the information is not available, return null for the corresponding key."
|
||||
|
||||
# Log the query
|
||||
app.logger.info(f"ChatGPT Query: {prompt}")
|
||||
|
||||
content = None
|
||||
|
||||
# Check which version of the OpenAI library is being used
|
||||
if hasattr(openai, 'chat') and hasattr(openai.chat, 'completions'):
|
||||
# New OpenAI API client (>= 1.0.0)
|
||||
if openai_url:
|
||||
openai.base_url = openai_url
|
||||
|
||||
# Call OpenAI API with new client
|
||||
try:
|
||||
response = openai.chat.completions.create(
|
||||
model=openai_model,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
response_format={"type": "json_object"}
|
||||
)
|
||||
|
||||
if response and hasattr(response, 'choices') and response.choices:
|
||||
content = response.choices[0].message.content
|
||||
app.logger.info(f"ChatGPT Response: {content}")
|
||||
except Exception as e:
|
||||
app.logger.error(f"OpenAI chat completions error: {e}")
|
||||
# Try falling back to completion API if available
|
||||
try:
|
||||
if hasattr(openai, 'Completion'):
|
||||
response = openai.Completion.create(
|
||||
engine=openai_model,
|
||||
prompt=prompt,
|
||||
max_tokens=200,
|
||||
temperature=0.2,
|
||||
top_p=1.0
|
||||
)
|
||||
if response and hasattr(response, 'choices') and len(response.choices) > 0:
|
||||
content = response.choices[0].text.strip()
|
||||
app.logger.info(f"OpenAI Completion Response: {content}")
|
||||
except Exception as inner_e:
|
||||
app.logger.error(f"OpenAI completion fallback error: {inner_e}")
|
||||
else:
|
||||
# Old OpenAI API client (< 1.0.0)
|
||||
if openai_url:
|
||||
openai.api_base = openai_url # Different attribute in old client
|
||||
|
||||
# Call OpenAI API with old client
|
||||
try:
|
||||
response = openai.Completion.create(
|
||||
engine=openai_model, # In old API, it's 'engine' instead of 'model'
|
||||
prompt=prompt,
|
||||
max_tokens=200,
|
||||
temperature=0.2,
|
||||
top_p=1.0
|
||||
)
|
||||
|
||||
if response and hasattr(response, 'choices') and len(response.choices) > 0:
|
||||
content = response.choices[0].text.strip()
|
||||
app.logger.info(f"ChatGPT Response: {content}")
|
||||
except Exception as e:
|
||||
app.logger.error(f"OpenAI completion error: {e}")
|
||||
|
||||
# Process the response content
|
||||
if content:
|
||||
try:
|
||||
# Try to extract JSON from the content (handle cases where there might be extra text)
|
||||
import re
|
||||
json_match = re.search(r'(\{.*\})', content, re.DOTALL)
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
data = json.loads(json_str)
|
||||
else:
|
||||
data = json.loads(content)
|
||||
|
||||
if data.get("genre"):
|
||||
result["genre"] = data["genre"]
|
||||
if data.get("year"):
|
||||
# Always convert year to string
|
||||
result["year"] = str(data["year"])
|
||||
|
||||
# If we got valid data, return it
|
||||
if "genre" in result or "year" in result:
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error parsing OpenAI response: {e}")
|
||||
app.logger.error(f"Raw response content: {content}")
|
||||
except AttributeError as e:
|
||||
app.logger.error(f"OpenAI module error: {e}")
|
||||
except Exception as e:
|
||||
if app:
|
||||
app.logger.error(f"OpenAI API error: {e}")
|
||||
|
||||
return result
|
||||
|
||||
def get_acrcloud_data(isrc, app=None):
|
||||
"""Query ACRCloud API using ISRC"""
|
||||
result = {}
|
||||
|
||||
if not app:
|
||||
return result
|
||||
|
||||
try:
|
||||
# Get ACRCloud API key from app config
|
||||
acrcloud_token = app.config.get('ACRCLOUD_TOKEN')
|
||||
if not acrcloud_token:
|
||||
logger = app.logger if app else None
|
||||
if logger:
|
||||
logger.warning("ACRCloud token not found in app config.")
|
||||
return result
|
||||
|
||||
# Query ACRCloud API for track metadata
|
||||
url = "https://eu-api-v2.acrcloud.com/api/external-metadata/tracks"
|
||||
headers = {
|
||||
'Authorization': f'Bearer {acrcloud_token}'
|
||||
}
|
||||
params = {
|
||||
'isrc': isrc,
|
||||
'platforms': 'spotify,deezer,youtube,applemusic',
|
||||
'include_works': 1 # Include additional work metadata
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
if response.status_code != 200:
|
||||
app.logger.warning(f"ACRCloud API error: {response.status_code} - {response.text}")
|
||||
return result
|
||||
|
||||
data = response.json()
|
||||
if not data or not data.get('data') or not len(data['data']) > 0:
|
||||
return result
|
||||
|
||||
track_data = data['data'][0]
|
||||
logger = app.logger if app else None
|
||||
if logger:
|
||||
logger.info(f"ACRCloud API response: {json.dumps(data, default=str)}")
|
||||
# Extract basic metadata
|
||||
if track_data.get('name'):
|
||||
result['title'] = track_data['name']
|
||||
|
||||
if track_data.get('artists') and len(track_data['artists']) > 0:
|
||||
artist_names = [artist['name'] for artist in track_data['artists'] if 'name' in artist]
|
||||
result['artist_name'] = ', '.join(artist_names)
|
||||
|
||||
if track_data.get('release_date'):
|
||||
result['year'] = track_data['release_date'][:4] # Extract year
|
||||
|
||||
if track_data.get('genres'):
|
||||
result['genre'] = track_data['genres']
|
||||
|
||||
# Extract album cover if available
|
||||
if track_data.get('album') and track_data['album'].get('cover'):
|
||||
result['album_cover'] = track_data['album']['cover']
|
||||
|
||||
# Also get covers from specific sizes if available
|
||||
if track_data['album'].get('covers'):
|
||||
covers = track_data['album']['covers']
|
||||
if covers.get('large'):
|
||||
result['album_cover_large'] = covers['large']
|
||||
if covers.get('medium'):
|
||||
result['album_cover_medium'] = covers['medium']
|
||||
|
||||
# Get external metadata from platforms
|
||||
ext_meta = track_data.get('external_metadata', {})
|
||||
|
||||
# Get Spotify metadata
|
||||
if 'spotify' in ext_meta and ext_meta['spotify'] and len(ext_meta['spotify']) > 0:
|
||||
spotify_data = ext_meta['spotify'][0]
|
||||
if spotify_data.get('id'):
|
||||
result['spotify_id'] = spotify_data['id']
|
||||
if spotify_data.get('preview'):
|
||||
result['spotify_preview_url'] = spotify_data['preview']
|
||||
if spotify_data.get('album') and spotify_data['album'].get('cover'):
|
||||
result['spotify_cover_url'] = spotify_data['album']['cover']
|
||||
|
||||
# Get Deezer metadata
|
||||
if 'deezer' in ext_meta and ext_meta['deezer'] and len(ext_meta['deezer']) > 0:
|
||||
deezer_data = ext_meta['deezer'][0]
|
||||
if deezer_data.get('id'):
|
||||
result['deezer_id'] = deezer_data['id']
|
||||
# Deezer preview URL might come from additional API call
|
||||
if deezer_data.get('album') and deezer_data['album'].get('cover'):
|
||||
result['deezer_cover_url'] = deezer_data['album']['cover']
|
||||
|
||||
# Get Apple Music metadata
|
||||
if 'applemusic' in ext_meta and ext_meta['applemusic'] and len(ext_meta['applemusic']) > 0:
|
||||
apple_data = ext_meta['applemusic'][0]
|
||||
if apple_data.get('preview'):
|
||||
result['apple_preview_url'] = apple_data['preview']
|
||||
if apple_data.get('album') and apple_data['album'].get('cover'):
|
||||
result['apple_cover_url'] = apple_data['album']['cover']
|
||||
|
||||
# Get YouTube metadata
|
||||
if 'youtube' in ext_meta and ext_meta['youtube'] and len(ext_meta['youtube']) > 0:
|
||||
youtube_data = ext_meta['youtube'][0]
|
||||
if youtube_data.get('id'):
|
||||
youtube_id = youtube_data['id']
|
||||
result['youtube_id'] = youtube_id
|
||||
# Construct a YouTube Music playback URL
|
||||
result['youtube_preview_url'] = f"https://music.youtube.com/watch?v={youtube_id}"
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
if app:
|
||||
app.logger.error(f"ACRCloud API error: {e}")
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user