library page updates

pull/49/head
Broque Thomas 8 months ago
parent 329e665db9
commit 3e8b469adb

@ -1186,15 +1186,9 @@ class JellyfinClient:
# Metadata update methods for compatibility with metadata updater
def update_artist_genres(self, artist, genres: List[str]):
"""Update artist genres - currently not implemented for Jellyfin"""
try:
# TODO: Implement Jellyfin genre update API
# For now, just log and return success to continue processing
logger.debug(f"Genre update not yet implemented for Jellyfin artist: {artist.title}")
return True
except Exception as e:
logger.error(f"Error updating genres for {artist.title}: {e}")
return False
"""Update artist genres - not implemented for Jellyfin"""
# Genre updates not supported via Jellyfin API - silently skip
return True
def update_artist_poster(self, artist, image_data: bytes):
"""Update artist poster image using Jellyfin API"""
@ -1206,49 +1200,33 @@ class JellyfinClient:
import requests
url = f"{self.base_url}/Items/{artist_id}/Images/Primary"
# Use the working approach from successful Jellyfin implementation
from base64 import b64encode
# Base64 encode the image data (key difference!)
encoded_data = b64encode(image_data)
# Add /0 to URL for image index
url = f"{self.base_url}/Items/{artist_id}/Images/Primary/0"
headers = {
'X-Emby-Token': self.api_key
'X-Emby-Token': self.api_key,
'Content-Type': 'image/jpeg'
}
# Try multiple approaches to find what works with Jellyfin
# Method 1: Try with different field names that Jellyfin might expect
method1_files = {'data': ('poster.jpg', image_data, 'image/jpeg')}
try:
response = requests.post(url, files=method1_files, headers=headers, timeout=30)
response.raise_for_status()
logger.info(f"Updated poster for {artist.title} (method 1)")
return True
except Exception as e1:
logger.debug(f"Method 1 failed for {artist.title}: {e1}")
# Method 2: Try with raw data and proper content-type
try:
headers_raw = {
'X-Emby-Token': self.api_key,
'Content-Type': 'image/jpeg'
}
response = requests.post(url, data=image_data, headers=headers_raw, timeout=30)
response.raise_for_status()
logger.info(f"Updated poster for {artist.title} (method 2)")
return True
except Exception as e2:
logger.debug(f"Method 2 failed for {artist.title}: {e2}")
# Method 3: Try with different endpoint structure
try:
alt_url = f"{self.base_url}/Items/{artist_id}/Images/Primary/0"
response = requests.post(alt_url, data=image_data, headers=headers_raw, timeout=30)
logger.debug(f"Uploading {len(image_data)} bytes (base64 encoded) for {artist.title}")
response = requests.post(url, data=encoded_data, headers=headers, timeout=30)
response.raise_for_status()
logger.info(f"Updated poster for {artist.title} (method 3)")
logger.info(f"Updated poster for {artist.title} - HTTP {response.status_code}")
return True
except Exception as e3:
logger.debug(f"Method 3 failed for {artist.title}: {e3}")
# All methods failed
logger.error(f"All image upload methods failed for {artist.title}")
return False
except Exception as e:
logger.error(f"Failed to upload poster for {artist.title}: {e}")
return False
except Exception as e:
logger.error(f"Error updating poster for {artist.title}: {e}")
return False
@ -1311,44 +1289,48 @@ class JellyfinClient:
return False
def update_artist_biography(self, artist) -> bool:
"""Update artist overview/biography - currently not implemented for Jellyfin"""
try:
# TODO: Implement Jellyfin biography update API
# For now, just log and return success to continue processing
logger.debug(f"Biography update not yet implemented for Jellyfin artist: {artist.title}")
return True
except Exception as e:
logger.error(f"Error updating biography for {artist.title}: {e}")
return False
"""Update artist overview/biography - not implemented for Jellyfin"""
# Biography updates not supported via Jellyfin API - silently skip
return True
def needs_update_by_age(self, artist, refresh_interval_days: int) -> bool:
"""Check if artist needs updating based on age threshold - simplified for Jellyfin"""
"""Check if artist needs updating based on age threshold"""
try:
# For now, just return True for all artists since we don't have timestamp tracking yet
# TODO: Implement timestamp tracking in Jellyfin artist metadata
return True
last_update = self.parse_update_timestamp(artist)
if not last_update:
# No timestamp found, needs update
return True
# Calculate days since last update
from datetime import datetime
days_since_update = (datetime.now() - last_update).days
# Use same logic as Plex client
needs_update = days_since_update >= refresh_interval_days
if not needs_update:
logger.debug(f"Skipping {artist.title}: updated {days_since_update} days ago (threshold: {refresh_interval_days})")
return needs_update
except Exception as e:
logger.debug(f"Error checking update age for {artist.title}: {e}")
return True
return True # Default to needing update if error
def is_artist_ignored(self, artist) -> bool:
"""Check if artist is manually marked to be ignored - simplified for Jellyfin"""
"""Check if artist is manually marked to be ignored"""
try:
# For now, no artists are ignored
# TODO: Implement ignore flag tracking in Jellyfin artist metadata
return False
# Check overview field where we store timestamps and ignore flags
overview = getattr(artist, 'overview', '') or ''
return '-IgnoreUpdate' in overview
except Exception as e:
logger.debug(f"Error checking ignore status for {artist.title}: {e}")
return False
def parse_update_timestamp(self, artist) -> Optional[datetime]:
"""Parse the last update timestamp from artist summary - not implemented for Jellyfin"""
try:
# TODO: Implement timestamp parsing from Jellyfin artist overview
return None
except Exception as e:
logger.debug(f"Error parsing timestamp for {artist.title}: {e}")
return None
"""Parse the last update timestamp - not implemented for Jellyfin"""
# No timestamp tracking for Jellyfin - always return None (needs update)
return None
def set_metadata_only_mode(self, enabled: bool = True):
"""Enable metadata-only mode to skip expensive track caching"""

