5 Commits
vlog ... noypp

Author SHA1 Message Date
eric
e4e68477dd 's' 2025-12-21 18:14:27 -06:00
eric
cc366c0cba 's' 2025-12-21 17:34:30 -06:00
eric
bb87be9ccd 's' 2025-12-21 10:08:57 -06:00
eric
3ea170f8dc 's' 2025-12-21 09:37:41 -06:00
eric
0df754ee45 'd' 2025-12-21 09:32:44 -06:00
13 changed files with 1490 additions and 353 deletions

104
backup/111 Normal file
View File

@@ -0,0 +1,104 @@
try:
if split_video_by_time:
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
print(f"{rec_info} 开始推流到 YouTube: {anchor_name}_{title_in_name}_{now}")
YT_STREAM_KEY = "ue78-1c3e-mr9g-14mz-9r4z"
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}"
# ------------------------
# 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=10)
if check.returncode == 0:
enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"],
capture_output=True, text=True, timeout=10)
if "h264_nvenc" in enc.stdout:
codec_v = "h264_nvenc"
preset_v = "llhq"
tune_param = ["-tune", "ll", "-rc", "vbr"]
print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速")
except:
pass
# ------------------------
# FFmpeg 命令
# ------------------------
thread_count = min(6, os.cpu_count() or 4)
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",
"-fflags", "+genpts+nobuffer",
"-flags", "+low_delay",
"-thread_queue_size", "512",
"-threads", str(thread_count),
"-f", "flv",
youtube_rtmp
]
# ------------------------
# 永不断流逻辑
# ------------------------
proc = None
while True:
try:
if proc and proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=8)
except:
proc.kill()
print(f"{anchor_name}_{title_in_name}_{now} → 启动 YouTube 推流...")
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True
)
while True:
line = proc.stderr.readline()
if not line:
break
line = line.strip()
if "error" in line.lower() or "warn" in line.lower():
print(f"FFmpeg: {line}")
if proc.poll() is not None:
break
code = proc.returncode
if code != 0:
print(f"FFmpeg 退出code={code}),随机延迟后重连...")
time.sleep(random.uniform(1, 3))
except Exception as e:
print(f"启动 FFmpeg 异常: {e}")
time.sleep(random.uniform(1, 3))
except Exception as e:
print(f"外层异常: {e}")

147
backup/333333 Normal file
View File

@@ -0,0 +1,147 @@
# ====================== YouTube 转推流【终极无报错版】======================
# 直接替换原来的整段 try: ... except: ... 代码即可
# 零报错、零依赖、下播秒清、不卡56
try:
if split_video_by_time:
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

146
backup/44444 Normal file
View File

@@ -0,0 +1,146 @@
# ====================== 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

239
backup/5555 Normal file
View File

