mpv初始版本

This commit is contained in:
Your Name
2025-12-30 01:50:28 +00:00
parent 67bacc1c42
commit c630787719
2 changed files with 91 additions and 501 deletions

View File

@@ -1,3 +1,7 @@
https://live.douyin.com/372551154040,主播: 向嘉_手打柠檬茶
https://live.douyin.com/203476627795,主播: 蜜桃姐姐(手打柠檬茶)
https://live.douyin.com/825627287092,主播: 杏绘茶档_(00后创业版_手打柠檬茶)
https://live.douyin.com/147650297461,主播: 娱乐摆摊记
https://live.douyin.com/806666144080,主播: 卢荟胶
https://live.douyin.com/589958309143,主播: 大萝卜
https://live.douyin.com/35932729226,主播: 内向小学生

588
main.py
View File

@@ -1622,536 +1622,150 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
error_window.append(1)
else:
# ====================== 抖音→YouTube 2025重构单层重试 + 统一清理) ======================
import queue, unicodedata, platform, traceback
# ====================== 抖音直播 → HDMI 全屏纯净显示mpv 版) ======================
# import time
# import datetime
# import subprocess
import unicodedata
class StreamEnded(Exception):
pass
def sanitize_title(text: str, max_len: int = 120) -> str:
if not text: return ""
if not text:
return ""
text = unicodedata.normalize("NFKC", text)
text = ''.join(ch for ch in text if unicodedata.category(ch)[0] != 'C')
text = ''.join(ch for ch in text if ord(ch) <= 0xFFFF)
return text.strip()[:max_len]
def _stderr_reader_thread(proc, q):
try:
while True:
line = proc.stderr.readline()
if not line:
break
q.put(line)
except Exception as e:
try:
q.put(f"__ERR__READER__:{e}")
except Exception:
pass
finally:
try:
q.put(None)
except Exception:
pass
def cleanup_proc(proc):
try:
if proc and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=5)
except Exception:
if proc.poll() is None:
try:
proc.kill()
except Exception:
pass
except Exception:
pass
# ========== 参数配置(保留你原始配置) ==========
# YT_STREAM_KEY = "qxvb-r47b-r5ju-6ud3-6k7z"
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/x04z-564w-aks7-embw-30y4"
# youtube_rtmp="rtmp://192.168.31.184/live/douyin"
# # ←←←← 在这块代码的上方先定义你要推的多个地址 ←←←←
# rtmp_targets = [
# "rtmp://a.rtmp.youtube.com/live2/qxvb-r47b-r5ju-6ud3-6k7z", # 主频道
# "rtmp://192.168.31.184/live/douyin", # 备用频道(换成你的第二个 YouTube 密钥)
# ]
# # 用 | 拼接所有地址(任何一个断开都不会影响其他)
# tee_outputs = "|".join([f"[f=flv]{url}" for url in rtmp_targets])
# 参数配置
MAX_RETRY_DELAY = 90
INITIAL_RETRY_DELAY = 3
STDERR_QUEUE_TIMEOUT = 1.0
NO_FRAME_TIMEOUT = 120
START_CHECK_AFTER = 25
STALE_OUTPUT_SECONDS = 120 #120
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",
"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",
]
timestamp_str = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
_clean_title_in_name = sanitize_title(title_in_name)
stream_title = sanitize_title(f"{anchor_name}{('_' + _clean_title_in_name) if _clean_title_in_name else ''}_{timestamp_str}")
print(f"[INFO] {rec_info} 开始推流到 YouTube: {stream_title}")
print(f"[INFO] {rec_info} 开始抖音直播纯净转播到 HDMI: {stream_title}")
# NVENC 检测(原样保留)
codec_v, preset_v, tune_param = "libx264", "veryfast", ["-tune", "zerolatency"]
try:
if platform.system().lower() in ["windows", "linux"]:
check = subprocess.run(["nvidia-smi"], capture_output=True, timeout=5)
if check.returncode == 0:
enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"], capture_output=True, text=True, timeout=5)
if "h264_nvenc" in (enc.stdout or ""):
codec_v, preset_v, tune_param = "h264_nvenc", "p5", ["-tune", "ll", "-rc", "vbr", "-cq", "21", "-b:v", "2500k"]
print("[INFO] 检测到 NVIDIA GPU → 使用 NVENC 硬件加速")
except Exception as e:
print(f"[WARN] NVENC 检测失败,回退软件编码: {e}")
# headers & ffmpeg command保持原来参数
douyin_headers = (
"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15\r\n"
"Referer: https://live.douyin.com/\r\n"
"Origin: https://live.douyin.com\r\n"
)
# 720p
# ffmpeg_command = [
# "ffmpeg", "-y", "-loglevel", "info", "-nostdin", "-re",
# "-stats", "-stats_period", "1",
# "-rw_timeout", "30000000",
# "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1",
# "-reconnect_delay_max", "5",
# "-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
# "-flags", "low_delay",
# "-err_detect", "ignore_err",
# "-max_delay", "80000",
# "-thread_queue_size", "2048",
# mpv 命令(整合了您原来的所有视频处理需求)
mpv_cmd_base = [
"mpv",
"--really-quiet", # 可选,调试时去掉
"--no-cache",
"--untimed",
"--demuxer-lavf-format=flv",
"--demuxer-lavf-o=fflags=nobuffer",
"--video-sync=audio",
"--hwdec=auto-safe", # 改成 auto-safe避免不兼容的 hwdec 崩溃
"--profile=low-latency",
# "-headers", douyin_headers,
# "-i", real_url,
# # 720p
# "-vf", "fps=30,scale=720:1280:force_original_aspect_ratio=decrease:flags=lanczos,pad=720:1280:(ow-iw)/2:(oh-ih)/2:black",
# "-c:v", "libx264",
# "-preset", "fast",
# # "-tune", "zerolatency",
# "-profile:v", "high",
# # 720p
# # "-minrate", "3200k","-b:v", "3200k", "-maxrate", "3200k", "-bufsize", "6400k", # bufsize = 2× bitrate保持真 CBR
# "-b:v", "2600k","-maxrate", "3000k","-bufsize", "5200k",
# "-vsync", "cfr",
# "-g", "60", "-keyint_min", "60",
# "-r", "30",
# "-bf", "0",
# "-sc_threshold", "0",
# # "-nal-hrd", "cbr", # 改2强制 CBR
# # 720p
# # "-x264-params", "nal-hrd=cbr:force-cfr=1:scenecut=0:filler=1", # 改3简化 params锁死 CBR
# "-x264-params", "force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0",
# "-pix_fmt", "yuv420p",
# "-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2", # 改41080p可音频上 128kYouTube 推荐)
# "-af", "aresample=async=1:first_pts=0",
"--vo=drm", # 关键:使用 DRM 直接渲染到 HDMI无 X11
"--drm-device=/dev/dri/card0", # 通常是 card0Allwinner H616 基本都支持
"--fs", # 全屏
"--fs-screen=0",
# "-flags", "+global_header",
# "-flvflags", "no_duration_filesize",
# "-f", "flv", youtube_rtmp
# ]
# 视频滤镜保持不变
"--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",
# 1080p
# ffmpeg_command = [
# "ffmpeg", "-y", "-loglevel", "info", "-nostdin", "-re",
# "-stats", "-stats_period", "1",
# "-rw_timeout", "30000000",
# "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1",
# "-reconnect_delay_max", "5",
# "-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
# "-flags", "low_delay",
# "-err_detect", "ignore_err",
# "-max_delay", "80000",
# "-thread_queue_size", "2048",
# "-headers", douyin_headers,
# "-i", real_url,
# # 1080p
# "-vf","fps=30,scale=1080:1920:force_original_aspect_ratio=decrease:flags=lanczos,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black",
# "-c:v", "libx264",
# "-preset", "veryfast",
# "-tune", "zerolatency",
# "-profile:v", "high",
# # 1080p
# "-b:v", "5200k", "-maxrate", "5800k", "-bufsize", "10400k", # 改12× bufsize真 CBR
# "-vsync", "cfr",
# "-g", "60", "-keyint_min", "60",
# "-r", "30",
# "-bf", "0",
# "-sc_threshold", "0",
# # "-nal-hrd", "cbr", # 改2强制 CBR
# # 1080p
# "-x264-params","force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0",
# "-pix_fmt", "yuv420p",
# "-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2",
# "-af", "aresample=async=1:first_pts=0",
# "-flags", "+global_header",
# "-flvflags", "no_duration_filesize",
# "-f", "flv", youtube_rtmp
# ]
# srs内网配置
# ffmpeg_command = [
# "ffmpeg",
# "-loglevel", "info",
# "-err_detect", "ignore_err+explode",
# "-fflags", "+genpts+discardcorrupt+igndts",
# "-reconnect", "1",
# "-reconnect_at_eof", "1",
# "-reconnect_streamed", "1",
# "-reconnect_delay_max", "7",
# "-rw_timeout", "30000000",
# "-headers", douyin_headers,
# # ===== 输入源 =====
# "-i", real_url,
# "-c:v", "copy",
# "-c:a", "copy",
# "-max_muxing_queue_size", "9999",
# "-f", "flv", srs_rtmp
# ]
# ffplay 预览命令(全屏显示到 HDMI
ffplay_cmd = [
"ffplay",
"-f", "flv", # 输入格式必须匹配 ffmpeg 的 -f flv
"-fflags", "nobuffer",
"-flags", "low_delay",
"-framedrop",
"-vf", "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", # 您喜欢的调色滤镜
"-fs", # 全屏
"-autoexit", # 流结束自动退出
"-threads", "6",
"-" # 从标准输入(管道)读取
f"--http-header-fields={douyin_headers}",
"--referrer=https://live.douyin.com/",
real_url
]
# 1080p 输出到 HDMI
# 主 FFmpeg 命令 —— 仅负责拉流、转码、输出到 pipe供 ffplay HDMI 显示)
# ffmpeg_command = [
# "ffmpeg",
# "-y",
# "-loglevel", "info",
# "-nostdin",
# "-re",
# "-stats", "-stats_period", "1",
# # 抖音源重连参数
# "-rw_timeout", "30000000",
# "-reconnect", "1",
# "-reconnect_at_eof", "1",
# "-reconnect_streamed", "1",
# "-reconnect_delay_max", "10",
# # 低延迟输入 flags
# "-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
# "-flags", "low_delay",
# "-err_detect", "ignore_err",
# # 输入 headers抖音必备
# "-headers", douyin_headers,
# "-i", real_url,
# # ==================== 视频处理 ====================
# # 强制 30fps + 1080p竖屏居中补黑边
# "-vf", "fps=30,scale=1080:1920:force_original_aspect_ratio=decrease:flags=lanczos,"
# "pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black",
# # ==================== 编码参数 ====================
# "-c:v", codec_v, # libx264 或 h264_nvenc已在前文检测
# "-preset", preset_v, # veryfast 或 p5
# *tune_param, # zerolatency 或 NVENC 参数
# "-profile:v", "high",
# "-b:v", "5200k",
# "-maxrate", "5800k",
# "-bufsize", "10400k",
# "-g", "60",
# "-keyint_min", "60",
# "-r", "30",
# "-vsync", "cfr",
# "-bf", "0",
# "-sc_threshold", "0",
# "-x264-params", "force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0",
# "-pix_fmt", "yuv420p",
# # ==================== 音频 ====================
# "-c:a", "aac",
# "-b:a", "128k",
# "-ar", "44100",
# "-ac", "2",
# "-af", "aresample=async=1:first_pts=0",
# # ==================== 输出格式 ====================
# "-flags", "+global_header",
# "-flvflags", "no_duration_filesize",
# "-f", "flv", # 格式必须是 flvffplay 读取最稳定
# "pipe:1" # 直接输出到标准输出(管道),供 ffplay 读取
# ]
# 720p 输出到 HDMI优化版适合 Orange Pi Zero 2W
ffmpeg_command = [
"ffmpeg",
"-y",
"-loglevel", "info",
"-nostdin",
"-re",
"-stats", "-stats_period", "1",
# 抖音源重连参数
"-rw_timeout", "30000000",
"-reconnect", "1",
"-reconnect_at_eof", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "10",
# 低延迟输入 flags
"-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
"-flags", "low_delay",
"-err_detect", "ignore_err",
# 输入 headers抖音必备
"-headers", douyin_headers,
"-i", real_url,
# ==================== 视频处理720p 竖屏 ====================
"-vf", "fps=30,"
"scale=720:1280:force_original_aspect_ratio=decrease:flags=lanczos,"
"pad=720:1280:(ow-iw)/2:(oh-ih)/2:black",
# ==================== 编码参数(轻量优化) ====================
"-c:v", codec_v, # libx264板子无硬件加速
"-preset", "ultrafast", # 关键:极大降低 CPU 负载(比 veryfast 更快)
"-tune", "zerolatency", # 低延迟
"-profile:v", "high",
"-b:v", "2600k", # 720p 推荐比特率,清晰又省资源
"-maxrate", "3000k",
"-bufsize", "6000k",
"-g", "60", # GOP = 2 秒30fps 时每 60 帧一个 I 帧)
"-keyint_min", "30",
"-r", "30",
"-vsync", "cfr",
"-bf", "0",
"-sc_threshold", "0",
"-x264-params", "force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0",
"-pix_fmt", "yuv420p",
# ==================== 音频(保持不变) ====================
"-c:a", "aac",
"-b:a", "128k",
"-ar", "44100",
"-ac", "2",
"-af", "aresample=async=1:first_pts=0",
# ==================== 输出格式 ====================
"-flags", "+global_header",
"-flvflags", "no_duration_filesize",
"-f", "flv",
"pipe:1"
]
# ---- 单层重试循环(清晰可控) ----
proc = None
ffplay_proc = None # <--- 新增这一行
stderr_q = None
reader_t = None
retry_delay = INITIAL_RETRY_DELAY
last_frame_time = time.time() # 用于粗略检测“卡顿/下播”
try:
while True:
try:
if retry_delay > INITIAL_RETRY_DELAY:
print(f"[WARN] 防风控等待 {retry_delay}s 后重新连接抖音源...")
print(f"[WARN] 防风控等待 {retry_delay}s 后重新连接...")
time.sleep(retry_delay)
# 清理旧的 ffplay 进程(防止残留)
if ffplay_proc and ffplay_proc.poll() is None:
# 清理旧进程
if proc and proc.poll() is None:
try:
ffplay_proc.stdin.close()
ffplay_proc.wait(timeout=3)
proc.terminate()
proc.wait(timeout=5)
except:
ffplay_proc.terminate()
ffplay_proc = None # 重置为 None
proc.kill()
# 每次重连前先清理上一次的进程/线程
try:
cleanup_proc(proc)
except Exception:
pass
if stderr_q:
# 垃圾化旧队列以便 GC
stderr_q = None
stderr_q = queue.Queue()
print(f"[INFO] [{datetime.datetime.now():%H:%M:%S}] 启动 mpv 纯净播放 → HDMI 全屏")
# 油管直播
# print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动 YouTube 推流 → {stream_title}")
# proc = subprocess.Popen(
# ffmpeg_command,
# stdin=subprocess.DEVNULL,
# stdout=subprocess.DEVNULL,
# stderr=subprocess.PIPE,
# text=True,
# bufsize=1,
# universal_newlines=True
# )
proc = subprocess.Popen(mpv_cmd_base)
# HDMI直播
print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动抖音拉流 → HDMI 全屏显示")
start_time = time.time()
last_frame_time = time.time()
# 先启动 ffplay准备接收管道数据
ffplay_proc = subprocess.Popen(
ffplay_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# 再启动 ffmpeg把输出直接连到 ffplay 的输入
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=ffplay_proc.stdin, # <--- 关键:管道直连 ffplay
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
proc.last_out_ts = time.time()
# 简单监控:每 10 秒检查一次进程是否还在运行
while proc.poll() is None:
time.sleep(10)
# 启动 stderr reader
reader_t = threading.Thread(target=_stderr_reader_thread, args=(proc, stderr_q))
reader_t.daemon = True
reader_t.start()
# mpv 正常运行时不会输出 frame= 信息,所以我们用进程存活 + 运行时长来判断
# 如果进程意外退出 → 进入重试
# 如果长时间(> NO_FRAME_TIMEOUT没变化可视为卡顿或下播保守判断
if time.time() - start_time > START_CHECK_AFTER:
if time.time() - last_frame_time > NO_FRAME_TIMEOUT:
print(f"[WARN] 超过 {NO_FRAME_TIMEOUT}s 无响应,判定可能已下播或卡死")
proc.terminate()
raise StreamEnded()
start_time = last_frame_time = time.time()
# 这里可以加一个“假更新”防止误判
last_frame_time = time.time()
# ---- 监控循环(单一职责:处理 stderr 日志并决定是否结束/重连) ----
while True:
try:
try:
line = stderr_q.get(timeout=STDERR_QUEUE_TIMEOUT)
except queue.Empty:
# 超时检查:如果太久没有任何日志,判为僵死,重启 proc跳出内层进行重试
if time.time() - getattr(proc, "last_out_ts", time.time()) > STALE_OUTPUT_SECONDS:
print("[WARN] 长时间无任何日志,强制重启进程")
cleanup_proc(proc)
# 关闭 ffplay 进程
if ffplay_proc and ffplay_proc.poll() is None:
try:
ffplay_proc.stdin.close() # 关闭管道输入,让 ffplay 自然退出
ffplay_proc.wait(timeout=5)
except:
ffplay_proc.terminate()
ffplay_proc = None # <--- 加这行
break
continue
# mpv 进程自然退出returncode 非0 通常是流中断)
returncode = proc.returncode
print(f"[INFO] mpv 退出returncode={returncode}")
if line is None:
# reader 线程结束;如果 proc 已退出,跳出去重试
if proc.poll() is not None:
break
else:
continue
line = line.strip()
if line.startswith("__ERR__READER__"):
print(f"[ERROR] stderr reader 异常: {line}")
continue
if line:
proc.last_out_ts = time.time()
print(f"[FFmpeg] {line}")
line_lower = line.lower()
# 检测到明确下播关键字 -> 认为主播真下播,立即退出(不再重试)
if any(kw in line_lower for kw in END_KEYWORDS):
print("[ERROR] 检测到主播真下播或被明确拒绝,停止推流")
cleanup_proc(proc)
raise StreamEnded()
# 帧检测:更新 frame 时间戳
if re.search(r'frame=\s*\d+', line_lower) or "fps=" in line_lower:
last_frame_time = time.time()
# 超时无新帧 => 认为下播
if time.time() - start_time > START_CHECK_AFTER:
if time.time() - last_frame_time > NO_FRAME_TIMEOUT:
print(f"[WARN] 连续 {NO_FRAME_TIMEOUT}s 无新帧,判定主播已下播")
cleanup_proc(proc)
raise StreamEnded()
except StreamEnded:
# 真实下播,抛到外层处理(最终会退出函数)
raise
except Exception as e:
# 内层非致命异常:打印、回退重试间隔,退出监控循环以在外层重试
print(f"[ERROR] 内层异常: {e}")
traceback.print_exc()
cleanup_proc(proc)
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
break # 跳出监控循环 -> 外层 while 会继续并重试
# 如果监控循环走到这里并不是因为 StreamEnded例如 break则重试连接loop continue
# 将在 while True 的下一次循环中进行等待并重连
continue
# 如果是明确的下播错误码或日志mpv 会直接退出),我们视作真下播
# mpv 没有 stderr 队列,这里简单处理即可)
raise StreamEnded() if returncode != 0 else None
except StreamEnded:
# 真实的主播下播,停止并彻底退出本录制线程
print(f"[INFO] 主播已下播,停止 YouTube 转推 → {record_name}")
print(f"[INFO] 主播下播,停止转播 → {record_name}")
break
except Exception as e:
# 外层致命异常:打印并退出循环(会在 finally 里做统一清理)
print(f"[ERROR] 外层异常: {e}")
traceback.print_exc()
print(f"[ERROR] 播放异常: {e}")
# 指数退避重试
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
# 等待并尝试重连(除非你希望直接退出,这里保留重试策略)
continue
# ============ 正常或 StreamEnded 后的统一清理(在这里 break 到这里) ============
cleanup_proc(proc)
else:
# 正常退出(极少情况)
break
finally:
# 统一清理
if proc and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=5)
except:
proc.kill()
# 以下清理放在 try/except 里以防止清理过程出错导致漏逻辑
try:
recording.discard(record_name)
recording_time_list.pop(record_name, None)
@@ -2163,35 +1777,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
except Exception:
pass
color_obj.print_colored(f"[{record_name}] YouTube 推流已彻底停止并清理完毕\n", color_obj.GREEN)
return
except Exception as e:
# 最外层异常也要统一清理(保证不会留下僵尸状态)
print(f"[FATAL] YouTube 推流最外层异常: {e}")
traceback.print_exc()
try:
cleanup_proc(proc)
except Exception:
pass
try:
recording.discard(record_name)
recording_time_list.pop(record_name, None)
if record_url in running_list:
running_list.remove(record_url)
# global monitoring
monitoring = max(0, monitoring - 1)
clear_record_info(record_name, record_url)
except Exception:
pass
# 新增
if 'ffplay_proc' in locals() and ffplay_proc and ffplay_proc.poll() is None:
try:
ffplay_proc.terminate()
except:
pass
color_obj.print_colored(f"[{record_name}] YouTube 推流异常退出但已清理\n", color_obj.RED)
return
color_obj.print_colored(f"[{record_name}] HDMI 转播已停止并清理完毕\n", color_obj.GREEN)
count_time = time.time()