This commit is contained in:
2026-01-16 19:39:45 -08:00
parent 9dce32cbe3
commit 385307c5b9
5 changed files with 47 additions and 12 deletions

View File

@@ -20,6 +20,7 @@ from API.Tidal import (
stringify, stringify,
) )
from ProviderCore.base import Provider, SearchResult, parse_inline_query_arguments from ProviderCore.base import Provider, SearchResult, parse_inline_query_arguments
from cmdlet._shared import get_field
from SYS import pipeline as pipeline_context from SYS import pipeline as pipeline_context
from SYS.logger import debug, log from SYS.logger import debug, log
@@ -1565,7 +1566,7 @@ class Tidal(Provider):
except (TypeError, ValueError): except (TypeError, ValueError):
return None return None
path = f"hifi://artist/{artist_id}" path = f"tidal://artist/{artist_id}"
columns: List[tuple[str, str]] = [("Artist", name), ("Artist ID", str(artist_id))] columns: List[tuple[str, str]] = [("Artist", name), ("Artist ID", str(artist_id))]
popularity = stringify(item.get("popularity")) popularity = stringify(item.get("popularity"))
@@ -1573,10 +1574,10 @@ class Tidal(Provider):
columns.append(("Popularity", popularity)) columns.append(("Popularity", popularity))
return SearchResult( return SearchResult(
table="hifi.artist", table="tidal.artist",
title=name, title=name,
path=path, path=path,
detail="hifi.artist", detail="tidal.artist",
annotations=["tidal", "artist"], annotations=["tidal", "artist"],
media_kind="audio", media_kind="audio",
columns=columns, columns=columns,
@@ -1611,7 +1612,7 @@ class Tidal(Provider):
return None return None
# Avoid tidal.com URLs entirely; selection will resolve to a decoded MPD. # Avoid tidal.com URLs entirely; selection will resolve to a decoded MPD.
path = f"hifi://track/{track_id}" path = f"tidal://track/{track_id}"
artists = extract_artists(item) artists = extract_artists(item)
artist_display = ", ".join(artists) artist_display = ", ".join(artists)
@@ -1660,10 +1661,10 @@ class Tidal(Provider):
tags = build_track_tags(full_md) tags = build_track_tags(full_md)
result = SearchResult( result = SearchResult(
table="hifi.track", table="tidal.track",
title=title, title=title,
path=path, path=path,
detail="hifi.track", detail="tidal.track",
annotations=["tidal", "track"], annotations=["tidal", "track"],
media_kind="audio", media_kind="audio",
tag=tags, tag=tags,
@@ -1738,7 +1739,7 @@ class Tidal(Provider):
path = ( path = (
payload.get("path") payload.get("path")
or payload.get("url") or payload.get("url")
or f"hifi://track/{track_id}" or f"tidal://track/{track_id}"
) )
contexts.append((track_id, str(title).strip(), str(path).strip())) contexts.append((track_id, str(title).strip(), str(path).strip()))
return contexts return contexts

View File

@@ -172,6 +172,8 @@ class ProviderRegistry:
for finder, module_name, _ in pkgutil.iter_modules(package_path): for finder, module_name, _ in pkgutil.iter_modules(package_path):
if module_name.startswith("_"): if module_name.startswith("_"):
continue continue
if module_name.strip().lower() == "hifi":
continue
module_path = f"{self.package_name}.{module_name}" module_path = f"{self.package_name}.{module_name}"
try: try:
module = importlib.import_module(module_path) module = importlib.import_module(module_path)

View File

@@ -343,6 +343,7 @@ def normalize_urls(value: Any) -> List[str]:
"magnet:", "magnet:",
"torrent:", "torrent:",
"ytdl://", "ytdl://",
"tidal:",
"data:", "data:",
"ftp:", "ftp:",
"sftp:")) "sftp:"))

View File

