Implement comprehensive track information display on hover and resolve all code quality issues to achieve complete ruff compliance. MAJOR FEATURES: - Track details preview on hover with rich metadata display - Async BPM fetching with loading indicator - Enhanced metadata utilities with fuzzy matching - TIDAL API extras integration FILES MODIFIED: 15+ files (core, UI, tests, docs) RUFF COMPLIANCE: 0 errors (89+ violations resolved) - Fixed E999 syntax error in metadata_utils.py - Refactored 10+ complex functions (C901) - Updated error handling (S110, S112, SIM105) - Modernized type hints (UP038) - Fixed naming conflicts (A001, F811) BUG FIXES: - Added missing _on_update_cover() method (11 test failures) - Fixed BPM display and async loading - Improved thread safety TESTING: 30 passed, 1 skipped DOCUMENTATION: Windows PowerShell support, tox troubleshooting
116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
"""Helpers for populating media details in InfoTabWidget - Reduces code duplication."""
|
|
|
|
from tidal_dl_ng.helper.metadata_utils import find_attr, safe_str
|
|
from tidal_dl_ng.helper.tidal import name_builder_title
|
|
from tidal_dl_ng.ui.info_tab_widget import TrackInfoFormatter
|
|
|
|
|
|
class MediaDetailsHelper:
|
|
"""Helper class to populate media details, reducing duplication in InfoTabWidget."""
|
|
|
|
@staticmethod
|
|
def populate_basic_fields(labels: dict, media, media_type: str = "track") -> None:
|
|
"""Populate fields common to all media types.
|
|
|
|
Args:
|
|
labels: Dictionary mapping field names to QLabel widgets
|
|
media: The media object (Track, Video, Album, etc.)
|
|
media_type: Type of media ("track", "video", "album")
|
|
"""
|
|
# Title and version
|
|
title = name_builder_title(media) if media_type != "album" else getattr(media, "name", None)
|
|
version = getattr(media, "version", None)
|
|
labels["title"].setText(safe_str(title))
|
|
labels["version"].setText(safe_str(version))
|
|
|
|
# Artists
|
|
artists = TrackInfoFormatter.format_artists(media)
|
|
labels["artists"].setText(safe_str(artists))
|
|
|
|
# Duration
|
|
duration = TrackInfoFormatter.format_duration(getattr(media, "duration", None))
|
|
labels["duration"].setText(safe_str(duration))
|
|
|
|
# Popularity
|
|
popularity = getattr(media, "popularity", None)
|
|
labels["popularity"].setText(safe_str(popularity))
|
|
|
|
@staticmethod
|
|
def populate_album_fields(labels: dict, media) -> None:
|
|
"""Populate album-related fields."""
|
|
album_name = None
|
|
release_date = None
|
|
|
|
if hasattr(media, "album") and media.album:
|
|
album_name = getattr(media.album, "name", None)
|
|
if hasattr(media.album, "release_date"):
|
|
release_date = TrackInfoFormatter.format_date(media.album.release_date)
|
|
elif hasattr(media, "release_date"):
|
|
release_date = TrackInfoFormatter.format_date(media.release_date)
|
|
|
|
labels["album"].setText(safe_str(album_name))
|
|
labels["release_date"].setText(safe_str(release_date))
|
|
|
|
@staticmethod
|
|
def populate_technical_fields(labels: dict, media) -> None:
|
|
"""Populate technical fields (codec, bitrate, ISRC, track number)."""
|
|
# Codec & Bitrate
|
|
codec = TrackInfoFormatter.format_codec(media)
|
|
labels["codec"].setText(safe_str(codec))
|
|
bitrate = TrackInfoFormatter.format_bitrate(media)
|
|
labels["bitrate"].setText(safe_str(bitrate))
|
|
|
|
# ISRC
|
|
isrc = getattr(media, "isrc", None)
|
|
labels["isrc"].setText(safe_str(isrc))
|
|
|
|
# Track Number
|
|
track_number = find_attr(media, ["track_number", "tracknumber", "number", "position"])
|
|
if not track_number and hasattr(media, "album") and media.album:
|
|
track_number = find_attr(media.album, ["track_number", "tracknumber"])
|
|
labels["track_number"].setText(safe_str(track_number))
|
|
|
|
@staticmethod
|
|
def populate_metadata_fields(labels: dict, media) -> None:
|
|
"""Populate metadata fields (BPM, Label, Producers, Composers)."""
|
|
# BPM
|
|
bpm = find_attr(media, ["bpm", "tempo"])
|
|
if not bpm and hasattr(media, "album") and media.album:
|
|
bpm = find_attr(media.album, ["bpm", "tempo"])
|
|
labels["bpm"].setText(safe_str(bpm))
|
|
|
|
# Label
|
|
label = None
|
|
if hasattr(media, "album") and media.album:
|
|
label = find_attr(media.album, ["label", "label_name", "recordLabel"])
|
|
if not label:
|
|
label = find_attr(media, ["label", "label_name", "recordLabel"])
|
|
labels["label"].setText(safe_str(label))
|
|
|
|
# Producers/Composers
|
|
producers = find_attr(media, ["producers", "producer"])
|
|
labels["producers"].setText(safe_str(producers) if producers else "—")
|
|
|
|
composers = find_attr(media, ["composers", "composer"])
|
|
labels["composers"].setText(safe_str(composers) if composers else "—")
|
|
|
|
@staticmethod
|
|
def populate_genres(labels: dict, media) -> None:
|
|
"""Extract and populate genre information with filtering."""
|
|
genres = find_attr(media, ["genres", "genre"])
|
|
if not genres and hasattr(media, "album") and media.album:
|
|
genres = find_attr(media.album, ["genres", "genre"])
|
|
|
|
if not genres:
|
|
labels["genres"].setText("—")
|
|
return
|
|
|
|
# Normalize to list and filter technical tags
|
|
genre_list = genres if isinstance(genres, list | tuple) else [genres]
|
|
excluded = ("atmos", "dolby", "hq", "lossless", "mqa", "flac", "wav", "pcm", "audio", "bit", "track")
|
|
filtered = [
|
|
str(g) for g in genre_list if g and isinstance(g, str) and not any(kw in g.lower() for kw in excluded)
|
|
]
|
|
|
|
labels["genres"].setText(safe_str(", ".join(filtered)) if filtered else "—")
|