147 lines
9.8 KiB
Plaintext
147 lines
9.8 KiB
Plaintext
# ====================== YouTube 转推流【终极无报错版】======================
|
||
# 直接替换原来的整段 try: ... except: ... 代码即可
|
||
# 零报错、零依赖、下播秒清、不卡56
|
||
try:
|
||
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
|
||
title_suffix = f"_{title_in_name}" if title_in_name else ""
|
||
stream_title = f"{anchor_name}{title_suffix}_{now}"
|
||
|
||
# 改这里就行,你的 YouTube 推流密钥
|
||
YT_STREAM_KEY = "qxvb-r47b-r5ju-6ud3-6k7z"
|
||
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}"
|
||
|
||
print(f"{rec_info} 开始推流到 YouTube: {stream_title}")
|
||
|
||
# ----------------- NVENC 检测(不变)-----------------
|
||
codec_v = "libx264"
|
||
preset_v = "veryfast"
|
||
tune_param = ["-tune", "zerolatency"]
|
||
try:
|
||
if platform.system().lower() in ["windows", "linux"]:
|
||
check = subprocess.run(["nvidia-smi"], capture_output=True, timeout=8)
|
||
if check.returncode == 0:
|
||
enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"],
|
||
capture_output=True, text=True, timeout=8)
|
||
if "h264_nvenc" in enc.stdout:
|
||
codec_v = "h264_nvenc"
|
||
preset_v = "p5"
|
||
tune_param = ["-tune", "ll", "-rc", "vbr"]
|
||
print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速")
|
||
except:
|
||
pass
|
||
|
||
# ----------------- FFmpeg 命令 -----------------
|
||
ffmpeg_command = [
|
||
"ffmpeg", "-re", "-i", real_url,
|
||
"-vf", "scale=720:1280:force_original_aspect_ratio=decrease,pad=720:1280:(ow-iw)/2:(oh-ih)/2:black",
|
||
"-c:v", codec_v, "-preset", preset_v, *tune_param,
|
||
"-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k",
|
||
"-g", "50", "-r", "25", "-pix_fmt", "yuv420p", "-bf", "0",
|
||
"-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2",
|
||
"-af", "aresample=async=1:first_pts=0",
|
||
"-flags", "+low_delay", "-fflags", "+genpts",
|
||
"-thread_queue_size", "512", "-threads", "6",
|
||
"-f", "flv", youtube_rtmp
|
||
]
|
||
|
||
# ----------------- 永不断流主循环 -----------------
|
||
proc = None
|
||
while True:
|
||
try:
|
||
# 杀掉旧进程
|
||
if proc and proc.poll() is None:
|
||
proc.terminate()
|
||
try:
|
||
proc.wait(timeout=6)
|
||
except:
|
||
proc.kill()
|
||
|
||
print(f"启动 YouTube 推流 → {stream_title}")
|
||
proc = subprocess.Popen(
|
||
ffmpeg_command,
|
||
stdin=subprocess.DEVNULL,
|
||
stdout=subprocess.PIPE,
|
||
stderr=subprocess.PIPE,
|
||
text=True,
|
||
bufsize=1,
|
||
universal_newlines=True
|
||
)
|
||
|
||
error_counter = 0
|
||
last_error_time = time.time()
|
||
|
||
while True:
|
||
line = proc.stderr.readline()
|
||
if not line and proc.poll() is not None:
|
||
break
|
||
if not line:
|
||
continue
|
||
|
||
line = line.strip()
|
||
print(f"FFmpeg: {line}")
|
||
|
||
if any(kw in line.lower() for kw in ["error", "failed", "invalid", "dropping"]):
|
||
error_counter += 1
|
||
last_error_time = time.time()
|
||
else:
|
||
error_counter = 0
|
||
|
||
if error_counter >= 6 and (time.time() - last_error_time) < 1.3:
|
||
print("检测到主播已下播,停止推流")
|
||
break
|
||
|
||
if time.time() - last_error_time > 2:
|
||
error_counter = 0
|
||
|
||
returncode = proc.returncode if proc.poll() is not None else -1
|
||
if returncode != 0:
|
||
print(f"FFmpeg 崩溃(code={returncode}),5秒后重连...")
|
||
time.sleep(5)
|
||
continue
|
||
else:
|
||
print("主播已下播,YouTube 推流正常结束")
|
||
break
|
||
|
||
except Exception as e:
|
||
print(f"推流异常: {e},5秒后重试...")
|
||
time.sleep(5)
|
||
|
||
# ====================== 彻底清理(关键!)======================
|
||
if proc and proc.poll() is None:
|
||
proc.terminate()
|
||
try:
|
||
proc.wait(timeout=5)
|
||
except:
|
||
proc.kill()
|
||
|
||
# 精准移除本直播间状态(不影响其他直播)
|
||
recording.discard(record_name)
|
||
recording_time_list.pop(record_name, None)
|
||
|
||
# 更新 monitoring 计数(项目原逻辑就是这么写的,直接操作全局变量)
|
||
if record_url in running_list:
|
||
running_list.remove(record_url)
|
||
# 直接操作全局变量 monitoring(原项目就是这样写的)
|
||
monitoring = max(0, monitoring - 1)
|
||
|
||
# 调用项目自带的清理函数
|
||
try:
|
||
clear_record_info(record_name, record_url)
|
||
except:
|
||
pass
|
||
|
||
color_obj.print_colored(f"[{record_name}] YouTube 推流已彻底停止并清理完毕\n", color_obj.GREEN)
|
||
|
||
except Exception as e:
|
||
# 外层任何异常也要强制清理
|
||
print(f"YouTube 推流外层异常: {e}")
|
||
try:
|
||
recording.discard(record_name)
|
||
recording_time_list.pop(record_name, None)
|
||
if record_url in running_list:
|
||
running_list.remove(record_url)
|
||
monitoring = max(0, monitoring - 1)
|
||
clear_record_info(record_name, record_url)
|
||
except:
|
||
pass
|