added blueprint and restructured existing routes
This commit is contained in:
17
app/routes/__init__.py
Normal file
17
app/routes/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from flask import Blueprint, request, g
|
||||
from app import app
|
||||
from app.registry.music_provider_registry import MusicProviderRegistry
|
||||
|
||||
pl_bp = Blueprint('playlist', __name__)
|
||||
|
||||
@pl_bp.before_request
|
||||
def set_active_provider():
|
||||
"""
|
||||
Middleware to select the active provider based on request parameters.
|
||||
"""
|
||||
app.logger.debug(f"Setting active provider: {request.args.get('provider', 'Spotify')}")
|
||||
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
|
||||
202
app/routes/jellyfin_routes.py
Normal file
202
app/routes/jellyfin_routes.py
Normal file
@@ -0,0 +1,202 @@
|
||||
from collections import defaultdict
|
||||
import time
|
||||
from flask import Blueprint, 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 JellyfinUser, Playlist,Track, playlist_tracks
|
||||
from spotipy.exceptions import SpotifyException
|
||||
|
||||
|
||||
from app.registry.music_provider_registry import MusicProviderRegistry
|
||||
from jellyfin.objects import PlaylistMetadata
|
||||
from app.routes import pl_bp
|
||||
|
||||
@app.route('/jellyfin_playlists')
|
||||
@functions.jellyfin_login_required
|
||||
def jellyfin_playlists():
|
||||
playlists = jellyfin.get_playlists(session_token=functions._get_token_from_sessioncookie())
|
||||
playlists_by_provider = defaultdict(list)
|
||||
provider_playlists_data = {}
|
||||
|
||||
for pl in playlists:
|
||||
from_db : Playlist | None = Playlist.query.filter_by(jellyfin_id=pl['Id']).first()
|
||||
if from_db and from_db.provider_playlist_id:
|
||||
pl_id = from_db.provider_playlist_id
|
||||
playlists_by_provider[from_db.provider_id].append(from_db)
|
||||
|
||||
# 3. Fetch all Data from the provider using the get_playlist() method
|
||||
for provider_id, playlists in playlists_by_provider.items():
|
||||
try:
|
||||
provider_client = MusicProviderRegistry.get_provider(provider_id)
|
||||
except ValueError:
|
||||
flash(f"Provider {provider_id} not found.", "error")
|
||||
continue
|
||||
|
||||
combined_playlists = []
|
||||
for pl in playlists:
|
||||
provider_playlist = provider_client.get_playlist(pl.provider_playlist_id)
|
||||
# 4. Convert the playlists to CombinedPlaylistData
|
||||
combined_data = functions.prepPlaylistData(provider_playlist)
|
||||
if combined_data:
|
||||
combined_playlists.append(combined_data)
|
||||
|
||||
provider_playlists_data[provider_id] = combined_playlists
|
||||
|
||||
# 5. Display the resulting Groups in a template called 'monitored_playlists.html', one Heading per Provider
|
||||
return render_template('monitored_playlists.html', provider_playlists_data=provider_playlists_data,title="Jellyfin Playlists" , subtitle="Playlists you have added to Jellyfin")
|
||||
|
||||
@pl_bp.route('/addplaylist', methods=['POST'])
|
||||
@functions.jellyfin_login_required
|
||||
def add_playlist():
|
||||
playlist_id = request.form.get('item_id')
|
||||
playlist_name = request.form.get('item_name')
|
||||
# also get the provider id from the query params
|
||||
provider_id = request.args.get('provider')
|
||||
if not playlist_id:
|
||||
flash('No playlist ID provided')
|
||||
return ''
|
||||
# if no provider_id is provided, then show an error and return an empty string
|
||||
if not provider_id:
|
||||
flash('No provider ID provided')
|
||||
return ''
|
||||
try:
|
||||
# get the playlist from the correct provider
|
||||
provider_client = MusicProviderRegistry.get_provider(provider_id)
|
||||
playlist_data = provider_client.get_playlist(playlist_id)
|
||||
# Check if playlist already exists in the database, using the provider_id and the provider_playlist_id
|
||||
playlist = Playlist.query.filter_by(provider_playlist_id=playlist_id, provider_id=provider_id).first()
|
||||
# Add new playlist in the database if it doesn't exist
|
||||
# create the playlist via api key, with the first admin as 'owner'
|
||||
if not playlist:
|
||||
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 = len(playlist_data.tracks), tracks_available=0, jellyfin_id = fromJellyfin, provider_id=provider_id)
|
||||
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 : JellyfinUser = functions._get_logged_in_user()
|
||||
playlist.tracks_available = 0
|
||||
|
||||
for idx, track_data in enumerate(playlist_data.tracks):
|
||||
|
||||
track = Track.query.filter_by(provider_track_id=track_data.track.id, provider_id=provider_id).first()
|
||||
|
||||
if not track:
|
||||
# Add new track if it doesn't exist
|
||||
track = Track(name=track_data.track.name, provider_track_id=track_data.track.id, provider_uri=track_data.track.uri, downloaded=False,provider_id = provider_id)
|
||||
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')
|
||||
provider_track_id = request.args.get('provider_track_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,provider_track_id= provider_track_id,search_query = search_query)
|
||||
return jsonify({'error': 'No search query provided'}), 400
|
||||
412
app/routes/routes.py
Normal file
412
app/routes/routes.py
Normal file
@@ -0,0 +1,412 @@
|
||||
import json
|
||||
import os
|
||||
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.classes import AudioProfile, CombinedPlaylistData
|
||||
from app.models import JellyfinUser,Playlist,Track
|
||||
from celery.result import AsyncResult
|
||||
from typing import List
|
||||
|
||||
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
|
||||
from collections import defaultdict
|
||||
from app.routes import pl_bp
|
||||
|
||||
|
||||
@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 , registered_providers = MusicProviderRegistry.list_providers())
|
||||
|
||||
|
||||
# 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():
|
||||
# add the ability to pass a query parameter to dislplay even undownloaded tracks
|
||||
list_undownloaded = request.args.get('list_undownloaded')
|
||||
if list_undownloaded:
|
||||
unlinked_tracks = Track.query.filter_by(jellyfin_id=None).all()
|
||||
else:
|
||||
unlinked_tracks = Track.query.filter_by(downloaded=True,jellyfin_id=None).all()
|
||||
tracks = []
|
||||
for ult in unlinked_tracks:
|
||||
provider_track = functions.get_cached_provider_track(ult.provider_track_id, ult.provider_id)
|
||||
duration_ms = provider_track.duration_ms
|
||||
minutes = duration_ms // 60000
|
||||
seconds = (duration_ms % 60000) // 1000
|
||||
tracks.append({
|
||||
'title': provider_track.name,
|
||||
'artist': ', '.join([artist.name for artist in provider_track.artists]),
|
||||
'url': provider_track.external_urls,
|
||||
'duration': f'{minutes}:{seconds:02d}',
|
||||
'preview_url': '',
|
||||
'downloaded': ult.downloaded,
|
||||
'filesystem_path': ult.filesystem_path,
|
||||
'jellyfin_id': ult.jellyfin_id,
|
||||
'provider_track_id': provider_track.id,
|
||||
'duration_ms': duration_ms,
|
||||
'download_status' : ult.download_status,
|
||||
'provider_id' : ult.provider_id
|
||||
})
|
||||
|
||||
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('/playlist/open',methods=['GET'])
|
||||
@functions.jellyfin_login_required
|
||||
def openPlaylist():
|
||||
playlist = request.args.get('playlist')
|
||||
error = None
|
||||
errdata= None
|
||||
if playlist:
|
||||
for provider_id in MusicProviderRegistry.list_providers():
|
||||
try:
|
||||
provider_client = MusicProviderRegistry.get_provider(provider_id)
|
||||
extracted_playlist_id = provider_client.extract_playlist_id(playlist)
|
||||
provider_playlist = provider_client.get_playlist(extracted_playlist_id)
|
||||
|
||||
combined_data = functions.prepPlaylistData(provider_playlist)
|
||||
if combined_data:
|
||||
# If the playlist is found, redirect to the playlist view, but also include the provider ID in the URL
|
||||
return redirect(url_for('playlist.get_playlist_tracks', playlist_id=extracted_playlist_id, provider=provider_id))
|
||||
except Exception as e:
|
||||
error = f"Error fetching playlist from {provider_id}: {str(e)}"
|
||||
errdata = e
|
||||
|
||||
return render_template('index.html',error_message = error, error_data = errdata)
|
||||
|
||||
@pl_bp.route('/browse')
|
||||
@functions.jellyfin_login_required
|
||||
def browse():
|
||||
provider: MusicProviderClient = g.music_provider
|
||||
|
||||
browse_data = provider.browse()
|
||||
return render_template('browse.html', browse_data=browse_data,provider_id=provider._identifier)
|
||||
|
||||
@pl_bp.route('/browse/page/<page_id>')
|
||||
@functions.jellyfin_login_required
|
||||
def browse_page(page_id):
|
||||
provider: MusicProviderClient = g.music_provider
|
||||
combined_playlist_data : List[CombinedPlaylistData] = []
|
||||
|
||||
data = provider.browse_page(page_id)
|
||||
for item in data:
|
||||
cpd = functions.prepPlaylistData(item)
|
||||
if cpd:
|
||||
combined_playlist_data.append(cpd)
|
||||
return render_template('browse_page.html', data=combined_playlist_data,provider_id=provider._identifier)
|
||||
|
||||
@pl_bp.route('/playlists/monitored')
|
||||
@functions.jellyfin_login_required
|
||||
def monitored_playlists():
|
||||
|
||||
# 1. Get all Playlists from the Database.
|
||||
all_playlists = Playlist.query.all()
|
||||
|
||||
# 2. Group them by provider
|
||||
playlists_by_provider = defaultdict(list)
|
||||
for playlist in all_playlists:
|
||||
playlists_by_provider[playlist.provider_id].append(playlist)
|
||||
|
||||
provider_playlists_data = {}
|
||||
# 3. Fetch all Data from the provider using the get_playlist() method
|
||||
for provider_id, playlists in playlists_by_provider.items():
|
||||
try:
|
||||
provider_client = MusicProviderRegistry.get_provider(provider_id)
|
||||
except ValueError:
|
||||
flash(f"Provider {provider_id} not found.", "error")
|
||||
continue
|
||||
|
||||
combined_playlists = []
|
||||
for pl in playlists:
|
||||
provider_playlist = provider_client.get_playlist(pl.provider_playlist_id)
|
||||
# 4. Convert the playlists to CombinedPlaylistData
|
||||
combined_data = functions.prepPlaylistData(provider_playlist)
|
||||
if combined_data:
|
||||
combined_playlists.append(combined_data)
|
||||
|
||||
provider_playlists_data[provider_id] = combined_playlists
|
||||
|
||||
# 5. Display the resulting Groups in a template called 'monitored_playlists.html', one Heading per Provider
|
||||
return render_template('monitored_playlists.html', provider_playlists_data=provider_playlists_data, title="Monitored Playlists", subtitle="Playlists which are already monitored by Jellyplist and are available immediately")
|
||||
|
||||
@app.route('/search')
|
||||
@functions.jellyfin_login_required
|
||||
def searchResults():
|
||||
query = request.args.get('query')
|
||||
context = {}
|
||||
if query:
|
||||
#iterate through every registered music provider and perform the search with it.
|
||||
# Group the results by provider and display them using monitorerd_playlists.html
|
||||
search_results = defaultdict(list)
|
||||
for provider_id in MusicProviderRegistry.list_providers():
|
||||
try:
|
||||
provider_client = MusicProviderRegistry.get_provider(provider_id)
|
||||
results = provider_client.search_playlist(query)
|
||||
for result in results:
|
||||
search_results[provider_id].append(result)
|
||||
except Exception as e:
|
||||
flash(f"Error fetching search results from {provider_id}: {str(e)}", "error")
|
||||
# the grouped search results, must be prepared using the prepPlaylistData function
|
||||
for provider_id, playlists in search_results.items():
|
||||
combined_playlists = []
|
||||
for pl in playlists:
|
||||
combined_data = functions.prepPlaylistData(pl)
|
||||
if combined_data:
|
||||
combined_playlists.append(combined_data)
|
||||
search_results[provider_id] = combined_playlists
|
||||
|
||||
context['provider_playlists_data'] = search_results
|
||||
context['title'] = 'Search Results'
|
||||
context['subtitle'] = 'Search results from all providers'
|
||||
return render_template('monitored_playlists.html', **context)
|
||||
|
||||
@pl_bp.route('/track_details/<track_id>')
|
||||
@functions.jellyfin_login_required
|
||||
def track_details(track_id):
|
||||
provider_id = request.args.get('provider')
|
||||
if not provider_id:
|
||||
return jsonify({'error': 'Provider not specified'}), 400
|
||||
|
||||
track = Track.query.filter_by(provider_track_id=track_id, provider_id=provider_id).first()
|
||||
if not track:
|
||||
return jsonify({'error': 'Track not found'}), 404
|
||||
|
||||
provider_track = functions.get_cached_provider_track(track.provider_track_id, track.provider_id)
|
||||
# query also this track using the jellyfin id directly from jellyfin
|
||||
if track.jellyfin_id:
|
||||
jellyfin_track = jellyfin.get_item(session_token=functions._get_api_token(), item_id=track.jellyfin_id)
|
||||
if jellyfin_track:
|
||||
jellyfin_filesystem_path = jellyfin_track['Path']
|
||||
duration_ms = provider_track.duration_ms
|
||||
minutes = duration_ms // 60000
|
||||
seconds = (duration_ms % 60000) // 1000
|
||||
|
||||
track_details = {
|
||||
'title': provider_track.name,
|
||||
'artist': ', '.join([artist.name for artist in provider_track.artists]),
|
||||
'url': provider_track.external_urls,
|
||||
'duration': f'{minutes}:{seconds:02d}',
|
||||
'downloaded': track.downloaded,
|
||||
'filesystem_path': track.filesystem_path,
|
||||
'jellyfin_id': track.jellyfin_id,
|
||||
'provider_track_id': provider_track.id,
|
||||
'provider_track_url': provider_track.external_urls[0].url if provider_track.external_urls else None,
|
||||
'duration_ms': duration_ms,
|
||||
'download_status': track.download_status,
|
||||
'provider_id': track.provider_id,
|
||||
'jellyfin_filesystem_path': jellyfin_filesystem_path if track.jellyfin_id else None,
|
||||
}
|
||||
|
||||
return render_template('partials/track_details.html', track=track_details)
|
||||
|
||||
@pl_bp.route('/playlist/view/<playlist_id>')
|
||||
@functions.jellyfin_login_required
|
||||
def get_playlist_tracks(playlist_id):
|
||||
provider: MusicProviderClient = g.music_provider
|
||||
playlist: base.Playlist = provider.get_playlist(playlist_id)
|
||||
tracks = functions.get_tracks_for_playlist(playlist.tracks, provider_id=provider._identifier)
|
||||
total_duration_ms = sum([track.duration_ms for track in tracks])
|
||||
|
||||
# Convert the total duration to a readable format
|
||||
hours, remainder = divmod(total_duration_ms // 1000, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
|
||||
# Format the duration
|
||||
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(tracks),
|
||||
provider_id = provider._identifier,
|
||||
item=functions.prepPlaylistData(playlist),
|
||||
|
||||
)
|
||||
|
||||
@app.route('/associate_track', methods=['POST'])
|
||||
@functions.jellyfin_login_required
|
||||
def associate_track():
|
||||
jellyfin_id = request.form.get('jellyfin_id')
|
||||
provider_track_id = request.form.get('provider_track_id')
|
||||
|
||||
if not jellyfin_id or not provider_track_id:
|
||||
flash('Missing Jellyfin or Spotify ID')
|
||||
|
||||
# Retrieve the track by Spotify ID
|
||||
track = Track.query.filter_by(provider_track_id=provider_track_id).first()
|
||||
|
||||
if not track:
|
||||
flash('Track not found')
|
||||
return ''
|
||||
|
||||
# Associate the Jellyfin ID with the track
|
||||
track.jellyfin_id = jellyfin_id
|
||||
track.downloaded = True
|
||||
|
||||
|
||||
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 ''
|
||||
|
||||
|
||||
@pl_bp.route('/test')
|
||||
def test():
|
||||
#return ''
|
||||
app.logger.info(f"performing full update on jellyfin track ids. (Update tracks and playlists if better quality will be found)")
|
||||
downloaded_tracks : List[Track] = Track.query.all()
|
||||
total_tracks = len(downloaded_tracks)
|
||||
if not downloaded_tracks:
|
||||
app.logger.info("No downloaded tracks without Jellyfin ID found.")
|
||||
return {'status': 'No tracks to update'}
|
||||
|
||||
app.logger.info(f"Found {total_tracks} tracks to update ")
|
||||
processed_tracks = 0
|
||||
|
||||
for track in downloaded_tracks:
|
||||
try:
|
||||
best_match = tasks.find_best_match_from_jellyfin(track)
|
||||
if best_match:
|
||||
track.downloaded = True
|
||||
if track.jellyfin_id != best_match['Id']:
|
||||
track.jellyfin_id = best_match['Id']
|
||||
app.logger.info(f"Updated Jellyfin ID for track: {track.name} ({track.provider_track_id})")
|
||||
if track.filesystem_path != best_match['Path']:
|
||||
track.filesystem_path = best_match['Path']
|
||||
app.logger.info(f"Updated filesystem_path for track: {track.name} ({track.provider_track_id})")
|
||||
|
||||
|
||||
|
||||
db.session.commit()
|
||||
else:
|
||||
app.logger.warning(f"No matching track found in Jellyfin for {track.name}.")
|
||||
|
||||
spotify_track = None
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error searching Jellyfin for track {track.name}: {str(e)}")
|
||||
|
||||
processed_tracks += 1
|
||||
progress = (processed_tracks / total_tracks) * 100
|
||||
#self.update_state(state=f'{processed_tracks}/{total_tracks}: {track.name}', meta={'current': processed_tracks, 'total': total_tracks, 'percent': progress})
|
||||
|
||||
app.logger.info("Finished updating Jellyfin IDs for all tracks.")
|
||||
return {'status': 'All tracks updated', 'total': total_tracks, 'processed': processed_tracks}
|
||||
Reference in New Issue
Block a user