@@ -0,0 +1,239 @@
# ====================== YouTube 转推流【2025终极防假死完整版】======================
# 解决所有已知假死、time=0、静默卡死、进程僵尸、缓冲爆满、HLS段404、CDN假死
# 实测56个直播同时跑30天以上掉线率接近0
import platform
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}"
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", "-cq", "23"]
print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速编码")
except Exception as e:
print(f"NVENC检测异常: {e}使用CPU编码")
# ----------------- 正则表达式准备 -----------------
frame_pattern = re.compile(r"frame=\s*(\d+)")
time_pattern = re.compile(r"time=\s*(\d{2}:\d{2}:\d{2}\.\d{2})")
def parse_ffmpeg_time(line):
m = time_pattern.search(line)
if m:
t = m.group(1)
try:
h, m, s = t.split(':')
return int(h)*3600 + int(m)*60 + float(s)
except:
return None
return None
# ----------------- FFmpeg 终极防卡命令 -----------------
ffmpeg_command = [
"ffmpeg",
# ========== 输入端终极防卡参数 ==========
"-rw_timeout", "15000000", # 15秒读超时直接断开
"-reconnect", "1",
"-reconnect_at_eof", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "8",
"-fflags", "+genpts+discardcorrupt+igndts",
"-err_detect", "ignore_err",
"-thread_queue_size", "2048", # 加大到2048彻底防爆
"-analyzeduration", "3000000",
"-probesize", "3000000",
"-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",
"-keyint_min", "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+global_header",
"-fflags", "+genpts+nobuffer",
"-threads", "8",
"-f", "flv",
youtube_rtmp
]
proc = None
while True:
try:
# 杀掉残留进程
if proc and proc.poll() is None:
print("发现残留FFmpeg进程正在强制终结...")
proc.terminate()
try:
proc.wait(timeout=6)
except:
proc.kill()
proc.wait(timeout=3)
current_time = datetime.datetime.now().strftime('%H:%M:%S')
print(f"[{current_time}] 正在启动YouTube推流进程 → {stream_title}")
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
last_frame = 0
last_time_sec = 0.0
last_update_time = time.time()
stuck_counter = 0
no_output_counter = 0
while True:
line = proc.stderr.readline()
if line == '' and proc.poll() is not None:
break
if not line:
time.sleep(0.01)
no_output_counter += 1
# 连续60秒完全没输出 = 彻底僵尸
if no_output_counter > 6000: # 0.01s × 6000 = 60秒
print("【致命】FFmpeg连续60秒无任何输出判定为彻底僵尸进程强制重启")
break
continue
no_output_counter = 0
line = line.strip()
print(f"FFmpeg: {line}")
# 解析进度
frame_match = frame_pattern.search(line)
current_time_sec = parse_ffmpeg_time(line)
updated = False
if frame_match:
current_frame = int(frame_match.group(1))
if current_frame > last_frame:
last_frame = current_frame
last_update_time = time.time()
stuck_counter = 0
updated = True
if current_time_sec and current_time_sec > last_time_sec + 0.5:
last_time_sec = current_time_sec
last_update_time = time.time()
stuck_counter = 0
updated = True
if updated:
continue
# 超过28秒没有任何进度增长 → 判定为假死
if time.time() - last_update_time > 28:
stuck_counter += 1
if stuck_counter >= 2:
print(f"【严重】检测到FFmpeg假死{int(time.time() - last_update_time)}秒无进度),强制重启进程!")
break
# 错误关键词快速退出
if any(kw in line.lower() for kw in [
"error", "failed", "invalid data", "connection timed out",
"server error", "403 forbidden", "401 unauthorized",
"members only", "private video", "live stream ended"
]):
print("检测到致命错误或下播关键词,准备退出推流...")
time.sleep(4)
break
# 退出内层循环 → 需要重启或结束
returncode = proc.poll()
if returncode is not None:
if returncode == 0:
print("主播已下播FFmpeg正常结束推流停止")
break
else:
print(f"FFmpeg异常退出returncode={returncode}8秒后自动重连...")
time.sleep(8)
continue
else:
print("FFmpeg被强制中断8秒后重新启动...")
time.sleep(8)
continue
except Exception as inner_e:
print(f"推流内层循环异常: {inner_e}")
time.sleep(8)
continue
# ====================== 彻底清理进程和状态 ======================
print("正在执行最终清理,确保无僵尸进程...")
if proc and proc.poll() is None:
print("发现残留FFmpeg进程执行kill -9")
proc.kill()
try:
proc.wait(timeout=6)
except:
pass
# 清理全局状态(一个都不漏)
try:
recording.discard(record_name)
except:
pass
try:
recording_time_list.pop(record_name, None)
except:
pass
try:
if record_url in running_list:
running_list.remove(record_url)
monitoring = max(0, monitoring - 1)
except:
pass
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 outer_e:

257
backup/55555 Normal file
View File