@@ -4,7 +4,7 @@
<h3>4 TEXT BASED FILE ONTOLOGY </h3> <h3>4 TEXT BASED FILE ONTOLOGY </h3>
</div> </div>
Medios-Macina is a CLI file media manager and toolkit focused on downloading, tagging, and media storage (audio, video, images, and text) from a variety of providers and sources. It is designed around a compact, pipeable command language ("cmdlets") so complex workflows can be composed simply and repeatably. Medios-Macina is a file media manager and virtual toolbox capable of downloading, tagging, archiving, sharing, and media (audio, video, images, and text) managing from a variety of providers and sources. It is designed around a compact, pipeable command language ("cmdlets") so complex workflows can be composed simply and repeatably.
<h3>ELEVATED PITCH</h3> <h3>ELEVATED PITCH</h3>
<ul> <ul>

View File

@@ -716,6 +716,10 @@ class YtDlpTool:
# Progress + utility helpers for yt-dlp driven downloads (previously in cmdlet/download_media). # Progress + utility helpers for yt-dlp driven downloads (previously in cmdlet/download_media).
_YTDLP_PROGRESS_BAR = ProgressBar() _YTDLP_PROGRESS_BAR = ProgressBar()
_YTDLP_TRANSFER_STATE: Dict[str, Dict[str, Any]] = {} _YTDLP_TRANSFER_STATE: Dict[str, Dict[str, Any]] = {}
_YTDLP_PROGRESS_ACTIVITY_LOCK = threading.Lock()
_YTDLP_PROGRESS_LAST_ACTIVITY = 0.0
_YTDLP_PROGRESS_ACTIVITY_LOCK = threading.Lock()
_YTDLP_PROGRESS_LAST_ACTIVITY = 0.0
_SUBTITLE_EXTS = (".vtt", ".srt", ".ass", ".ssa", ".lrc") _SUBTITLE_EXTS = (".vtt", ".srt", ".ass", ".ssa", ".lrc")
@@ -744,6 +748,21 @@ def _progress_label(status: Dict[str, Any]) -> str:
return "download" return "download"
def _record_progress_activity(timestamp: Optional[float] = None) -> None:
global _YTDLP_PROGRESS_LAST_ACTIVITY
with _YTDLP_PROGRESS_ACTIVITY_LOCK:
_YTDLP_PROGRESS_LAST_ACTIVITY = timestamp if timestamp is not None else time.monotonic()
def _get_last_progress_activity() -> float:
with _YTDLP_PROGRESS_ACTIVITY_LOCK:
return _YTDLP_PROGRESS_LAST_ACTIVITY
def _clear_progress_activity() -> None:
_record_progress_activity(0.0)
def _live_ui_and_pipe_index() -> tuple[Optional[Any], int]: def _live_ui_and_pipe_index() -> tuple[Optional[Any], int]:
ui = None ui = None
try: try:
@@ -1117,6 +1136,8 @@ def _progress_callback(status: Dict[str, Any]) -> None:
event = status.get("status") event = status.get("status")
downloaded = status.get("downloaded_bytes") downloaded = status.get("downloaded_bytes")
total = status.get("total_bytes") or status.get("total_bytes_estimate") total = status.get("total_bytes") or status.get("total_bytes_estimate")
if event == "downloading":
_record_progress_activity()
pipeline = PipelineProgress(pipeline_context) pipeline = PipelineProgress(pipeline_context)
live_ui, _ = pipeline.ui_and_pipe_index() live_ui, _ = pipeline.ui_and_pipe_index()
@@ -1517,10 +1538,20 @@ def _download_with_timeout(opts: DownloadOptions, timeout_seconds: int = 300) ->
thread = threading.Thread(target=_do_download, daemon=False) thread = threading.Thread(target=_do_download, daemon=False)
thread.start() thread.start()
thread.join(timeout=timeout_seconds) start_time = time.monotonic()
_record_progress_activity(start_time)
if thread.is_alive(): try:
raise DownloadError(f"Download timeout after {timeout_seconds} seconds for {opts.url}") while thread.is_alive():
thread.join(1)
if not thread.is_alive():
break
last_activity = _get_last_progress_activity()
if last_activity <= 0:
last_activity = start_time
if time.monotonic() - last_activity > timeout_seconds:
raise DownloadError(f"Download timeout after {timeout_seconds} seconds for {opts.url}")
finally:
_clear_progress_activity()
if result_container[1] is not None: if result_container[1] is not None:
raise cast(Exception, result_container[1]) raise cast(Exception, result_container[1])