Fix Plex playlists crash on Tag objects and add Unknown Artist guard

Plex API can return Tag objects mixed with playlists — these lack the
playlistType attribute, causing AttributeError. Use getattr with safe
default instead of direct attribute access.

Add 3-tier Unknown Artist guard in post-processing: checks track_info
artists, original search result, then re-fetches from metadata API
before building folder paths or embedding tags. Prevents files from
landing in Unknown Artist folders when the download context has
incomplete artist data.
This commit is contained in:
Broque Thomas 2026-04-12 10:43:51 -07:00
parent d44c78c87b
commit 1c1be6190a
2 changed files with 59 additions and 4 deletions

View file

@ -277,13 +277,13 @@ class PlexClient:
try:
for playlist in self.server.playlists():
if playlist.playlistType == 'audio':
if getattr(playlist, 'playlistType', None) == 'audio':
playlist_info = PlexPlaylistInfo.from_plex_playlist(playlist)
playlists.append(playlist_info)
logger.info(f"Retrieved {len(playlists)} audio playlists")
return playlists
except Exception as e:
logger.error(f"Error fetching playlists: {e}")
return []
@ -294,7 +294,7 @@ class PlexClient:
try:
playlist = self.server.playlist(name)
if playlist.playlistType == 'audio':
if getattr(playlist, 'playlistType', None) == 'audio':
return PlexPlaylistInfo.from_plex_playlist(playlist)
return None

View file

@ -20092,6 +20092,61 @@ def _post_process_matched_download(context_key, context, file_path):
print(f"Post-processing failed: Missing spotify_artist context.")
return
# ── UNKNOWN ARTIST GUARD ──
# If artist name is junk, attempt to resolve from track metadata before proceeding.
# This prevents files from landing in "Unknown Artist/" folders.
_junk_artist_names = {'', 'unknown', 'unknown artist', 'various artists', 'none', 'null'}
_artist_name = (spotify_artist.get('name', '') if isinstance(spotify_artist, dict) else '').strip()
if _artist_name.lower() in _junk_artist_names:
print(f"[Unknown Artist Guard] Artist name is '{_artist_name}' — attempting to resolve")
_resolved = False
track_info_guard = context.get("track_info", {}) or {}
original_search_guard = context.get("original_search_result", {}) or {}
# Try 1: Pull artist from track_info.artists
_ti_artists = track_info_guard.get('artists', [])
if isinstance(_ti_artists, list) and _ti_artists:
_first = _ti_artists[0]
_name = _first.get('name', '') if isinstance(_first, dict) else str(_first)
if _name and _name.strip().lower() not in _junk_artist_names:
spotify_artist['name'] = _name.strip()
print(f"[Unknown Artist Guard] Resolved from track_info.artists: '{_name}'")
_resolved = True
# Try 2: Pull from original_search_result
if not _resolved:
_os_artist = original_search_guard.get('artist') or original_search_guard.get('artist_name') or ''
if isinstance(_os_artist, str) and _os_artist.strip().lower() not in _junk_artist_names:
spotify_artist['name'] = _os_artist.strip()
print(f"[Unknown Artist Guard] Resolved from original_search_result: '{_os_artist}'")
_resolved = True
# Try 3: Re-fetch from metadata source using track ID
if not _resolved:
_track_id = track_info_guard.get('id') or track_info_guard.get('track_id') or ''
if _track_id:
try:
_fb_client = _get_metadata_fallback_client()
if hasattr(_fb_client, 'get_track_details'):
_details = _fb_client.get_track_details(str(_track_id))
if _details and isinstance(_details, dict):
_d_artists = _details.get('artists', [])
if isinstance(_d_artists, list) and _d_artists:
_d_first = _d_artists[0]
_d_name = _d_first.get('name', '') if isinstance(_d_first, dict) else str(_d_first)
if _d_name and _d_name.strip().lower() not in _junk_artist_names:
spotify_artist['name'] = _d_name.strip()
print(f"[Unknown Artist Guard] Resolved from metadata API: '{_d_name}'")
_resolved = True
except Exception as _guard_err:
print(f"[Unknown Artist Guard] Metadata re-fetch failed: {_guard_err}")
if not _resolved:
print(f"[Unknown Artist Guard] Could not resolve artist — proceeding with '{_artist_name}'")
context['spotify_artist'] = spotify_artist
# ── END UNKNOWN ARTIST GUARD ──
# Check if playlist folder mode is enabled (sync page playlists only)
track_info = context.get("track_info", {})
playlist_folder_mode = track_info.get("_playlist_folder_mode", False)