changed "spotify" to "provider"
This commit is contained in:
144
app/functions.py
144
app/functions.py
@@ -1,4 +1,7 @@
|
|||||||
|
import json
|
||||||
|
from typing import Optional
|
||||||
from flask import flash, redirect, session, url_for
|
from flask import flash, redirect, session, url_for
|
||||||
|
import requests
|
||||||
from app.models import JellyfinUser, Playlist,Track
|
from app.models import JellyfinUser, Playlist,Track
|
||||||
from app import sp, cache, app, jellyfin ,jellyfin_admin_token, jellyfin_admin_id,device_id, cache
|
from app import sp, cache, app, jellyfin ,jellyfin_admin_token, jellyfin_admin_id,device_id, cache
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@@ -57,51 +60,57 @@ def prepPlaylistData(data):
|
|||||||
|
|
||||||
for playlist_data in data['playlists']['items']:
|
for playlist_data in data['playlists']['items']:
|
||||||
# Fetch the playlist from the database if it exists
|
# Fetch the playlist from the database if it exists
|
||||||
db_playlist = Playlist.query.filter_by(spotify_playlist_id=playlist_data['id']).first()
|
if playlist_data:
|
||||||
|
db_playlist = Playlist.query.filter_by(provider_playlist_id=playlist_data['id']).first()
|
||||||
|
|
||||||
|
if db_playlist:
|
||||||
|
# If the playlist is in the database, use the stored values
|
||||||
|
if playlist_data.get('tracks'):
|
||||||
|
if isinstance(playlist_data['tracks'],list):
|
||||||
|
track_count = len(playlist_data['tracks'] )
|
||||||
|
else:
|
||||||
|
track_count = playlist_data['tracks']['total'] or 0
|
||||||
|
else:
|
||||||
|
track_count = 0
|
||||||
|
tracks_available = db_playlist.tracks_available or 0
|
||||||
|
tracks_linked = len([track for track in db_playlist.tracks if track.jellyfin_id]) or 0
|
||||||
|
percent_available = (tracks_available / track_count * 100) if track_count > 0 else 0
|
||||||
|
|
||||||
|
# Determine playlist status
|
||||||
|
if not playlist_data.get('status'):
|
||||||
|
if tracks_available == track_count and track_count > 0:
|
||||||
|
playlist_data['status'] = 'green' # Fully available
|
||||||
|
elif tracks_available > 0:
|
||||||
|
playlist_data['status'] = 'yellow' # Partially available
|
||||||
|
else:
|
||||||
|
playlist_data['status'] = 'red' # Not available
|
||||||
|
|
||||||
if db_playlist:
|
|
||||||
# If the playlist is in the database, use the stored values
|
|
||||||
if isinstance(playlist_data['tracks'],list):
|
|
||||||
track_count = len(playlist_data['tracks'] )
|
|
||||||
else:
|
else:
|
||||||
track_count = playlist_data['tracks']['total'] or 0
|
# If the playlist is not in the database, initialize with 0
|
||||||
tracks_available = db_playlist.tracks_available or 0
|
track_count = 0
|
||||||
tracks_linked = len([track for track in db_playlist.tracks if track.jellyfin_id]) or 0
|
tracks_available = 0
|
||||||
percent_available = (tracks_available / track_count * 100) if track_count > 0 else 0
|
tracks_linked = 0
|
||||||
|
percent_available = 0
|
||||||
|
playlist_data['status'] = 'red' # Not requested yet
|
||||||
|
|
||||||
# Determine playlist status
|
# Append playlist data to the list
|
||||||
if tracks_available == track_count and track_count > 0:
|
playlists.append({
|
||||||
status = 'green' # Fully available
|
'name': playlist_data['name'],
|
||||||
elif tracks_available > 0:
|
'description': playlist_data['description'],
|
||||||
status = 'yellow' # Partially available
|
'image': playlist_data['images'][0]['url'] if playlist_data.get('images') else '/static/images/placeholder.png',
|
||||||
else:
|
'url': playlist_data['external_urls']['spotify'] if playlist_data.get('external_urls') else '',
|
||||||
status = 'red' # Not available
|
'id': playlist_data['id'] if playlist_data['id'] else '',
|
||||||
else:
|
'jellyfin_id': db_playlist.jellyfin_id if db_playlist else '',
|
||||||
# If the playlist is not in the database, initialize with 0
|
'can_add': (db_playlist not in jellyfin_user.playlists) if db_playlist else True,
|
||||||
track_count = 0
|
'can_remove' : (db_playlist in jellyfin_user.playlists) if db_playlist else False,
|
||||||
tracks_available = 0
|
'last_updated':db_playlist.last_updated if db_playlist else '',
|
||||||
tracks_linked = 0
|
'last_changed':db_playlist.last_changed if db_playlist else '',
|
||||||
percent_available = 0
|
'tracks_available': tracks_available,
|
||||||
status = 'red' # Not requested yet
|
'track_count': track_count,
|
||||||
|
'tracks_linked': tracks_linked,
|
||||||
# Append playlist data to the list
|
'percent_available': percent_available,
|
||||||
playlists.append({
|
'status': playlist_data['status'] # Red, yellow, or green based on availability
|
||||||
'name': playlist_data['name'],
|
})
|
||||||
'description': playlist_data['description'],
|
|
||||||
'image': playlist_data['images'][0]['url'] if playlist_data['images'] else 'default-image.jpg',
|
|
||||||
'url': playlist_data['external_urls']['spotify'],
|
|
||||||
'id': playlist_data['id'],
|
|
||||||
'jellyfin_id': db_playlist.jellyfin_id if db_playlist else '',
|
|
||||||
'can_add': (db_playlist not in jellyfin_user.playlists) if db_playlist else True,
|
|
||||||
'can_remove' : (db_playlist in jellyfin_user.playlists) if db_playlist else False,
|
|
||||||
'last_updated':db_playlist.last_updated if db_playlist else '',
|
|
||||||
'last_changed':db_playlist.last_changed if db_playlist else '',
|
|
||||||
'tracks_available': tracks_available,
|
|
||||||
'track_count': track_count,
|
|
||||||
'tracks_linked': tracks_linked,
|
|
||||||
'percent_available': percent_available,
|
|
||||||
'status': status # Red, yellow, or green based on availability
|
|
||||||
})
|
|
||||||
|
|
||||||
return playlists
|
return playlists
|
||||||
|
|
||||||
@@ -115,24 +124,57 @@ def get_cached_spotify_playlists(playlist_ids):
|
|||||||
spotify_data = {'playlists': {'items': []}}
|
spotify_data = {'playlists': {'items': []}}
|
||||||
|
|
||||||
for playlist_id in playlist_ids:
|
for playlist_id in playlist_ids:
|
||||||
playlist_data = get_cached_spotify_playlist(playlist_id)
|
playlist_data = None
|
||||||
|
not_found = False
|
||||||
|
try:
|
||||||
|
playlist_data = get_cached_spotify_playlist(playlist_id)
|
||||||
|
|
||||||
|
except SpotifyException as e:
|
||||||
|
app.logger.error(f"Error Fetching Playlist {playlist_id}: {e}")
|
||||||
|
not_found = 'http status: 404' in str(e)
|
||||||
|
if not_found:
|
||||||
|
playlist_data = {
|
||||||
|
'status':'red',
|
||||||
|
'description': 'Playlist has most likely been removed. You can keep it, but won´t receive Updates.',
|
||||||
|
'id': playlist_id,
|
||||||
|
'name' : ''
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if playlist_data:
|
if playlist_data:
|
||||||
spotify_data['playlists']['items'].append(playlist_data)
|
spotify_data['playlists']['items'].append(playlist_data)
|
||||||
else:
|
|
||||||
app.logger.warning(f"Playlist data for ID {playlist_id} could not be retrieved.")
|
|
||||||
|
|
||||||
return spotify_data
|
return spotify_data
|
||||||
|
|
||||||
@cache.memoize(timeout=3600)
|
@cache.memoize(timeout=3600)
|
||||||
def get_cached_spotify_playlist(playlist_id):
|
def get_cached_playlist(playlist_id):
|
||||||
"""
|
"""
|
||||||
Fetches a Spotify playlist by its ID, utilizing caching to minimize API calls.
|
Fetches a Spotify playlist by its ID, utilizing caching to minimize API calls.
|
||||||
|
|
||||||
:param playlist_id: The Spotify playlist ID.
|
:param playlist_id: The Spotify playlist ID.
|
||||||
:return: Playlist data as a dictionary, or None if an error occurs.
|
:return: Playlist data as a dictionary, or None if an error occurs.
|
||||||
"""
|
"""
|
||||||
playlist_data = sp.playlist(playlist_id) # Fetch data from Spotify API
|
# When the playlist_id starts with 37i9dQZF1, we need to use the new function
|
||||||
return playlist_data
|
# as the standard Spotify API endpoints are deprecated for these playlists.
|
||||||
|
# Reference: https://github.com/kamilkosek/jellyplist/issues/25
|
||||||
|
|
||||||
|
if playlist_id.startswith("37i9dQZF1"):
|
||||||
|
app.logger.warning(f"Algorithmic or Spotify-owned editorial playlist, using custom Implementation to fetch details")
|
||||||
|
# Use the custom implementation for these playlists
|
||||||
|
try:
|
||||||
|
data = fetch_spotify_playlist(playlist_id)
|
||||||
|
return transform_playlist_response(data)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error fetching playlist with custom method: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Otherwise, use the standard Spotipy API
|
||||||
|
try:
|
||||||
|
playlist_data = sp.playlist(playlist_id) # Fetch data using Spotipy
|
||||||
|
return playlist_data
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error fetching playlist with Spotipy: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
@cache.memoize(timeout=3600*24*10)
|
@cache.memoize(timeout=3600*24*10)
|
||||||
def get_cached_spotify_track(track_id):
|
def get_cached_spotify_track(track_id):
|
||||||
@@ -189,7 +231,7 @@ def getFeaturedPlaylists(country: str, offset: int):
|
|||||||
|
|
||||||
def getCategoryPlaylists(category: str, offset: int):
|
def getCategoryPlaylists(category: str, offset: int):
|
||||||
try:
|
try:
|
||||||
playlists_data = sp.category_playlists(category_id=category, limit=16, offset=offset)
|
playlists_data = sp.category_playlists(category_id=category, country=app.config['SPOTIFY_COUNTRY_CODE'], limit=16, offset=offset)
|
||||||
return prepPlaylistData(playlists_data), playlists_data['playlists']['total'], f"Category {playlists_data['message']}"
|
return prepPlaylistData(playlists_data), playlists_data['playlists']['total'], f"Category {playlists_data['message']}"
|
||||||
except SpotifyException as e:
|
except SpotifyException as e:
|
||||||
app.logger.error(f"Spotify API error in getCategoryPlaylists: {e}")
|
app.logger.error(f"Spotify API error in getCategoryPlaylists: {e}")
|
||||||
@@ -215,14 +257,14 @@ def get_tracks_for_playlist(data):
|
|||||||
tracks = []
|
tracks = []
|
||||||
is_admin = session.get('is_admin', False)
|
is_admin = session.get('is_admin', False)
|
||||||
|
|
||||||
for idx, item in enumerate(results['tracks']):
|
for idx, item in enumerate(results['tracks']['items']):
|
||||||
track_data = item['track']
|
track_data = item['track']
|
||||||
if track_data:
|
if track_data:
|
||||||
duration_ms = track_data['duration_ms']
|
duration_ms = track_data['duration_ms']
|
||||||
minutes = duration_ms // 60000
|
minutes = duration_ms // 60000
|
||||||
seconds = (duration_ms % 60000) // 1000
|
seconds = (duration_ms % 60000) // 1000
|
||||||
|
|
||||||
track_db = Track.query.filter_by(spotify_track_id=track_data['id']).first()
|
track_db = Track.query.filter_by(provider_track_id=track_data['id']).first()
|
||||||
|
|
||||||
if track_db:
|
if track_db:
|
||||||
downloaded = track_db.downloaded
|
downloaded = track_db.downloaded
|
||||||
|
|||||||
@@ -16,25 +16,37 @@ def jellyfin_playlists():
|
|||||||
try:
|
try:
|
||||||
# Fetch playlists from Jellyfin
|
# Fetch playlists from Jellyfin
|
||||||
playlists = jellyfin.get_playlists(session_token=functions._get_token_from_sessioncookie())
|
playlists = jellyfin.get_playlists(session_token=functions._get_token_from_sessioncookie())
|
||||||
|
spotify_data = {'playlists': {'items': []}}
|
||||||
|
|
||||||
# Extract Spotify playlist IDs from the database
|
# Extract Spotify playlist IDs from the database
|
||||||
spotify_playlist_ids = []
|
|
||||||
for pl in playlists:
|
for pl in playlists:
|
||||||
# Retrieve the playlist from the database using Jellyfin ID
|
# Retrieve the playlist from the database using Jellyfin ID
|
||||||
from_db = Playlist.query.filter_by(jellyfin_id=pl['Id']).first()
|
from_db = Playlist.query.filter_by(jellyfin_id=pl['Id']).first()
|
||||||
if from_db and from_db.spotify_playlist_id:
|
playlist_data = None
|
||||||
spotify_playlist_ids.append(from_db.spotify_playlist_id)
|
not_found = False
|
||||||
|
if from_db and from_db.provider_playlist_id:
|
||||||
|
pl_id = from_db.provider_playlist_id
|
||||||
|
try:
|
||||||
|
playlist_data = functions.get_cached_spotify_playlist(pl_id)
|
||||||
|
|
||||||
|
except SpotifyException as e:
|
||||||
|
app.logger.error(f"Error Fetching Playlist {pl_id}: {e}")
|
||||||
|
not_found = 'http status: 404' in str(e)
|
||||||
|
if not_found:
|
||||||
|
playlist_data = {
|
||||||
|
'status':'red',
|
||||||
|
'description': 'Playlist has most likely been removed. You can keep it, but won´t receive Updates.',
|
||||||
|
'id': from_db.provider_playlist_id,
|
||||||
|
'name' : from_db.name
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if playlist_data:
|
||||||
|
spotify_data['playlists']['items'].append(playlist_data)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
app.logger.warning(f"No database entry found for Jellyfin playlist ID: {pl['Id']}")
|
app.logger.warning(f"No database entry found for Jellyfin playlist ID: {pl['Id']}")
|
||||||
|
|
||||||
if not spotify_playlist_ids:
|
|
||||||
flash('No Spotify playlists found to display.', 'warning')
|
|
||||||
return render_template('jellyfin_playlists.html', playlists=functions.prepPlaylistData({'playlists': {'items': []}}))
|
|
||||||
|
|
||||||
# Use the cached function to fetch Spotify playlists
|
|
||||||
spotify_data = functions.get_cached_spotify_playlists(spotify_playlist_ids)
|
|
||||||
|
|
||||||
# Prepare the data for the template
|
|
||||||
prepared_data = functions.prepPlaylistData(spotify_data)
|
prepared_data = functions.prepPlaylistData(spotify_data)
|
||||||
|
|
||||||
return render_template('jellyfin_playlists.html', playlists=prepared_data)
|
return render_template('jellyfin_playlists.html', playlists=prepared_data)
|
||||||
@@ -63,13 +75,13 @@ def add_playlist():
|
|||||||
playlist_data = functions.get_cached_spotify_playlist(playlist_id)
|
playlist_data = functions.get_cached_spotify_playlist(playlist_id)
|
||||||
|
|
||||||
# Check if playlist already exists in the database
|
# Check if playlist already exists in the database
|
||||||
playlist = Playlist.query.filter_by(spotify_playlist_id=playlist_id).first()
|
playlist = Playlist.query.filter_by(provider_playlist_id=playlist_id).first()
|
||||||
|
|
||||||
if not playlist:
|
if not playlist:
|
||||||
# Add new playlist if it doesn't exist
|
# Add new playlist if it doesn't exist
|
||||||
# create the playlist via api key, with the first admin as 'owner'
|
# create the playlist via api key, with the first admin as 'owner'
|
||||||
fromJellyfin = jellyfin.create_music_playlist(functions._get_api_token(),playlist_data['name'],[],functions._get_admin_id())['Id']
|
fromJellyfin = jellyfin.create_music_playlist(functions._get_api_token(),playlist_data['name'],[],functions._get_admin_id())['Id']
|
||||||
playlist = Playlist(name=playlist_data['name'], spotify_playlist_id=playlist_id,spotify_uri=playlist_data['uri'],track_count = playlist_data['tracks']['total'], tracks_available=0, jellyfin_id = fromJellyfin)
|
playlist = Playlist(name=playlist_data['name'], provider_playlist_id=playlist_id,provider_uri=playlist_data['uri'],track_count = playlist_data['tracks']['total'], tracks_available=0, jellyfin_id = fromJellyfin)
|
||||||
db.session.add(playlist)
|
db.session.add(playlist)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if app.config['START_DOWNLOAD_AFTER_PLAYLIST_ADD']:
|
if app.config['START_DOWNLOAD_AFTER_PLAYLIST_ADD']:
|
||||||
@@ -83,7 +95,7 @@ def add_playlist():
|
|||||||
spotify_tracks = {}
|
spotify_tracks = {}
|
||||||
offset = 0
|
offset = 0
|
||||||
while True:
|
while True:
|
||||||
playlist_items = sp.playlist_items(playlist.spotify_playlist_id, offset=offset, limit=100)
|
playlist_items = sp.playlist_items(playlist.provider_playlist_id, offset=offset, limit=100)
|
||||||
items = playlist_items['items']
|
items = playlist_items['items']
|
||||||
spotify_tracks.update({offset + idx: track['track'] for idx, track in enumerate(items) if track['track']})
|
spotify_tracks.update({offset + idx: track['track'] for idx, track in enumerate(items) if track['track']})
|
||||||
|
|
||||||
@@ -94,11 +106,11 @@ def add_playlist():
|
|||||||
track_info = track_data
|
track_info = track_data
|
||||||
if not track_info:
|
if not track_info:
|
||||||
continue
|
continue
|
||||||
track = Track.query.filter_by(spotify_track_id=track_info['id']).first()
|
track = Track.query.filter_by(provider_track_id=track_info['id']).first()
|
||||||
|
|
||||||
if not track:
|
if not track:
|
||||||
# Add new track if it doesn't exist
|
# Add new track if it doesn't exist
|
||||||
track = Track(name=track_info['name'], spotify_track_id=track_info['id'], spotify_uri=track_info['uri'], downloaded=False)
|
track = Track(name=track_info['name'], provider_track_id=track_info['id'], provider_uri=track_info['uri'], downloaded=False)
|
||||||
db.session.add(track)
|
db.session.add(track)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
elif track.downloaded:
|
elif track.downloaded:
|
||||||
@@ -158,7 +170,7 @@ def delete_playlist(playlist_id):
|
|||||||
flash('Playlist removed')
|
flash('Playlist removed')
|
||||||
item = {
|
item = {
|
||||||
"name" : playlist.name,
|
"name" : playlist.name,
|
||||||
"id" : playlist.spotify_playlist_id,
|
"id" : playlist.provider_playlist_id,
|
||||||
"can_add":True,
|
"can_add":True,
|
||||||
"can_remove":False,
|
"can_remove":False,
|
||||||
"jellyfin_id" : playlist.jellyfin_id
|
"jellyfin_id" : playlist.jellyfin_id
|
||||||
@@ -182,7 +194,7 @@ def wipe_playlist(playlist_id):
|
|||||||
if playlist:
|
if playlist:
|
||||||
# Delete the playlist
|
# Delete the playlist
|
||||||
name = playlist.name
|
name = playlist.name
|
||||||
id = playlist.spotify_playlist_id
|
id = playlist.provider_playlist_id
|
||||||
jf_id = playlist.jellyfin_id
|
jf_id = playlist.jellyfin_id
|
||||||
db.session.delete(playlist)
|
db.session.delete(playlist)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ user_playlists = db.Table('user_playlists',
|
|||||||
class Playlist(db.Model):
|
class Playlist(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(150), nullable=False)
|
name = db.Column(db.String(150), nullable=False)
|
||||||
spotify_playlist_id = db.Column(db.String(120), unique=True, nullable=False)
|
provider_playlist_id = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
spotify_uri = db.Column(db.String(120), unique=True, nullable=False)
|
provider_uri = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
|
|
||||||
# Relationship with Tracks
|
# Relationship with Tracks
|
||||||
tracks = db.relationship('Track', secondary='playlist_tracks', back_populates='playlists')
|
tracks = db.relationship('Track', secondary='playlist_tracks', back_populates='playlists')
|
||||||
@@ -37,7 +37,7 @@ class Playlist(db.Model):
|
|||||||
users = db.relationship('JellyfinUser', secondary=user_playlists, back_populates='playlists')
|
users = db.relationship('JellyfinUser', secondary=user_playlists, back_populates='playlists')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<Playlist {self.name}:{self.spotify_playlist_id}>'
|
return f'<Playlist {self.name}:{self.provider_playlist_id}>'
|
||||||
|
|
||||||
# Association table between Playlists and Tracks
|
# Association table between Playlists and Tracks
|
||||||
playlist_tracks = db.Table('playlist_tracks',
|
playlist_tracks = db.Table('playlist_tracks',
|
||||||
@@ -50,8 +50,8 @@ playlist_tracks = db.Table('playlist_tracks',
|
|||||||
class Track(db.Model):
|
class Track(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(150), nullable=False)
|
name = db.Column(db.String(150), nullable=False)
|
||||||
spotify_track_id = db.Column(db.String(120), unique=True, nullable=False)
|
provider_track_id = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
spotify_uri = db.Column(db.String(120), unique=True, nullable=False)
|
provider_uri = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
downloaded = db.Column(db.Boolean())
|
downloaded = db.Column(db.Boolean())
|
||||||
filesystem_path = db.Column(db.String(), nullable=True)
|
filesystem_path = db.Column(db.String(), nullable=True)
|
||||||
jellyfin_id = db.Column(db.String(120), nullable=True) # Add Jellyfin track ID field
|
jellyfin_id = db.Column(db.String(120), nullable=True) # Add Jellyfin track ID field
|
||||||
@@ -60,4 +60,4 @@ class Track(db.Model):
|
|||||||
# Many-to-Many relationship with Playlists
|
# Many-to-Many relationship with Playlists
|
||||||
playlists = db.relationship('Playlist', secondary=playlist_tracks, back_populates='tracks')
|
playlists = db.relationship('Playlist', secondary=playlist_tracks, back_populates='tracks')
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'<Track {self.name}:{self.spotify_track_id}>'
|
return f'<Track {self.name}:{self.provider_track_id}>'
|
||||||
|
|||||||
@@ -1,10 +1,29 @@
|
|||||||
from flask import Flask, Response, jsonify, render_template, request, redirect, url_for, session, flash
|
import json
|
||||||
|
import re
|
||||||
|
from flask import Flask, Response, jsonify, render_template, request, redirect, url_for, session, flash, Blueprint, g
|
||||||
from app import app, db, functions, sp, jellyfin, celery, jellyfin_admin_token, jellyfin_admin_id,device_id, cache, read_dev_build_file, tasks
|
from app import app, db, functions, sp, jellyfin, celery, jellyfin_admin_token, jellyfin_admin_id,device_id, cache, read_dev_build_file, tasks
|
||||||
from app.models import JellyfinUser,Playlist,Track
|
from app.models import JellyfinUser,Playlist,Track
|
||||||
from celery.result import AsyncResult
|
from celery.result import AsyncResult
|
||||||
|
|
||||||
|
from app.providers import base
|
||||||
|
from app.providers.base import MusicProviderClient
|
||||||
|
from app.providers.spotify import SpotifyClient
|
||||||
|
from app.registry.music_provider_registry import MusicProviderRegistry
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
from spotipy.exceptions import SpotifyException
|
from spotipy.exceptions import SpotifyException
|
||||||
|
|
||||||
|
pl_bp = Blueprint('playlist', __name__)
|
||||||
|
@pl_bp.before_request
|
||||||
|
def set_active_provider():
|
||||||
|
"""
|
||||||
|
Middleware to select the active provider based on request parameters.
|
||||||
|
"""
|
||||||
|
provider_id = request.args.get('provider', 'Spotify') # Default to Spotify
|
||||||
|
try:
|
||||||
|
g.music_provider = MusicProviderRegistry.get_provider(provider_id)
|
||||||
|
except ValueError as e:
|
||||||
|
return {"error": str(e)}, 400
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def add_context():
|
def add_context():
|
||||||
unlinked_track_count = len(Track.query.filter_by(downloaded=True,jellyfin_id=None).all())
|
unlinked_track_count = len(Track.query.filter_by(downloaded=True,jellyfin_id=None).all())
|
||||||
@@ -45,7 +64,7 @@ def link_issues():
|
|||||||
unlinked_tracks = Track.query.filter_by(downloaded=True,jellyfin_id=None).all()
|
unlinked_tracks = Track.query.filter_by(downloaded=True,jellyfin_id=None).all()
|
||||||
tracks = []
|
tracks = []
|
||||||
for ult in unlinked_tracks:
|
for ult in unlinked_tracks:
|
||||||
sp_track = functions.get_cached_spotify_track(ult.spotify_track_id)
|
sp_track = functions.get_cached_spotify_track(ult.provider_track_id)
|
||||||
duration_ms = sp_track['duration_ms']
|
duration_ms = sp_track['duration_ms']
|
||||||
minutes = duration_ms // 60000
|
minutes = duration_ms // 60000
|
||||||
seconds = (duration_ms % 60000) // 1000
|
seconds = (duration_ms % 60000) // 1000
|
||||||
@@ -117,7 +136,7 @@ def login():
|
|||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return redirect('/playlists')
|
return redirect('/')
|
||||||
except:
|
except:
|
||||||
flash('Login failed. Please check your Jellyfin credentials and try again.', 'error')
|
flash('Login failed. Please check your Jellyfin credentials and try again.', 'error')
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
@@ -130,6 +149,32 @@ def logout():
|
|||||||
session.pop('jellyfin_access_token', None)
|
session.pop('jellyfin_access_token', None)
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/add_single',methods=['GET'])
|
||||||
|
@functions.jellyfin_login_required
|
||||||
|
def add_single():
|
||||||
|
playlist = request.args.get('playlist')
|
||||||
|
error = None
|
||||||
|
errdata= None
|
||||||
|
if playlist:
|
||||||
|
parsed = sp._get_id(type='playlist',id=playlist)
|
||||||
|
if parsed:
|
||||||
|
try:
|
||||||
|
functions.get_cached_spotify_playlist(parsed)
|
||||||
|
|
||||||
|
return redirect(f'/playlist/view/{parsed}')
|
||||||
|
except SpotifyException as e:
|
||||||
|
url_match = re.search(sp._regex_spotify_url, playlist)
|
||||||
|
if url_match is not None:
|
||||||
|
resp = functions.fetch_spotify_playlist(playlist,None)
|
||||||
|
parsed_data = functions.parse_spotify_playlist_html(resp)
|
||||||
|
error = (f'Playlist can´t be fetched')
|
||||||
|
|
||||||
|
errdata = str(e)
|
||||||
|
|
||||||
|
return render_template('index.html',error_message = error, error_data = errdata)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/playlists')
|
@app.route('/playlists')
|
||||||
@app.route('/categories')
|
@app.route('/categories')
|
||||||
@@ -147,8 +192,13 @@ def loaditems():
|
|||||||
try:
|
try:
|
||||||
db_playlists = db.session.query(Playlist).offset(offset).limit(limit).all()
|
db_playlists = db.session.query(Playlist).offset(offset).limit(limit).all()
|
||||||
max_items = db.session.query(Playlist).count()
|
max_items = db.session.query(Playlist).count()
|
||||||
spotify_playlist_ids = [playlist.spotify_playlist_id for playlist in db_playlists]
|
|
||||||
spotify_data = functions.get_cached_spotify_playlists(tuple(spotify_playlist_ids))
|
provider_playlist_ids = [playlist.provider_playlist_id for playlist in db_playlists]
|
||||||
|
spotify_data = functions.get_cached_spotify_playlists(tuple(provider_playlist_ids))
|
||||||
|
for x in spotify_data['playlists']['items']:
|
||||||
|
for from_db in db_playlists:
|
||||||
|
if x['id'] == from_db.provider_playlist_id:
|
||||||
|
x['name'] = from_db.name
|
||||||
data = functions.prepPlaylistData(spotify_data)
|
data = functions.prepPlaylistData(spotify_data)
|
||||||
items_title = "Monitored Playlists"
|
items_title = "Monitored Playlists"
|
||||||
items_subtitle = "These playlists are already monitored by the Server. If you add one to your Jellyfin account, they will be available immediately."
|
items_subtitle = "These playlists are already monitored by the Server. If you add one to your Jellyfin account, they will be available immediately."
|
||||||
@@ -209,11 +259,11 @@ def searchResults():
|
|||||||
context = {}
|
context = {}
|
||||||
if query:
|
if query:
|
||||||
# Add your logic here to perform the search on Spotify (or Jellyfin)
|
# Add your logic here to perform the search on Spotify (or Jellyfin)
|
||||||
search_result = sp.search(q = query, type= 'track,album,artist,playlist')
|
search_result = sp.search(q = query, type= 'playlist',limit= 50, market=app.config['SPOTIFY_COUNTRY_CODE'])
|
||||||
context = {
|
context = {
|
||||||
'artists' : functions.prepArtistData(search_result ),
|
#'artists' : functions.prepArtistData(search_result ),
|
||||||
'playlists' : functions.prepPlaylistData(search_result ),
|
'playlists' : functions.prepPlaylistData(search_result ),
|
||||||
'albums' : functions.prepAlbumData(search_result ),
|
#'albums' : functions.prepAlbumData(search_result ),
|
||||||
'query' : query
|
'query' : query
|
||||||
}
|
}
|
||||||
return render_template('search.html', **context)
|
return render_template('search.html', **context)
|
||||||
@@ -221,14 +271,14 @@ def searchResults():
|
|||||||
return render_template('search.html', query=None, results={})
|
return render_template('search.html', query=None, results={})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/playlist/view/<playlist_id>')
|
@pl_bp.route('/playlist/view/<playlist_id>')
|
||||||
@functions.jellyfin_login_required
|
@functions.jellyfin_login_required
|
||||||
def get_playlist_tracks(playlist_id):
|
def get_playlist_tracks(playlist_id):
|
||||||
# Hol dir alle Tracks für die Playlist
|
provider: MusicProviderClient = g.music_provider # Explicit type hint for g.music_provider
|
||||||
data = functions.get_full_playlist_data(playlist_id) # Diese neue Funktion holt alle Tracks der Playlist
|
playlist: base.Playlist = provider.get_playlist(playlist_id)
|
||||||
tracks = functions.get_tracks_for_playlist(data) # Deine Funktion, um Tracks zu holen
|
tracks = functions.get_tracks_for_playlist(playlist.tracks) # Deine Funktion, um Tracks zu holen
|
||||||
# Berechne die gesamte Dauer der Playlist
|
# Berechne die gesamte Dauer der Playlist
|
||||||
total_duration_ms = sum([track['track']['duration_ms'] for track in data['tracks'] if track['track']])
|
total_duration_ms = sum([track['track']['duration_ms'] for track in data['tracks']['items'] if track['track']])
|
||||||
|
|
||||||
# Konvertiere die Gesamtdauer in ein lesbares Format
|
# Konvertiere die Gesamtdauer in ein lesbares Format
|
||||||
hours, remainder = divmod(total_duration_ms // 1000, 3600)
|
hours, remainder = divmod(total_duration_ms // 1000, 3600)
|
||||||
@@ -263,7 +313,7 @@ def associate_track():
|
|||||||
flash('Missing Jellyfin or Spotify ID')
|
flash('Missing Jellyfin or Spotify ID')
|
||||||
|
|
||||||
# Retrieve the track by Spotify ID
|
# Retrieve the track by Spotify ID
|
||||||
track = Track.query.filter_by(spotify_track_id=spotify_id).first()
|
track = Track.query.filter_by(provider_track_id=spotify_id).first()
|
||||||
|
|
||||||
if not track:
|
if not track:
|
||||||
flash('Track not found')
|
flash('Track not found')
|
||||||
@@ -296,4 +346,10 @@ def unlock_key():
|
|||||||
|
|
||||||
@app.route('/test')
|
@app.route('/test')
|
||||||
def test():
|
def test():
|
||||||
|
playlist_id = "37i9dQZF1DX12qgyzUprB6"
|
||||||
|
client = SpotifyClient(cookie_file='/jellyplist/open.spotify.com_cookies.txt')
|
||||||
|
client.authenticate()
|
||||||
|
pl = client.get_playlist(playlist_id=playlist_id)
|
||||||
|
browse = client.browse_all()
|
||||||
|
page = client.browse_page(browse[0].items[12])
|
||||||
return ''
|
return ''
|
||||||
32
app/tasks.py
32
app/tasks.py
@@ -49,7 +49,7 @@ def update_all_playlists_track_status(self):
|
|||||||
for playlist in playlists:
|
for playlist in playlists:
|
||||||
total_tracks = 0
|
total_tracks = 0
|
||||||
available_tracks = 0
|
available_tracks = 0
|
||||||
app.logger.debug(f"Current Playlist: {playlist.name} [{playlist.id}:{playlist.spotify_playlist_id}]" )
|
app.logger.debug(f"Current Playlist: {playlist.name} [{playlist.id}:{playlist.provider_playlist_id}]" )
|
||||||
for track in playlist.tracks:
|
for track in playlist.tracks:
|
||||||
total_tracks += 1
|
total_tracks += 1
|
||||||
if track.filesystem_path and os.path.exists(track.filesystem_path):
|
if track.filesystem_path and os.path.exists(track.filesystem_path):
|
||||||
@@ -106,21 +106,21 @@ def download_missing_tracks(self):
|
|||||||
processed_tracks = 0
|
processed_tracks = 0
|
||||||
failed_downloads = 0
|
failed_downloads = 0
|
||||||
for track in undownloaded_tracks:
|
for track in undownloaded_tracks:
|
||||||
app.logger.info(f"Processing track: {track.name} [{track.spotify_track_id}]")
|
app.logger.info(f"Processing track: {track.name} [{track.provider_track_id}]")
|
||||||
|
|
||||||
# Check if the track already exists in the output directory
|
# Check if the track already exists in the output directory
|
||||||
file_path = f"{output_dir.replace('{track-id}', track.spotify_track_id)}.mp3"
|
file_path = f"{output_dir.replace('{track-id}', track.provider_track_id)}.mp3"
|
||||||
# region search before download
|
# region search before download
|
||||||
if search_before_download:
|
if search_before_download:
|
||||||
app.logger.info(f"Searching for track in Jellyfin: {track.name}")
|
app.logger.info(f"Searching for track in Jellyfin: {track.name}")
|
||||||
spotify_track = functions.get_cached_spotify_track(track.spotify_track_id)
|
spotify_track = functions.get_cached_spotify_track(track.provider_track_id)
|
||||||
# at first try to find the track without fingerprinting it
|
# at first try to find the track without fingerprinting it
|
||||||
best_match = find_best_match_from_jellyfin(track)
|
best_match = find_best_match_from_jellyfin(track)
|
||||||
if best_match:
|
if best_match:
|
||||||
track.downloaded = True
|
track.downloaded = True
|
||||||
if track.jellyfin_id != best_match['Id']:
|
if track.jellyfin_id != best_match['Id']:
|
||||||
track.jellyfin_id = best_match['Id']
|
track.jellyfin_id = best_match['Id']
|
||||||
app.logger.info(f"Updated Jellyfin ID for track: {track.name} ({track.spotify_track_id})")
|
app.logger.info(f"Updated Jellyfin ID for track: {track.name} ({track.provider_track_id})")
|
||||||
if track.filesystem_path != best_match['Path']:
|
if track.filesystem_path != best_match['Path']:
|
||||||
track.filesystem_path = best_match['Path']
|
track.filesystem_path = best_match['Path']
|
||||||
|
|
||||||
@@ -171,8 +171,8 @@ def download_missing_tracks(self):
|
|||||||
|
|
||||||
# Attempt to download the track using spotdl
|
# Attempt to download the track using spotdl
|
||||||
try:
|
try:
|
||||||
app.logger.info(f"Trying to download track: {track.name} ({track.spotify_track_id}), spotdl timeout = 90")
|
app.logger.info(f"Trying to download track: {track.name} ({track.provider_track_id}), spotdl timeout = 90")
|
||||||
s_url = f"https://open.spotify.com/track/{track.spotify_track_id}"
|
s_url = f"https://open.spotify.com/track/{track.provider_track_id}"
|
||||||
|
|
||||||
command = [
|
command = [
|
||||||
"spotdl", "download", s_url,
|
"spotdl", "download", s_url,
|
||||||
@@ -251,7 +251,7 @@ def check_for_playlist_updates(self):
|
|||||||
|
|
||||||
for playlist in playlists:
|
for playlist in playlists:
|
||||||
playlist.last_updated = datetime.now( timezone.utc)
|
playlist.last_updated = datetime.now( timezone.utc)
|
||||||
sp_playlist = sp.playlist(playlist.spotify_playlist_id)
|
sp_playlist = sp.playlist(playlist.provider_playlist_id)
|
||||||
full_update = True
|
full_update = True
|
||||||
app.logger.info(f'Checking updates for playlist: {playlist.name}, s_snapshot = {sp_playlist['snapshot_id']}')
|
app.logger.info(f'Checking updates for playlist: {playlist.name}, s_snapshot = {sp_playlist['snapshot_id']}')
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -266,7 +266,7 @@ def check_for_playlist_updates(self):
|
|||||||
offset = 0
|
offset = 0
|
||||||
playlist.snapshot_id = sp_playlist['snapshot_id']
|
playlist.snapshot_id = sp_playlist['snapshot_id']
|
||||||
while True:
|
while True:
|
||||||
playlist_data = sp.playlist_items(playlist.spotify_playlist_id, offset=offset, limit=100)
|
playlist_data = sp.playlist_items(playlist.provider_playlist_id, offset=offset, limit=100)
|
||||||
items = playlist_data['items']
|
items = playlist_data['items']
|
||||||
spotify_tracks.update({offset + idx: track['track'] for idx, track in enumerate(items) if track['track']})
|
spotify_tracks.update({offset + idx: track['track'] for idx, track in enumerate(items) if track['track']})
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ def check_for_playlist_updates(self):
|
|||||||
break
|
break
|
||||||
offset += 100 # Move to the next batch
|
offset += 100 # Move to the next batch
|
||||||
|
|
||||||
existing_tracks = {track.spotify_track_id: track for track in playlist.tracks}
|
existing_tracks = {track.provider_track_id: track for track in playlist.tracks}
|
||||||
|
|
||||||
# Determine tracks to add and remove
|
# Determine tracks to add and remove
|
||||||
tracks_to_add = []
|
tracks_to_add = []
|
||||||
@@ -282,9 +282,9 @@ def check_for_playlist_updates(self):
|
|||||||
if track_info:
|
if track_info:
|
||||||
track_id = track_info['id']
|
track_id = track_info['id']
|
||||||
if track_id not in existing_tracks:
|
if track_id not in existing_tracks:
|
||||||
track = Track.query.filter_by(spotify_track_id=track_id).first()
|
track = Track.query.filter_by(provider_track_id=track_id).first()
|
||||||
if not track:
|
if not track:
|
||||||
track = Track(name=track_info['name'], spotify_track_id=track_id, spotify_uri=track_info['uri'], downloaded=False)
|
track = Track(name=track_info['name'], provider_track_id=track_id, provider_uri=track_info['uri'], downloaded=False)
|
||||||
db.session.add(track)
|
db.session.add(track)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
app.logger.info(f'Added new track: {track.name}')
|
app.logger.info(f'Added new track: {track.name}')
|
||||||
@@ -382,10 +382,10 @@ def update_jellyfin_id_for_downloaded_tracks(self):
|
|||||||
track.downloaded = True
|
track.downloaded = True
|
||||||
if track.jellyfin_id != best_match['Id']:
|
if track.jellyfin_id != best_match['Id']:
|
||||||
track.jellyfin_id = best_match['Id']
|
track.jellyfin_id = best_match['Id']
|
||||||
app.logger.info(f"Updated Jellyfin ID for track: {track.name} ({track.spotify_track_id})")
|
app.logger.info(f"Updated Jellyfin ID for track: {track.name} ({track.provider_track_id})")
|
||||||
if track.filesystem_path != best_match['Path']:
|
if track.filesystem_path != best_match['Path']:
|
||||||
track.filesystem_path = best_match['Path']
|
track.filesystem_path = best_match['Path']
|
||||||
app.logger.info(f"Updated filesystem_path for track: {track.name} ({track.spotify_track_id})")
|
app.logger.info(f"Updated filesystem_path for track: {track.name} ({track.provider_track_id})")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -422,10 +422,10 @@ def find_best_match_from_jellyfin(track: Track):
|
|||||||
|
|
||||||
for result in search_results:
|
for result in search_results:
|
||||||
|
|
||||||
app.logger.debug(f"Processing search result: {result['Id']}")
|
app.logger.debug(f"Processing search result: {result['Id']}, Path = {result['Path']}")
|
||||||
quality_score = compute_quality_score(result, app.config['FIND_BEST_MATCH_USE_FFPROBE'])
|
quality_score = compute_quality_score(result, app.config['FIND_BEST_MATCH_USE_FFPROBE'])
|
||||||
try:
|
try:
|
||||||
spotify_track = functions.get_cached_spotify_track(track.spotify_track_id)
|
spotify_track = functions.get_cached_spotify_track(track.provider_track_id)
|
||||||
spotify_track_name = spotify_track['name'].lower()
|
spotify_track_name = spotify_track['name'].lower()
|
||||||
spotify_artists = [artist['name'].lower() for artist in spotify_track['artists']]
|
spotify_artists = [artist['name'].lower() for artist in spotify_track['artists']]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user