将 README 改写为产品文档;移除微信/EasyTier/工作台等遗留模块;新增 youLIVE 目标端监控、摄像头推流、质量守卫与转播历史能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""YouTube relay diagnostics: source + FFmpeg + target watch URL layers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
from web2_quality_guard import build_youtube_quality_guard_report, parse_ffmpeg_metrics
|
|
|
|
|
|
def _source_configured(url_lines: str) -> bool:
|
|
for raw in str(url_lines or "").splitlines():
|
|
t = raw.strip()
|
|
if not t or t.startswith("#"):
|
|
continue
|
|
if "douyin" in t.lower() or "live.douyin.com" in t.lower():
|
|
return True
|
|
return False
|
|
|
|
|
|
def build_youtube_diagnostics_report(
|
|
rows: list[dict[str, Any]],
|
|
*,
|
|
config_by_process: dict[str, dict[str, str]] | None = None,
|
|
) -> dict[str, Any]:
|
|
config_by_process = config_by_process or {}
|
|
guard = build_youtube_quality_guard_report(rows, config_by_process=config_by_process)
|
|
lanes: list[dict[str, Any]] = []
|
|
target_ok = 0
|
|
source_ok = 0
|
|
ffmpeg_ok = 0
|
|
|
|
for lane in guard.get("lanes") or []:
|
|
proc = str(lane.get("process") or "")
|
|
cfg = config_by_process.get(proc) or {}
|
|
watch = str(cfg.get("watch_url") or "").strip()
|
|
src_cfg = _source_configured(cfg.get("url_lines", ""))
|
|
pushing = bool(lane.get("is_pushing"))
|
|
metrics = lane.get("metrics") if isinstance(lane.get("metrics"), dict) else {}
|
|
if not metrics and pushing:
|
|
metrics = parse_ffmpeg_metrics(
|
|
str((rows[0] if rows else {}).get("recent_log") or ""),
|
|
)
|
|
|
|
issues = list(lane.get("issues") or [])
|
|
score = int(lane.get("score") or 0)
|
|
|
|
if src_cfg:
|
|
source_ok += 1
|
|
else:
|
|
issues.append({"level": "warning", "text": "源站地址未配置"})
|
|
score = max(0, score - 12)
|
|
|
|
if watch:
|
|
target_ok += 1
|
|
else:
|
|
issues.append({"level": "warning", "text": "未配置 YouTube 目标监听地址"})
|
|
score = max(0, score - 8)
|
|
|
|
ffmpeg_active = bool(lane.get("ffmpeg_active"))
|
|
if pushing and ffmpeg_active:
|
|
ffmpeg_ok += 1
|
|
elif pushing and not ffmpeg_active:
|
|
issues.append({"level": "critical", "text": "推流中但未检测到 FFmpeg 进程"})
|
|
|
|
lanes.append(
|
|
{
|
|
**lane,
|
|
"score": max(0, min(100, score)),
|
|
"issues": issues[:8],
|
|
"layers": {
|
|
"source": {
|
|
"ok": src_cfg,
|
|
"hint": lane.get("source_url") or "",
|
|
},
|
|
"ffmpeg": {
|
|
"ok": ffmpeg_active or not pushing,
|
|
"pushing": pushing,
|
|
"metrics": metrics,
|
|
},
|
|
"target": {
|
|
"ok": bool(watch),
|
|
"watch_url": watch,
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
lane_count = len(lanes)
|
|
overall = int(sum(l["score"] for l in lanes) / lane_count) if lane_count else 0
|
|
return {
|
|
**guard,
|
|
"score": max(0, min(100, overall)),
|
|
"lanes": lanes,
|
|
"diagnostics": {
|
|
"generated_at": time.time(),
|
|
"layers": ["source", "ffmpeg", "target"],
|
|
"summary": {
|
|
"lanes": lane_count,
|
|
"source_configured": source_ok,
|
|
"target_configured": target_ok,
|
|
"ffmpeg_active": ffmpeg_ok,
|
|
},
|
|
},
|
|
}
|