Files
Agent-Reach/agent_reach/channels/youtube.py
T
Pnant 762824c590 feat(routing): ordered backend candidates + real-probing doctor
- backends is now an ordered candidate list (first = preferred); channels
  report the backend actually serving via active_backend, surfaced in the
  doctor text report and --json
- new agent_reach/probe.py really executes upstream commands and tells
  apart missing / broken (stale venv shebang after a system Python
  upgrade) / timeout, with a reinstall prescription for broken installs
- all 13 channels migrated off which()-only checks: fixes bilibili
  false-positive "bili-cli 可用" on broken shims, misleading xiaohongshu
  "连接失败", rdt OSError crashing doctor, mcporter breakage masquerading
  as "未配置"
- twitter: 15s probe + 1 retry (flaky 10s timeout), broken twitter-cli
  now falls back to bird instead of aborting the check
- doctor survives per-channel exceptions; config supports per-channel
  backend override (<channel>_backend / <CHANNEL>_BACKEND env)
- fix skill install/uninstall crash on symlinked skill dirs (the
  "[Errno None] None" warning from shutil.rmtree on a symlink)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:48:33 +08:00

92 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""YouTube — check if yt-dlp is available with JS runtime."""
import shutil
from agent_reach.probe import probe_command
from agent_reach.utils.paths import get_ytdlp_config_path, render_ytdlp_fix_command
from agent_reach.utils.text import read_utf8_text
from .base import Channel
def _has_js_runtime_config(config_path) -> bool:
"""Return whether yt-dlp config explicitly enables a JS runtime."""
try:
if not config_path.exists():
return False
return "--js-runtimes" in read_utf8_text(config_path)
except OSError:
return False
class YouTubeChannel(Channel):
name = "youtube"
description = "YouTube 视频和字幕"
backends = ["yt-dlp"]
tier = 0
def can_handle(self, url: str) -> bool:
from urllib.parse import urlparse
d = urlparse(url).netloc.lower()
return "youtube.com" in d or "youtu.be" in d
def check(self, config=None):
# 真跑 yt-dlp --version 探活,区分未装 / venv 断链 / 跑不动
probe = probe_command("yt-dlp", ["--version"], timeout=10, package="yt-dlp")
if probe.status == "missing":
self.active_backend = None
return "off", "yt-dlp 未安装。安装:pip install yt-dlp"
if probe.status == "broken":
self.active_backend = None
return "error", f"yt-dlp 已安装但无法执行\n{probe.hint}"
if not probe.ok: # timeout / error:装了但跑不动
self.active_backend = None
detail = probe.hint or probe.output or probe.status
return "error", f"yt-dlp 无法正常运行:{detail}"
# yt-dlp 本体是活的;后面的 JS runtime/转写检查只影响 ok/warn,不影响后端归属
self.active_backend = "yt-dlp"
# Check JS runtime
has_js = shutil.which("deno") or shutil.which("node")
if not has_js:
return "warn", (
"yt-dlp 已安装但缺少 JS runtimeYouTube 必须)。\n"
" 安装 Node.js 或 deno,然后运行:agent-reach install"
)
# Check yt-dlp config for --js-runtimes
# Deno works out of the box; Node.js requires explicit config
has_deno = shutil.which("deno")
if not has_deno:
ytdlp_config = get_ytdlp_config_path()
if not _has_js_runtime_config(ytdlp_config):
return "warn", (
f"yt-dlp 已安装但未配置 JS runtime。运行:\n {render_ytdlp_fix_command()}"
)
# Surface transcription readiness so `doctor` reports it.
msg = "可提取视频信息和字幕"
if config is not None:
providers = []
if config.is_configured("groq_whisper"):
providers.append("groq")
if config.is_configured("openai_whisper"):
providers.append("openai")
if providers:
if not shutil.which("ffmpeg"):
msg += "(音频转写需安装 ffmpeg"
else:
msg += f",可转写音频({''.join(providers)}"
return "ok", msg
def transcribe(self, url: str, *, provider: str = "auto", config=None) -> str:
"""Download a YouTube video's audio and return its transcript.
Delegates to :func:`agent_reach.transcribe.transcribe`. Imported lazily
so the channel module stays cheap to import for users who never
transcribe.
"""
from agent_reach.transcribe import transcribe as _transcribe
return _transcribe(url, provider=provider, config=config)