Major Overhaul: Cleanup Unused Files
This commit is contained in:
@@ -1,228 +0,0 @@
|
|||||||
import time
|
|
||||||
from flask import Flask, jsonify, render_template, request, redirect, url_for, session, flash
|
|
||||||
from sqlalchemy import insert
|
|
||||||
from app import app, db, jellyfin, functions, device_id,sp
|
|
||||||
from app.models import Playlist,Track, playlist_tracks
|
|
||||||
from spotipy.exceptions import SpotifyException
|
|
||||||
|
|
||||||
|
|
||||||
from jellyfin.objects import PlaylistMetadata
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/jellyfin_playlists')
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def jellyfin_playlists():
|
|
||||||
try:
|
|
||||||
# Fetch playlists from Jellyfin
|
|
||||||
playlists = jellyfin.get_playlists(session_token=functions._get_token_from_sessioncookie())
|
|
||||||
spotify_data = {'playlists': {'items': []}}
|
|
||||||
|
|
||||||
# Extract Spotify playlist IDs from the database
|
|
||||||
for pl in playlists:
|
|
||||||
# Retrieve the playlist from the database using Jellyfin ID
|
|
||||||
from_db = Playlist.query.filter_by(jellyfin_id=pl['Id']).first()
|
|
||||||
playlist_data = None
|
|
||||||
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:
|
|
||||||
app.logger.warning(f"No database entry found for Jellyfin playlist ID: {pl['Id']}")
|
|
||||||
|
|
||||||
prepared_data = functions.prepPlaylistData(spotify_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:
|
|
||||||
app.logger.error(f"Error in /jellyfin_playlists route: {str(e)}")
|
|
||||||
flash('An error occurred while fetching playlists.', 'danger')
|
|
||||||
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'])
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def add_playlist():
|
|
||||||
playlist_id = request.form.get('item_id') # HTMX sends the form data
|
|
||||||
playlist_name = request.form.get('item_name') # Optionally retrieve playlist name from the form
|
|
||||||
if not playlist_id:
|
|
||||||
flash('No playlist ID provided')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Fetch playlist from Spotify API (or any relevant API)
|
|
||||||
playlist_data = functions.get_cached_spotify_playlist(playlist_id)
|
|
||||||
|
|
||||||
# Check if playlist already exists in the database
|
|
||||||
playlist = Playlist.query.filter_by(provider_playlist_id=playlist_id).first()
|
|
||||||
|
|
||||||
if not playlist:
|
|
||||||
# Add new playlist if it doesn't exist
|
|
||||||
# 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']
|
|
||||||
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.commit()
|
|
||||||
if app.config['START_DOWNLOAD_AFTER_PLAYLIST_ADD']:
|
|
||||||
functions.manage_task('download_missing_tracks')
|
|
||||||
|
|
||||||
|
|
||||||
# Get the logged-in user
|
|
||||||
user = functions._get_logged_in_user()
|
|
||||||
playlist.tracks_available = 0
|
|
||||||
|
|
||||||
spotify_tracks = {}
|
|
||||||
offset = 0
|
|
||||||
while True:
|
|
||||||
playlist_items = sp.playlist_items(playlist.provider_playlist_id, offset=offset, limit=100)
|
|
||||||
items = playlist_items['items']
|
|
||||||
spotify_tracks.update({offset + idx: track['track'] for idx, track in enumerate(items) if track['track']})
|
|
||||||
|
|
||||||
if len(items) < 100: # No more tracks to fetch
|
|
||||||
break
|
|
||||||
offset += 100 # Move to the next batch
|
|
||||||
for idx, track_data in spotify_tracks.items():
|
|
||||||
track_info = track_data
|
|
||||||
if not track_info:
|
|
||||||
continue
|
|
||||||
track = Track.query.filter_by(provider_track_id=track_info['id']).first()
|
|
||||||
|
|
||||||
if not track:
|
|
||||||
# Add new track if it doesn't exist
|
|
||||||
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.commit()
|
|
||||||
elif track.downloaded:
|
|
||||||
playlist.tracks_available += 1
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Add track to playlist with order if it's not already associated
|
|
||||||
if track not in playlist.tracks:
|
|
||||||
# Insert into playlist_tracks with track order
|
|
||||||
stmt = insert(playlist_tracks).values(
|
|
||||||
playlist_id=playlist.id,
|
|
||||||
track_id=track.id,
|
|
||||||
track_order=idx # Maintain the order of tracks
|
|
||||||
)
|
|
||||||
db.session.execute(stmt)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
functions.update_playlist_metadata(playlist,playlist_data)
|
|
||||||
|
|
||||||
if playlist not in user.playlists:
|
|
||||||
user.playlists.append(playlist)
|
|
||||||
db.session.commit()
|
|
||||||
jellyfin.add_users_to_playlist(session_token=functions._get_api_token(), user_id=functions._get_admin_id(),playlist_id = playlist.jellyfin_id,user_ids= [user.jellyfin_user_id])
|
|
||||||
flash(f'Playlist "{playlist_data["name"]}" successfully added','success')
|
|
||||||
|
|
||||||
else:
|
|
||||||
flash(f'Playlist "{playlist_data["name"]}" already in your list')
|
|
||||||
item = {
|
|
||||||
"name" : playlist_data["name"],
|
|
||||||
"id" : playlist_id,
|
|
||||||
"can_add":False,
|
|
||||||
"can_remove":True,
|
|
||||||
"jellyfin_id" : playlist.jellyfin_id
|
|
||||||
}
|
|
||||||
return render_template('partials/_add_remove_button.html',item= item)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
flash(str(e))
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/delete_playlist/<playlist_id>', methods=['DELETE'])
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def delete_playlist(playlist_id):
|
|
||||||
# Logic to delete the playlist using JellyfinClient
|
|
||||||
try:
|
|
||||||
user = functions._get_logged_in_user()
|
|
||||||
for pl in user.playlists:
|
|
||||||
if pl.jellyfin_id == playlist_id:
|
|
||||||
user.playlists.remove(pl)
|
|
||||||
playlist = pl
|
|
||||||
jellyfin.remove_user_from_playlist(session_token= functions._get_api_token(), playlist_id= playlist_id, user_id=user.jellyfin_user_id)
|
|
||||||
db.session.commit()
|
|
||||||
flash('Playlist removed')
|
|
||||||
item = {
|
|
||||||
"name" : playlist.name,
|
|
||||||
"id" : playlist.provider_playlist_id,
|
|
||||||
"can_add":True,
|
|
||||||
"can_remove":False,
|
|
||||||
"jellyfin_id" : playlist.jellyfin_id
|
|
||||||
}
|
|
||||||
return render_template('partials/_add_remove_button.html',item= item)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f'Failed to remove item: {str(e)}')
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/wipe_playlist/<playlist_id>', methods=['DELETE'])
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def wipe_playlist(playlist_id):
|
|
||||||
playlist = Playlist.query.filter_by(jellyfin_id=playlist_id).first()
|
|
||||||
name = ""
|
|
||||||
id = ""
|
|
||||||
jf_id = ""
|
|
||||||
try:
|
|
||||||
jellyfin.remove_item(session_token=functions._get_api_token(), playlist_id=playlist_id)
|
|
||||||
except Exception as e:
|
|
||||||
flash(f"Jellyfin API Error: {str(e)}")
|
|
||||||
if playlist:
|
|
||||||
# Delete the playlist
|
|
||||||
name = playlist.name
|
|
||||||
id = playlist.provider_playlist_id
|
|
||||||
jf_id = playlist.jellyfin_id
|
|
||||||
db.session.delete(playlist)
|
|
||||||
db.session.commit()
|
|
||||||
flash('Playlist Deleted', category='info')
|
|
||||||
item = {
|
|
||||||
"name" : name,
|
|
||||||
"id" : id,
|
|
||||||
"can_add":True,
|
|
||||||
"can_remove":False,
|
|
||||||
"jellyfin_id" : jf_id
|
|
||||||
}
|
|
||||||
return render_template('partials/_add_remove_button.html',item= item)
|
|
||||||
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
@app.route('/get_jellyfin_stream/<string:jellyfin_id>')
|
|
||||||
def get_jellyfin_stream(jellyfin_id):
|
|
||||||
user_id = session['jellyfin_user_id'] # Beispiel: dynamischer Benutzer
|
|
||||||
api_key = functions._get_token_from_sessioncookie() # Beispiel: dynamischer API-Schlüssel
|
|
||||||
stream_url = f"{app.config['JELLYFIN_SERVER_URL']}/Audio/{jellyfin_id}/universal?UserId={user_id}&DeviceId={device_id}&MaxStreamingBitrate=140000000&Container=opus,webm|opus,mp3,aac,m4a|aac,m4b|aac,flac,webma,webm|webma,wav,ogg&TranscodingContainer=mp4&TranscodingProtocol=hls&AudioCodec=aac&api_key={api_key}&PlaySessionId={int(time.time())}&StartTimeTicks=0&EnableRedirection=true&EnableRemoteMedia=false"
|
|
||||||
return jsonify({'stream_url': stream_url})
|
|
||||||
|
|
||||||
@app.route('/search_jellyfin', methods=['GET'])
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def search_jellyfin():
|
|
||||||
search_query = request.args.get('search_query')
|
|
||||||
spotify_id = request.args.get('spotify_id')
|
|
||||||
if search_query:
|
|
||||||
results = jellyfin.search_music_tracks(functions._get_token_from_sessioncookie(), search_query)
|
|
||||||
# Render only the search results section as response
|
|
||||||
return render_template('partials/_jf_search_results.html', results=results,spotify_id= spotify_id,search_query = search_query)
|
|
||||||
return jsonify({'error': 'No search query provided'}), 400
|
|
||||||
355
app/routes.py
355
app/routes.py
@@ -1,355 +0,0 @@
|
|||||||
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.models import JellyfinUser,Playlist,Track
|
|
||||||
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 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
|
|
||||||
def add_context():
|
|
||||||
unlinked_track_count = len(Track.query.filter_by(downloaded=True,jellyfin_id=None).all())
|
|
||||||
version = f"v{__version__}{read_dev_build_file()}"
|
|
||||||
return dict(unlinked_track_count = unlinked_track_count, version = version, config = app.config)
|
|
||||||
|
|
||||||
|
|
||||||
# this feels wrong
|
|
||||||
skip_endpoints = ['task_status']
|
|
||||||
@app.after_request
|
|
||||||
def render_messages(response: Response) -> Response:
|
|
||||||
if request.headers.get("HX-Request"):
|
|
||||||
if request.endpoint not in skip_endpoints:
|
|
||||||
messages = render_template("partials/alerts.jinja2")
|
|
||||||
response.headers['HX-Trigger'] = 'showToastMessages'
|
|
||||||
response.data = response.data + messages.encode("utf-8")
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/tasks')
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def task_manager():
|
|
||||||
statuses = {}
|
|
||||||
for task_name, task_id in functions.TASK_STATUS.items():
|
|
||||||
if task_id:
|
|
||||||
result = AsyncResult(task_id)
|
|
||||||
statuses[task_name] = {'state': result.state, 'info': result.info if result.info else {}}
|
|
||||||
else:
|
|
||||||
statuses[task_name] = {'state': 'NOT STARTED', 'info': {}}
|
|
||||||
|
|
||||||
return render_template('admin/tasks.html', tasks=statuses,lock_keys = functions.LOCK_KEYS)
|
|
||||||
|
|
||||||
@app.route('/admin')
|
|
||||||
@app.route('/admin/link_issues')
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def link_issues():
|
|
||||||
unlinked_tracks = Track.query.filter_by(downloaded=True,jellyfin_id=None).all()
|
|
||||||
tracks = []
|
|
||||||
for ult in unlinked_tracks:
|
|
||||||
sp_track = functions.get_cached_spotify_track(ult.provider_track_id)
|
|
||||||
duration_ms = sp_track['duration_ms']
|
|
||||||
minutes = duration_ms // 60000
|
|
||||||
seconds = (duration_ms % 60000) // 1000
|
|
||||||
tracks.append({
|
|
||||||
'title': sp_track['name'],
|
|
||||||
'artist': ', '.join([artist['name'] for artist in sp_track['artists']]),
|
|
||||||
'url': sp_track['external_urls']['spotify'],
|
|
||||||
'duration': f'{minutes}:{seconds:02d}',
|
|
||||||
'preview_url': sp_track['preview_url'],
|
|
||||||
'downloaded': ult.downloaded,
|
|
||||||
'filesystem_path': ult.filesystem_path,
|
|
||||||
'jellyfin_id': ult.jellyfin_id,
|
|
||||||
'spotify_id': sp_track['id'],
|
|
||||||
'duration_ms': duration_ms,
|
|
||||||
'download_status' : ult.download_status
|
|
||||||
})
|
|
||||||
|
|
||||||
return render_template('admin/link_issues.html' , tracks = tracks )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/run_task/<task_name>', methods=['POST'])
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def run_task(task_name):
|
|
||||||
status, info = functions.manage_task(task_name)
|
|
||||||
|
|
||||||
# Rendere nur die aktualisierte Zeile der Task
|
|
||||||
task_info = {task_name: {'state': status, 'info': info}}
|
|
||||||
return render_template('partials/_task_status.html', tasks=task_info)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/task_status')
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def task_status():
|
|
||||||
statuses = {}
|
|
||||||
for task_name, task_id in functions.TASK_STATUS.items():
|
|
||||||
if task_id:
|
|
||||||
result = AsyncResult(task_id)
|
|
||||||
statuses[task_name] = {'state': result.state, 'info': result.info if result.info else {}}
|
|
||||||
else:
|
|
||||||
statuses[task_name] = {'state': 'NOT STARTED', 'info': {}}
|
|
||||||
|
|
||||||
# Render the HTML partial template instead of returning JSON
|
|
||||||
return render_template('partials/_task_status.html', tasks=statuses)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def index():
|
|
||||||
users = JellyfinUser.query.all()
|
|
||||||
return render_template('index.html', user=session['jellyfin_user_name'], users=users)
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
|
||||||
def login():
|
|
||||||
if request.method == 'POST':
|
|
||||||
username = request.form['username']
|
|
||||||
password = request.form['password']
|
|
||||||
try:
|
|
||||||
jellylogin = jellyfin.login_with_password(username=username, password=password)
|
|
||||||
if jellylogin:
|
|
||||||
session['jellyfin_access_token'], session['jellyfin_user_id'], session['jellyfin_user_name'],session['is_admin'] = jellylogin
|
|
||||||
session['debug'] = app.debug
|
|
||||||
# Check if the user already exists
|
|
||||||
user = JellyfinUser.query.filter_by(jellyfin_user_id=session['jellyfin_user_id']).first()
|
|
||||||
if not user:
|
|
||||||
# Add the user to the database if they don't exist
|
|
||||||
new_user = JellyfinUser(name=session['jellyfin_user_name'], jellyfin_user_id=session['jellyfin_user_id'], is_admin = session['is_admin'])
|
|
||||||
db.session.add(new_user)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return redirect('/')
|
|
||||||
except:
|
|
||||||
flash('Login failed. Please check your Jellyfin credentials and try again.', 'error')
|
|
||||||
return redirect(url_for('login'))
|
|
||||||
|
|
||||||
return render_template('login.html')
|
|
||||||
|
|
||||||
@app.route('/logout')
|
|
||||||
def logout():
|
|
||||||
session.pop('jellyfin_user_name', None)
|
|
||||||
session.pop('jellyfin_access_token', None)
|
|
||||||
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('/categories')
|
|
||||||
@app.route('/playlists/monitored')
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def loaditems():
|
|
||||||
country = app.config['SPOTIFY_COUNTRY_CODE']
|
|
||||||
offset = int(request.args.get('offset', 0)) # Get the offset (default to 0 for initial load)
|
|
||||||
limit = 20 # Define a limit for pagination
|
|
||||||
additional_query = ''
|
|
||||||
items_subtitle = ''
|
|
||||||
error_message = None # Placeholder for error messages
|
|
||||||
error_data = ''
|
|
||||||
if request.path == '/playlists/monitored':
|
|
||||||
try:
|
|
||||||
db_playlists = db.session.query(Playlist).offset(offset).limit(limit).all()
|
|
||||||
max_items = db.session.query(Playlist).count()
|
|
||||||
|
|
||||||
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)
|
|
||||||
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."
|
|
||||||
except SpotifyException as e:
|
|
||||||
app.logger.error(f"Error fetching monitored playlists: {e}")
|
|
||||||
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.'
|
|
||||||
error_message = items_title
|
|
||||||
error_data = max_items
|
|
||||||
|
|
||||||
elif request.path == '/playlists':
|
|
||||||
cat = request.args.get('cat', None)
|
|
||||||
if cat is not None:
|
|
||||||
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}"
|
|
||||||
else:
|
|
||||||
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':
|
|
||||||
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)
|
|
||||||
total_items = max_items
|
|
||||||
context = {
|
|
||||||
'items': data,
|
|
||||||
'next_offset': next_offset,
|
|
||||||
'total_items': total_items,
|
|
||||||
'endpoint': request.path,
|
|
||||||
'items_title': items_title,
|
|
||||||
'items_subtitle': items_subtitle,
|
|
||||||
'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
|
|
||||||
return render_template('partials/_spotify_items.html', **context)
|
|
||||||
else:
|
|
||||||
return render_template('items.html', **context)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/search')
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def searchResults():
|
|
||||||
query = request.args.get('query')
|
|
||||||
context = {}
|
|
||||||
if query:
|
|
||||||
# Add your logic here to perform the search on Spotify (or Jellyfin)
|
|
||||||
search_result = sp.search(q = query, type= 'playlist',limit= 50, market=app.config['SPOTIFY_COUNTRY_CODE'])
|
|
||||||
context = {
|
|
||||||
#'artists' : functions.prepArtistData(search_result ),
|
|
||||||
'playlists' : functions.prepPlaylistData(search_result ),
|
|
||||||
#'albums' : functions.prepAlbumData(search_result ),
|
|
||||||
'query' : query
|
|
||||||
}
|
|
||||||
return render_template('search.html', **context)
|
|
||||||
else:
|
|
||||||
return render_template('search.html', query=None, results={})
|
|
||||||
|
|
||||||
|
|
||||||
@pl_bp.route('/playlist/view/<playlist_id>')
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def get_playlist_tracks(playlist_id):
|
|
||||||
provider: MusicProviderClient = g.music_provider # Explicit type hint for g.music_provider
|
|
||||||
playlist: base.Playlist = provider.get_playlist(playlist_id)
|
|
||||||
tracks = functions.get_tracks_for_playlist(playlist.tracks) # Deine Funktion, um Tracks zu holen
|
|
||||||
# Berechne die gesamte Dauer der Playlist
|
|
||||||
total_duration_ms = sum([track['track']['duration_ms'] for track in data['tracks']['items'] if track['track']])
|
|
||||||
|
|
||||||
# Konvertiere die Gesamtdauer in ein lesbares Format
|
|
||||||
hours, remainder = divmod(total_duration_ms // 1000, 3600)
|
|
||||||
minutes, seconds = divmod(remainder, 60)
|
|
||||||
|
|
||||||
# Formatierung der Dauer
|
|
||||||
if hours > 0:
|
|
||||||
total_duration = f"{hours}h {minutes}min"
|
|
||||||
else:
|
|
||||||
total_duration = f"{minutes}min"
|
|
||||||
|
|
||||||
return render_template(
|
|
||||||
'tracks_table.html',
|
|
||||||
tracks=tracks,
|
|
||||||
total_duration=total_duration,
|
|
||||||
track_count=len(data['tracks']),
|
|
||||||
playlist_name=data['name'],
|
|
||||||
playlist_cover=data['images'][0]['url'],
|
|
||||||
playlist_description=data['description'],
|
|
||||||
last_updated = data['prepped_data'][0]['last_updated'],
|
|
||||||
last_changed = data['prepped_data'][0]['last_changed'],
|
|
||||||
item = data['prepped_data'][0],
|
|
||||||
|
|
||||||
)
|
|
||||||
@app.route('/associate_track', methods=['POST'])
|
|
||||||
@functions.jellyfin_login_required
|
|
||||||
def associate_track():
|
|
||||||
jellyfin_id = request.form.get('jellyfin_id')
|
|
||||||
spotify_id = request.form.get('spotify_id')
|
|
||||||
|
|
||||||
if not jellyfin_id or not spotify_id:
|
|
||||||
flash('Missing Jellyfin or Spotify ID')
|
|
||||||
|
|
||||||
# Retrieve the track by Spotify ID
|
|
||||||
track = Track.query.filter_by(provider_track_id=spotify_id).first()
|
|
||||||
|
|
||||||
if not track:
|
|
||||||
flash('Track not found')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
# Associate the Jellyfin ID with the track
|
|
||||||
track.jellyfin_id = jellyfin_id
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Commit the changes to the database
|
|
||||||
db.session.commit()
|
|
||||||
flash("Track associated","success")
|
|
||||||
return ''
|
|
||||||
except Exception as e:
|
|
||||||
db.session.rollback() # Roll back the session in case of an error
|
|
||||||
flash(str(e))
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/unlock_key",methods = ['POST'])
|
|
||||||
@functions.jellyfin_admin_required
|
|
||||||
def unlock_key():
|
|
||||||
|
|
||||||
key_name = request.form.get('inputLockKey')
|
|
||||||
if key_name:
|
|
||||||
tasks.release_lock(key_name)
|
|
||||||
flash(f'Lock {key_name} released', category='success')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/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 ''
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<div class="col" id="item-id-{{ item.id }}">
|
|
||||||
<div class="card shadow h-100 d-flex flex-column position-relative">
|
|
||||||
|
|
||||||
<!-- Badge: Only show if status is available (i.e., playlist has been requested) -->
|
|
||||||
{% if item.status %}
|
|
||||||
<span style="z-index: 99;" class="badge position-absolute top-0 end-0 m-2
|
|
||||||
{% if item.status == 'green' %} bg-success
|
|
||||||
{% elif item.status == 'yellow' %} bg-warning text-dark
|
|
||||||
{% else %} bg-danger {% endif %}" data-bs-toggle="tooltip" title="{% if item.track_count > 0 %}
|
|
||||||
{{ item.tracks_available }} Track Available / {{ item.tracks_linked}} Tracks linked/ {{ item.track_count}} Total
|
|
||||||
{%endif%}
|
|
||||||
">
|
|
||||||
{% if item.track_count > 0 %}
|
|
||||||
{{ item.tracks_available }} / {{ item.tracks_linked}} / {{ item.track_count}}
|
|
||||||
{% else %}
|
|
||||||
not Available
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Card Image -->
|
|
||||||
<div style="position: relative;">
|
|
||||||
<img src="{{ item.image }}" class="card-img-top" alt="{{ item.name }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Card Body -->
|
|
||||||
<div class="card-body d-flex flex-column justify-content-between">
|
|
||||||
<div>
|
|
||||||
<h5 class="card-title">{{ item.name }}</h5>
|
|
||||||
<p class="card-text">{{ item.description }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="mt-auto pt-3">
|
|
||||||
{% if item.type == 'category'%}
|
|
||||||
<a href="{{ item.url }}" class="btn btn-primary" data-bs-toggle="tooltip" title="View Playlists">
|
|
||||||
<i class="fa-solid fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
{%else%}
|
|
||||||
|
|
||||||
<a href="/playlist/view/{{ item.id }}" class="btn btn-primary" data-bs-toggle="tooltip"
|
|
||||||
title="View Playlist details">
|
|
||||||
<i class="fa-solid fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
{%endif%}
|
|
||||||
{% include 'partials/_add_remove_button.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{% for item in items %}
|
|
||||||
{% include 'partials/_spotify_item.html' %}
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if next_offset < total_items %}
|
|
||||||
<div hx-get="{{ endpoint }}?offset={{ next_offset }}{{ additional_query }}"
|
|
||||||
hx-trigger="revealed"
|
|
||||||
hx-swap="beforeend"
|
|
||||||
hx-indicator=".loading-indicator"
|
|
||||||
hx-target="#items-container"
|
|
||||||
class="loading-indicator text-center">
|
|
||||||
Loading more items...
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Show the loading indicator only when it is active
|
|
||||||
document.querySelectorAll('.loading-indicator').forEach(indicator => {
|
|
||||||
indicator.addEventListener('htmx:afterRequest', () => {
|
|
||||||
indicator.style.display = 'none'; // Hide the indicator after the request completes
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="toast align-items-center text-white {{ 'bg-success' if success else 'bg-danger' }} border-0" role="alert"
|
|
||||||
aria-live="assertive" aria-atomic="true" style="position: fixed; bottom: 20px; right: 20px; z-index: 1000;">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"
|
|
||||||
aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
|
|
||||||
var toastList = toastElList.map(function (toastEl) {
|
|
||||||
return new bootstrap.Toast(toastEl)
|
|
||||||
})
|
|
||||||
toastList.forEach(toast => toast.show());
|
|
||||||
</script>
|
|
||||||
Reference in New Issue
Block a user