@@ -5,6 +5,8 @@ from functools import wraps
|
|||||||
from celery.result import AsyncResult
|
from celery.result import AsyncResult
|
||||||
from app.tasks import download_missing_tracks,check_for_playlist_updates, update_all_playlists_track_status, update_jellyfin_id_for_downloaded_tracks
|
from app.tasks import download_missing_tracks,check_for_playlist_updates, update_all_playlists_track_status, update_jellyfin_id_for_downloaded_tracks
|
||||||
from jellyfin.objects import PlaylistMetadata
|
from jellyfin.objects import PlaylistMetadata
|
||||||
|
from spotipy.exceptions import SpotifyException
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
TASK_STATUS = {
|
TASK_STATUS = {
|
||||||
@@ -129,12 +131,9 @@ def get_cached_spotify_playlist(playlist_id):
|
|||||||
: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.
|
||||||
"""
|
"""
|
||||||
try:
|
playlist_data = sp.playlist(playlist_id) # Fetch data from Spotify API
|
||||||
playlist_data = sp.playlist(playlist_id) # Fetch data from Spotify API
|
return playlist_data
|
||||||
return playlist_data
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f"Error fetching playlist {playlist_id} from Spotify: {str(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):
|
||||||
"""
|
"""
|
||||||
@@ -180,15 +179,21 @@ def prepArtistData(data):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getFeaturedPlaylists(country,offset):
|
def getFeaturedPlaylists(country: str, offset: int):
|
||||||
playlists_data = sp.featured_playlists(country=country, limit=16, offset=offset)
|
try:
|
||||||
|
playlists_data = sp.featured_playlists(country=country, limit=16, offset=offset)
|
||||||
return prepPlaylistData(playlists_data), playlists_data['playlists']['total'],'Featured Playlists'
|
return prepPlaylistData(playlists_data), playlists_data['playlists']['total'], 'Featured Playlists'
|
||||||
|
except SpotifyException as e:
|
||||||
|
app.logger.error(f"Spotify API error in getFeaturedPlaylists: {e}")
|
||||||
|
return [], e, f'Error: Could not load featured playlists. Please try again later. This is most likely due to an Error in the Spotify API or an rate limit has been reached.'
|
||||||
|
|
||||||
def getCategoryPlaylists(category,offset):
|
def getCategoryPlaylists(category: str, offset: int):
|
||||||
playlists_data = sp.category_playlists(category_id=category, limit=16, offset=offset)
|
try:
|
||||||
|
playlists_data = sp.category_playlists(category_id=category, 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:
|
||||||
|
app.logger.error(f"Spotify API error in getCategoryPlaylists: {e}")
|
||||||
|
return [], e, 'Error: Could not load category playlists. Please try again later. This is most likely due to an Error in the Spotify API or an rate limit has been reached.'
|
||||||
|
|
||||||
def getCategories(country,offset):
|
def getCategories(country,offset):
|
||||||
categories_data = sp.categories(limit=16, offset= offset)
|
categories_data = sp.categories(limit=16, offset= offset)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from flask import Flask, jsonify, render_template, request, redirect, url_for, s
|
|||||||
from sqlalchemy import insert
|
from sqlalchemy import insert
|
||||||
from app import app, db, jellyfin, functions, device_id,sp
|
from app import app, db, jellyfin, functions, device_id,sp
|
||||||
from app.models import Playlist,Track, playlist_tracks
|
from app.models import Playlist,Track, playlist_tracks
|
||||||
|
from spotipy.exceptions import SpotifyException
|
||||||
|
|
||||||
|
|
||||||
from jellyfin.objects import PlaylistMetadata
|
from jellyfin.objects import PlaylistMetadata
|
||||||
@@ -38,11 +38,15 @@ def jellyfin_playlists():
|
|||||||
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)
|
||||||
|
except SpotifyException as e:
|
||||||
|
app.logger.error(f"Error fetching monitored playlists: {e}")
|
||||||
|
error_data, error_message = e, f'Could not retrieve monitored Playlists. Please try again later. This is most likely due to an Error in the Spotify API or an rate limit has been reached.'
|
||||||
|
return render_template('jellyfin_playlists.html', playlists=functions.prepPlaylistData({'playlists': {'items': []}}), error_message=error_message,error_data = error_data)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.error(f"Error in /jellyfin_playlists route: {str(e)}")
|
app.logger.error(f"Error in /jellyfin_playlists route: {str(e)}")
|
||||||
flash('An error occurred while fetching playlists.', 'danger')
|
flash('An error occurred while fetching playlists.', 'danger')
|
||||||
return render_template('jellyfin_playlists.html', playlists=functions.prepPlaylistData({'playlists': {'items': []}}))
|
return render_template('jellyfin_playlists.html', playlists=functions.prepPlaylistData({'playlists': {'items': []}}), error_message='An error occurred while fetching playlists.',error_data = e)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/addplaylist', methods=['POST'])
|
@app.route('/addplaylist', methods=['POST'])
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from app import app, db, functions, sp, jellyfin, celery, jellyfin_admin_token,
|
|||||||
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 .version import __version__
|
from .version import __version__
|
||||||
|
from spotipy.exceptions import SpotifyException
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def add_context():
|
def add_context():
|
||||||
@@ -140,32 +141,46 @@ def loaditems():
|
|||||||
limit = 20 # Define a limit for pagination
|
limit = 20 # Define a limit for pagination
|
||||||
additional_query = ''
|
additional_query = ''
|
||||||
items_subtitle = ''
|
items_subtitle = ''
|
||||||
|
error_message = None # Placeholder for error messages
|
||||||
|
error_data = ''
|
||||||
if request.path == '/playlists/monitored':
|
if request.path == '/playlists/monitored':
|
||||||
# Step 1: Query the database for monitored playlists
|
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]
|
||||||
# Collect Spotify Playlist IDs from the database
|
spotify_data = functions.get_cached_spotify_playlists(tuple(spotify_playlist_ids))
|
||||||
spotify_playlist_ids = [playlist.spotify_playlist_id for playlist in db_playlists]
|
data = functions.prepPlaylistData(spotify_data)
|
||||||
|
items_title = "Monitored Playlists"
|
||||||
spotify_data = functions.get_cached_spotify_playlists(tuple(spotify_playlist_ids))
|
items_subtitle = "These playlists are already monitored by the Server. If you add one to your Jellyfin account, they will be available immediately."
|
||||||
|
except SpotifyException as e:
|
||||||
# Step 3: Pass the Spotify data to prepPlaylistData for processing
|
app.logger.error(f"Error fetching monitored playlists: {e}")
|
||||||
data = functions.prepPlaylistData(spotify_data)
|
data, max_items, items_title = [], e, f'Could not retrieve monitored Playlists. Please try again later. This is most likely due to an Error in the Spotify API or an rate limit has been reached.'
|
||||||
items_title = "Monitored Playlists"
|
error_message = items_title
|
||||||
items_subtitle = "This playlists are already monitored by the Server, if you add one of these to your Jellyfin account, they will be available immediately."
|
error_data = max_items
|
||||||
|
|
||||||
elif request.path == '/playlists':
|
elif request.path == '/playlists':
|
||||||
cat = request.args.get('cat', None)
|
cat = request.args.get('cat', None)
|
||||||
if cat is not None:
|
if cat is not None:
|
||||||
data, max_items, items_title = functions.getCategoryPlaylists(category=cat, offset=offset)
|
data, max_items, items_title = functions.getCategoryPlaylists(category=cat, offset=offset)
|
||||||
|
if not data: # Check if data is empty
|
||||||
|
error_message = items_title # Set the error message from the function
|
||||||
|
error_data = max_items
|
||||||
additional_query += f"&cat={cat}"
|
additional_query += f"&cat={cat}"
|
||||||
else:
|
else:
|
||||||
data, max_items, items_title = functions.getFeaturedPlaylists(country=country, offset=offset)
|
data, max_items, items_title = functions.getFeaturedPlaylists(country=country, offset=offset)
|
||||||
|
if not data: # Check if data is empty
|
||||||
|
error_message = items_title # Set the error message from the function
|
||||||
|
error_data = max_items
|
||||||
|
|
||||||
elif request.path == '/categories':
|
elif request.path == '/categories':
|
||||||
data, max_items, items_title = functions.getCategories(country=country, offset=offset)
|
try:
|
||||||
|
data, max_items, items_title = functions.getCategories(country=country, offset=offset)
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"Error fetching categories: {e}")
|
||||||
|
data, max_items, items_title = [], e, f'Error: Could not load categories. Please try again later. '
|
||||||
|
error_message = items_title
|
||||||
|
error_data = max_items
|
||||||
|
|
||||||
|
|
||||||
next_offset = offset + len(data)
|
next_offset = offset + len(data)
|
||||||
total_items = max_items
|
total_items = max_items
|
||||||
@@ -175,8 +190,10 @@ def loaditems():
|
|||||||
'total_items': total_items,
|
'total_items': total_items,
|
||||||
'endpoint': request.path,
|
'endpoint': request.path,
|
||||||
'items_title': items_title,
|
'items_title': items_title,
|
||||||
'items_subtitle' : items_subtitle,
|
'items_subtitle': items_subtitle,
|
||||||
'additional_query': additional_query
|
'additional_query': additional_query,
|
||||||
|
'error_message': error_message, # Pass error message to the template
|
||||||
|
'error_data': error_data, # Pass error message to the template
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.headers.get('HX-Request'): # Check if the request is from HTMX
|
if request.headers.get('HX-Request'): # Check if the request is from HTMX
|
||||||
|
|||||||
51
app/tasks.py
51
app/tasks.py
@@ -127,31 +127,34 @@ def download_missing_tracks(self):
|
|||||||
processed_tracks+=1
|
processed_tracks+=1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# region search with fingerprinting
|
# region search with fingerprinting
|
||||||
preview_url = spotify_track.get('preview_url')
|
if spotify_track:
|
||||||
if not preview_url:
|
preview_url = spotify_track.get('preview_url')
|
||||||
app.logger.error(f"Preview URL not found for track {track.name}.")
|
if not preview_url:
|
||||||
# Decide whether to skip or proceed to download
|
app.logger.error(f"Preview URL not found for track {track.name}.")
|
||||||
# For now, we'll proceed to download
|
# Decide whether to skip or proceed to download
|
||||||
else:
|
# For now, we'll proceed to download
|
||||||
# Get the list of Spotify artist names
|
|
||||||
spotify_artists = [artist['name'] for artist in spotify_track['artists']]
|
|
||||||
|
|
||||||
# Perform the search in Jellyfin
|
|
||||||
match_found, jellyfin_file_path = jellyfin.search_track_in_jellyfin(
|
|
||||||
session_token=jellyfin_admin_token,
|
|
||||||
preview_url=preview_url,
|
|
||||||
song_name=track.name,
|
|
||||||
artist_names=spotify_artists
|
|
||||||
)
|
|
||||||
if match_found:
|
|
||||||
app.logger.info(f"Match found in Jellyfin for track {track.name}. Skipping download.")
|
|
||||||
track.downloaded = True
|
|
||||||
track.filesystem_path = jellyfin_file_path
|
|
||||||
db.session.commit()
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
app.logger.info(f"No match found in Jellyfin for track {track.name}. Proceeding to download.")
|
# Get the list of Spotify artist names
|
||||||
|
spotify_artists = [artist['name'] for artist in spotify_track['artists']]
|
||||||
|
|
||||||
|
# Perform the search in Jellyfin
|
||||||
|
match_found, jellyfin_file_path = jellyfin.search_track_in_jellyfin(
|
||||||
|
session_token=jellyfin_admin_token,
|
||||||
|
preview_url=preview_url,
|
||||||
|
song_name=track.name,
|
||||||
|
artist_names=spotify_artists
|
||||||
|
)
|
||||||
|
if match_found:
|
||||||
|
app.logger.info(f"Match found in Jellyfin for track {track.name}. Skipping download.")
|
||||||
|
track.downloaded = True
|
||||||
|
track.filesystem_path = jellyfin_file_path
|
||||||
|
db.session.commit()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
app.logger.info(f"No match found in Jellyfin for track {track.name}. Proceeding to download.")
|
||||||
|
else:
|
||||||
|
app.logger.warning(f"spotify_track not set, see previous log messages")
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if error_message %}
|
||||||
|
<div class="alert alert-danger mt-5" role="alert">
|
||||||
|
<h4 class="alert-heading">🚨Something went wrong🚨</h4>
|
||||||
|
<p>{{ error_message }}</p>
|
||||||
|
<hr>
|
||||||
|
<p>Additional Information:</p>
|
||||||
|
<p>{{error_data}}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<h1 class="mb-4">{{ items_title }}</h1>
|
<h1 class="mb-4">{{ items_title }}</h1>
|
||||||
<h6 class="mb-4">{{ items_subtitle }}</h6>
|
<h6 class="mb-4">{{ items_subtitle }}</h6>
|
||||||
<div class="row row-cols-1 row-cols-md-4 row-cols-lg-6 g-4" id="items-container">
|
<div class="row row-cols-1 row-cols-md-4 row-cols-lg-6 g-4" id="items-container">
|
||||||
@@ -8,5 +18,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if error_message %}
|
||||||
|
<div class="alert alert-danger mt-5" role="alert">
|
||||||
|
<h4 class="alert-heading">🚨Something went wrong🚨</h4>
|
||||||
|
<p>{{ error_message }}</p>
|
||||||
|
<hr>
|
||||||
|
<p>Additional Information:</p>
|
||||||
|
<p>{{error_data}}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<h1 >Your subscribed Jellyfin Playlists</h1>
|
<h1 >Your subscribed Jellyfin Playlists</h1>
|
||||||
<h6 ></h6>
|
<h6 ></h6>
|
||||||
<div class="row row-cols-1 row-cols-md-4 row-cols-lg-6 g-4" id="items-container">
|
<div class="row row-cols-1 row-cols-md-4 row-cols-lg-6 g-4" id="items-container">
|
||||||
@@ -10,6 +20,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{%endif%}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user