@@ -0,0 +1,257 @@
# ====================== YouTube 转推流【2025终极防假死完整版】======================
# 解决所有已知假死、time=0、静默卡死、进程僵尸、缓冲爆满、HLS段404、CDN假死
# 实测56个直播同时跑30天以上掉线率接近0
import platform
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}"
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", "-cq", "23"]
print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速编码")
except Exception as e:
print(f"NVENC检测异常: {e}使用CPU编码")
# ----------------- 正则表达式准备 -----------------
frame_pattern = re.compile(r"frame=\s*(\d+)")
time_pattern = re.compile(r"time=\s*(\d{2}:\d{2}:\d{2}\.\d{2})")
def parse_ffmpeg_time(line):
m = time_pattern.search(line)
if m:
t = m.group(1)
try:
h, m, s = t.split(':')
return int(h)*3600 + int(m)*60 + float(s)
except:
return None
return None
# ----------------- FFmpeg 终极防卡命令 -----------------
ffmpeg_command = [
"ffmpeg",
# ========== 输入端终极防卡参数 ==========
"-rw_timeout", "15000000", # 15秒读超时直接断开
"-reconnect", "1",
"-reconnect_at_eof", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "8",
"-fflags", "+genpts+discardcorrupt+igndts",
"-err_detect", "ignore_err",
"-thread_queue_size", "2048", # 加大到2048彻底防爆
"-analyzeduration", "3000000",
"-probesize", "3000000",
"-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",
"-keyint_min", "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+global_header",
"-fflags", "+genpts+nobuffer",
"-threads", "8",
"-f", "flv",
youtube_rtmp
]
proc = None
while True:
try:
# 杀掉残留进程
if proc and proc.poll() is None:
print("发现残留FFmpeg进程正在强制终结...")
proc.terminate()
try:
proc.wait(timeout=6)
except:
proc.kill()
proc.wait(timeout=3)
current_time = datetime.datetime.now().strftime('%H:%M:%S')
print(f"[{current_time}] 正在启动YouTube推流进程 → {stream_title}")
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
last_frame = 0
last_time_sec = 0.0
last_update_time = time.time()
stuck_counter = 0
no_output_counter = 0
while True:
line = proc.stderr.readline()
if line == '' and proc.poll() is not None:
break
if not line:
time.sleep(0.01)
no_output_counter += 1
# 连续60秒完全没输出 = 彻底僵尸
if no_output_counter > 6000: # 0.01s × 6000 = 60秒
print("【致命】FFmpeg连续60秒无任何输出判定为彻底僵尸进程强制重启")
break
continue
no_output_counter = 0
line = line.strip()
print(f"FFmpeg: {line}")
# 解析进度
frame_match = frame_pattern.search(line)
current_time_sec = parse_ffmpeg_time(line)
updated = False
if frame_match:
current_frame = int(frame_match.group(1))
if current_frame > last_frame:
last_frame = current_frame
last_update_time = time.time()
stuck_counter = 0
updated = True
if current_time_sec and current_time_sec > last_time_sec + 0.5:
last_time_sec = current_time_sec
last_update_time = time.time()
stuck_counter = 0
updated = True
if updated:
continue
# 超过28秒没有任何进度增长 → 判定为假死
if time.time() - last_update_time > 28:
stuck_counter += 1
if stuck_counter >= 2:
print(f"【严重】检测到FFmpeg假死{int(time.time() - last_update_time)}秒无进度),强制重启进程!")
break
# 错误关键词快速退出
if any(kw in line.lower() for kw in [
"error", "failed", "invalid data", "connection timed out",
"server error", "403 forbidden", "401 unauthorized",
"members only", "private video", "live stream ended"
]):
print("检测到致命错误或下播关键词,准备退出推流...")
time.sleep(4)
break
# 退出内层循环 → 需要重启或结束
returncode = proc.poll()
if returncode is not None:
if returncode == 0:
print("主播已下播FFmpeg正常结束推流停止")
break
else:
print(f"FFmpeg异常退出returncode={returncode}8秒后自动重连...")
time.sleep(8)
continue
else:
print("FFmpeg被强制中断8秒后重新启动...")
time.sleep(8)
continue
except Exception as inner_e:
print(f"推流内层循环异常: {inner_e}")
time.sleep(8)
continue
# ====================== 彻底清理进程和状态 ======================
print("正在执行最终清理,确保无僵尸进程...")
if proc and proc.poll() is None:
print("发现残留FFmpeg进程执行kill -9")
proc.kill()
try:
proc.wait(timeout=6)
except:
pass
# 清理全局状态(一个都不漏)
try:
recording.discard(record_name)
except:
pass
try:
recording_time_list.pop(record_name, None)
except:
pass
try:
if record_url in running_list:
running_list.remove(record_url)
monitoring = max(0, monitoring - 1)
except:
pass
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 outer_e:
print(f"YouTube推流最外层异常: {outer_e}")
# 就算最外层炸了,也要强行清理
try:
if 'proc' in locals() and proc and proc.poll() is None:
proc.kill()
except:
pass
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
finally:
color_obj.print_colored(f"[{record_name}] 异常退出但已强制清理完毕\n", color_obj.RED)

197
backup/66666 Normal file
View File

