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
This commit is contained in:
Warry
2025-11-29 12:10:06 +01:00
parent 28a6fe5a7d
commit d2c3e8ab8f
26 changed files with 4890 additions and 1626 deletions
+149
View File
@@ -0,0 +1,149 @@
# Hover Info Feature
## Quick Preview on Hover
TIDAL Downloader NG includes a revolutionary hover preview system that displays track metadata instantly when you hover over tracks in the results list.
### Features
- **Instant Display**: Track information appears immediately on hover (no clicks required)
- **Tabbed Interface**:
- **Details Tab**: Shows comprehensive track metadata
- **Cover Art Tab**: Displays album artwork
- **Smart Debouncing**: 350ms delay prevents UI flickering when moving the mouse
- **Zero Extra API Calls**: Uses pre-loaded track data from search results
- **Efficient Caching**: Previously viewed tracks display instantly
### Displayed Information
#### Basic Information (Instant)
- **Title**: Track name
- **Version**: Track version/remix info (if available)
- **Artists**: Track artists
- **Track #**: Track number in album
- **Album**: Album name
- **Duration**: Track length (mm:ss)
- **Codec**: Audio quality (LOSSLESS, AAC, etc.)
- **Bitrate**: Audio bitrate (or N/A for variable bitrate)
- **Release Date**: Album release date
- **Popularity**: Track popularity score
- **ISRC**: International Standard Recording Code
#### Enhanced Information (Async)
- **BPM**: Beats per minute ⏳ (loaded asynchronously from API)
- Shows `⏳ Loading...` while fetching (~100-500ms)
- Displays actual BPM value when available
- Shows `—` if not available
### Cover Art Display
- **Automatic Loading**: Album covers are loaded in the background
- **Smart Caching**: Covers are cached for instant display on re-hover
- **Preloading**: First 50 tracks in playlists are preloaded for better UX
- **Fallback**: Default album image shown if cover is unavailable
### Technical Details
#### Architecture
```
User hovers over track
↓
HoverManager detects hover (debounced 350ms)
↓
InfoTabWidget receives update signal
↓
_populate_track_details() displays basic info instantly
↓
_request_track_extras_if_needed() triggers async fetch
↓
Worker thread fetches BPM from TIDAL API
↓
Qt Signal invokes callback in main thread
↓
_update_extras_ui() updates BPM label
```
#### Performance Optimizations
1. **LRU Cache**: 256-entry cache for track extras
2. **Cover Cache**: 100-entry cache for album covers
3. **Thread-Safe**: All background operations are thread-safe
4. **Event Filtering**: Precise hover detection on tree view viewport
5. **Graceful Degradation**: Handles missing metadata gracefully
### User Experience
#### On First Hover
```
Title: Fortuna ✅ Instant
Artists: Asco ✅ Instant
Duration: 03:40 ✅ Instant
Codec: LOSSLESS ✅ Instant
BPM: ⏳ Loading... ⏱️ Loading indicator (~200ms)
↓
BPM: 136 ✅ Loaded!
```
#### On Subsequent Hover (Same Track)
```
Title: Fortuna ✅ Instant
Artists: Asco ✅ Instant
Duration: 03:40 ✅ Instant
BPM: 136 ✅ Instant (from cache)
```
### Limitations
Due to TIDAL API limitations, the following fields are **not applicable** in this hover view (they are generally not provided by the public API and thus not displayed):
- ❌ Genres
- ❌ Label
- ❌ Producers
- ❌ Composers
See [missing_metadata.md](missing_metadata.md) for detailed information about API limitations.
### Code Components
- **`InfoTabWidget`**: Main widget for displaying track information
- **`HoverManager`**: Manages hover events on tree view
- **`CoverManager`**: Handles cover art loading and caching
- **`TrackInfoFormatter`**: Formats track metadata for display
- **`TrackExtrasCache`**: LRU cache for async-loaded metadata
### Configuration
The hover delay is configurable in the code:
```python
# In hover_manager.py
self.hover_delay = 350 # milliseconds
```
Lower values = more responsive but may flicker
Higher values = more stable but less responsive
### Troubleshooting
**BPM not displaying?**
- Check console for API errors
- Verify track has BPM in TIDAL (not all tracks do)
- Cache may have stale data (restart application)
**Cover not loading?**
- Check network connection
- Some tracks may not have cover art
- Default image will be shown as fallback
**Hover not working?**
- Ensure you're hovering over the results tree view
- Check that the application has focus
- Debounce delay may need adjustment
+105
View File
@@ -0,0 +1,105 @@
# Missing Metadata in the Interface
## Why do some fields display "—" or "N/A"?
When using TIDAL Downloader NG, some metadata fields may display `—` (dash) or `N/A`. This means that **the data is not provided by the TIDAL API** for that specific track/album.
### Affected Fields
The following fields depend on data availability from the TIDAL API:
| Field | API Source | Notes | Status |
| ------------- | ------------------------------------------------------------------- | -------------------------------------- | ------------------------- |
| **BPM** | Track JSON (`bpm`) | Loaded asynchronously from API | ✅ Functional |
| **Bitrate** | Calculated based on codec | `N/A` for LOSSLESS (variable bitrate) | ✅ Functional |
| **Codec** | Track metadata | Always available (LOSSLESS, AAC, etc.) | ✅ Functional |
| **Genres** | Album JSON (`genres` or `genre`) | Not available via TIDAL API v2 | ❌ Removed from interface |
| **Label** | Album JSON (`label` or `recordLabel`) | Not reliably available | ❌ Removed from interface |
| **Producers** | Track/Album JSON (`credits` or `contributors` with role="producer") | Not available via TIDAL API v2 | ❌ Removed from interface |
| **Composers** | Track/Album JSON (`credits` or `contributors` with role="composer") | Not available via TIDAL API v2 | ❌ Removed from interface |
### Recent Improvements & API Limitations
**Current version**: The application retrieves available metadata from the TIDAL API:
- ✅ **BPM**: Loaded asynchronously from `track._data['bpm']` via API
- ✅ Display of basic information: title, artists, album, duration, codec, bitrate
- ✅ ISRC, track number, release date, popularity
- ❌ **Genres, Label, Producers, Composers**: Removed from interface (not available via API)
**TIDAL API Limitations**:
According to the official TIDAL OpenAPI specification analysis (https://tidal-music.github.io/tidal-api-reference/):
- ❌ **No reliable `genres` field** in API v2 (sometimes present but often empty)
- ❌ **No `credits` field** in API v2
- ❌ **No `contributors` field** in API v2
- ❌ **No `producers` field** available
- ❌ **No `composers` field** available
- ❌ **`label` and `recordLabel`** only available at album level, and often absent
The TIDAL API v2 (official JSON:API) does **NOT** reliably provide these metadata. The older API v1 (undocumented) may contain some of these fields randomly, but they are not guaranteed and depend entirely on what music labels provide to TIDAL.
### What is Actually Available
Fields **guaranteed** by the TIDAL API:
- ✅ `title`, `duration`, `isrc`, `explicit`
- ✅ `bpm` (optional, when provided by the label)
- ✅ `popularity` (0.0 - 1.0)
- ✅ `mediaTags` (HIRES_LOSSLESS, etc.)
- ✅ `artists` via relationship
- ✅ `album` via relationship
- ✅ `genres` via relationship (but often empty)
### Why is this Data Missing?
1. **TIDAL doesn't have the information**: Some metadata is not provided by music labels
2. **Data not exposed by API**: Even if TIDAL displays certain information on their website, the public API may not provide it
3. **Variability by track**: An album may have genres, but individual tracks may not
4. **Metadata quality**: Some independent or older tracks may have incomplete metadata
### API Response Example
For the track "Breathing (Techno)" in your screenshot:
```
Track ID: 336084531
- BPM: Not provided by TIDAL
- Label: Not provided by TIDAL
- Genres: Not provided by TIDAL
- Contributors: Maybe available but without specific role (producer/composer)
```
### How to Get More Data?
1. **Check TIDAL directly**: Sometimes data is visible on the TIDAL website but not via API
2. **Use other sources**: MusicBrainz, Discogs, etc. (not integrated in tidal-dl-ng)
3. **Manual editing**: After download, use a tag editor (Mp3tag, Kid3, etc.)
### Debugging
To see what the TIDAL API returns, you can check the console output when hovering over tracks. The application silently fetches BPM data in the background without cluttering the console.
**Technical Architecture**: BPM is loaded asynchronously:
1. Worker thread fetches raw JSON data from TIDAL API
2. `parse_track_and_album_extras()` extracts BPM from JSON
3. Qt Signal (`s_invoke_callback`) emits result to main thread
4. Stored callback is invoked in main thread (safe for UI updates)
5. `_handle_track_extras_ready` verifies it's the correct track
6. `_update_extras_ui` updates the BPM label in the interface
**Loading Indicator**: While BPM is being fetched, you'll see:
- `⏳ Loading...` - BPM data is being fetched from API (~100-500ms)
- `136` - BPM value successfully loaded
- `—` - BPM not available for this track
**Note**: If you previously hovered over a track, the BPM will display instantly on subsequent hovers thanks to the built-in cache system.
## Bitrate = N/A for LOSSLESS
The **LOSSLESS** codec (FLAC) uses lossless compression with a **variable bitrate (VBR)**. The exact bitrate depends on the audio content and changes constantly during playback. This is why it is displayed as `N/A` rather than a fixed number.
For codecs with fixed bitrate (AAC, etc.), you will see a number like "320 kbps".