df
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user