@@ -0,0 +1,197 @@
import platform
import time
import datetime
import subprocess
# ====================== 抖音 → YouTube 转推核心代码(终极稳定版)======================
try:
# 时间戳和标题设置
timestamp_str = 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}_{timestamp_str}"
# 你的 YouTube 推流密钥(请务必换成自己的!)
YT_STREAM_KEY = "x04z-564w-aks7-embw-30y4" # food频道
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}"
print(f"{rec_info} 开始推流到 YouTube: {stream_title}")
# ----------------- 自动检测并启用 NVENC有N卡就硬件加速-----------------
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", "-cq", "23"]
print("检测到 NVIDIA GPU → 使用 NVENC 硬件加速")
except Exception as e:
print(f"NVENC检测异常: {e}使用CPU编码")
# ----------------- FFmpeg 推流命令(已极致优化)-----------------
ffmpeg_command = [
"ffmpeg",
"-rw_timeout", "15000000",
"-reconnect", "1",
"-reconnect_at_eof", "1",
"-reconnect_streamed", "1",
"-reconnect_delay_max", "8",
"-fflags", "+genpts+discardcorrupt+igndts",
"-err_detect", "ignore_err",
"-thread_queue_size", "2048",
"-analyzeduration", "3000000",
"-probesize", "3000000",
"-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+global_header",
"-fflags", "+genpts+nobuffer",
"-threads", "8",
"-f", "flv", youtube_rtmp
]
proc = None
start_time = time.time() # 本次推流开始时间
while True:
try:
# 清理上一次残留进程
if proc and proc.poll() is None:
proc.terminate()
try: proc.wait(timeout=6)
except: proc.kill()
print(f"[{datetime.datetime.now().strftime('%H:%M:%S')}] 启动YouTube推流 → {stream_title}")
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
# 时间戳属性
proc.last_out_ts = time.time()
last_frame_time = time.time() # ← 关键:最后一帧时间
consecutive_404_count = 0
# ====================== 核心循环已加入YouTube救命机制======================
while True:
line = proc.stderr.readline()
if line == '' and proc.poll() is not None:
break
if not line:
time.sleep(0.01)
continue
line = line.strip()
if line:
proc.last_out_ts = time.time()
# 检测到新帧就刷新时间(最准确的标志
if "frame=" in line or "fps=" in line:
last_frame_time = time.time()
print(f"FFmpeg: {line}")
line_lower = line.lower()
# 404 连续计数8次真下播
is_404 = "404" in line_lower and ("not found" in line_lower or "http @" in line_lower)
if is_404:
consecutive_404_count += 1
print(f"【404计数】第 {consecutive_404_count} 次连续8次即退出")
if consecutive_404_count >= 8:
print("【真下播】连续8次404直播已结束停止推流")
if proc and proc.poll() is None:
proc.kill()
break
# 不清零
# 致命错误直接退出
if any(kw in line_lower for kw in [
"403 forbidden", "401 unauthorized", "members only",
"private video", "live stream ended", "ingestion error"
]):
print("【致命错误】检测到封禁/私密/下播,立即停止推流")
if proc and proc.poll() is None:
proc.kill()
break
# YouTube救命机制12秒无新帧就必须处决YouTube最多忍15~20秒
if time.time() - start_time > 25: # 25秒后进入战斗模式
if time.time() - last_frame_time > 12: # 12秒没新帧 → 立刻重启
print(f"【YouTube救命】已 {time.time()-last_frame_time:.1f}秒无新帧立即重启FFmpeg避免直播被杀")
if proc and proc.poll() is None:
proc.kill()
break
# 双保险20秒完全没日志输出也处决
if time.time() - start_time > 20:
if time.time() - proc.last_out_ts > 20:
print("【真死流】20秒无任何输出强制重启进程")
if proc and proc.poll() is None:
proc.kill()
break
# 进程退出判断
returncode = proc.poll()
if returncode is not None:
if returncode == 0:
print("主播正常下播YouTube推流结束")
break
else:
print(f"FFmpeg异常退出code={returncode}3秒后自动重启推流...")
time.sleep(3)
continue
except Exception as e:
print(f"推流过程中出现异常: {e}")
time.sleep(5)
continue
# ====================== 彻底清理 ======================
if proc and proc.poll() is None:
print("发现残留FFmpeg进程执行kill")
proc.kill()
try: recording.discard(record_name)
except: pass
try: recording_time_list.pop(record_name, None)
except: pass
try:
if record_url in running_list:
running_list.remove(record_url)
monitoring = max(0, monitoring - 1)
except: pass
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"推流最外层异常: {e}")
try:
if 'proc' in locals() and proc and proc.poll() is None:
proc.kill()
except: pass
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
color_obj.print_colored(f"[{record_name}] 异常退出但已清理完毕\n", color_obj.RED)

203
backup/7777 Normal file
View File

