Fixed Spotify stuff

This commit is contained in:
Christian Krakau-Louis
2025-06-09 13:13:30 +02:00
parent 213f53593c
commit bc4055ded5
6 changed files with 271 additions and 51 deletions
+42 -8
View File
@@ -353,22 +353,56 @@ def get_songs_from_deezer_playlist(playlist_id):
def get_songs_from_spotify_playlist(playlist_id):
"""
Fetch songs from a Spotify playlist, properly import them with metadata, and return them
Always returns the songs in the playlist, even if all already exist in the DB.
"""
try:
songs_per_round = current_app.config.get('SONGS_PER_ROUND', 10)
imported_songs = ImportHelper.import_item(
import_result = ImportHelper.import_item(
item_id=playlist_id,
item_type='playlist',
source='spotify',
service_name='spotify',
oauth_spotify=oauth.spotify
)
if not imported_songs:
current_app.logger.warning(f"No songs returned from ImportHelper.import_item for Spotify playlist {playlist_id}")
from musicround.models import Song
# If we have imported_song_ids, use them (these are DB IDs)
if import_result.get('imported_song_ids'):
song_db_ids = import_result['imported_song_ids']
imported_songs = Song.query.filter(Song.id.in_(song_db_ids)).all()
return imported_songs[:songs_per_round]
# If no imported_song_ids, fetch all Spotify IDs from the playlist and get those songs from DB
# Use the Spotify API directly to get the playlist track IDs
sp = oauth.spotify
# Get playlist tracks (paginated)
all_spotify_ids = []
next_url = f'playlists/{playlist_id}/tracks'
authlib_token = {
'access_token': current_user.spotify_token,
'refresh_token': current_user.spotify_refresh_token,
'token_type': 'Bearer',
'expires_at': int(current_user.spotify_token_expiry.timestamp()) if current_user.spotify_token_expiry else None
}
while next_url:
resp = sp.get(next_url, token=authlib_token)
resp.raise_for_status()
data = resp.json()
for item in data.get('items', []):
track = item.get('track')
if track and track.get('id'):
all_spotify_ids.append(track['id'])
next_url = data.get('next')
# If next_url is a full URL, convert to relative for sp.get
if next_url and next_url.startswith('https://api.spotify.com/v1/'):
next_url = next_url.replace('https://api.spotify.com/v1/', '')
if not all_spotify_ids:
current_app.logger.warning(f"No valid tracks found in Spotify playlist {playlist_id}")
return []
return imported_songs[:songs_per_round]
# Query all songs in DB with those Spotify IDs, preserving playlist order
songs_by_spotify_id = {s.spotify_id: s for s in Song.query.filter(Song.spotify_id.in_(all_spotify_ids)).all()}
ordered_songs = [songs_by_spotify_id[sid] for sid in all_spotify_ids if sid in songs_by_spotify_id]
return ordered_songs[:songs_per_round]
except Exception as e:
current_app.logger.error(f"Error fetching or importing Spotify playlist {playlist_id}: {e}")
import traceback
+144 -26
View File
@@ -6,7 +6,7 @@ import time
import random
from datetime import datetime # Add datetime import
from flask import Blueprint, render_template, redirect, url_for, request, current_app, flash, session, jsonify
from flask_login import current_user
from flask_login import current_user, login_required
from musicround.models import Song, db
from musicround.routes.import_songs import import_pl
from musicround.helpers.import_helper import ImportHelper
@@ -140,20 +140,34 @@ def import_official_playlists():
elif not auth_token:
flash("No active Spotify session. Please connect your Spotify account.", "warning")
return redirect(url_for('users.spotify_link'))
# Handle POST request for importing a playlist
# Handle POST request for importing a playlist
if request.method == 'POST':
playlist_id = request.form['playlist_id']
result = ImportHelper.import_item('spotify', 'playlist', playlist_id)
if result['imported_count'] > 0:
flash(f'Successfully imported {result["imported_count"]} songs from official Spotify playlist!', 'success')
elif result['skipped_count'] > 0 and result['error_count'] == 0:
flash(f'All {result["skipped_count"]} songs were already in the database.', 'info')
elif result['error_count'] > 0:
flash(f'Encountered {result["error_count"]} errors during import.', 'warning')
else:
flash(f'Error importing playlist: {", ".join(result["errors"])}', 'danger')
# Check if user is authenticated for queue system
if not current_user.is_authenticated:
flash("Please log in to import playlists.", "warning")
return redirect(url_for('users.login'))
# Get the import queue from app config
queue = current_app.config.get('import_queue')
if not queue:
flash("Import queue not initialized.", "danger")
return redirect(url_for('core.view_songs'))
# Create import job and add to queue
from musicround.helpers.import_queue import ImportJob
priority = int(request.form.get('priority', 10))
job = ImportJob(
priority=priority,
service_name='spotify',
item_type='playlist',
item_id=playlist_id,
user_id=current_user.id,
)
queue.add_job(job)
flash('Official Spotify playlist import queued successfully. You will be notified when it completes.', 'info')
return redirect(url_for('core.view_songs'))
@@ -280,22 +294,35 @@ def direct_official_playlists():
# Initialize direct Spotify client with bearer token
from musicround.helpers.spotify_direct import SpotifyDirectClient
direct_client = SpotifyDirectClient(bearer_token=bearer_token)
# Handle POST request for importing a playlist
# Handle POST request for importing a playlist
if request.method == 'POST':
playlist_id = request.form['playlist_id']
# Use the new unified ImportHelper
result = ImportHelper.import_item('spotify', 'playlist', playlist_id)
if result['imported_count'] > 0:
flash(f'Successfully imported {result["imported_count"]} songs from official Spotify playlist!', 'success')
elif result['skipped_count'] > 0 and result['error_count'] == 0:
flash(f'All {result["skipped_count"]} songs were already in the database.', 'info')
elif result['error_count'] > 0:
flash(f'Encountered {result["error_count"]} errors during import.', 'warning')
else:
flash(f'Error importing playlist: {", ".join(result["errors"])}', 'danger')
# Check if user is authenticated for queue system
if not current_user.is_authenticated:
flash("Please log in to import playlists.", "warning")
return redirect(url_for('users.login'))
# Get the import queue from app config
queue = current_app.config.get('import_queue')
if not queue:
flash("Import queue not initialized.", "danger")
return redirect(url_for('core.view_songs'))
# Create import job and add to queue
from musicround.helpers.import_queue import ImportJob
priority = int(request.form.get('priority', 10))
job = ImportJob(
priority=priority,
service_name='spotify',
item_type='playlist',
item_id=playlist_id,
user_id=current_user.id,
)
queue.add_job(job)
flash('Direct Spotify playlist import queued successfully. You will be notified when it completes.', 'info')
return redirect(url_for('core.view_songs'))
# Get filter keywords from the query string (default to empty list)
@@ -713,4 +740,95 @@ def update_direct_token():
current_app.logger.error(f"Error validating bearer token: {e}")
flash(f'Error validating token: {str(e)}', 'error')
return redirect(return_url)
return redirect(return_url)
@import_bp.route('/queue-status')
@login_required
def queue_status():
"""
Display real-time status of the import queue for administrators
"""
# Check if user is an admin
if not current_user.is_admin():
flash('Admin access required for Import Queue view.', 'danger')
return redirect(url_for('core.index'))
# Helper function to get current time
from datetime import datetime
def now():
return datetime.utcnow()
# Get the import queue from app config
queue = current_app.config.get('import_queue')
if not queue:
flash("Import queue not initialized.", "danger")
return redirect(url_for('core.view_songs'))
# Access queue internals for display - this won't modify the queue
queue_size = queue._queue.qsize()
# Extract information about jobs in the queue (without removing them)
# This is a bit of a hack but necessary to see what's in the PriorityQueue
# without removing items
queue_snapshot = []
if hasattr(queue._queue, 'queue'):
# Make a copy of the internal queue list
with queue._lock: # Ensure thread safety while accessing the queue
queue_items = list(queue._queue.queue)
for priority, counter, job in queue_items:
queue_snapshot.append({
'priority': priority,
'counter': counter,
'service': job.service_name,
'type': job.item_type,
'item_id': job.item_id,
'user_id': job.user_id
})
# Get active and recent jobs from database if available
active_jobs = []
recent_jobs = []
# Check if ImportJobRecord is defined
try:
from musicround.models import ImportJobRecord
# Get last 50 jobs from the database, sorted by most recent first
recent_jobs = ImportJobRecord.query.order_by(ImportJobRecord.created_at.desc()).limit(50).all()
# Get the active jobs (status='processing')
active_jobs = ImportJobRecord.query.filter_by(status='processing').all()
except (ImportError, AttributeError):
# ImportJobRecord might not be defined yet, handle this case
pass
# Get some basic stats
stats = {
'queue_size': queue_size,
'active_jobs': len(active_jobs),
'completed_today': 0,
'failed_today': 0
}
# If we have ImportJobRecord, get some stats
if recent_jobs:
import datetime
today = datetime.datetime.utcnow().date()
for job in recent_jobs:
if job.completed_at and job.completed_at.date() == today:
if job.status == 'completed':
stats['completed_today'] += 1
elif job.status == 'failed':
stats['failed_today'] += 1
return render_template(
'import_queue_status.html',
stats=stats,
active_jobs=active_jobs,
recent_jobs=recent_jobs,
queue_snapshot=queue_snapshot,
queue=queue,
now=now
)