@ -11635,12 +11635,17 @@ def start_metadata_update():
# Check active server and client availability - EXACTLY like dashboard.py
active_server = config_manager.get_active_media_server()
# Get appropriate media client - EXACTLY like dashboard.py start_metadata_update()
# Get appropriate media client - Support all three servers
if active_server == "jellyfin":
media_client = jellyfin_client
if not media_client:
add_activity_item("", "Metadata Update", "Jellyfin client not available", "Now")
return jsonify({"success": False, "error": "Jellyfin client not available"}), 400
elif active_server == "navidrome":
media_client = navidrome_client
if not media_client:
add_activity_item("", "Metadata Update", "Navidrome client not available", "Now")
return jsonify({"success": False, "error": "Navidrome client not available"}), 400
else: # plex
media_client = plex_client
if not media_client:
@ -11791,6 +11796,9 @@ class WebMetadataUpdateWorker:
# Enable lightweight mode for Jellyfin to skip track caching
if self.server_type == "jellyfin":
self.media_client.set_metadata_only_mode(True)
elif self.server_type == "navidrome":
# Navidrome doesn't need special mode setting
pass
all_artists = self.media_client.get_all_artists()
print(f"[DEBUG] Raw artists returned: {[getattr(a, 'title', 'NO_TITLE') for a in (all_artists or [])]}")
@ -11972,27 +11980,37 @@ class WebMetadataUpdateWorker:
def update_artist_photo(self, artist, spotify_artist):
"""Update artist photo from Spotify - EXACT copy from dashboard.py"""
try:
# Check if artist already has a good photo
if self.artist_has_valid_photo(artist):
# Check if artist already has a good photo (skip check for Jellyfin)
if self.server_type != "jellyfin" and self.artist_has_valid_photo(artist):
print(f"🖼️ Skipping {artist.title}: already has valid photo ({getattr(artist, 'thumb', 'None')})")
return False
# Get the image URL from Spotify
if not spotify_artist.image_url:
print(f"🚫 Skipping {artist.title}: no Spotify image URL available")
return False
print(f"📸 Processing {artist.title}: downloading from Spotify...")
image_url = spotify_artist.image_url
# Download and validate image
response = requests.get(image_url, timeout=10)
response.raise_for_status()
# Validate and convert image (skip conversion for Jellyfin to preserve format)
if self.server_type == "jellyfin":
# For Jellyfin, use raw image data to preserve original format
image_data = response.content
print(f"📸 Using raw image data for Jellyfin ({len(image_data)} bytes)")
else:
# For other servers, validate and convert
image_data = self.validate_and_convert_image(response.content)
if not image_data:
return False
# Validate and convert image
image_data = self.validate_and_convert_image(response.content)
if not image_data:
return False
# Upload to media server
return self.upload_artist_poster(artist, image_data)
# Upload to media server using client's method
return self.media_client.update_artist_poster(artist, image_data)
except Exception as e:
print(f"Error updating photo for {getattr(artist, 'title', 'Unknown')}: {e}")
@ -12216,8 +12234,35 @@ class WebMetadataUpdateWorker:
# Refresh artist to see changes
artist.refresh()
return True
# Jellyfin: Use Jellyfin API to upload artist image
elif self.server_type == "jellyfin":
import requests
jellyfin_config = config_manager.get_jellyfin_config()
jellyfin_base_url = jellyfin_config.get('base_url', '')
jellyfin_token = jellyfin_config.get('api_key', '')
if not jellyfin_base_url or not jellyfin_token:
print("❌ Jellyfin configuration missing for image upload")
return False
upload_url = f"{jellyfin_base_url.rstrip('/')}/Items/{artist.ratingKey}/Images/Primary"
headers = {
'Authorization': f'MediaBrowser Token="{jellyfin_token}"',
'Content-Type': 'image/jpeg'
}
response = requests.post(upload_url, data=image_data, headers=headers)
response.raise_for_status()
return True
# Navidrome: Currently not supported (Subsonic API doesn't support image uploads)
elif self.server_type == "navidrome":
print(" Navidrome does not support artist image uploads via Subsonic API")
return False
else:
# For other server types, return False since we only have fallback for Plex
# Unknown server type
return False
except Exception as e:

@ -277,8 +277,10 @@
</div>
<div class="tool-card" id="metadata-updater-card">
<h4 class="tool-card-title">Metadata Updater</h4>
<p class="tool-card-info">Updates artist photos, genres, and album art from Spotify.</p>
<div class="card-header">
<h3>Metadata Updater</h3>
</div>
<p class="metadata-updater-description tool-card-info">Updates artist photos, genres, and album art from Spotify.</p>
<div class="tool-card-controls">
<select id="metadata-refresh-interval">
<option value="180">6 months</option>

@ -13476,14 +13476,31 @@ async function checkAndHideMetadataUpdaterForNonPlex() {
if (data.success) {
const metadataCard = document.getElementById('metadata-updater-card');
if (metadataCard) {
if (data.active_server === 'plex') {
// Show metadata updater only for Plex
metadataCard.style.display = 'block';
console.log('Metadata updater shown: Plex is active server');
// Show metadata updater only for Plex and Jellyfin
if (data.active_server === 'plex' || data.active_server === 'jellyfin') {
metadataCard.style.display = 'flex';
console.log(`Metadata updater shown: ${data.active_server} is active server`);
// Update the header text to reflect the current server
const headerElement = metadataCard.querySelector('.card-header h3');
if (headerElement) {
const serverDisplayName = data.active_server.charAt(0).toUpperCase() + data.active_server.slice(1);
headerElement.textContent = `${serverDisplayName} Metadata Updater`;
}
// Update the description based on the server type
const descElement = metadataCard.querySelector('.metadata-updater-description');
if (descElement) {
if (data.active_server === 'jellyfin') {
descElement.textContent = 'Download and upload high-quality artist images from Spotify to your Jellyfin server for artists without photos.';
} else {
descElement.textContent = 'Download and upload high-quality artist images from Spotify to your Plex server for artists without photos.';
}
}
} else {
// Hide metadata updater for Jellyfin and Navidrome (same as dashboard.py behavior)
// Hide metadata updater for Navidrome
metadataCard.style.display = 'none';
console.log(`Metadata updater hidden: ${data.active_server} is active server`);
console.log(`Metadata updater hidden: ${data.active_server} does not support image uploads`);
}
}
}

@ -8457,7 +8457,6 @@ body {
}
#metadata-updater-card{
display: flex!important;
justify-content: space-between;
}
/* ===============================

Loading…
Cancel
Save