library page updates
This commit is contained in:
parent
329e665db9
commit
3e8b469adb
5 changed files with 138 additions and 93 deletions
|
|
@ -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)
|
||||
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 1)")
|
||||
logger.info(f"Updated poster for {artist.title} - HTTP {response.status_code}")
|
||||
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)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Updated poster for {artist.title} (method 3)")
|
||||
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…
Reference in a new issue