Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
/lib/
/lib64/
parts/
sdist/
var/
Expand Down
21 changes: 1 addition & 20 deletions UI_Front/src/components/PreferenceInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Music, Zap, Clock, Users, ArrowRight, Shuffle, Plus, Waves, Activity, Sparkles, X, PenTool } from "lucide-react";
import { playlistService } from "@/lib/api";
import type { Preferences } from "@/lib/api";
import { generatePlaylistName } from "@/lib/playlist";
import { SonicIdentity } from "./SonicIdentity";

interface PreferenceInputProps {
Expand Down Expand Up @@ -145,26 +146,6 @@ export const PreferenceInput: React.FC<PreferenceInputProps> = ({ onGenerate })
}
};

const generatePlaylistName = (preferences: Preferences): string => {
const moodName = preferences.moods[0] || "Amazing";
const genreName = preferences.genres[0] || "Music";
const names = [
`${moodName} ${genreName} Vibes`,
`My ${moodName} Mix`,
`${genreName} Discovery`,
`${moodName} ${genreName} Journey`,
`Perfect ${moodName} Playlist`,
];
return names[Math.floor(Math.random() * names.length)];
};

const calculateDuration = (trackCount: number): string => {
const totalMinutes = trackCount * 3;
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return hours > 0 ? `${hours}:${minutes.toString().padStart(2, '0')}:00` : `${minutes}:00`;
};

return (
<div className="min-h-screen bg-background p-4 md:p-8">
<div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-8">
Expand Down
22 changes: 0 additions & 22 deletions UI_Front/src/components/SpotifyPlaylistGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,28 +224,6 @@ const SpotifyPlaylistGenerator = () => {
clearPersistedState();
};

const generatePlaylistName = (prefs: Preferences): string => {
const moodName = prefs.moods[0] || "Amazing";
const genreName = prefs.genres[0] || "Music";
const names = [
`${moodName} ${genreName} Vibes`,
`My ${moodName} Mix`,
`${genreName} Discovery`,
`${moodName} ${genreName} Journey`,
`Perfect ${moodName} Playlist`,
];
return names[Math.floor(Math.random() * names.length)];
};

// NEW: Helper to calculate duration
const calculateDuration = (trackCount: number): string => {
const totalMinutes = trackCount * 3; // Estimate 3 minutes per track
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return hours > 0
? `${hours}:${minutes.toString().padStart(2, '0')}:00`
: `${minutes}:00`;
};
// ---- Show loading while checking auth ----
if (isCheckingAuth) {
return (
Expand Down
26 changes: 26 additions & 0 deletions UI_Front/src/lib/playlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
interface NamingPreferences {
moods: string[];
genres: string[];
}

export const generatePlaylistName = (preferences: NamingPreferences): string => {
const moodName = preferences.moods[0] || "Amazing";
const genreName = preferences.genres[0] || "Music";
const names = [
`${moodName} ${genreName} Vibes`,
`My ${moodName} Mix`,
`${genreName} Discovery`,
`${moodName} ${genreName} Journey`,
`Perfect ${moodName} Playlist`,
];
return names[Math.floor(Math.random() * names.length)];
};

export const calculateDuration = (trackCount: number): string => {
const totalMinutes = trackCount * 3; // Estimate 3 minutes per track
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return hours > 0
? `${hours}:${minutes.toString().padStart(2, "0")}:00`
: `${minutes}:00`;
};
19 changes: 5 additions & 14 deletions backend/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import Blueprint, request, session, redirect, jsonify, current_app
from ..config import Config
from ..services.spotify import SpotifyService
from ..utils import get_spotify_service, store_token_session
import secrets
import time
from datetime import datetime
Expand Down Expand Up @@ -68,17 +68,14 @@ def callback():
return jsonify({"error": "No code provided"}), 400

# Exchange Code
spotify = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)
spotify = get_spotify_service()
try:
token_info = spotify.exchange_code_for_token(code, Config.SPOTIFY_REDIRECT_URI)
except Exception as e:
return jsonify({"error": "Token exchange failed", "details": str(e)}), 500

# Store Session
session['access_token'] = token_info.get('access_token')
session['refresh_token'] = token_info.get('refresh_token')
expires_in = token_info.get('expires_in', 3600)
session['expires_at'] = datetime.now().timestamp() + expires_in
store_token_session(token_info)

# Get User Profile immediately to store ID
try:
Expand All @@ -105,16 +102,10 @@ def auth_status():
# Try refresh
if 'refresh_token' in session:
try:
spotify = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)
spotify = get_spotify_service()
new_tokens = spotify.refresh_token(session['refresh_token'])

session['access_token'] = new_tokens.get('access_token')
# Refresh token might not always be returned in a refresh flow, keep old one if so
if 'refresh_token' in new_tokens:
session['refresh_token'] = new_tokens.get('refresh_token')

session['expires_at'] = datetime.now().timestamp() + new_tokens.get('expires_in', 3600)
session.modified = True
store_token_session(new_tokens)
except Exception as e:
print(f"Refresh failed: {e}")
session.clear()
Expand Down
115 changes: 18 additions & 97 deletions backend/routes/playlist.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
from flask import Blueprint, request, session, jsonify
import concurrent.futures
from ..config import Config
from ..services.spotify import SpotifyService
from ..services.ai import AIService
from ..utils import require_spotify_auth, get_spotify_service, format_track

playlist_bp = Blueprint('playlist', __name__)

@playlist_bp.route('/Playlist_Generator', methods=['POST'])
@playlist_bp.route('/Generate_Preview', methods=['POST'])
@require_spotify_auth
def generate_preview():
# 1. Auth Check
if 'access_token' not in session:
return jsonify({"error": "Not authenticated", "redirect": "/login"}), 401

# 2. Get Params
# 1. Get Params
data = request.get_json() or {}
preferences = data.get('preferences')
if not preferences:
Expand All @@ -29,22 +25,22 @@ def generate_preview():
except:
playlist_length = 20

# 3. AI Generation
# 2. AI Generation
# Request extra songs to buffer against those not found on Spotify
# Increase buffer to 100% of requested length or at least 10 songs to be safe
buffer_count = max(10, int(playlist_length * 1.0))
target_ai_count = playlist_length + buffer_count

ai_service = AIService()
spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)
spotify_service = get_spotify_service()

