diff options
| author | Warry <FunWarry@users.noreply.github.com> | 2025-11-29 12:10:06 +0100 |
|---|---|---|
| committer | Warry <FunWarry@users.noreply.github.com> | 2025-11-29 12:10:06 +0100 |
| commit | d2c3e8ab8f1dcaec2b8be7ff9d1fb6c0df2d5296 (patch) | |
| tree | 2cf51e324cca81e198758e57a1c6e5780627e255 /tidal_dl_ng/ui/media_details_helper.py | |
| parent | 28a6fe5a7dbadc9532de228a539c47ec49ca4aa2 (diff) | |
| download | tidal-dl-ng-d2c3e8ab8f1dcaec2b8be7ff9d1fb6c0df2d5296.tar.gz | |
feat(hover): add track details preview on hover + fix all ruff violations
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
Diffstat (limited to 'tidal_dl_ng/ui/media_details_helper.py')
| -rw-r--r-- | tidal_dl_ng/ui/media_details_helper.py | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/tidal_dl_ng/ui/media_details_helper.py b/tidal_dl_ng/ui/media_details_helper.py new file mode 100644 index 0000000..191bc38 --- /dev/null +++ b/tidal_dl_ng/ui/media_details_helper.py @@ -0,0 +1,115 @@ +"""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 "—") |
