mpv检测第1版

This commit is contained in:
Your Name
2025-12-30 02:40:13 +00:00
parent c630787719
commit 88d54c1679
2 changed files with 61 additions and 70 deletions

View File

@@ -5,3 +5,4 @@ https://live.douyin.com/147650297461,主播: 娱乐摆摊记
https://live.douyin.com/806666144080,主播: 卢荟胶
https://live.douyin.com/589958309143,主播: 大萝卜
https://live.douyin.com/35932729226,主播: 内向小学生
https://live.douyin.com/9638535297,主播: 异度世界

130
main.py
View File

@@ -1622,11 +1622,13 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
error_window.append(1)
else:
# ====================== 抖音直播 → HDMI 全屏纯净显示mpv 版) ======================
# ====================== 抖音直播 → HDMI 全屏纯净显示mpv 原始结构 + 检测/重连优化版) ======================
import unicodedata
import re # 新增:用于精确检测活跃输出
import select # 新增:非阻塞读取 mpv stderr
# import time
# import datetime
# import subprocess
import unicodedata
class StreamEnded(Exception):
pass
@@ -1639,18 +1641,11 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
text = ''.join(ch for ch in text if ord(ch) <= 0xFFFF)
return text.strip()[:max_len]
# 参数配置
# 参数配置(原始不变)
MAX_RETRY_DELAY = 90
INITIAL_RETRY_DELAY = 3
NO_FRAME_TIMEOUT = 120 # 连续多久无新帧视为下播
START_CHECK_AFTER = 25 # 启动多少秒后开始检测无帧
END_KEYWORDS = [
"connection reset by peer", "failed to write trailer", "invalid data found",
"server returned 403", "server returned 404", "ingestion error",
"access denied", "packet too short", "client disconnected",
"live has ended", "no route to host", "connection timed out",
]
NO_OUTPUT_TIMEOUT = 120 # 连续无活跃输出视为下播(更准确)
START_CHECK_AFTER = 25
timestamp_str = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
_clean_title_in_name = sanitize_title(title_in_name)
@@ -1663,31 +1658,27 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
"Origin: https://live.douyin.com\r\n"
)
# mpv 命令(整合了您原来的所有视频处理需求
# mpv 命令(完全保持原始 vo=drm + 所有滤镜
mpv_cmd_base = [
"mpv",
"--really-quiet", # 可选,调试时去掉
"--really-quiet", # 正式运行静音,调试时去掉看日志
"--no-cache",
"--untimed",
"--demuxer-lavf-format=flv",
"--demuxer-lavf-o=fflags=nobuffer",
"--video-sync=audio",
"--hwdec=auto-safe", # 改成 auto-safe避免不兼容的 hwdec 崩溃
"--hwdec=auto-safe",
"--profile=low-latency",
"--vo=drm", # 关键:使用 DRM 直接渲染到 HDMI无 X11
"--drm-device=/dev/dri/card0", # 通常是 card0Allwinner H616 基本都支持
"--fs", # 全屏
"--vo=drm", # 保持原始
"--drm-device=/dev/dri/card0", # 保持原始
"--fs",
"--fs-screen=0",
# 视频滤镜保持不变
"--vf=fps=30,"
"scale=720:1280:force_original_aspect_ratio=decrease:flags=lanczos,"
"pad=720:1280:(ow-iw)/2:(oh-ih)/2:black,"
"transpose=2,"
"colorspace=all=bt709:iall=bt709,"
"eq=gamma_r=0.95:gamma_g=1.0:gamma_b=1.05:brightness=0.03:saturation=0.85:contrast=1.05",
f"--http-header-fields={douyin_headers}",
"--referrer=https://live.douyin.com/",
real_url
@@ -1695,70 +1686,68 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
proc = None
retry_delay = INITIAL_RETRY_DELAY
last_frame_time = time.time() # 用于粗略检测“卡顿/下播”
# 活跃输出正则覆盖抖音FLV常见进度/缓存/帧信息)
active_pattern = re.compile(r'A-V:|AO:|VO:|cache|fps|packet|dropped|queue|buffered|Playing')
try:
while True:
try:
if retry_delay > INITIAL_RETRY_DELAY:
print(f"[WARN] 防风控等待 {retry_delay}s 后重新连接...")
if retry_delay > INITIAL_RETRY_DELAY:
print(f"[WARN] 防风控等待 {retry_delay}s 后重新连接...")
time.sleep(retry_delay)
# 清理旧进程
if proc and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=5)
except:
proc.kill()
if proc and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=5)
except:
proc.kill()
print(f"[INFO] [{datetime.datetime.now():%H:%M:%S}] 启动 mpv 纯净播放 → HDMI 全屏")
print(f"[INFO] [{datetime.datetime.now():%H:%M:%S}] 启动 mpv 纯净播放 → HDMI 全屏")
proc = subprocess.Popen(
mpv_cmd_base,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
proc = subprocess.Popen(mpv_cmd_base)
start_time = time.time()
last_active_time = time.time()
no_output_streak = 0
start_time = time.time()
last_frame_time = time.time()
# 简单监控:每 10 秒检查一次进程是否还在运行
while proc.poll() is None:
time.sleep(10)
# mpv 正常运行时不会输出 frame= 信息,所以我们用进程存活 + 运行时长来判断
# 如果进程意外退出 → 进入重试
# 如果长时间(> NO_FRAME_TIMEOUT没变化可视为卡顿或下播保守判断
while proc.poll() is None:
ready, _, _ = select.select([proc.stderr], [], [], 10)
if ready:
line = proc.stderr.readline()
if line and active_pattern.search(line):
last_active_time = time.time()
no_output_streak = 0
else:
no_output_streak += 1
if time.time() - start_time > START_CHECK_AFTER:
if time.time() - last_frame_time > NO_FRAME_TIMEOUT:
print(f"[WARN] 超过 {NO_FRAME_TIMEOUT}s 无响应,判定可能已下播或卡死")
if no_output_streak >= (NO_OUTPUT_TIMEOUT // 10): # 120s 无活跃
print(f"[WARN] 连续 {NO_OUTPUT_TIMEOUT}s 无活跃输出,判定主播已下播")
proc.terminate()
raise StreamEnded()
# 这里可以加一个“假更新”防止误判
last_frame_time = time.time()
returncode = proc.returncode
print(f"[INFO] mpv 退出returncode={returncode}")
# mpv 进程自然退出returncode 非0 通常是流中断)
returncode = proc.returncode
print(f"[INFO] mpv 退出returncode={returncode}")
if returncode in [-15, None]: # 我们主动 terminate
raise StreamEnded()
# 如果是明确的下播错误码或日志mpv 会直接退出),我们视作真下播
# mpv 没有 stderr 队列,这里简单处理即可)
raise StreamEnded() if returncode != 0 else None
except StreamEnded:
print(f"[INFO] 主播已下播,停止转播 → {record_name}")
break
except Exception as e:
print(f"[ERROR] 播放异常: {e}")
# 指数退避重试
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
continue
else:
# 正常退出(极少情况)
break
# 网络波动/风控断联 → 快速重连(不拉长延迟)
print(f"[WARN] mpv 非预期退出(网络波动/风控),快速重连...")
retry_delay = INITIAL_RETRY_DELAY if retry_delay > 30 else min(retry_delay * 2, MAX_RETRY_DELAY)
except StreamEnded:
print(f"[INFO] 主播已下播,停止转播 → {record_name}")
retry_delay = INITIAL_RETRY_DELAY # 下次新直播快速连接
except Exception as e:
print(f"[ERROR] 播放异常: {e}")
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
finally:
# 统一清理
if proc and proc.poll() is None:
try:
proc.terminate()
@@ -1766,6 +1755,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
except:
proc.kill()
# 原始清理逻辑完全保留
try:
recording.discard(record_name)
recording_time_list.pop(record_name, None)