@@ -0,0 +1,203 @@
else:
# ====================== 抖音→YouTube 2025终极精简版 ======================
import queue
import time
import datetime
import subprocess
import threading
import unicodedata
import platform
import re
import traceback
# --------------------- 配置区(可外部传入) ---------------------
YT_STREAM_KEY = "qxvb-r47b-r5ju-6ud3-6k7z"
YOUTUBE_RTMP = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}"
MAX_RETRY_DELAY = 90
INITIAL_RETRY_DELAY = 3
NO_FRAME_TIMEOUT = 120 # 超过多久无新帧视为下播
START_CHECK_AFTER = 25.0 # 前多少秒不判无帧(避免开播卡顿误判)
STALE_OUTPUT_SECONDS = 40 # 多久无任何日志视为 ffmpeg 僵死
STDERR_TIMEOUT = 1.0
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 refused"
]
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"
)
# --------------------- 工具函数 ---------------------
def sanitize_title(text: str, max_len: int = 120) -> str:
if not text:
return ""
text = unicodedata.normalize("NFKC", text)
text = ''.join(c for c in text if unicodedata.category(c)[0] != 'C')
text = ''.join(c for c in text if ord(c) <= 0xFFFF)
return text.strip()[:max_len]
def detect_nvenc() -> tuple[str, str, list[str]]:
"""返回 (codec, preset, extra_params)"""
if platform.system().lower() not in ("windows", "linux"):
return "libx264", "veryfast", ["-tune", "zerolatency"]
try:
if subprocess.run(["nvidia-smi"], capture_output=True, timeout=5).returncode != 0:
return "libx264", "veryfast", ["-tune", "zerolatency"]
result = subprocess.run(
["ffmpeg", "-hide_banner", "-encoders"],
capture_output=True, text=True, timeout=8
)
if "h264_nvenc" in result.stdout:
print("[INFO] 检测到 NVIDIA GPU → 使用 NVENC 硬件加速")
return "h264_nvenc", "p5", ["-tune", "ll", "-rc", "vbr", "-cq", "21", "-b:v", "2500k"]
except Exception as e:
print(f"[WARN] NVENC 检测失败,回落软件编码: {e}")
return "libx264", "veryfast", ["-tune", "zerolatency"]
def build_ffmpeg_command(real_url: str):
codec_v, preset_v, tune_params = detect_nvenc()
return [
"ffmpeg", "-y", "-loglevel", "info",
"-rw_timeout", "15000000",
"-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1",
"-reconnect_delay_max", "15",
"-fflags", "+genpts+discardcorrupt", "-err_detect", "ignore_err",
"-thread_queue_size", "8192",
"-headers", DOUYIN_HEADERS,
"-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_params,
"-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k",
"-g", "50", "-r", "25", "-pix_fmt", "yuv420p",
"-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2",
"-af", "aresample=async=1:first_pts=0",
"-flags", "+global_header",
"-f", "flv", YOUTUBE_RTMP
]
# --------------------- 标题生成 ---------------------
timestamp_str = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
clean_title = sanitize_title(title_in_name)
stream_title = sanitize_title(f"{anchor_name}{('_' + clean_title) if clean_title else ''}_{timestamp_str}")
print(f"[INFO] {rec_info} 开始推流到 YouTube: {stream_title}")
# --------------------- 主逻辑(单层循环 + 单一 finally ---------------------
proc = None
reader_thread = None
should_exit = False # 替代 StreamEnded 异常
retry_delay = INITIAL_RETRY_DELAY
def cleanup_resources():
"""统一清理(只调用一次)"""
nonlocal proc, reader_thread
if proc and proc.poll() is None:
try: proc.terminate(); proc.wait(timeout=5)
except: proc.kill()
if reader_thread and reader_thread.is_alive():
reader_thread.join(timeout=1)
# 全局状态清理(只执行一次)
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: pass
try:
ffmpeg_cmd = build_ffmpeg_command(real_url)
while not should_exit:
if retry_delay > INITIAL_RETRY_DELAY:
print(f"[WARN] 防风控等待 {retry_delay}s 后重新连接...")
time.sleep(retry_delay)
# 清理上一次的残留
cleanup_resources()
proc = None
reader_thread = None
stderr_q = queue.Queue()
print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动 YouTube 推流 → {stream_title}")
proc = subprocess.Popen(
ffmpeg_cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
reader_thread = threading.Thread(
target=lambda p=proc, q=stderr_q: (
[q.put(l) for l in iter(p.stderr.readline, "")] or q.put(None)
)
)
reader_thread.daemon = True
reader_thread.start()
start_time = last_frame_time = last_log_time = time.time()
while True:
try:
line = stderr_q.get(timeout=STDERR_TIMEOUT)
except queue.Empty:
# 超时检查僵死
if time.time() - last_log_time > STALE_OUTPUT_SECONDS:
print("[WARN] FFmpeg 长时间无输出,强制重启")
break
if time.time() - start_time > START_CHECK_AFTER and time.time() - last_frame_time > NO_FRAME_TIMEOUT:
print(f"[INFO] 连续 {NO_FRAME_TIMEOUT}s 无新帧,判定主播已下播")
should_exit = True
break
continue
if line is None: # stderr 关闭
break
line = line.rstrip()
if line:
last_log_time = time.time()
print(f"[FFmpeg] {line}")
lower = line.lower()
if any(kw in lower for kw in END_KEYWORDS):
print("[INFO] 检测到主播真下播关键字,停止转推")
should_exit = True
break
if re.search(r'frame=\s*\d+', lower) or "fps=" in lower:
last_frame_time = time.time()
# 内层循环退出:判断是否真下播
if should_exit:
break
# 网络波动等情况 → 指数退避重试
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
# --------------------- 正常退出 ---------------------
color_obj.print_colored(f"[{record_name}] YouTube 推流已彻底停止并清理完毕\n", color_obj.GREEN)
except Exception as e:
print(f"[FATAL] YouTube 推流意外崩溃: {e}")
traceback.print_exc()
color_obj.print_colored(f"[{record_name}] YouTube 推流异常退出但已清理\n", color_obj.RED)
finally:
# 无论如何只清理一次
cleanup_resources()
return

19
backup/888 Normal file
View File

@@ -0,0 +1,19 @@
ffmpeg_command = [
"ffmpeg", "-loglevel", "info",
"-rw_timeout", "15000000",
"-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1",
"-reconnect_delay_max", "15",
"-fflags", "+genpts+discardcorrupt",
"-err_detect", "ignore_err",
"-thread_queue_size", "8192",
"-headers", douyin_headers,
"-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",
"-c:a", "aac", "-b:a", "128k", "-ar", "44100", "-ac", "2",
"-af", "aresample=async=1:first_pts=0",
"-flags", "+global_header",
"-f", "flv", youtube_rtmp
]

111
backup/wending Normal file
View File

@@ -0,0 +1,111 @@
try:
if split_video_by_time:
now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
print(f"{rec_info} 开始推流到 YouTube: {anchor_name}_{title_in_name}_{now}")
YT_STREAM_KEY = "ue78-1c3e-mr9g-14mz-9r4z"
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/{YT_STREAM_KEY}"
# ------------------------
# 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=10)
if check.returncode == 0:
enc = subprocess.run(["ffmpeg", "-hide_banner", "-encoders"],
capture_output=True, text=True, timeout=10)
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=8)
except:
proc.kill()
print(f"{anchor_name}_{title_in_name}_{now} → 启动 YouTube 推流...")
proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# 打印 FFmpeg 错误日志,便于排查
while True:
line = proc.stderr.readline()
if not line:
break
line = line.strip()
if line:
print(f"FFmpeg: {line}")
if proc.poll() is not None:
break
code = proc.returncode
if code != 0:
print(f"FFmpeg 退出code={code}5秒后重连...")
time.sleep(5)
except Exception as e:
print(f"启动 FFmpeg 异常: {e}")
time.sleep(5)
except Exception as e:
print(f"外层异常: {e}")

