kl
This commit is contained in:
@@ -13,13 +13,15 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from API.Tidal import (
|
||||
HifiApiClient,
|
||||
Tidal as TidalApiClient,
|
||||
build_track_tags,
|
||||
coerce_duration_seconds,
|
||||
extract_artists,
|
||||
stringify,
|
||||
)
|
||||
from ProviderCore.base import Provider, SearchResult, parse_inline_query_arguments
|
||||
from ProviderCore.inline_utils import collect_choice
|
||||
from cmdlet._shared import get_field
|
||||
from SYS import pipeline as pipeline_context
|
||||
from SYS.logger import debug, log
|
||||
|
||||
@@ -64,7 +66,9 @@ def _format_total_seconds(seconds: Any) -> str:
|
||||
return f"{mins}:{secs:02d}"
|
||||
|
||||
|
||||
class Tidal(Provider):
|
||||
class HIFI(Provider):
|
||||
|
||||
PROVIDER_NAME = "hifi"
|
||||
|
||||
TABLE_AUTO_STAGES = {
|
||||
"hifi.track": ["download-file"],
|
||||
@@ -97,7 +101,7 @@ class Tidal(Provider):
|
||||
self.api_timeout = float(self.config.get("timeout", 10.0))
|
||||
except Exception:
|
||||
self.api_timeout = 10.0
|
||||
self.api_clients = [HifiApiClient(base_url=url, timeout=self.api_timeout) for url in self.api_urls]
|
||||
self.api_clients = [TidalApiClient(base_url=url, timeout=self.api_timeout) for url in self.api_urls]
|
||||
|
||||
def extract_query_arguments(self, query: str) -> Tuple[str, Dict[str, Any]]:
|
||||
normalized, parsed = parse_inline_query_arguments(query)
|
||||
@@ -281,7 +285,7 @@ class Tidal(Provider):
|
||||
if isinstance(detail, dict):
|
||||
title = self._stringify(detail.get("title")) or title
|
||||
|
||||
return SearchResult(
|
||||
res = SearchResult(
|
||||
table="hifi.track",
|
||||
title=title,
|
||||
path=f"hifi://track/{track_id}",
|
||||
@@ -291,6 +295,12 @@ class Tidal(Provider):
|
||||
full_metadata=dict(detail) if isinstance(detail, dict) else {},
|
||||
selection_args=["-url", f"hifi://track/{track_id}"],
|
||||
)
|
||||
if isinstance(detail, dict):
|
||||
try:
|
||||
res.tag = self._build_track_tags(detail)
|
||||
except Exception:
|
||||
pass
|
||||
return res
|
||||
|
||||
def _extract_artist_selection_context(self, selected_items: List[Any]) -> List[Tuple[int, str]]:
|
||||
contexts: List[Tuple[int, str]] = []
|
||||
@@ -1130,25 +1140,36 @@ class Tidal(Provider):
|
||||
md = dict(getattr(result, "full_metadata") or {})
|
||||
|
||||
track_id = self._extract_track_id_from_result(result)
|
||||
if track_id:
|
||||
debug(f"[hifi] download: track_id={track_id}, manifest_present={bool(md.get('manifest'))}, tag_count={len(result.tag) if result.tag else 0}")
|
||||
|
||||
# Enrichment: fetch full metadata if manifest or detailed info (like tags/lyrics) is missing.
|
||||
# We check for 'manifest' because it's required for DASH playback.
|
||||
# We also check for lyrics/subtitles to ensure they are available for add-file.
|
||||
has_lyrics = bool(md.get("_tidal_lyrics_subtitles")) or bool(md.get("lyrics"))
|
||||
|
||||
if track_id and (not md.get("manifest") or not md.get("artist") or len(result.tag or []) <= 1 or not has_lyrics):
|
||||
debug(f"[hifi] Enriching track data (reason: manifest={not md.get('manifest')}, lyrics={not has_lyrics}, tags={len(result.tag or [])})")
|
||||
# Multi-part enrichment from API: metadata, tags, and lyrics.
|
||||
full_data = self._fetch_all_track_data(track_id)
|
||||
debug(f"[hifi] download: enrichment full_data present={bool(full_data)}")
|
||||
if isinstance(full_data, dict):
|
||||
# 1. Update metadata
|
||||
api_md = full_data.get("metadata")
|
||||
if isinstance(api_md, dict):
|
||||
debug(f"[hifi] download: updating metadata with {len(api_md)} keys")
|
||||
md.update(api_md)
|
||||
|
||||
# 2. Update tags (re-sync result.tag so cmdlet sees them)
|
||||
api_tags = full_data.get("tags")
|
||||
debug(f"[hifi] download: enrichment tags={api_tags}")
|
||||
if isinstance(api_tags, list) and api_tags:
|
||||
result.tag = set(api_tags)
|
||||
|
||||
# 3. Handle lyrics
|
||||
lyrics = full_data.get("lyrics")
|
||||
if isinstance(lyrics, dict) and lyrics:
|
||||
md.setdefault("lyrics", lyrics)
|
||||
subtitles = lyrics.get("subtitles")
|
||||
lyrics_dict = full_data.get("lyrics")
|
||||
if isinstance(lyrics_dict, dict) and lyrics_dict:
|
||||
md.setdefault("lyrics", lyrics_dict)
|
||||
subtitles = lyrics_dict.get("subtitles")
|
||||
if isinstance(subtitles, str) and subtitles.strip():
|
||||
md["_tidal_lyrics_subtitles"] = subtitles.strip()
|
||||
|
||||
@@ -1328,7 +1349,7 @@ class Tidal(Provider):
|
||||
|
||||
return False, None
|
||||
|
||||
def _get_api_client_for_base(self, base_url: str) -> Optional[HifiApiClient]:
|
||||
def _get_api_client_for_base(self, base_url: str) -> Optional[TidalApiClient]:
|
||||
base = base_url.rstrip("/")
|
||||
for client in self.api_clients:
|
||||
if getattr(client, "base_url", "").rstrip("/") == base:
|
||||
@@ -1739,6 +1760,10 @@ class Tidal(Provider):
|
||||
or payload.get("path")
|
||||
or payload.get("url")
|
||||
)
|
||||
# Guard against method binding (e.g. str.title) being returned by getattr(str, "title")
|
||||
if callable(title):
|
||||
title = None
|
||||
|
||||
if not title:
|
||||
title = f"Track {track_id}"
|
||||
path = (
|
||||
@@ -1983,12 +2008,6 @@ class Tidal(Provider):
|
||||
|
||||
return True
|
||||
|
||||
# Optimization: If we are selecting tracks, do NOT force a "Detail View" (resolving manifest) here.
|
||||
# This allows batch selection to flow immediately to `download-file` (via TABLE_AUTO_STAGES)
|
||||
# or other downstream cmdlets. The download logic (HIFI.download) handles manifest resolution locally.
|
||||
if table_type == "hifi.track" or (is_generic_hifi and any(str(get_field(i, "path")).startswith("hifi://track/") for i in selected_items)):
|
||||
return False
|
||||
|
||||
contexts = self._extract_track_selection_context(selected_items)
|
||||
try:
|
||||
debug(f"[hifi.selector] track contexts={len(contexts)}")
|
||||
|
||||
@@ -501,6 +501,26 @@ class InternetArchive(Provider):
|
||||
"internetarchive.formats": ["download-file"],
|
||||
}
|
||||
|
||||
def maybe_show_picker(
|
||||
self,
|
||||
*,
|
||||
url: str,
|
||||
item: Optional[Any] = None,
|
||||
parsed: Dict[str, Any],
|
||||
config: Dict[str, Any],
|
||||
quiet_mode: bool,
|
||||
) -> Optional[int]:
|
||||
"""Generic hook for download-file to show a selection table for IA items."""
|
||||
from cmdlet._shared import get_field as sh_get_field
|
||||
return maybe_show_formats_table(
|
||||
raw_urls=[url] if url else [],
|
||||
piped_items=[item] if item else [],
|
||||
parsed=parsed,
|
||||
config=config,
|
||||
quiet_mode=quiet_mode,
|
||||
get_field=sh_get_field,
|
||||
)
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(config)
|
||||
conf = _pick_provider_config(self.config)
|
||||
|
||||
Reference in New Issue
Block a user