|
|
|
|
@ -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"""
|
|
|
|
|
|