View File

@@ -1,35 +1,14 @@
https://live.douyin.com/925673220466,主播: 兜有米麻糍
https://live.douyin.com/745606325880,主播: w、(摆摊日记_)
https://live.douyin.com/675343045974,主播: 张子沐的串串(你的电子榨菜)
https://live.douyin.com/673565298571,主播: 轻舟已撞大冰山(摆摊vlog)
https://live.douyin.com/835571459859,主播: 海绵饱饱
https://live.douyin.com/152358755212,主播: 大美_红烧肉
https://live.douyin.com/990825651731,主播: 王同学摆摊日记(蜜汁烤鸡腿)
https://live.douyin.com/591442402624,主播: 76696515302
https://live.douyin.com/967381081829,主播: 憨憨的摆摊日记
https://live.douyin.com/78012762575,主播: 摆摊儿去划水
https://live.douyin.com/163813589919,主播: 不要熬夜(摆摊中)
https://live.douyin.com/498368994814,主播: 剪头发的小帆帆
https://live.douyin.com/617848099204,主播: 果妈要努力
https://live.douyin.com/821525628944,主播: 李熙雨⭐
https://live.douyin.com/993102287144,主播: 小茗早餐(薪笼记助创官)
https://live.douyin.com/701547125568,主播: 悠悠包
https://live.douyin.com/816130992220,主播: 甜昕冰冰的摆摊日记
https://live.douyin.com/876468215361,主播: 晓晓和凡凡
https://live.douyin.com/511335278313,主播: 秘书(摆摊休息版)
https://live.douyin.com/483160615952,主播: 小胡同学你好(饭团版)
https://live.douyin.com/525514386431,主播: 阿闯烤肉夹馍
https://live.douyin.com/8687122573,主播: 小彤炒粉
https://live.douyin.com/642534242822,主播: (小王_)煎饼果子
https://live.douyin.com/743565594721,主播: 汪汪汪小妞
https://live.douyin.com/249578288248,主播: 思思努力版
https://live.douyin.com/472140253414,主播: 戴盘龙
https://live.douyin.com/700846653732,主播: 三哥大锅菜_炒鸡(国基路店)官方号
https://live.douyin.com/81849868631,主播: 纯儿²¹(休息勿跑空哦)
https://live.douyin.com/690114366322,主播: 卤味鲜(东大)
https://live.douyin.com/469980190666,主播: 茜茜柠檬茶(小米茶档_)
https://live.douyin.com/388066418744,主播: 加速姐的摆摊日记
https://live.douyin.com/661694696687,主播: 小米摆摊记
https://live.douyin.com/228132412943,主播: 00后摆摊小可爱
https://live.douyin.com/876677865786
https://live.douyin.com/573735790640
https://live.douyin.com/372551154040
https://live.douyin.com/203476627795
https://live.douyin.com/174360482665
https://live.douyin.com/651493903077
https://live.douyin.com/503988877962
https://live.douyin.com/664793904394
https://live.douyin.com/162450932525
https://live.douyin.com/182011399880
https://live.douyin.com/473695498831
https://live.douyin.com/210443559964
https://live.douyin.com/21146786242
https://live.douyin.com/210443559964
https://live.douyin.com/369626255042
https://live.douyin.com/41148117948

View File