try:
# Generate raw song list with buffer
ai_songs = ai_service.generate_playlist_params(preferences, count=target_ai_count)
except Exception as e:
return jsonify({"error": "AI Generation failed", "details": str(e)}), 500

# 4. Spotify Search (Parallelized)
# 3. Spotify Search (Parallelized)
found_tracks = []

# Extract token from session in the main thread
Expand Down Expand Up @@ -77,23 +73,7 @@ def search_worker(token, song_info):
found_tracks = found_tracks[:playlist_length]

# Return preview data (no playlist created yet)
track_previews = []
for t in found_tracks:
# Check if we have image
image = "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=100&h=100&fit=crop" # Default
if t.get('album', {}).get('images'):
image = t['album']['images'][0]['url']

track_previews.append({
"id": t['id'],
"uri": t['uri'],
"title": t['name'],
"artist": t['artists'][0]['name'],
"album": t['album']['name'],
"duration": f"{int(t['duration_ms']/60000)}:{int((t['duration_ms']%60000)/1000):02d}",
"image": image,
"preview_url": t.get('preview_url')
})
track_previews = [format_track(t) for t in found_tracks]

return jsonify({
"tracks": track_previews,
Expand All @@ -102,10 +82,8 @@ def search_worker(token, song_info):
})

@playlist_bp.route('/Create_Playlist', methods=['POST'])
@require_spotify_auth
def create_playlist():
if 'access_token' not in session:
return jsonify({"error": "Not authenticated", "redirect": "/login"}), 401

data = request.get_json() or {}
name = data.get('name', 'AI Generated Bundle')
description = data.get('description', 'Generated by PlaylistAI')
Expand All @@ -115,7 +93,7 @@ def create_playlist():
if not uris:
return jsonify({"error": "No tracks provided"}), 400

spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)
spotify_service = get_spotify_service()

try:
user_id = session.get('spotify_user_id')
Expand Down Expand Up @@ -164,83 +142,26 @@ def create_playlist():
return jsonify({"error": "Failed to create playlist on Spotify", "details": str(e)}), 500

@playlist_bp.route('/Search_Track', methods=['GET'])
@require_spotify_auth
def search_spotify_track():
if 'access_token' not in session:
return jsonify({"error": "Not authenticated"}), 401

query = request.args.get('q')
if not query:
return jsonify({"error": "Missing query"}), 400

spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)

# We'll use a direct search here, reusing search_track logic or calling raw search
# Since search_track in service is specific to track/artist, let's just do a raw search here or add a general search to service.
# For now, let's use the service's existing base URL but we need a general search.
# Actually, let's just use requests here for simplicity or extend service.
# Extending service is cleaner.

try:
# Reuse search_track logic but treat query as song name and ignore artist if not provided?
# Or better, just call Spotify Search API directly here for flexible search
params = {
"q": query,
"type": "track",
"market": "US",
"limit": 10
}

response = requests.get(
f"{SpotifyService.BASE_URL}/search",
headers=spotify_service.get_auth_headers(session['access_token']),
params=params
)

if response.status_code != 200:
return jsonify({"error": "Spotify search failed"}), response.status_code

data = response.json()
tracks = data.get('tracks', {}).get('items', [])

# Format for frontend
results = []
for t in tracks:
image = "https://images.unsplash.com/photo-1493225457124-a3eb161ffa5f?w=100&h=100&fit=crop"
if t.get('album', {}).get('images'):
image = t['album']['images'][0]['url']

results.append({
"id": t['id'],
"uri": t['uri'],
"title": t['name'],
"artist": t['artists'][0]['name'],
"album": t['album']['name'],
"duration": f"{int(t['duration_ms']/60000)}:{int((t['duration_ms']%60000)/1000):02d}",
"image": image
})

return jsonify(results)
spotify_service = get_spotify_service()

try:
tracks = spotify_service.search_tracks(session['access_token'], query, limit=10)
return jsonify([format_track(t) for t in tracks])
except Exception as e:
return jsonify({"error": str(e)}), 500

@playlist_bp.route('/Get_Playlists', methods=['GET'])
@require_spotify_auth
def get_playlists():
if 'access_token' not in session:
return jsonify({"error": "Not authenticated"}), 401

spotify_service = SpotifyService(Config.SPOTIFY_CLIENT_ID, Config.SPOTIFY_CLIENT_SECRET)
spotify_service = get_spotify_service()
try:
# Simplified for now, just getting user's playlists
# Logic from main2.py could be adapted if specific playlist fetching is needed
# But this route seemed generic in main2.py

params = {"limit": 50}
response = requests.get(
f"{SpotifyService.BASE_URL}/me/playlists",
headers=spotify_service.get_auth_headers(session['access_token']),
params=params
)
return jsonify(response.json())
playlists = spotify_service.get_user_playlists(session['access_token'])
return jsonify(playlists)
except Exception as e:
return jsonify({"error": str(e)}), 500
Loading