This commit is contained in:
2025-12-31 05:17:37 -08:00
parent 3bbaa28fb4
commit e8842ceded
10 changed files with 1255 additions and 29 deletions

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Any, Dict, Optional, Sequence, Tuple, List
from typing import Any, Dict, Optional, Sequence, Tuple, List, Union
from pathlib import Path
import sys
import shutil
@@ -582,6 +582,82 @@ class Add_File(Cmdlet):
failures += 1
continue
# If we got a hifi://track/<id> placeholder, resolve it to a decoded MPD first.
try:
if isinstance(media_path_or_url, Path):
mp_url = str(media_path_or_url)
if mp_url.lower().startswith("hifi:"):
manifest_path = sh.resolve_tidal_manifest_path(item)
if not manifest_path:
try:
meta = getattr(item, "full_metadata", None)
if isinstance(meta, dict) and meta.get("_tidal_manifest_error"):
log(str(meta.get("_tidal_manifest_error")), file=sys.stderr)
except Exception:
pass
log("HIFI selection has no playable DASH MPD manifest.", file=sys.stderr)
failures += 1
continue
media_path_or_url = Path(manifest_path)
pipe_obj.path = str(media_path_or_url)
elif isinstance(media_path_or_url, str):
if str(media_path_or_url).strip().lower().startswith("hifi:"):
manifest_path = sh.resolve_tidal_manifest_path(item)
if not manifest_path:
try:
meta = getattr(item, "full_metadata", None)
if isinstance(meta, dict) and meta.get("_tidal_manifest_error"):
log(str(meta.get("_tidal_manifest_error")), file=sys.stderr)
except Exception:
pass
log("HIFI selection has no playable DASH MPD manifest.", file=sys.stderr)
failures += 1
continue
media_path_or_url = Path(manifest_path)
pipe_obj.path = str(media_path_or_url)
except Exception:
pass
manifest_source: Optional[Union[str, Path]] = None
tidal_metadata = None
try:
if isinstance(item, dict):
tidal_metadata = item.get("full_metadata") or item.get("metadata")
else:
tidal_metadata = (
getattr(item, "full_metadata", None)
or getattr(item, "metadata", None)
)
except Exception:
tidal_metadata = None
if not tidal_metadata and isinstance(pipe_obj.extra, dict):
tidal_metadata = pipe_obj.extra.get("full_metadata") or pipe_obj.extra.get("metadata")
if isinstance(tidal_metadata, dict):
manifest_source = (
tidal_metadata.get("_tidal_manifest_path")
or tidal_metadata.get("_tidal_manifest_url")
)
if not manifest_source:
if isinstance(media_path_or_url, Path):
manifest_source = media_path_or_url
elif isinstance(media_path_or_url, str):
if media_path_or_url.lower().endswith(".mpd"):
manifest_source = media_path_or_url
if manifest_source:
downloaded, tmp_dir = self._download_manifest_with_ffmpeg(manifest_source)
if downloaded is None:
failures += 1
continue
media_path_or_url = str(downloaded)
pipe_obj.path = str(downloaded)
pipe_obj.is_temp = True
delete_after_item = True
if tmp_dir is not None:
temp_dir_to_cleanup = tmp_dir
is_url_target = isinstance(
media_path_or_url,
str
@@ -2016,10 +2092,159 @@ class Add_File(Cmdlet):
# Call download-media with the URL in args
return dl_cmdlet.run(None, dl_args, config)
@staticmethod
def _download_manifest_with_ffmpeg(source: Union[str, Path]) -> Tuple[Optional[Path], Optional[Path]]:
"""Run ffmpeg on the manifest or stream URL and return a local file path for ingestion."""
import subprocess
ffmpeg_bin = shutil.which("ffmpeg")
if not ffmpeg_bin:
log("ffmpeg not found on PATH; cannot download HIFI manifest.", file=sys.stderr)
return None, None
tmp_dir = Path(tempfile.mkdtemp(prefix="medeia_hifi_mpd_"))
stream_mp4 = tmp_dir / "stream.mp4"
input_target: Optional[str] = None
if isinstance(source, Path):
input_target = str(source)
elif isinstance(source, str):
candidate = source.strip()
if candidate.lower().startswith("file://"):
try:
from urllib.parse import unquote, urlparse
parsed = urlparse(candidate)
raw_path = unquote(parsed.path or "")
raw_path = raw_path.lstrip("/")
candidate = raw_path
except Exception:
pass
input_target = candidate
if not input_target:
return None, None
try:
subprocess.run(
[
ffmpeg_bin,
"-hide_banner",
"-loglevel",
"error",
"-y",
"-protocol_whitelist",
"file,https,tcp,tls,crypto,data",
"-i",
input_target,
"-c",
"copy",
str(stream_mp4),
],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as exc:
err = (exc.stderr or "").strip()
if err:
log(f"ffmpeg manifest download failed: {err}", file=sys.stderr)
else:
log(f"ffmpeg manifest download failed (exit {exc.returncode})", file=sys.stderr)
return None, tmp_dir
except Exception as exc:
log(f"ffmpeg manifest download failed: {exc}", file=sys.stderr)
return None, tmp_dir
codec = None
ffprobe_bin = shutil.which("ffprobe")
if ffprobe_bin:
try:
probe = subprocess.run(
[
ffprobe_bin,
"-v",
"error",
"-select_streams",
"a:0",
"-show_entries",
"stream=codec_name",
"-of",
"default=nw=1:nk=1",
str(stream_mp4),
],
capture_output=True,
text=True,
check=True,
)
codec = (probe.stdout or "").strip().lower() or None
except Exception:
codec = None
ext = None
if codec == "flac":
ext = "flac"
elif codec == "aac":
ext = "m4a"
elif codec == "mp3":
ext = "mp3"
elif codec == "opus":
ext = "opus"
else:
ext = "mka"
audio_out = tmp_dir / f"audio.{ext}"
try:
subprocess.run(
[
ffmpeg_bin,
"-hide_banner",
"-loglevel",
"error",
"-y",
"-i",
str(stream_mp4),
"-vn",
"-c:a",
"copy",
str(audio_out),
],
check=True,
capture_output=True,
text=True,
)
if audio_out.exists():
return audio_out, tmp_dir
except subprocess.CalledProcessError as exc:
err = (exc.stderr or "").strip()
if err:
log(f"ffmpeg audio extract failed: {err}", file=sys.stderr)
except Exception:
pass
if stream_mp4.exists():
return stream_mp4, tmp_dir
return None, tmp_dir
@staticmethod
def _get_url(result: Any, pipe_obj: models.PipeObject) -> List[str]:
from SYS.metadata import normalize_urls
# If this is a HIFI selection, we only support the decoded MPD (never tidal.com URLs).
is_hifi = False
try:
if isinstance(result, dict):
is_hifi = str(result.get("table") or result.get("provider") or "").strip().lower().startswith("hifi")
else:
is_hifi = str(getattr(result, "table", "") or getattr(result, "provider", "")).strip().lower().startswith("hifi")
except Exception:
is_hifi = False
try:
if not is_hifi:
is_hifi = str(getattr(pipe_obj, "path", "") or "").strip().lower().startswith("hifi:")
except Exception:
pass
# Prefer explicit PipeObject.url if present
urls: List[str] = []
try:
@@ -2043,6 +2268,13 @@ class Add_File(Cmdlet):
if not urls:
urls = normalize_urls(extract_url_from_result(result))
# If this is a Tidal/HIFI selection with a decodable manifest, do NOT fall back to
# tidal.com track URLs. The only supported target is the decoded local MPD.
manifest_path = sh.resolve_tidal_manifest_path(result)
if manifest_path:
return [manifest_path]
if is_hifi:
return []
return urls
@staticmethod