remove metadata updater tool for jellyfin since most of the support is lost.

pull/8/head
Broque Thomas 9 months ago
parent fe84f1dbb8
commit ceeb4c22dc

@ -37,6 +37,13 @@ class JellyfinArtist:
self.title = jellyfin_data.get('Name', 'Unknown Artist')
self.addedAt = self._parse_date(jellyfin_data.get('DateCreated'))
# Create genres property from Jellyfin data (empty list for now since data structure needs investigation)
self.genres = []
# TODO: Map Jellyfin genre data to match Plex format
# Create summary property from Jellyfin data (used for timestamp storage)
self.summary = jellyfin_data.get('Overview', '') or ''
def _parse_date(self, date_str: Optional[str]) -> Optional[datetime]:
"""Parse Jellyfin date string to datetime"""
if not date_str:
@ -128,6 +135,9 @@ class JellyfinClient:
self._album_cache = {}
self._track_cache = {}
self._artist_cache = {}
# Metadata-only mode flag for performance optimization
self._metadata_only_mode = False
self._all_albums_cache = None
self._all_tracks_cache = None
self._cache_populated = False
@ -245,6 +255,12 @@ class JellyfinClient:
"""Aggressively pre-populate ALL caches to eliminate individual API calls"""
if self._cache_populated:
return
# Check if we're in metadata-only mode and skip expensive operations
if self._metadata_only_mode:
logger.info("🎯 Skipping cache population for metadata-only operation")
self._cache_populated = True
return
logger.info("🚀 Starting aggressive Jellyfin cache population to eliminate slow individual API calls...")
if self._progress_callback:
@ -1155,4 +1171,183 @@ class JellyfinClient:
except Exception as e:
logger.debug(f"Error checking if Jellyfin library is scanning: {e}")
return False
# 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
def update_artist_poster(self, artist, image_data: bytes):
"""Update artist poster image using Jellyfin API"""
try:
artist_id = artist.ratingKey
if not artist_id:
return False
import requests
url = f"{self.base_url}/Items/{artist_id}/Images/Primary"
headers = {
'X-Emby-Token': self.api_key
}
# 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)
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"Error updating poster for {artist.title}: {e}")
return False
def update_album_poster(self, album, image_data: bytes):
"""Update album poster image using Jellyfin API"""
try:
album_id = album.ratingKey
if not album_id:
return False
import requests
url = f"{self.base_url}/Items/{album_id}/Images/Primary"
headers = {
'X-Emby-Token': self.api_key
}
# 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 album '{album.title}' (method 1)")
return True
except Exception as e1:
logger.debug(f"Method 1 failed for album '{album.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 album '{album.title}' (method 2)")
return True
except Exception as e2:
logger.debug(f"Method 2 failed for album '{album.title}': {e2}")
# Method 3: Try with different endpoint structure
try:
alt_url = f"{self.base_url}/Items/{album_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 album '{album.title}' (method 3)")
return True
except Exception as e3:
logger.debug(f"Method 3 failed for album '{album.title}': {e3}")
# All methods failed
logger.error(f"All image upload methods failed for album '{album.title}'")
return False
except Exception as e:
logger.error(f"Error updating poster for album '{album.title}': {e}")
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
def needs_update_by_age(self, artist, refresh_interval_days: int) -> bool:
"""Check if artist needs updating based on age threshold - simplified for Jellyfin"""
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
except Exception as e:
logger.debug(f"Error checking update age for {artist.title}: {e}")
return True
def is_artist_ignored(self, artist) -> bool:
"""Check if artist is manually marked to be ignored - simplified for Jellyfin"""
try:
# For now, no artists are ignored
# TODO: Implement ignore flag tracking in Jellyfin artist metadata
return False
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
def set_metadata_only_mode(self, enabled: bool = True):
"""Enable metadata-only mode to skip expensive track caching"""
try:
self._metadata_only_mode = enabled
if enabled:
logger.info("🎯 Metadata-only mode enabled - will skip expensive track caching")
else:
logger.info("🎯 Metadata-only mode disabled")
return True
except Exception as e:
logger.error(f"Error setting metadata-only mode: {e}")
return False

