This commit is contained in:
2026-01-07 05:09:59 -08:00
parent edc33f4528
commit f0799191ff
10 changed files with 956 additions and 353 deletions

View File

@@ -11,6 +11,15 @@ import subprocess
from API.HTTP import HTTPClient
from ProviderCore.base import SearchResult
try:
from Provider.HIFI import HIFI
except ImportError: # pragma: no cover - optional
HIFI = None
from Provider.tidal_shared import (
build_track_tags,
extract_artists,
stringify,
)
try: # Optional dependency for IMDb scraping
from imdbinfo.services import search_title # type: ignore
except ImportError: # pragma: no cover - optional
@@ -1416,6 +1425,95 @@ except Exception:
# Registry ---------------------------------------------------------------
class TidalMetadataProvider(MetadataProvider):
"""Metadata provider that reuses the HIFI search provider for tidal info."""
@property
def name(self) -> str: # type: ignore[override]
return "tidal"
def __init__(self, config: Optional[Dict[str, Any]] = None) -> None:
if HIFI is None:
raise RuntimeError("HIFI provider unavailable for tidal metadata")
super().__init__(config)
self._provider = HIFI(self.config)
def search(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
normalized = str(query or "").strip()
if not normalized:
return []
try:
results = self._provider.search(normalized, limit=limit)
except Exception as exc:
debug(f"[tidal-meta] search failed for '{normalized}': {exc}")
return []
items: List[Dict[str, Any]] = []
for result in results:
metadata = getattr(result, "full_metadata", {}) or {}
if not isinstance(metadata, dict):
metadata = {}
title = stringify(metadata.get("title") or result.title)
if not title:
continue
artists = extract_artists(metadata)
artist_display = ", ".join(artists) if artists else stringify(metadata.get("artist"))
album_obj = metadata.get("album")
album = ""
if isinstance(album_obj, dict):
album = stringify(album_obj.get("title"))
else:
album = stringify(metadata.get("album"))
year = stringify(metadata.get("releaseDate") or metadata.get("year") or metadata.get("date"))
track_id = self._provider._parse_track_id(metadata.get("trackId") or metadata.get("id"))
lyrics_data = None
if track_id is not None:
try:
lyrics_data = self._provider._fetch_track_lyrics(track_id)
except Exception as exc:
debug(f"[tidal-meta] lyrics lookup failed for {track_id}: {exc}")
lyrics = None
if isinstance(lyrics_data, dict):
lyrics = stringify(lyrics_data.get("lyrics") or lyrics_data.get("text"))
subtitles = stringify(lyrics_data.get("subtitles"))
if subtitles:
metadata.setdefault("_tidal_lyrics", {})["subtitles"] = subtitles
tags = sorted(build_track_tags(metadata))
items.append({
"title": title,
"artist": artist_display,
"album": album,
"year": year,
"lyrics": lyrics,
"tags": tags,
"provider": self.name,
"path": getattr(result, "path", ""),
"track_id": track_id,
"full_metadata": metadata,
})
return items
def to_tags(self, item: Dict[str, Any]) -> List[str]:
tags: List[str] = []
for value in item.get("tags", []):
value_text = stringify(value)
if value_text:
normalized = value_text.lower()
if normalized in {"tidal", "lossless"}:
continue
if normalized.startswith("quality:lossless"):
continue
tags.append(value_text)
return tags
_METADATA_PROVIDERS: Dict[str,
Type[MetadataProvider]] = {
"itunes": ITunesProvider,
@@ -1426,6 +1524,7 @@ _METADATA_PROVIDERS: Dict[str,
"musicbrainz": MusicBrainzMetadataProvider,
"imdb": ImdbMetadataProvider,
"ytdlp": YtdlpMetadataProvider,
"tidal": TidalMetadataProvider,
}