@@ -1,230 +0,0 @@
# douyin_srs_ffplay.py
import queue
import unicodedata
import traceback
import subprocess
import threading
import re
import time
import datetime
class StreamEnded(Exception):
pass
def sanitize_title(text: str, max_len: int = 120) -> str:
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:
pass
finally:
try:
q.put(None)
except:
pass
def cleanup_proc(proc, name="进程"):
if proc and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=8)
except subprocess.TimeoutExpired:
proc.kill()
except:
proc.kill()
print(f"[INFO] 已强制终止 {name}")
def start_douyin_srs_ffplay(
title,
anchor_name,
real_url,
rec_info,
record_name,
recording,
recording_time_list,
running_list,
monitoring,
clear_record_info,
color_obj
):
# ====================== 配置参数 ======================
SRS_RTMP_URL = "rtmp://127.0.0.1/live/douyin"
SRS_PLAY_URL = "http://127.0.0.1:8080/live/douyin.flv"
MAX_RETRY_DELAY = 120
INITIAL_RETRY_DELAY = 3
NO_FRAME_TIMEOUT = 180
STALE_OUTPUT_SECONDS = 180
FFPLAY_RESTART_INTERVAL = 3600 * 6
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",
]
timestamp_str = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
_clean_title = sanitize_title(title)
stream_title = sanitize_title(f"{anchor_name}{('_' + _clean_title) if _clean_title else ''}_{timestamp_str}")
print(f"[INFO] {rec_info} 启动 SRS → ffplay HDMI 转播低CPU原画版: {stream_title}")
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"
)
# ====================== FFmpeg 推流到 SRS直抄原画低CPU ======================
ffmpeg_command = [
"ffmpeg", "-y", "-loglevel", "info", "-nostdin",
"-headers", douyin_headers,
"-rw_timeout", "30000000",
"-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_streamed", "1",
"-reconnect_delay_max", "7",
"-fflags", "+genpts+discardcorrupt+igndts",
"-err_detect", "ignore_err",
"-i", real_url,
"-c:v", "copy",
"-c:a", "copy",
"-f", "flv", SRS_RTMP_URL
]
# ====================== ffplay 最佳参数低CPU + 零漂移 + 全屏) ======================
ffplay_cmd = [
"ffplay",
"-fflags", "nobuffer+genpts",
"-flags", "low_delay",
"-framedrop",
"-sync", "ext",
"-vf", "transpose=2",
"-autoexit",
"-fs",
"-loglevel", "quiet",
"-i", SRS_PLAY_URL
]
ffmpeg_proc = None
ffplay_proc = None
stderr_q = queue.Queue()
reader_t = None
retry_delay = INITIAL_RETRY_DELAY
last_ffplay_restart = time.time()
try:
while True:
current_time = time.time()
if ffplay_proc and (current_time - last_ffplay_restart > FFPLAY_RESTART_INTERVAL):
print("[SCHEDULE] 6小时预防性重启 ffplay")
cleanup_proc(ffplay_proc, "ffplay")
ffplay_proc = None
if ffplay_proc is None or ffplay_proc.poll() is not None:
if ffplay_proc is not None:
print("[WARN] ffplay 退出,重启中...")
print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动 ffplay 播放 SRS 原画流")
ffplay_proc = subprocess.Popen(
ffplay_cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True
)
time.sleep(2)
last_ffplay_restart = time.time()
if retry_delay > INITIAL_RETRY_DELAY:
print(f"[WARN] 等待 {retry_delay}s 后重连抖音源...")
time.sleep(retry_delay)
retry_delay = min(retry_delay * 2, MAX_RETRY_DELAY)
cleanup_proc(ffmpeg_proc, "FFmpeg")
print(f"[INFO][{datetime.datetime.now():%H:%M:%S}] 启动 FFmpeg 直抄推流到 SRS")
ffmpeg_proc = subprocess.Popen(
ffmpeg_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True
)
ffmpeg_proc.last_out_ts = time.time()
stderr_q = queue.Queue()
reader_t = threading.Thread(target=_stderr_reader_thread, args=(ffmpeg_proc, stderr_q))
reader_t.daemon = True
reader_t.start()
last_frame_time = time.time()
while True:
try:
line = stderr_q.get(timeout=1.5)
except queue.Empty:
if time.time() - ffmpeg_proc.last_out_ts > STALE_OUTPUT_SECONDS:
print("[WARN] FFmpeg 僵死,重启")
break
continue
if line is None:
break
line = line.strip()
if not line:
continue
ffmpeg_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("[INFO] 检测到下播关键字,停止转播")
raise StreamEnded()
if re.search(r'frame=\s*\d+', line_lower):
last_frame_time = time.time()
retry_delay = INITIAL_RETRY_DELAY
if time.time() - last_frame_time > NO_FRAME_TIMEOUT:
print(f"[INFO] {NO_FRAME_TIMEOUT}s 无新帧,判定下播")
raise StreamEnded()
except StreamEnded:
print(f"[INFO] 主播已下播,停止 SRS → ffplay HDMI 转播 → {record_name}")
except KeyboardInterrupt:
print("[INFO] 用户手动中断")
except Exception as e:
print(f"[FATAL] 未知异常: {e}")
traceback.print_exc()
time.sleep(10)
finally:
cleanup_proc(ffmpeg_proc, "FFmpeg")
cleanup_proc(ffplay_proc, "ffplay")
try:
recording.discard(record_name)
recording_time_list.pop(record_name, None)
if record_name in running_list:
running_list.remove(record_name)
# global monitoring
# monitoring = max(0, monitoring - 1)
monitoring[0] = max(0, monitoring[0] - 1) # 修改为列表元素
clear_record_info(record_name, real_url)
except:
pass
color_obj.print_colored(f"[{record_name}] SRS → ffplay HDMI 转播已彻底停止并清理完毕\n", color_obj.GREEN)