@ -1404,18 +1404,19 @@ class SimpleWishlistDownloadWorker(QRunnable):
class MetadataUpdateWorker(QThread):
"""Worker thread for updating Plex artist metadata using Spotify data"""
"""Worker thread for updating artist metadata using Spotify data (supports both Plex and Jellyfin)"""
progress_updated = pyqtSignal(str, int, int, float) # current_artist, processed, total, percentage
artist_updated = pyqtSignal(str, bool, str) # artist_name, success, details
finished = pyqtSignal(int, int, int) # total_processed, successful, failed
error = pyqtSignal(str) # error_message
artists_loaded = pyqtSignal(int, int) # total_artists, artists_to_process
def __init__(self, artists, plex_client, spotify_client, refresh_interval_days=30):
def __init__(self, artists, media_client, spotify_client, server_type, refresh_interval_days=30):
super().__init__()
self.artists = artists
self.plex_client = plex_client
self.media_client = media_client # Can be plex_client or jellyfin_client
self.spotify_client = spotify_client
self.server_type = server_type # "plex" or "jellyfin"
self.matching_engine = MusicMatchingEngine()
self.refresh_interval_days = refresh_interval_days
self.should_stop = False
@ -1428,14 +1429,23 @@ class MetadataUpdateWorker(QThread):
def stop(self):
self.should_stop = True
def get_artist_name(self, artist):
"""Get artist name consistently across Plex and Jellyfin"""
# Both Plex and Jellyfin wrapper objects have .title attribute
return getattr(artist, 'title', 'Unknown Artist')
def run(self):
"""Process all artists one by one"""
try:
# Load artists in background if not provided
if self.artists is None:
all_artists = self.plex_client.get_all_artists()
# Enable lightweight mode for Jellyfin to skip track caching
if self.server_type == "jellyfin":
self.media_client.set_metadata_only_mode(True)
all_artists = self.media_client.get_all_artists()
if not all_artists:
self.error.emit("No artists found in Plex library")
self.error.emit(f"No artists found in {self.server_type.title()} library")
return
# Filter artists that need processing
@ -1460,7 +1470,7 @@ class MetadataUpdateWorker(QThread):
artist_name = getattr(artist, 'title', 'Unknown Artist')
# Double-check ignore flag right before processing (in case it was added after loading)
if self.plex_client.is_artist_ignored(artist):
if self.media_client.is_artist_ignored(artist):
return (artist_name, True, "Skipped (ignored)")
try:
@ -1505,9 +1515,12 @@ class MetadataUpdateWorker(QThread):
def artist_needs_processing(self, artist):
"""Check if an artist needs metadata processing using age-based detection"""
try:
# Use PlexClient's age-based checking with configured interval
# This also handles the ignore flag check internally
return self.plex_client.needs_update_by_age(artist, self.refresh_interval_days)
# Check if artist is manually ignored
if self.media_client.is_artist_ignored(artist):
return False
# Use media client's age-based checking with configured interval
return self.media_client.needs_update_by_age(artist, self.refresh_interval_days)
except Exception as e:
print(f"Error checking artist {getattr(artist, 'title', 'Unknown')}: {e}")
@ -1557,14 +1570,18 @@ class MetadataUpdateWorker(QThread):
if genres_updated:
changes_made.append("genres")
# Update album artwork
albums_updated = self.update_album_artwork(artist, spotify_artist)
if albums_updated > 0:
changes_made.append(f"{albums_updated} album art")
# Update album artwork (only for Plex, skip for Jellyfin due to API issues)
if self.server_type == "plex":
albums_updated = self.update_album_artwork(artist, spotify_artist)
if albums_updated > 0:
changes_made.append(f"{albums_updated} album art")
else:
# Skip album artwork for Jellyfin until API issues are resolved
logger.debug(f"Skipping album artwork updates for Jellyfin artist: {artist.title}")
if changes_made:
# Update artist biography with timestamp to track last update
biography_updated = self.plex_client.update_artist_biography(artist)
biography_updated = self.media_client.update_artist_biography(artist)
if biography_updated:
changes_made.append("timestamp")
@ -1572,7 +1589,7 @@ class MetadataUpdateWorker(QThread):
return True, details
else:
# Even if no metadata changes, update biography to record we checked this artist
self.plex_client.update_artist_biography(artist)
self.media_client.update_artist_biography(artist)
return True, "Already up to date"
except Exception as e:
@ -1642,8 +1659,8 @@ class MetadataUpdateWorker(QThread):
print(f"[DEBUG] Updating genres for '{artist.title}' to: {genre_list}")
# Use Plex API to update genres
success = self.plex_client.update_artist_genres(artist, genre_list)
# Use media client API to update genres
success = self.media_client.update_artist_genres(artist, genre_list)
if success:
print(f"[DEBUG] Successfully updated genres for '{artist.title}'")
return True
@ -1801,8 +1818,8 @@ class MetadataUpdateWorker(QThread):
print(f"Invalid image data for album '{album_title}'")
return False
# Upload to Plex using our new method
success = self.plex_client.update_album_poster(album, image_data)
# Upload using media client
success = self.media_client.update_album_poster(album, image_data)
if success:
print(f"✅ Updated artwork for album '{album_title}'")
else:
@ -1852,26 +1869,31 @@ class MetadataUpdateWorker(QThread):
return None
def upload_artist_poster(self, artist, image_data):
"""Upload poster to Plex"""
"""Upload poster using media client"""
try:
# Use Plex client's update method if available
if hasattr(self.plex_client, 'update_artist_poster'):
return self.plex_client.update_artist_poster(artist, image_data)
# Fallback: direct Plex API call
server = self.plex_client.server
upload_url = f"{server._baseurl}/library/metadata/{artist.ratingKey}/posters"
headers = {
'X-Plex-Token': server._token,
'Content-Type': 'image/jpeg'
}
response = requests.post(upload_url, data=image_data, headers=headers)
response.raise_for_status()
# Refresh artist to see changes
artist.refresh()
return True
# Use media client's update method if available
if hasattr(self.media_client, 'update_artist_poster'):
return self.media_client.update_artist_poster(artist, image_data)
# Fallback for Plex: direct API call
if self.server_type == "plex":
import requests
server = self.media_client.server
upload_url = f"{server._baseurl}/library/metadata/{artist.ratingKey}/posters"
headers = {
'X-Plex-Token': server._token,
'Content-Type': 'image/jpeg'
}
response = requests.post(upload_url, data=image_data, headers=headers)
response.raise_for_status()
# Refresh artist to see changes
artist.refresh()
return True
else:
# For other server types, return False since we only have fallback for Plex
return False
except Exception as e:
print(f"Error uploading poster: {e}")
@ -3092,13 +3114,20 @@ class DashboardPage(QWidget):
self.database_widget = DatabaseUpdaterWidget()
self.database_widget.start_button.clicked.connect(self.toggle_database_update)
# Metadata updater widget (SECOND)
self.metadata_widget = MetadataUpdaterWidget()
self.metadata_widget.start_button.clicked.connect(self.toggle_metadata_update)
# Metadata updater widget (SECOND) - only show for Plex
from config.settings import config_manager
active_server = config_manager.get_active_media_server()
if active_server == "plex":
self.metadata_widget = MetadataUpdaterWidget()
self.metadata_widget.start_button.clicked.connect(self.toggle_metadata_update)
else:
self.metadata_widget = None # Hide for Jellyfin
layout.addWidget(header_label)
layout.addWidget(self.database_widget)
layout.addWidget(self.metadata_widget)
if self.metadata_widget: # Only add if it exists
layout.addWidget(self.metadata_widget)
return section
@ -3399,6 +3428,9 @@ class DashboardPage(QWidget):
def toggle_metadata_update(self):
"""Toggle metadata update process"""
if not self.metadata_widget:
return # Metadata widget not available (Jellyfin server)
current_text = self.metadata_widget.start_button.text()
if "Begin" in current_text:
# Start metadata update
@ -3418,13 +3450,17 @@ class DashboardPage(QWidget):
active_server = config_manager.get_active_media_server()
# Currently metadata updater only supports Plex
if active_server != "plex":
self.add_activity_item("", "Metadata Update", f"Metadata updater only supports Plex (active: {active_server.title()})", "Now")
return
if not hasattr(self, 'data_provider') or not self.data_provider.service_clients.get('plex_client'):
self.add_activity_item("", "Metadata Update", "Plex client not available", "Now")
return
# Check if we have the active media server client
if active_server == "jellyfin":
media_client = self.data_provider.service_clients.get('jellyfin_client')
if not media_client:
self.add_activity_item("", "Metadata Update", "Jellyfin client not available", "Now")
return
else:
media_client = self.data_provider.service_clients.get('plex_client')
if not media_client:
self.add_activity_item("", "Metadata Update", "Plex client not available", "Now")
return
if not self.data_provider.service_clients.get('spotify_client'):
self.add_activity_item("", "Metadata Update", "Spotify client not available", "Now")
@ -3432,13 +3468,14 @@ class DashboardPage(QWidget):
try:
# Get refresh interval from dropdown
refresh_interval_days = self.metadata_widget.get_refresh_interval_days()
refresh_interval_days = self.metadata_widget.get_refresh_interval_days() if self.metadata_widget else 30
# Start the metadata update worker (it will handle artist retrieval in background)
self.metadata_worker = MetadataUpdateWorker(
None, # Artists will be loaded in the worker thread
self.data_provider.service_clients['plex_client'],
media_client,
self.data_provider.service_clients['spotify_client'],
active_server,
refresh_interval_days
)
@ -3450,8 +3487,9 @@ class DashboardPage(QWidget):
self.metadata_worker.artists_loaded.connect(self.on_artists_loaded)
# Update UI and start
self.metadata_widget.update_progress(True, "Loading artists...", 0, 0, 0.0)
self.add_activity_item("🎵", "Metadata Update", "Loading artists from Plex library...", "Now")
if self.metadata_widget:
self.metadata_widget.update_progress(True, "Loading artists...", 0, 0, 0.0)
self.add_activity_item("🎵", "Metadata Update", "Loading artists from library...", "Now")
self.metadata_worker.start()
@ -3473,7 +3511,8 @@ class DashboardPage(QWidget):
if self.metadata_worker.isRunning():
self.metadata_worker.terminate()
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
if self.metadata_widget:
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
self.add_activity_item("⏹️", "Metadata Update", "Stopped metadata update process", "Now")
def artist_needs_processing(self, artist):
@ -3512,7 +3551,8 @@ class DashboardPage(QWidget):
def on_metadata_progress(self, current_artist, processed, total, percentage):
"""Handle metadata update progress"""
self.metadata_widget.update_progress(True, current_artist, processed, total, percentage)
if self.metadata_widget:
self.metadata_widget.update_progress(True, current_artist, processed, total, percentage)
def on_artist_updated(self, artist_name, success, details):
"""Handle individual artist update completion"""
@ -3523,13 +3563,15 @@ class DashboardPage(QWidget):
def on_metadata_finished(self, total_processed, successful, failed):
"""Handle metadata update completion"""
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
if self.metadata_widget:
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
summary = f"Processed {total_processed} artists: {successful} updated, {failed} failed"
self.add_activity_item("🎵", "Metadata Complete", summary, "Now")
def on_metadata_error(self, error_message):
"""Handle metadata update error"""
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
if self.metadata_widget:
self.metadata_widget.update_progress(False, "", 0, 0, 0.0)
self.add_activity_item("", "Metadata Error", error_message, "Now")
def on_service_status_updated(self, service: str, connected: bool, response_time: float, error: str):
@ -3569,7 +3611,8 @@ class DashboardPage(QWidget):
def on_metadata_progress_updated(self, is_running: bool, current_artist: str, processed: int, total: int, percentage: float):
"""Handle metadata update progress"""
self.metadata_widget.update_progress(is_running, current_artist, processed, total, percentage)
if self.metadata_widget:
self.metadata_widget.update_progress(is_running, current_artist, processed, total, percentage)
def on_sync_progress_updated(self, current_playlist: str, active_syncs: int):
"""Handle sync progress updates"""

Loading…
Cancel
Save