840 lines
35 KiB
Python
840 lines
35 KiB
Python
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
|