This commit is contained in:
eric
2026-07-10 05:15:29 -05:00
parent a0104f6517
commit 924d05d06e
71 changed files with 6815 additions and 680 deletions

View File

@@ -6,6 +6,8 @@ import re
import time
from typing import Any
from web2_douyin_source_display import extract_douyin_source_meta
_FFMPEG_KV_RE = re.compile(
r"(?P<key>frame|fps|bitrate|speed|drop|dup)\s*=\s*(?P<value>[^\s]+)", re.IGNORECASE
)
@@ -31,16 +33,50 @@ def _as_int(value: Any) -> int | None:
return None if parsed is None else max(0, int(parsed))
_IDLE_MARKERS = (
"等待直播",
"waiting",
"获取失败",
"douyin web api failed",
"无录制任务",
"网址内容获取失败",
"没有正在监测的直播",
)
def _line_implies_relay_idle(line: str) -> bool:
lower = line.lower()
if "frame=" in lower and "speed=" in lower:
return False
return any(marker in lower for marker in _IDLE_MARKERS) or "error" in lower or "failed" in lower
def _latest_ffmpeg_stats_line(text: str, *, lookback: int = 12) -> str:
lines = [line.strip() for line in str(text or "").splitlines() if line.strip()]
if not lines:
return ""
tail = lines[-lookback:]
stats_idx = -1
stats_line = ""
for idx in range(len(tail) - 1, -1, -1):
line = tail[idx]
lower = line.lower()
if "frame=" in lower and "speed=" in lower:
stats_idx = idx
stats_line = line
break
if stats_idx < 0:
return ""
if any(_line_implies_relay_idle(line) for line in tail[stats_idx + 1 :]):
return ""
return stats_line
def parse_ffmpeg_metrics(*texts: str) -> dict[str, Any]:
raw = "\n".join(str(item or "") for item in texts)
tail = raw[-16000:]
metrics: dict[str, Any] = {}
stats_line = ""
for line in reversed([ln.strip() for ln in tail.splitlines() if ln.strip()]):
lower = line.lower()
if "frame=" in lower and "speed=" in lower:
stats_line = line
break
stats_line = _latest_ffmpeg_stats_line(tail)
if stats_line:
for match in _FFMPEG_KV_RE.finditer(stats_line):
key = match.group("key").lower()
@@ -153,8 +189,10 @@ def _lane_score(row: dict[str, Any], metrics: dict[str, Any], cfg: dict[str, str
def build_youtube_quality_guard_report(
rows: list[dict[str, Any]],
*,
live_summary: dict[str, Any] | None = None,
config_by_process: dict[str, dict[str, str]] | None = None,
) -> dict[str, Any]:
live_summary = live_summary or {}
config_by_process = config_by_process or {}
lanes: list[dict[str, Any]] = []
recommendations: list[dict[str, str]] = []
@@ -168,28 +206,51 @@ def build_youtube_quality_guard_report(
else {}
)
cfg = config_by_process.get(process) or {}
hint = _source_hint(cfg.get("url_lines", ""), str(row.get("recent_log") or ""))
source_meta = extract_douyin_source_meta(cfg.get("url_lines", ""), str(row.get("recent_log") or ""))
source_title = str(source_meta.get("source_title") or row.get("source_title") or "").strip()
source_room_id = str(source_meta.get("source_room_id") or row.get("source_room_id") or "").strip()
source_url = str(source_meta.get("source_url") or row.get("source_url") or "").strip()
source_display = str(
source_meta.get("source_display")
or row.get("source_display")
or source_title
or (f"抖音直播间 {source_room_id}" if source_room_id else "")
).strip()
score, issues = _lane_score(row, metrics, cfg)
rtmps = _ini_has_rtmps(cfg.get("youtube_ini_text", ""))
lanes.append(
{
"process": process,
"label": row.get("label") or process,
"source_url": hint["source_url"],
"source_room_id": hint["source_room_id"],
"source_title": source_title,
"source_room_id": source_room_id,
"source_url": source_url,
"source_display": source_display,
"status": row.get("business_status") or row.get("process_status") or "",
"is_pushing": pushing,
"ffmpeg_active": bool(row.get("ffmpeg_active") or row.get("ffmpeg_pids")),
"score": score,
"metrics": metrics,
"rtmps": rtmps,
"has_stream_key": _ini_has_key(cfg.get("youtube_ini_text", "")),
"issues": issues,
}
)
pushing_count = sum(1 for lane in lanes if lane["is_pushing"])
active_events = int(
((live_summary.get("stats") or {}).get("active_events") or len(live_summary.get("active") or [])) or 0
)
sample_count = int(((live_summary.get("stats") or {}).get("sample_count") or 0) or 0)
viewer_peak = int(((live_summary.get("stats") or {}).get("viewer_peak") or 0) or 0)
score = int(sum(lane["score"] for lane in lanes) / len(lanes)) if lanes else 0
if pushing_count == 0:
recommendations.append({"level": "critical", "text": "没有正在推流的线路"})
if active_events == 0 and pushing_count > 0:
recommendations.append({"level": "warning", "text": "推流已存在但事件中心未形成活动事件"})
if sample_count == 0 and pushing_count > 0:
recommendations.append({"level": "info", "text": "尚无 Studio/浏览器采样,可安装 ingest 扩展增强观测"})
elif all(lane["score"] >= 80 for lane in lanes if lane["is_pushing"]):
recommendations.append({"level": "ok", "text": "推流质量良好,保持监控即可"})
if any(lane["status"] == "relay_slow" for lane in lanes):
@@ -198,7 +259,13 @@ def build_youtube_quality_guard_report(
return {
"generated_at": time.time(),
"score": max(0, min(100, score)),
"summary": {"lanes": len(lanes), "pushing_lanes": pushing_count},
"summary": {
"lanes": len(lanes),
"pushing_lanes": pushing_count,
"active_events": active_events,
"sample_count": sample_count,
"viewer_peak": viewer_peak,
},
"lanes": lanes,
"recommendations": recommendations[:8],
}