Add Jellyfin music library selection support
Introduces backend and frontend functionality to list and select Jellyfin music libraries. Adds API endpoints, updates the client logic, and provides a UI selector for users to choose their preferred Jellyfin music library.
This commit is contained in:
parent
eae2a9c135
commit
b42d0252a1
4 changed files with 204 additions and 2 deletions
|
|
@ -266,7 +266,61 @@ class JellyfinClient:
|
|||
|
||||
except Exception as e:
|
||||
logger.error(f"Error finding music library: {e}")
|
||||
|
||||
|
||||
def get_available_music_libraries(self) -> List[Dict[str, str]]:
|
||||
"""Get list of all available music libraries from Jellyfin"""
|
||||
if not self.ensure_connection() or not self.user_id:
|
||||
return []
|
||||
|
||||
try:
|
||||
views_response = self._make_request(f'/Users/{self.user_id}/Views')
|
||||
if not views_response:
|
||||
return []
|
||||
|
||||
music_libraries = []
|
||||
for view in views_response.get('Items', []):
|
||||
collection_type = (view.get('CollectionType') or '').lower()
|
||||
if collection_type == 'music':
|
||||
music_libraries.append({
|
||||
'title': view.get('Name', 'Music'),
|
||||
'key': str(view['Id'])
|
||||
})
|
||||
|
||||
logger.debug(f"Found {len(music_libraries)} music libraries")
|
||||
return music_libraries
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting music libraries: {e}")
|
||||
return []
|
||||
|
||||
def set_music_library_by_name(self, library_name: str) -> bool:
|
||||
"""Set the active music library by name"""
|
||||
if not self.user_id:
|
||||
return False
|
||||
|
||||
try:
|
||||
views_response = self._make_request(f'/Users/{self.user_id}/Views')
|
||||
if not views_response:
|
||||
return False
|
||||
|
||||
for view in views_response.get('Items', []):
|
||||
collection_type = (view.get('CollectionType') or '').lower()
|
||||
if collection_type == 'music' and view.get('Name') == library_name:
|
||||
self.music_library_id = view['Id']
|
||||
logger.info(f"Set music library to: {library_name}")
|
||||
|
||||
# Store preference in database
|
||||
from database.music_database import MusicDatabase
|
||||
db = MusicDatabase()
|
||||
db.set_preference('jellyfin_music_library', library_name)
|
||||
|
||||
return True
|
||||
|
||||
logger.warning(f"Music library '{library_name}' not found")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting music library: {e}")
|
||||
return False
|
||||
|
||||
def _make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
"""Make authenticated request to Jellyfin API"""
|
||||
if not self.base_url or not self.api_key:
|
||||
|
|
|
|||
|
|
@ -2244,6 +2244,58 @@ def select_plex_music_library():
|
|||
logger.error(f"Error setting Plex music library: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/jellyfin/music-libraries', methods=['GET'])
|
||||
def get_jellyfin_music_libraries():
|
||||
"""Get list of all available music libraries from Jellyfin"""
|
||||
try:
|
||||
libraries = jellyfin_client.get_available_music_libraries()
|
||||
|
||||
# Get currently selected library
|
||||
from database.music_database import MusicDatabase
|
||||
db = MusicDatabase()
|
||||
selected_library = db.get_preference('jellyfin_music_library')
|
||||
|
||||
# Get the currently active library name (match Plex behavior)
|
||||
current_library = None
|
||||
if jellyfin_client.music_library_id:
|
||||
# Look up library name from ID
|
||||
for lib in libraries:
|
||||
if lib['key'] == jellyfin_client.music_library_id:
|
||||
current_library = lib['title']
|
||||
break
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"libraries": libraries,
|
||||
"selected": selected_library,
|
||||
"current": current_library
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Jellyfin music libraries: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/jellyfin/select-music-library', methods=['POST'])
|
||||
def select_jellyfin_music_library():
|
||||
"""Set the active Jellyfin music library"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
library_name = data.get('library_name')
|
||||
|
||||
if not library_name:
|
||||
return jsonify({"success": False, "error": "No library name provided"}), 400
|
||||
|
||||
success = jellyfin_client.set_music_library_by_name(library_name)
|
||||
|
||||
if success:
|
||||
add_activity_item("📚", "Library Selected", f"Jellyfin music library set to: {library_name}", "Now")
|
||||
return jsonify({"success": True, "message": f"Music library set to: {library_name}"})
|
||||
else:
|
||||
return jsonify({"success": False, "error": f"Library '{library_name}' not found"}), 404
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting Jellyfin music library: {e}")
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
# ===============================
|
||||
# == QUALITY PROFILE API ==
|
||||
# ===============================
|
||||
|
|
|
|||
|
|
@ -2353,6 +2353,14 @@
|
|||
<button class="detect-button" onclick="autoDetectJellyfin()">Auto-detect</button>
|
||||
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="jellyfin-library-selector-container" style="display: none;">
|
||||
<label>Music Library</label>
|
||||
<select id="jellyfin-music-library" onchange="selectJellyfinLibrary()">
|
||||
<option value="">Select Library</option>
|
||||
</select>
|
||||
<small>Select which music library to use (doesn't affect config file)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navidrome Settings -->
|
||||
|
|
|
|||
|
|
@ -1504,6 +1504,11 @@ async function loadSettingsData() {
|
|||
loadPlexMusicLibraries();
|
||||
}
|
||||
|
||||
// Load Jellyfin music libraries if Jellyfin is the active server
|
||||
if (activeServer === 'jellyfin') {
|
||||
loadJellyfinMusicLibraries();
|
||||
}
|
||||
|
||||
// Populate Soulseek settings
|
||||
document.getElementById('soulseek-url').value = settings.soulseek?.slskd_url || '';
|
||||
document.getElementById('soulseek-api-key').value = settings.soulseek?.api_key || '';
|
||||
|
|
@ -1576,6 +1581,11 @@ function toggleServer(serverType) {
|
|||
if (serverType === 'plex') {
|
||||
loadPlexMusicLibraries();
|
||||
}
|
||||
|
||||
// Load Jellyfin music libraries when switching to Jellyfin
|
||||
if (serverType === 'jellyfin') {
|
||||
loadJellyfinMusicLibraries();
|
||||
}
|
||||
}
|
||||
|
||||
// ===============================
|
||||
|
|
@ -1908,9 +1918,16 @@ async function testConnection(service) {
|
|||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
showToast(`${service} connection successful`, 'success');
|
||||
|
||||
// Load music libraries after successful connection
|
||||
if (service === 'plex') {
|
||||
loadPlexMusicLibraries();
|
||||
} else if (service === 'jellyfin') {
|
||||
loadJellyfinMusicLibraries();
|
||||
}
|
||||
} else {
|
||||
showToast(`${service} connection failed: ${result.error}`, 'error');
|
||||
}
|
||||
|
|
@ -26667,6 +26684,77 @@ async function selectPlexLibrary() {
|
|||
}
|
||||
}
|
||||
|
||||
// ============ Jellyfin Music Library Selection ============
|
||||
|
||||
async function loadJellyfinMusicLibraries() {
|
||||
try {
|
||||
const response = await fetch('/api/jellyfin/music-libraries');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.libraries && data.libraries.length > 0) {
|
||||
const selector = document.getElementById('jellyfin-music-library');
|
||||
const container = document.getElementById('jellyfin-library-selector-container');
|
||||
|
||||
// Clear existing options
|
||||
selector.innerHTML = '';
|
||||
|
||||
// Add options for each library
|
||||
data.libraries.forEach(library => {
|
||||
const option = document.createElement('option');
|
||||
option.value = library.title;
|
||||
option.textContent = library.title;
|
||||
|
||||
// Mark the currently selected library
|
||||
if (library.title === data.current || library.title === data.selected) {
|
||||
option.selected = true;
|
||||
}
|
||||
|
||||
selector.appendChild(option);
|
||||
});
|
||||
|
||||
// Show the container
|
||||
container.style.display = 'block';
|
||||
} else {
|
||||
// Hide if no libraries found or not connected
|
||||
document.getElementById('jellyfin-library-selector-container').style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading Jellyfin music libraries:', error);
|
||||
document.getElementById('jellyfin-library-selector-container').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function selectJellyfinLibrary() {
|
||||
const selector = document.getElementById('jellyfin-music-library');
|
||||
const selectedLibrary = selector.value;
|
||||
|
||||
if (!selectedLibrary) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/jellyfin/select-music-library', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
library_name: selectedLibrary
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
console.log(`Jellyfin music library switched to: ${selectedLibrary}`);
|
||||
} else {
|
||||
console.error('Failed to switch library:', data.error);
|
||||
alert(`Failed to switch library: ${data.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error selecting Jellyfin library:', error);
|
||||
alert('Error selecting library. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// == DISCOVER PAGE ==
|
||||
// ============================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue