Merge pull request #389 from Nezreka/fix/post-pr378-lint-cleanup

Drop stale post-PR378 redefs and fix B009
pull/390/head
BoulderBadgeDad 4 weeks ago committed by GitHub
commit c4626ae503
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -36,7 +36,7 @@ def extract_artist_name(artist: Any) -> str:
if isinstance(artist, dict):
return str(artist.get("name", "") or "")
if hasattr(artist, "name"):
return str(getattr(artist, "name") or "")
return str(artist.name or "")
return str(artist) if artist else ""

@ -16870,76 +16870,6 @@ def _normalize_base_album_name(base_album: str, artist_name: str) -> str:
logger.info(f"Album variant normalization: '{base_album}' -> '{normalized}'")
return normalized
def _resolve_album_group(spotify_artist: dict, album_info: dict, original_album: str = None) -> str:
"""
Smart album grouping: Start with standard, upgrade to deluxe if ANY track is deluxe.
This ensures all tracks from the same album get the same folder name.
(Adapted from GUI downloads.py)
"""
try:
with album_cache_lock:
artist_name = spotify_artist["name"]
detected_album = album_info.get('album_name', '')
# Extract base album name (without edition indicators)
if detected_album:
base_album = _get_base_album_name(detected_album)
elif original_album:
# Clean the original Soulseek album name
cleaned_original = _clean_album_title_web(original_album, artist_name)
base_album = _get_base_album_name(cleaned_original)
else:
base_album = _get_base_album_name(detected_album)
# Normalize the base name (handle case variations, etc.)
base_album = _normalize_base_album_name(base_album, artist_name)
# Create a key for this album group (artist + base album)
album_key = f"{artist_name}::{base_album}"
# Check if we already have a cached result for this album
if album_key in album_name_cache:
cached_name = album_name_cache[album_key]
logger.info(f"Using cached album name for '{album_key}': '{cached_name}'")
return cached_name
logger.info(f"Album grouping - Key: '{album_key}', Detected: '{detected_album}'")
# Check if this track indicates a deluxe edition
is_deluxe_track = False
if detected_album:
is_deluxe_track = _detect_deluxe_edition(detected_album)
elif original_album:
is_deluxe_track = _detect_deluxe_edition(original_album)
# Get current edition level for this album group (default to standard)
current_edition = album_editions.get(album_key, "standard")
# SMART ALGORITHM: Upgrade to deluxe if this track is deluxe
if is_deluxe_track and current_edition == "standard":
logger.info(f"UPGRADE: Album '{base_album}' upgraded from standard to deluxe!")
album_editions[album_key] = "deluxe"
current_edition = "deluxe"
# Build final album name based on edition level
if current_edition == "deluxe":
final_album_name = f"{base_album} (Deluxe Edition)"
else:
final_album_name = base_album
# Store the resolution in both caches
album_groups[album_key] = final_album_name
album_name_cache[album_key] = final_album_name
album_artists[album_key] = artist_name
logger.info(f"Album resolution: '{detected_album}' -> '{final_album_name}' (edition: {current_edition})")
return final_album_name
except Exception as e:
logger.error(f"Error resolving album group: {e}")
return album_info.get('album_name', 'Unknown Album')
def _clean_album_title_web(album_title: str, artist_name: str) -> str:
"""Clean up album title by removing common prefixes, suffixes, and artist redundancy"""
import re
@ -17451,302 +17381,6 @@ def _compute_m3u_folder(transfer_dir, context_type, playlist_name, artist_name='
return os.path.join(transfer_dir, playlist_sanitized)
def _build_final_path_for_track(context, spotify_artist, album_info, file_ext):
"""
SHARED PATH BUILDER - Used by both post-processing AND verification.
This ensures they always produce the same path.
Returns: (final_path, folder_created_successfully)
"""
transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer'))
track_info = context.get("track_info", {})
original_search = context.get("original_search_result", {})
playlist_folder_mode = track_info.get("_playlist_folder_mode", False)
# ENHANCE BYPASS: Place file in same location as original (different extension OK)
_source_info = track_info.get('source_info') or {}
if isinstance(_source_info, str):
try:
_source_info = json.loads(_source_info)
except (json.JSONDecodeError, TypeError):
_source_info = {}
if _source_info.get('enhance') and _source_info.get('original_file_path'):
original_path = _source_info['original_file_path']
original_dir = os.path.dirname(original_path)
original_stem = os.path.splitext(os.path.basename(original_path))[0]
final_path = os.path.join(original_dir, original_stem + file_ext)
os.makedirs(original_dir, exist_ok=True)
logger.info(f"[Enhance] Using original file location: {final_path}")
return final_path, True
# Extract year and album_type from spotify_album for template use (safe for all modes)
year = '' # Empty string instead of 'Unknown' to avoid "Unknown albumName"
spotify_album = context.get("spotify_album", {})
if spotify_album and spotify_album.get('release_date'):
release_date = spotify_album['release_date']
if release_date and len(release_date) >= 4:
year = release_date[:4]
# Album type for $albumtype template variable (Album, EP, Single, Compilation)
raw_album_type = ''
if spotify_album:
raw_album_type = spotify_album.get('album_type', '') or ''
total_tracks = (spotify_album.get('total_tracks', 0) or 0) if spotify_album else 0
if raw_album_type.lower() == 'compilation':
album_type_display = 'Compilation'
elif raw_album_type.lower() == 'album':
album_type_display = 'Album'
elif raw_album_type.lower() in ('single', 'ep'):
# Spotify labels both singles and EPs as 'single' — use track count to distinguish
if total_tracks <= 3:
album_type_display = 'Single'
elif total_tracks <= 6:
album_type_display = 'EP'
else:
album_type_display = 'Album'
elif not raw_album_type:
# No album_type from API (e.g. iTunes source) — infer from track count
if total_tracks <= 0:
album_type_display = 'Album' # Unknown, safe default
elif total_tracks <= 3:
album_type_display = 'Single'
elif total_tracks <= 6:
album_type_display = 'EP'
else:
album_type_display = 'Album'
else:
album_type_display = raw_album_type.capitalize() or 'Album'
# Determine which template type to use
if playlist_folder_mode:
# PLAYLIST MODE
playlist_name = track_info.get("_playlist_name", "Unknown Playlist")
track_name = original_search.get('spotify_clean_title') or original_search.get('title', 'Unknown Track')
_artists = original_search.get('artists') or track_info.get('artists') or []
template_context = {
'artist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name,
'albumartist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name,
'album': track_name,
'title': track_name,
'playlist_name': playlist_name,
'track_number': 1,
'disc_number': 1,
'year': year,
'quality': context.get('_audio_quality', ''),
'albumtype': album_type_display,
'_artists_list': _artists,
'_itunes_artist_id': str(spotify_artist.get('id', '')) if isinstance(spotify_artist, dict) and str(spotify_artist.get('id', '')).isdigit() and (original_search.get('_source') == 'itunes' or track_info.get('_source') == 'itunes') else None,
}
folder_path, filename_base = _get_file_path_from_template(template_context, 'playlist_path')
if folder_path and filename_base:
final_path = os.path.join(transfer_dir, folder_path, filename_base + file_ext)
os.makedirs(os.path.join(transfer_dir, folder_path), exist_ok=True)
return final_path, True
else:
# Fallback
playlist_name_sanitized = _sanitize_filename(playlist_name)
playlist_dir = os.path.join(transfer_dir, playlist_name_sanitized)
os.makedirs(playlist_dir, exist_ok=True)
artist_name_sanitized = _sanitize_filename(template_context['artist'])
track_name_sanitized = _sanitize_filename(track_name)
new_filename = f"{artist_name_sanitized} - {track_name_sanitized}{file_ext}"
return os.path.join(playlist_dir, new_filename), True
elif album_info and album_info.get('is_album'):
# ALBUM MODE
clean_track_name = album_info.get('clean_track_name', 'Unknown Track')
if original_search.get('spotify_clean_title'):
clean_track_name = original_search['spotify_clean_title']
elif album_info.get('clean_track_name'):
clean_track_name = album_info['clean_track_name']
else:
clean_track_name = original_search.get('title', 'Unknown Track')
track_number = album_info.get('track_number', 1)
if track_number is None or not isinstance(track_number, int) or track_number < 1:
track_number = 1
# Multi-disc album subfolder support
disc_number = album_info.get('disc_number', 1)
# Get structured artists list for collab artist handling
_artists = original_search.get('artists') or track_info.get('artists') or []
# Extract iTunes artist ID for primary artist resolution (only for iTunes source)
_spotify_album = context.get('spotify_album', {})
_itunes_aid = None
_track_source = original_search.get('_source') or track_info.get('_source', '')
_is_itunes = _track_source == 'itunes' or (isinstance(spotify_artist, dict) and str(spotify_artist.get('id', '')).isdigit() and _track_source != 'deezer')
if _is_itunes and isinstance(spotify_artist, dict):
_aid = spotify_artist.get('id', '')
if str(_aid).isdigit():
_itunes_aid = str(_aid)
if not _itunes_aid and _spotify_album:
_ext = _spotify_album.get('external_urls', {})
if isinstance(_ext, dict) and _ext.get('itunes_artist_id'):
_itunes_aid = _ext['itunes_artist_id']
# Resolve album-level artist name for $albumartist.
# Per-track spotify_artist may vary on collab albums or after artist name changes.
# Prefer stable album-level sources so all tracks land in the same folder.
_artist_name = spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name
_album_artist_name = _artist_name # default: same as track artist
# Build album-level artists list for collab mode resolution.
# Using album-level artists (instead of per-track _artists) ensures collab mode
# produces the SAME result for every track, preventing folder/tag splits.
_album_artists_for_collab = None # None = fall back to per-track _artists
_explicit_artist_ctx = track_info.get('_explicit_artist_context') if isinstance(track_info, dict) else None
if isinstance(_explicit_artist_ctx, dict) and _explicit_artist_ctx.get('name'):
_album_artist_name = _explicit_artist_ctx['name']
_album_artists_for_collab = [_explicit_artist_ctx]
elif isinstance(_explicit_artist_ctx, str) and _explicit_artist_ctx:
_album_artist_name = _explicit_artist_ctx
_album_artists_for_collab = [{'name': _explicit_artist_ctx}]
else:
_sa_artists = _spotify_album.get('artists', []) if _spotify_album else []
if _sa_artists:
_first_sa = _sa_artists[0]
if isinstance(_first_sa, dict) and _first_sa.get('name'):
_album_artist_name = _first_sa['name']
elif isinstance(_first_sa, str) and _first_sa:
_album_artist_name = _first_sa
_album_artists_for_collab = _sa_artists
template_context = {
'artist': _artist_name,
'albumartist': _album_artist_name,
'album': album_info['album_name'],
'title': clean_track_name,
'track_number': track_number,
'disc_number': disc_number,
'year': year,
'quality': context.get('_audio_quality', ''),
'albumtype': album_type_display,
'_artists_list': _album_artists_for_collab if _album_artists_for_collab else _artists,
'_itunes_artist_id': _itunes_aid,
}
spotify_album = context.get('spotify_album', {})
total_discs = spotify_album.get('total_discs', 1) if spotify_album else 1
# Single-track downloads from search don't know total_discs — resolve it
if total_discs <= 1 and spotify_album and spotify_album.get('id'):
# Quick check: if this track is on disc 2+, album is definitely multi-disc
if disc_number > 1:
total_discs = disc_number
else:
# Fetch album tracks to compute total_discs (cached by metadata client)
try:
_album_id = spotify_album['id']
_fb_client = _get_metadata_fallback_client()
if _fb_client:
_atd = _fb_client.get_album_tracks(str(_album_id))
if _atd and _atd.get('items'):
total_discs = max((t.get('disc_number', 1) for t in _atd['items']), default=1)
if total_discs > 1:
spotify_album['total_discs'] = total_discs
logger.info(f"[Multi-Disc] Resolved {total_discs} discs for single-track download of '{spotify_album.get('name')}'")
except Exception as _disc_err:
logger.warning(f"[Multi-Disc] Could not resolve total_discs: {_disc_err}")
# Check if user controls disc structure via $disc in their template
album_template = config_manager.get('file_organization.templates.album_path', '')
user_controls_disc = '$disc' in album_template
disc_label = config_manager.get('file_organization.disc_label', 'Disc')
# total_discs was resolved above — pass it into the template context so
# $cdnum can emit "CDxx" only for real multi-disc albums.
template_context['total_discs'] = total_discs
folder_path, filename_base = _get_file_path_from_template(template_context, 'album_path')
if folder_path and filename_base:
if total_discs > 1 and not user_controls_disc:
disc_folder = f"{disc_label} {disc_number}"
final_path = os.path.join(transfer_dir, folder_path, disc_folder, filename_base + file_ext)
os.makedirs(os.path.join(transfer_dir, folder_path, disc_folder), exist_ok=True)
else:
final_path = os.path.join(transfer_dir, folder_path, filename_base + file_ext)
os.makedirs(os.path.join(transfer_dir, folder_path), exist_ok=True)
return final_path, True
else:
# Fallback — use albumartist for folder consistency (same as template)
artist_name_sanitized = _sanitize_filename(template_context['albumartist'])
album_name_sanitized = _sanitize_filename(album_info['album_name'])
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
album_folder_name = f"{artist_name_sanitized} - {album_name_sanitized}"
album_dir = os.path.join(artist_dir, album_folder_name)
if total_discs > 1:
album_dir = os.path.join(album_dir, f"{disc_label} {disc_number}")
os.makedirs(album_dir, exist_ok=True)
final_track_name_sanitized = _sanitize_filename(clean_track_name)
new_filename = f"{track_number:02d} - {final_track_name_sanitized}{file_ext}"
return os.path.join(album_dir, new_filename), True
else:
# SINGLE MODE
clean_track_name = album_info.get('clean_track_name', 'Unknown Track') if album_info else 'Unknown Track'
if original_search.get('spotify_clean_title'):
clean_track_name = original_search['spotify_clean_title']
elif album_info and album_info.get('clean_track_name'):
clean_track_name = album_info['clean_track_name']
else:
clean_track_name = original_search.get('title', 'Unknown Track')
# Get structured artists list for collab artist handling (same as album/playlist modes)
_artists = original_search.get('artists') or track_info.get('artists') or []
# Extract iTunes artist ID for primary artist resolution (only for iTunes source)
_spotify_album = context.get('spotify_album', {})
_itunes_aid = None
_track_source = original_search.get('_source') or track_info.get('_source', '')
_is_itunes = _track_source == 'itunes' or (isinstance(spotify_artist, dict) and str(spotify_artist.get('id', '')).isdigit() and _track_source != 'deezer')
if _is_itunes and isinstance(spotify_artist, dict):
_aid = spotify_artist.get('id', '')
if str(_aid).isdigit():
_itunes_aid = str(_aid)
if not _itunes_aid and _spotify_album:
_ext = _spotify_album.get('external_urls', {})
if isinstance(_ext, dict) and _ext.get('itunes_artist_id'):
_itunes_aid = _ext['itunes_artist_id']
template_context = {
'artist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name,
'albumartist': spotify_artist["name"] if isinstance(spotify_artist, dict) else spotify_artist.name,
'album': album_info.get('album_name', clean_track_name) if album_info else clean_track_name,
'title': clean_track_name,
'track_number': 1,
'disc_number': 1,
'year': year,
'quality': context.get('_audio_quality', ''),
'albumtype': album_type_display,
'_artists_list': _artists,
'_itunes_artist_id': _itunes_aid,
}
folder_path, filename_base = _get_file_path_from_template(template_context, 'single_path')
if filename_base:
# folder_path may be '' for flat templates like "$artist - $title" (no subfolders)
if folder_path:
final_path = os.path.join(transfer_dir, folder_path, filename_base + file_ext)
os.makedirs(os.path.join(transfer_dir, folder_path), exist_ok=True)
else:
final_path = os.path.join(transfer_dir, filename_base + file_ext)
os.makedirs(transfer_dir, exist_ok=True)
return final_path, True
else:
# Fallback
artist_name_sanitized = _sanitize_filename(template_context['artist'])
final_track_name_sanitized = _sanitize_filename(clean_track_name)
artist_dir = os.path.join(transfer_dir, artist_name_sanitized)
single_folder_name = f"{artist_name_sanitized} - {final_track_name_sanitized}"
single_dir = os.path.join(artist_dir, single_folder_name)
os.makedirs(single_dir, exist_ok=True)
new_filename = f"{final_track_name_sanitized}{file_ext}"
return os.path.join(single_dir, new_filename), True
def _get_file_path_from_template_raw(template: str, context: dict) -> tuple:
"""
Build file path using a user-provided template string directly.
@ -18099,9 +17733,6 @@ def _get_file_path_from_template(context: dict, template_type: str = 'album_path
# ===================================================================
from mutagen import File as MutagenFile
from mutagen.id3 import ID3, TIT2, TPE1, TALB, TDRC, TRCK, TCON, TPE2, TPOS, TXXX, APIC, UFID, TSRC, TBPM, TCOP, TPUB, TMED, TDOR
from mutagen.flac import FLAC, Picture
from mutagen.mp4 import MP4, MP4Cover, MP4FreeForm
from mutagen.oggvorbis import OggVorbis
from mutagen.apev2 import APEv2, APENoHeaderError
import urllib.request

Loading…
Cancel
Save