j
This commit is contained in:
@@ -376,31 +376,14 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
||||
"table": None,
|
||||
}
|
||||
|
||||
# Fast gate: only for streaming URLs yt-dlp knows about.
|
||||
try:
|
||||
from tool.ytdlp import is_url_supported_by_ytdlp # noqa: WPS433
|
||||
|
||||
if not is_url_supported_by_ytdlp(url):
|
||||
return {
|
||||
"success": False,
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"error": "URL not supported by yt-dlp",
|
||||
"table": None,
|
||||
}
|
||||
except Exception:
|
||||
# If probing support fails, still attempt extraction and let yt-dlp decide.
|
||||
pass
|
||||
|
||||
try:
|
||||
import yt_dlp # type: ignore
|
||||
from tool.ytdlp import list_formats, is_browseable_format # noqa: WPS433
|
||||
except Exception as exc:
|
||||
return {
|
||||
"success": False,
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"error":
|
||||
f"yt-dlp module not available: {type(exc).__name__}: {exc}",
|
||||
"error": f"yt-dlp tool unavailable: {type(exc).__name__}: {exc}",
|
||||
"table": None,
|
||||
}
|
||||
|
||||
@@ -415,96 +398,27 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
||||
except Exception:
|
||||
cookiefile = None
|
||||
|
||||
ydl_opts: Dict[str,
|
||||
Any] = {
|
||||
"quiet": True,
|
||||
"no_warnings": True,
|
||||
"socket_timeout": 20,
|
||||
"retries": 2,
|
||||
"skip_download": True,
|
||||
# Avoid accidentally expanding huge playlists on load.
|
||||
"noplaylist": True,
|
||||
"noprogress": True,
|
||||
}
|
||||
if cookiefile:
|
||||
ydl_opts["cookiefile"] = cookiefile
|
||||
|
||||
def _format_bytes(n: Any) -> str:
|
||||
"""Format bytes using centralized utility."""
|
||||
return format_bytes(n)
|
||||
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[attr-defined]
|
||||
info = ydl.extract_info(url, download=False)
|
||||
formats = list_formats(
|
||||
url,
|
||||
no_playlist=True,
|
||||
cookiefile=cookiefile,
|
||||
timeout_seconds=25,
|
||||
)
|
||||
|
||||
# Debug: dump a short summary of the format list to the helper log.
|
||||
try:
|
||||
formats_any = info.get("formats") if isinstance(info, dict) else None
|
||||
count = len(formats_any) if isinstance(formats_any, list) else 0
|
||||
_append_helper_log(
|
||||
f"[ytdlp-formats] extracted formats count={count} url={url}"
|
||||
)
|
||||
|
||||
if isinstance(formats_any, list) and formats_any:
|
||||
limit = 60
|
||||
for i, f in enumerate(formats_any[:limit], start=1):
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
fid = str(f.get("format_id") or "")
|
||||
ext = str(f.get("ext") or "")
|
||||
note = f.get("format_note") or f.get("format") or ""
|
||||
vcodec = str(f.get("vcodec") or "")
|
||||
acodec = str(f.get("acodec") or "")
|
||||
size = f.get("filesize") or f.get("filesize_approx")
|
||||
res = str(f.get("resolution") or "")
|
||||
if not res:
|
||||
try:
|
||||
w = f.get("width")
|
||||
h = f.get("height")
|
||||
if w and h:
|
||||
res = f"{int(w)}x{int(h)}"
|
||||
elif h:
|
||||
res = f"{int(h)}p"
|
||||
except Exception:
|
||||
res = ""
|
||||
_append_helper_log(
|
||||
f"[ytdlp-format {i:02d}] id={fid} ext={ext} res={res} note={note} codecs={vcodec}/{acodec} size={size}"
|
||||
)
|
||||
if count > limit:
|
||||
_append_helper_log(
|
||||
f"[ytdlp-formats] (truncated; total={count})"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Optional: dump the full extracted JSON for inspection.
|
||||
try:
|
||||
dump = os.environ.get("MEDEIA_MPV_YTDLP_DUMP", "").strip()
|
||||
if dump and dump != "0" and isinstance(info, dict):
|
||||
h = hashlib.sha1(url.encode("utf-8",
|
||||
errors="replace")).hexdigest()[:10]
|
||||
out_path = _repo_root() / "Log" / f"ytdlp-probe-{h}.json"
|
||||
out_path.write_text(
|
||||
json.dumps(info,
|
||||
ensure_ascii=False,
|
||||
indent=2),
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
_append_helper_log(f"[ytdlp-formats] wrote probe json: {out_path}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not isinstance(info, dict):
|
||||
if formats is None:
|
||||
return {
|
||||
"success": False,
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"error": "yt-dlp returned non-dict info",
|
||||
"error": "yt-dlp format probe failed or timed out",
|
||||
"table": None,
|
||||
}
|
||||
|
||||
formats = info.get("formats")
|
||||
if not isinstance(formats, list) or not formats:
|
||||
if not formats:
|
||||
return {
|
||||
"success": True,
|
||||
"stdout": "",
|
||||
@@ -516,6 +430,48 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
||||
},
|
||||
}
|
||||
|
||||
browseable = [f for f in formats if is_browseable_format(f)]
|
||||
if browseable:
|
||||
formats = browseable
|
||||
|
||||
# Debug: dump a short summary of the format list to the helper log.
|
||||
try:
|
||||
count = len(formats)
|
||||
_append_helper_log(
|
||||
f"[ytdlp-formats] extracted formats count={count} url={url}"
|
||||
)
|
||||
|
||||
limit = 60
|
||||
for i, f in enumerate(formats[:limit], start=1):
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
fid = str(f.get("format_id") or "")
|
||||
ext = str(f.get("ext") or "")
|
||||
note = f.get("format_note") or f.get("format") or ""
|
||||
vcodec = str(f.get("vcodec") or "")
|
||||
acodec = str(f.get("acodec") or "")
|
||||
size = f.get("filesize") or f.get("filesize_approx")
|
||||
res = str(f.get("resolution") or "")
|
||||
if not res:
|
||||
try:
|
||||
w = f.get("width")
|
||||
h = f.get("height")
|
||||
if w and h:
|
||||
res = f"{int(w)}x{int(h)}"
|
||||
elif h:
|
||||
res = f"{int(h)}p"
|
||||
except Exception:
|
||||
res = ""
|
||||
_append_helper_log(
|
||||
f"[ytdlp-format {i:02d}] id={fid} ext={ext} res={res} note={note} codecs={vcodec}/{acodec} size={size}"
|
||||
)
|
||||
if count > limit:
|
||||
_append_helper_log(
|
||||
f"[ytdlp-formats] (truncated; total={count})"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rows = []
|
||||
for fmt in formats:
|
||||
if not isinstance(fmt, dict):
|
||||
@@ -540,8 +496,15 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
||||
ext = str(fmt.get("ext") or "").strip()
|
||||
size = _format_bytes(fmt.get("filesize") or fmt.get("filesize_approx"))
|
||||
|
||||
vcodec = str(fmt.get("vcodec") or "none")
|
||||
acodec = str(fmt.get("acodec") or "none")
|
||||
selection_id = format_id
|
||||
if vcodec != "none" and acodec == "none":
|
||||
selection_id = f"{format_id}+ba"
|
||||
|
||||
# Build selection args compatible with MPV Lua picker.
|
||||
selection_args = ["-query", f"format:{format_id}"]
|
||||
# Use -format instead of -query so Lua can extract the ID easily.
|
||||
selection_args = ["-format", selection_id]
|
||||
|
||||
rows.append(
|
||||
{
|
||||
@@ -962,6 +925,28 @@ def main(argv: Optional[list[str]] = None) -> int:
|
||||
f"[helper] failed to publish config temp: {type(exc).__name__}: {exc}"
|
||||
)
|
||||
|
||||
# Publish yt-dlp supported domains for Lua menu filtering
|
||||
try:
|
||||
from tool.ytdlp import _build_supported_domains
|
||||
domains = sorted(list(_build_supported_domains()))
|
||||
if domains:
|
||||
# We join them into a space-separated string for Lua to parse easily
|
||||
domains_str = " ".join(domains)
|
||||
client.send_command_no_wait(
|
||||
[
|
||||
"set_property_string",
|
||||
"user-data/medeia-ytdlp-domains-cached",
|
||||
domains_str
|
||||
]
|
||||
)
|
||||
_append_helper_log(
|
||||
f"[helper] published {len(domains)} ytdlp domains for Lua menu filtering"
|
||||
)
|
||||
except Exception as exc:
|
||||
_append_helper_log(
|
||||
f"[helper] failed to publish ytdlp domains: {type(exc).__name__}: {exc}"
|
||||
)
|
||||
|
||||
last_seen_id: Optional[str] = None
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user