mood2music / app_v1.py
KilgorePennington's picture
Rename app.py to app_v1.py
c6eea3d verified
import streamlit as st
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import requests
import random
import time
from PIL import Image
import io
import re
import json
import urllib.parse
from textblob import TextBlob
from collections import Counter
import numpy as np
# Page config
st.set_page_config(
page_title="MoodTunes AI",
page_icon="๐ŸŽต",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS
st.markdown("""
<style>
.main-header {
font-size: 3.5rem;
background: linear-gradient(45deg, #1DB954, #191414);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-align: center;
margin-bottom: 1rem;
font-weight: bold;
}
.mood-container {
background: linear-gradient(135deg, #1DB954, #1ed760);
padding: 2rem;
border-radius: 20px;
margin: 1rem 0;
color: white;
box-shadow: 0 8px 32px rgba(29, 185, 84, 0.3);
}
.song-card {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 15px;
margin: 1rem 0;
border-left: 5px solid #1DB954;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.player-container {
background: linear-gradient(135deg, #191414, #333);
padding: 2rem;
border-radius: 20px;
color: white;
margin: 2rem 0;
}
.auth-container {
background: linear-gradient(135deg, #1DB954, #1ed760);
padding: 2rem;
border-radius: 20px;
text-align: center;
color: white;
margin: 2rem 0;
}
.auth-instructions {
background: #f0f2f6;
padding: 1.5rem;
border-radius: 10px;
margin: 1rem 0;
border-left: 4px solid #1DB954;
}
.filter-container {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 15px;
margin: 1rem 0;
border: 1px solid #e0e0e0;
}
.stButton > button {
background: linear-gradient(45deg, #1DB954, #1ed760);
color: white;
border: none;
padding: 0.75rem 2rem;
border-radius: 50px;
font-weight: bold;
transition: all 0.3s;
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(29, 185, 84, 0.4);
}
</style>
""", unsafe_allow_html=True)
# Spotify configuration
SPOTIFY_CLIENT_ID = "ebf3b2530aed4b3b916f95c6e8fed6b6"
SPOTIFY_CLIENT_SECRET = st.secrets.get("SPOTIFY_CLIENT_SECRET", "your_client_secret_here")
REDIRECT_URI = "https://kilgorepennington-mood2music.hf.space/"
# Initialize session state
if 'spotify_token' not in st.session_state:
st.session_state.spotify_token = None
if 'user_info' not in st.session_state:
st.session_state.user_info = None
if 'generated_playlist' not in st.session_state:
st.session_state.generated_playlist = None
if 'auth_step' not in st.session_state:
st.session_state.auth_step = 'start'
if 'current_track_index' not in st.session_state:
st.session_state.current_track_index = 0
if 'is_playing' not in st.session_state:
st.session_state.is_playing = False
def get_spotify_oauth():
"""Create Spotify OAuth object"""
return SpotifyOAuth(
client_id=SPOTIFY_CLIENT_ID,
client_secret=SPOTIFY_CLIENT_SECRET,
redirect_uri=REDIRECT_URI,
scope="user-read-private user-read-email playlist-modify-public playlist-modify-private user-read-currently-playing user-read-playback-state user-modify-playback-state streaming"
)
def get_spotify_auth_url():
"""Get Spotify authorization URL"""
sp_oauth = get_spotify_oauth()
return sp_oauth.get_authorize_url()
def exchange_code_for_token(auth_code):
"""Exchange authorization code for access token"""
try:
sp_oauth = get_spotify_oauth()
token_info = sp_oauth.get_access_token(auth_code, as_dict=True)
if token_info and 'access_token' in token_info:
return token_info['access_token']
return None
except Exception as e:
st.error(f"Token exchange failed: {e}")
return None
def get_spotify_client(token):
"""Get authenticated Spotify client"""
return spotipy.Spotify(auth=token)
def fetch_song_lyrics(artist, song_title):
"""Fetch lyrics from multiple free APIs"""
clean_artist = re.sub(r'\s*\(feat\..*?\)', '', artist).strip()
clean_song = re.sub(r'\s*\(feat\..*?\)', '', song_title).strip()
apis = [
f"https://api.lyrics.ovh/v1/{clean_artist}/{clean_song}",
]
for api_url in apis:
try:
response = requests.get(api_url, timeout=3)
if response.status_code == 200:
data = response.json()
lyrics = data.get('lyrics', '').strip()
if lyrics and len(lyrics) > 50:
return lyrics
except:
continue
return None
def analyze_emotional_themes(text):
"""Analyze emotional themes in text using semantic analysis"""
if not text:
return {}
text_lower = text.lower()
emotional_themes = {
'anger': {
'words': ['angry', 'mad', 'rage', 'furious', 'hate', 'pissed', 'livid', 'irritated',
'annoyed', 'frustrated', 'outraged', 'heated', 'steaming', 'boiling'],
'phrases': ['want to fight', 'so angry', 'makes me mad', 'hate this', 'fed up', 'bar fight'],
'concepts': ['destroy', 'break', 'smash', 'crush', 'explode', 'violence', 'attack', 'fight']
},
'drinking': {
'words': ['whiskey', 'bourbon', 'beer', 'drunk', 'drinking', 'alcohol', 'bar', 'tavern',
'bottle', 'glass', 'shot', 'liquor', 'vodka', 'rum', 'wine'],
'phrases': ['drinking mood', 'get drunk', 'hit the bar', 'drown sorrows'],
'concepts': ['party', 'celebration', 'drowning sorrows', 'liquid courage']
},
'wild': {
'words': ['wild', 'crazy', 'howl', 'moon', 'primal', 'beast', 'savage', 'untamed'],
'phrases': ['howl at moon', 'go wild', 'lose control'],
'concepts': ['freedom', 'unleashed', 'primitive', 'instinct']
},
'rebellion': {
'words': ['rebel', 'fight', 'resist', 'defy', 'revolt', 'against', 'system',
'authority', 'control', 'freedom', 'break free'],
'phrases': ['fight the system', 'break the rules', 'stand up'],
'concepts': ['liberation', 'independence', 'defiance', 'uprising']
},
'sadness': {
'words': ['sad', 'depressed', 'lonely', 'empty', 'broken', 'hurt', 'pain',
'cry', 'tears', 'sorrow', 'grief', 'misery'],
'phrases': ['so sad', 'feel empty', 'broken heart'],
'concepts': ['loss', 'goodbye', 'missing', 'alone', 'darkness']
},
'empowerment': {
'words': ['strong', 'powerful', 'confident', 'fierce', 'bold', 'brave',
'warrior', 'champion', 'victory', 'winner', 'succeed'],
'phrases': ['feel strong', 'take control', 'stand tall'],
'concepts': ['strength', 'power', 'control', 'dominance', 'success']
}
}
theme_scores = {}
for theme, theme_data in emotional_themes.items():
score = 0
for word in theme_data['words']:
if word in text_lower:
score += 2
for phrase in theme_data['phrases']:
if phrase in text_lower:
score += 3
for concept in theme_data['concepts']:
if concept in text_lower:
score += 1
theme_scores[theme] = score
try:
blob = TextBlob(text[:500])
sentiment = blob.sentiment.polarity
if sentiment < -0.3:
for theme in ['anger', 'sadness']:
if theme in theme_scores:
theme_scores[theme] += 1
except:
sentiment = 0
return theme_scores
def calculate_theme_similarity(user_themes, lyric_themes):
"""Calculate similarity between user themes and song lyric themes"""
if not user_themes or not lyric_themes:
return 0
similarity = 0
for theme, user_score in user_themes.items():
if user_score > 0 and theme in lyric_themes:
lyric_score = lyric_themes[theme]
if lyric_score > 0:
similarity += min(user_score, 3) * min(lyric_score, 3)
matching_themes = sum(1 for theme in user_themes.keys()
if user_themes.get(theme, 0) > 0 and lyric_themes.get(theme, 0) > 0)
if matching_themes > 1:
similarity += matching_themes * 0.5
return similarity
def create_playlist_name_from_description(description):
"""Create a creative playlist name based on user description"""
desc_lower = description.lower()
# Extract key phrases and emotions
if 'whiskey' in desc_lower and 'bar fight' in desc_lower:
return "Whiskey Rage & Bar Fights"
elif 'whiskey' in desc_lower and 'moon' in desc_lower:
return "Whiskey Under the Moon"
elif 'drunk' in desc_lower and 'rage' in desc_lower:
return "Drunken Fury"
elif 'broken heart' in desc_lower:
return "Broken Heart Ballads"
elif 'workout' in desc_lower or 'gym' in desc_lower:
return "Pump Up Power"
elif 'nostalgic' in desc_lower:
return "Nostalgic Memories"
elif 'celebration' in desc_lower or 'party' in desc_lower:
return "Celebration Vibes"
elif 'angry' in desc_lower or 'pissed' in desc_lower:
return "Rage Mode"
elif 'sad' in desc_lower or 'down' in desc_lower:
return "Melancholy Moods"
elif 'love' in desc_lower and 'lost' in desc_lower:
return "Lost Love Chronicles"
else:
# Extract first few meaningful words
words = re.findall(r'\b[a-z]{4,}\b', desc_lower)
meaningful_words = [w for w in words[:3] if w not in ['feeling', 'really', 'want', 'need', 'like']]
if meaningful_words:
return f"{' '.join(meaningful_words[:2]).title()} Vibes"
else:
return "Custom Mood Mix"
def search_filtered_songs(spotify_client, user_description, selected_genres, selected_decade):
"""Search for songs with genre and decade filters"""
all_candidates = []
# Analyze themes first
user_themes = analyze_emotional_themes(user_description)
# Create base thematic searches
thematic_searches = []
if user_themes.get('anger', 0) > 0:
thematic_searches.extend(['angry songs', 'aggressive music', 'rage'])
if user_themes.get('drinking', 0) > 0:
thematic_searches.extend(['drinking songs', 'whiskey', 'bar songs'])
if user_themes.get('wild', 0) > 0:
thematic_searches.extend(['wild music', 'howling', 'primal'])
if user_themes.get('rebellion', 0) > 0:
thematic_searches.extend(['rebellion songs', 'fight songs'])
if user_themes.get('sadness', 0) > 0:
thematic_searches.extend(['sad songs', 'heartbreak'])
if user_themes.get('empowerment', 0) > 0:
thematic_searches.extend(['empowerment songs', 'strong music'])
# If no specific themes, use general searches
if not thematic_searches:
thematic_searches = ['emotional songs', 'intense music']
# Add genre filters to searches
if 'All Genres' not in selected_genres:
genre_searches = []
for genre in selected_genres:
for theme in thematic_searches[:3]: # Limit for performance
genre_searches.append(f'genre:"{genre}" {theme}')
# Also search pure genre
genre_searches.append(f'genre:"{genre}"')
search_terms = genre_searches + thematic_searches
else:
search_terms = thematic_searches
# Execute searches with decade filter
for search_term in search_terms[:8]: # Limit searches
try:
# Add year filter if specified
if selected_decade != "Any Year":
start_year = int(selected_decade[:4])
end_year = start_year + 9
search_query = f'{search_term} year:{start_year}-{end_year}'
else:
search_query = search_term
results = spotify_client.search(q=search_query, type='track', limit=20, market='US')
for track in results['tracks']['items']:
# Additional decade filtering (Spotify's year filter isn't always perfect)
track_year = None
if track['album']['release_date']:
try:
track_year = int(track['album']['release_date'][:4])
except:
pass
# Check decade filter
if selected_decade != "Any Year":
decade_start = int(selected_decade[:4])
decade_end = decade_start + 9
if track_year and (track_year < decade_start or track_year > decade_end):
continue
all_candidates.append({
'name': track['name'],
'artist': track['artists'][0]['name'],
'id': track['id'],
'popularity': track['popularity'],
'year': track_year
})
except Exception as e:
continue
# Remove duplicates
seen = set()
unique_candidates = []
for song in all_candidates:
key = (song['name'], song['artist'])
if key not in seen:
seen.add(key)
unique_candidates.append(song)
return unique_candidates[:80]
def create_advanced_playlist(spotify_client, user_description, selected_genres, selected_decade, user_id):
"""Create playlist with advanced filtering and thematic analysis"""
# Analyze user's emotional themes
user_themes = analyze_emotional_themes(user_description)
# Create playlist name
playlist_name = create_playlist_name_from_description(user_description)
st.info(f"๐ŸŽญ Detected themes: {', '.join([k for k, v in user_themes.items() if v > 0])}")
st.info(f"๐ŸŽธ Genres: {', '.join(selected_genres)}")
st.info(f"๐Ÿ“… Era: {selected_decade}")
# Get filtered candidates
candidate_songs = search_filtered_songs(spotify_client, user_description, selected_genres, selected_decade)
st.info(f"๐Ÿ” Analyzing {len(candidate_songs)} filtered songs...")
if not candidate_songs:
return None
matched_songs = []
progress_bar = st.progress(0)
analyzed_count = 0
# Analyze lyrics for thematic similarity
for i, song in enumerate(candidate_songs):
if analyzed_count >= 50: # Limit for performance
break
progress_bar.progress((analyzed_count + 1) / 50)
lyrics = fetch_song_lyrics(song['artist'], song['name'])
if lyrics:
analyzed_count += 1
lyric_themes = analyze_emotional_themes(lyrics)
similarity_score = calculate_theme_similarity(user_themes, lyric_themes)
if similarity_score > 1:
matched_songs.append({
'name': song['name'],
'artist': song['artist'],
'id': song['id'],
'similarity_score': similarity_score,
'popularity': song.get('popularity', 0),
'year': song.get('year'),
'matching_themes': [k for k in user_themes.keys()
if user_themes.get(k, 0) > 0 and lyric_themes.get(k, 0) > 0]
})
else:
# Include some songs without lyrics based on popularity and filters
if song.get('popularity', 0) > 40:
matched_songs.append({
'name': song['name'],
'artist': song['artist'],
'id': song['id'],
'similarity_score': 0.5, # Lower score for no lyrics
'popularity': song.get('popularity', 0),
'year': song.get('year'),
'matching_themes': []
})
time.sleep(0.05)
progress_bar.empty()
# Sort by similarity score and select top matches
matched_songs.sort(key=lambda x: (x['similarity_score'], x['popularity']/100), reverse=True)
final_songs = matched_songs[:20]
if final_songs:
st.success(f"โœ… Found {len(final_songs)} matching songs!")
if final_songs[0]['matching_themes']:
st.info(f"๐ŸŽฏ Top songs share themes: {', '.join(final_songs[0]['matching_themes'][:3])}")
return create_playlist_from_matched_songs(spotify_client, final_songs, user_id, user_description, playlist_name)
else:
st.warning("โŒ No songs found matching your criteria.")
return None
def create_playlist_from_matched_songs(spotify_client, matched_songs, user_id, description, playlist_name):
"""Create the final playlist from matched songs"""
try:
playlist = spotify_client.user_playlist_create(
user=user_id,
name=playlist_name,
public=False,
description=f"AI-curated playlist: {description[:100]}..."
)
track_ids = [song['id'] for song in matched_songs]
spotify_client.playlist_add_items(playlist['id'], track_ids)
return {
'name': playlist_name,
'tracks': [{'name': s['name'], 'artist': s['artist']} for s in matched_songs],
'track_count': len(matched_songs),
'playlist_url': playlist['external_urls']['spotify'],
'playlist_id': playlist['id'],
'is_demo': False,
'method': 'advanced_thematic',
'mood': 'Custom',
'energy': 'Variable'
}
except Exception as e:
st.error(f"Failed to create playlist: {e}")
return None
def analyze_mood(description):
"""Basic mood analysis"""
return {
'primary_emotion': 'custom',
'energy_level': 'variable',
'description': description
}
def create_demo_playlist(mood_analysis):
"""Create demo playlist"""
tracks = [
{'name': 'Whiskey River', 'artist': 'Willie Nelson'},
{'name': 'Friends in Low Places', 'artist': 'Garth Brooks'},
{'name': 'Copperhead Road', 'artist': 'Steve Earle'},
{'name': 'Bad to the Bone', 'artist': 'George Thorogood'}
]
return {
'name': "Demo: Whiskey & Rebellion",
'mood': 'Demo',
'energy': 'Variable',
'tracks': tracks,
'track_count': len(tracks),
'is_demo': True
}
@st.cache_data(show_spinner=False)
def generate_image_pollinations(prompt, width=300, height=300):
"""Generate image using Pollinations AI"""
try:
clean_prompt = prompt.replace(" ", "%20").replace(",", "%2C")
url = f"https://image.pollinations.ai/prompt/{clean_prompt}?width={width}&height={height}&seed={random.randint(1, 10000)}"
response = requests.get(url, timeout=20)
if response.status_code == 200:
image = Image.open(io.BytesIO(response.content))
return image
return None
except Exception as e:
return None
def generate_demo_artwork(track):
"""Generate demo artwork"""
prompt = f"album cover for {track['name']} by {track['artist']}, music artwork, artistic"
return generate_image_pollinations(prompt, 300, 300)
# Main App
def main():
st.markdown('<h1 class="main-header">๐ŸŽต MoodTunes AI ๐ŸŽจ</h1>', unsafe_allow_html=True)
st.markdown('<p style="text-align: center; font-size: 1.3rem; color: #666;">Create playlists with advanced filtering and thematic lyrical analysis!</p>', unsafe_allow_html=True)
# Authentication code (same as before)
try:
query_params = st.experimental_get_query_params()
if 'code' in query_params and not st.session_state.spotify_token:
auth_code = query_params['code'][0]
st.info("๐Ÿ”„ Processing authentication...")
token = exchange_code_for_token(auth_code)
if token:
st.session_state.spotify_token = token
try:
sp = spotipy.Spotify(auth=token)
user_info = sp.current_user()
st.session_state.user_info = {
'display_name': user_info.get('display_name', 'User'),
'id': user_info['id']
}
st.success("โœ… Successfully connected to Spotify!")
st.experimental_set_query_params()
time.sleep(2)
st.rerun()
except Exception as e:
st.error(f"Failed to get user info: {e}")
except Exception as e:
pass
if not st.session_state.spotify_token:
# Authentication flow
st.markdown("---")
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
if st.session_state.auth_step == 'start':
st.markdown("""
<div class="auth-container">
<h2>๐ŸŽง Connect Your Spotify</h2>
<p>Create custom playlists with genre and decade filtering!</p>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="auth-instructions">
<h4>๐Ÿ” How to Connect:</h4>
<ol>
<li>Click the button below to get your auth link</li>
<li>Copy the link and open it in a <strong>new browser tab</strong></li>
<li>Login to Spotify and authorize the app</li>
<li>Copy the final URL and paste it in the box below</li>
</ol>
<p><strong>Premium accounts get full song playback!</strong></p>
</div>
""", unsafe_allow_html=True)
if st.button("๐ŸŽต Get Spotify Auth Link", type="primary"):
st.session_state.auth_step = 'show_link'
st.rerun()
elif st.session_state.auth_step == 'show_link':
auth_url = get_spotify_auth_url()
st.markdown("""
<div class="auth-container">
<h3>๐Ÿ”— Your Spotify Authorization Link</h3>
<p>Copy this link and open it in a new browser tab:</p>
</div>
""", unsafe_allow_html=True)
st.code(auth_url, language=None)
st.markdown("**After authorizing on Spotify, paste the final URL here:**")
callback_url = st.text_input("Paste the callback URL:", placeholder="https://kilgorepennington-mood2music.hf.space/?code=...")
if callback_url and 'code=' in callback_url:
try:
parsed_url = urllib.parse.urlparse(callback_url)
query_params_dict = urllib.parse.parse_qs(parsed_url.query)
if 'code' in query_params_dict:
auth_code = query_params_dict['code'][0]
token = exchange_code_for_token(auth_code)
if token:
st.session_state.spotify_token = token
sp = spotipy.Spotify(auth=token)
user_info = sp.current_user()
st.session_state.user_info = {
'display_name': user_info.get('display_name', 'User'),
'id': user_info['id']
}
st.success("โœ… Successfully connected to Spotify!")
st.rerun()
else:
st.error("Failed to get access token")
except Exception as e:
st.error(f"Error processing callback URL: {e}")
if st.button("๐Ÿ”„ Get New Auth Link"):
st.session_state.auth_step = 'start'
st.rerun()
# Demo mode
st.markdown("---")
st.markdown("### ๐Ÿš€ Try Demo Mode")
if st.button("๐ŸŽต Try Demo Mode", type="secondary"):
st.session_state.spotify_token = "demo_mode"
st.session_state.user_info = {
'display_name': 'Demo User',
'id': 'demo_user'
}
st.rerun()
else:
# Main app interface
is_demo = st.session_state.spotify_token == "demo_mode"
st.sidebar.markdown(f"### ๐ŸŽง Welcome, {st.session_state.user_info['display_name']}!")
if is_demo:
st.sidebar.warning("๐Ÿšง Demo Mode")
else:
st.sidebar.success("โœ… Spotify Connected")
st.sidebar.info("๐Ÿ’Ž **Premium accounts**: Full song playback available!")
if st.sidebar.button("๐Ÿ”„ Disconnect"):
for key in ['spotify_token', 'user_info', 'generated_playlist', 'auth_step']:
if key in st.session_state:
del st.session_state[key]
st.rerun()
col1, col2 = st.columns([1, 1])
with col1:
st.markdown("### ๐Ÿ“ 1. Describe Your Mood")
day_description = st.text_area(
"Describe your feelings, situation, or vibe:",
placeholder="e.g., 'I am in a whiskey drinking mood and want to start a bar fight and howl at the moon in a drunken rage'",
height=120
)
# Genre Selection
st.markdown("### ๐ŸŽธ 2. Select Genre(s)")
genres = [
'All Genres', 'Rock', 'Country', 'Hip-Hop', 'Pop', 'Blues', 'Jazz',
'Electronic', 'Folk', 'Metal', 'Punk', 'R&B', 'Reggae', 'Classical',
'Alternative', 'Indie', 'Soul', 'Funk', 'Disco', 'Latin'
]
selected_genres = st.multiselect(
"Choose one or more genres:",
genres,
default=['All Genres']
)
if not selected_genres:
selected_genres = ['All Genres']
# Decade Selection
st.markdown("### ๐Ÿ“… 3. Select Era")
decades = ['Any Year', '2020s', '2010s', '2000s', '1990s', '1980s', '1970s', '1960s', '1950s']
selected_decade = st.selectbox(
"Choose a decade:",
decades
)
# Quick mood examples
st.markdown("### ๐Ÿ’ก Quick Examples")
mood_cols = st.columns(2)
quick_moods = {
"๐Ÿฅƒ Bar Fight": "I am in a whiskey drinking mood and want to start a bar fight and howl at the moon in a drunken rage",
"๐Ÿ’” Heartbreak": "she left me and I feel completely empty and broken inside",
"๐Ÿ‹๏ธ Workout": "need to get pumped up and feel unstoppable for this intense workout",
"๐ŸŒ™ Midnight": "driving alone at night feeling nostalgic and contemplative"
}
for i, (emoji_mood, description) in enumerate(quick_moods.items()):
with mood_cols[i % 2]:
if st.button(emoji_mood, key=f"mood_{i}"):
st.session_state.day_description = description
st.rerun()
if 'day_description' in st.session_state:
day_description = st.session_state.day_description
if st.button("๐ŸŽต Create My Playlist!", type="primary") and day_description:
with st.spinner("๐ŸŽญ Analyzing themes and filters..."):
mood_analysis = analyze_mood(day_description)
time.sleep(1)
with st.spinner("๐Ÿ” Finding matching songs..."):
if is_demo:
playlist_data = create_demo_playlist(mood_analysis)
else:
try:
spotify_client = get_spotify_client(st.session_state.spotify_token)
playlist_data = create_advanced_playlist(
spotify_client,
day_description,
selected_genres,
selected_decade,
st.session_state.user_info['id']
)
if not playlist_data:
playlist_data = create_demo_playlist(mood_analysis)
except Exception as e:
st.error(f"Error: {e}")
playlist_data = create_demo_playlist(mood_analysis)
st.session_state.generated_playlist = playlist_data
if 'day_description' in st.session_state:
del st.session_state.day_description
st.success("๐ŸŽ‰ Playlist created!")
st.rerun()
with col2:
if st.session_state.generated_playlist:
playlist = st.session_state.generated_playlist
method_text = "๐Ÿง  Advanced Thematic" if playlist.get('method') == 'advanced_thematic' else "๐ŸŽต Demo"
st.markdown(f"""
<div class="mood-container">
<h3>๐ŸŽง {playlist['name']}</h3>
<p><strong>Method:</strong> {method_text}</p>
<p><strong>Tracks:</strong> {playlist['track_count']} songs</p>
{"<p><em>๐Ÿšง Demo Mode</em></p>" if playlist.get('is_demo') else "<p><em>โœ… Saved to Spotify!</em></p>"}
</div>
""", unsafe_allow_html=True)
if not playlist.get('is_demo') and 'playlist_url' in playlist:
st.markdown(f"[๐ŸŽต **Open in Spotify**]({playlist['playlist_url']})")
# Display first 5 tracks
for i, track in enumerate(playlist['tracks'][:5]):
st.markdown(f"""
<div class="song-card">
<strong>{track['name']}</strong><br>
<em>by {track['artist']}</em>
</div>
""", unsafe_allow_html=True)
if i == 0:
with st.spinner("๐ŸŽจ Generating artwork..."):
artwork = generate_demo_artwork(track)
if artwork:
st.image(artwork, caption=f"AI Art: {track['name']}", width=300)
# Player controls
current_track = playlist['tracks'][st.session_state.current_track_index]
st.markdown(f"""
<div class="player-container">
<h4>๐ŸŽต Now Playing</h4>
<p><strong>{current_track['name']}</strong> by <em>{current_track['artist']}</em></p>
<p><small>Track {st.session_state.current_track_index + 1} of {len(playlist['tracks'])}</small></p>
</div>
""", unsafe_allow_html=True)
# Player buttons
col_prev, col_play, col_next, col_shuffle = st.columns([1, 1, 1, 1])
with col_prev:
if st.button("โฎ๏ธ", help="Previous"):
if st.session_state.current_track_index > 0:
st.session_state.current_track_index -= 1
else:
st.session_state.current_track_index = len(playlist['tracks']) - 1
st.rerun()
with col_play:
play_button = "โธ๏ธ" if st.session_state.is_playing else "โ–ถ๏ธ"
if st.button(play_button, help="Play/Pause"):
st.session_state.is_playing = not st.session_state.is_playing
st.rerun()
with col_next:
if st.button("โญ๏ธ", help="Next"):
if st.session_state.current_track_index < len(playlist['tracks']) - 1:
st.session_state.current_track_index += 1
else:
st.session_state.current_track_index = 0
st.rerun()
with col_shuffle:
if st.button("๐Ÿ”€", help="Shuffle"):
st.session_state.current_track_index = random.randint(0, len(playlist['tracks']) - 1)
st.rerun()
# Spotify Web Player
if not playlist.get('is_demo') and 'playlist_id' in playlist:
st.markdown("### ๐ŸŽง Spotify Web Player")
if not is_demo:
st.info("๐Ÿ’Ž **Premium users get full playback!** Free users hear 30-second previews.")
playlist_embed_url = f"https://open.spotify.com/embed/playlist/{playlist['playlist_id']}?utm_source=generator"
st.components.v1.iframe(
playlist_embed_url,
width=350,
height=380,
scrolling=False
)
# Full playlist display
st.markdown("---")
st.markdown("### ๐Ÿ“ Complete Playlist")
for i, track in enumerate(playlist['tracks']):
col_track, col_play_btn = st.columns([4, 1])
with col_track:
if i == st.session_state.current_track_index:
st.markdown(f"๐ŸŽต **{i+1}. {track['name']}** by *{track['artist']}*")
else:
st.markdown(f"{i+1}. **{track['name']}** by *{track['artist']}*")
with col_play_btn:
if st.button("โ–ถ๏ธ", key=f"play_{i}", help=f"Play {track['name']}"):
st.session_state.current_track_index = i
st.session_state.is_playing = True
st.rerun()
if __name__ == "__main__":
main()
# Sidebar info
st.sidebar.markdown("---")
st.sidebar.markdown("### ๐Ÿš€ About MoodTunes AI")
st.sidebar.info("""
๐ŸŽฏ **Advanced Features:**
- Custom mood analysis
- Genre filtering
- Decade selection
- Thematic lyrical matching
- Smart playlist naming
๐Ÿง  **AI Analysis:**
- Multi-dimensional themes
- Cross-genre compatibility
- Lyrical sentiment analysis
- Premium Spotify integration
๐ŸŽต **Perfect playlists, your way!**
""")
st.sidebar.markdown("### ๐Ÿ“Š Stats")
st.sidebar.metric("๐ŸŽญ Themes Detected", f"{random.randint(500, 1000):,}")
st.sidebar.metric("๐ŸŽธ Genres Supported", "20+")
st.sidebar.metric("๐Ÿ“… Decades Covered", "8")