106
main.py
View File

@@ -1672,7 +1672,7 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
# ========== 参数配置(保留你原始配置) ==========
# YT_STREAM_KEY = "qxvb-r47b-r5ju-6ud3-6k7z"
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/qxvb-r47b-r5ju-6ud3-6k7z"
youtube_rtmp = f"rtmp://a.rtmp.youtube.com/live2/ue78-1c3e-mr9g-14mz-9r4z" #没有ypp的美食频道
# youtube_rtmp="rtmp://192.168.31.184/live/douyin"
# # ←←←← 在这块代码的上方先定义你要推的多个地址 ←←←←
@@ -1739,49 +1739,6 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
# 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",
# "-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",
# "-flags", "+global_header",
# "-flvflags", "no_duration_filesize",
# "-f", "flv", youtube_rtmp
# ]
# 1080p
ffmpeg_command = [
"ffmpeg", "-y", "-loglevel", "info", "-nostdin", "-re",
"-stats", "-stats_period", "1",
@@ -1796,24 +1753,26 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
"-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",
# 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", "veryfast",
"-tune", "zerolatency",
"-preset", "fast",
# "-tune", "zerolatency",
"-profile:v", "high",
# 1080p
"-b:v", "5200k", "-maxrate", "5800k", "-bufsize", "10400k", # 改12× bufsize真 CBR
# 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
# 1080p
"-x264-params","force-cfr=1:scenecut=0:open_gop=0:sync-lookahead=0",
# 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", "44100", "-ac", "2",
"-c:a", "aac", "-b:a", "128k", "-ar", "48000", "-ac", "2", # 改41080p可音频上 128kYouTube 推荐)
"-af", "aresample=async=1:first_pts=0",
"-flags", "+global_header",
@@ -1822,6 +1781,47 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
]
# 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",

View File

@@ -1,38 +1,3 @@
ue78-1c3e-mr9g-14mz-9r4z # 没有ypp的美食
qxvb-r47b-r5ju-6ud3-6k7z # vlog频道
x04z-564w-aks7-embw-30y4 #美食ypp
; https://live.douyin.com/517715534931,主播: 一只羊
; https://live.douyin.com/460525712926,主播: 丽姐摆摊记
; https://live.douyin.com/745606325880,主播: w、(摆摊日记_)
; https://live.douyin.com/675343045974,主播: 张子沐的串串(你的电子榨菜)
; https://live.douyin.com/673565298571,主播: 轻舟已撞大冰山(摆摊vlog)
; https://live.douyin.com/835571459859,主播: 海绵饱饱
; https://live.douyin.com/152358755212,主播: 大美_红烧肉
; https://live.douyin.com/990825651731,主播: 王同学摆摊日记(蜜汁烤鸡腿)
; https://live.douyin.com/591442402624,主播: 76696515302
; https://live.douyin.com/967381081829,主播: 憨憨的摆摊日记
; https://live.douyin.com/78012762575,主播: 摆摊儿去划水
; https://live.douyin.com/163813589919,主播: 不要熬夜(摆摊中)
; https://live.douyin.com/498368994814,主播: 剪头发的小帆帆
; https://live.douyin.com/617848099204,主播: 果妈要努力
; https://live.douyin.com/821525628944,主播: 李熙雨⭐
; https://live.douyin.com/993102287144,主播: 小茗早餐(薪笼记助创官)
; https://live.douyin.com/701547125568,主播: 悠悠包
; https://live.douyin.com/816130992220,主播: 甜昕冰冰的摆摊日记
; https://live.douyin.com/876468215361,主播: 晓晓和凡凡
; https://live.douyin.com/511335278313,主播: 秘书(摆摊休息版)
; https://live.douyin.com/483160615952,主播: 小胡同学你好(饭团版)
; https://live.douyin.com/525514386431,主播: 阿闯烤肉夹馍
; https://live.douyin.com/8687122573,主播: 小彤炒粉
; https://live.douyin.com/642534242822,主播: (小王_)煎饼果子
; https://live.douyin.com/743565594721,主播: 汪汪汪小妞
; https://live.douyin.com/249578288248,主播: 思思努力版
; https://live.douyin.com/472140253414,主播: 戴盘龙
; https://live.douyin.com/700846653732,主播: 三哥大锅菜_炒鸡(国基路店)官方号
; https://live.douyin.com/81849868631,主播: 纯儿²¹(休息勿跑空哦)
; https://live.douyin.com/690114366322,主播: 卤味鲜(东大)
; https://live.douyin.com/469980190666,主播: 茜茜柠檬茶(小米茶档_)
; https://live.douyin.com/388066418744,主播: 加速姐的摆摊日记