'优化'
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
https://live.douyin.com/60379224194
|
https://live.douyin.com/898038766356
|
||||||
|
https://live.douyin.com/60379224194
|
||||||
https://live.douyin.com/72212955083
|
https://live.douyin.com/72212955083
|
||||||
https://live.douyin.com/621118100231
|
https://live.douyin.com/621118100231
|
||||||
https://live.douyin.com/280422307519
|
https://live.douyin.com/280422307519
|
||||||
|
|||||||
120
main.py
120
main.py
@@ -258,67 +258,102 @@ def launch_ffmpeg(input_url: str, out_url: str, extra_headers: Optional[str] = N
|
|||||||
"""
|
"""
|
||||||
启动一个 ffmpeg 进程:输入 input_url,输出 RTMP 到 out_url。
|
启动一个 ffmpeg 进程:输入 input_url,输出 RTMP 到 out_url。
|
||||||
"""
|
"""
|
||||||
|
import threading
|
||||||
|
|
||||||
user_agent = ("Mozilla/5.0 (Linux; Android 11; SM-G973U) "
|
user_agent = ("Mozilla/5.0 (Linux; Android 11; SM-G973U) "
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
"Chrome/87.0.4280.141 Mobile Safari/537.36")
|
"Chrome/87.0.4280.141 Mobile Safari/537.36")
|
||||||
|
|
||||||
rw_timeout = "15000000"
|
# —— 输入侧容错/重连/时间戳修复 ——
|
||||||
analyzeduration = "20000000"
|
rw_timeout = "15000000"
|
||||||
probesize = "10000000"
|
analyzeduration = "20000000"
|
||||||
|
probesize = "10000000"
|
||||||
|
|
||||||
base = [
|
base = [
|
||||||
"ffmpeg", "-nostdin", "-hide_banner",
|
"ffmpeg", "-nostdin", "-hide_banner",
|
||||||
# 超时和重连
|
# 输入容错 & 自动重连
|
||||||
"-rw_timeout", rw_timeout,
|
"-rw_timeout", "15000000",
|
||||||
"-timeout", "10000000",
|
"-timeout", "10000000",
|
||||||
"-reconnect", "1",
|
"-reconnect", "1",
|
||||||
"-reconnect_streamed", "1",
|
"-reconnect_streamed", "1",
|
||||||
"-reconnect_at_eof", "1",
|
"-reconnect_at_eof", "1",
|
||||||
"-reconnect_delay_max", "60",
|
"-reconnect_delay_max", "30",
|
||||||
|
|
||||||
# UA + 协议
|
# User-Agent 和协议
|
||||||
"-user_agent", user_agent,
|
"-user_agent", user_agent,
|
||||||
"-protocol_whitelist", "rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy",
|
"-protocol_whitelist", "file,http,https,tcp,tls,crypto,udp,rtmp,rtp,httpproxy",
|
||||||
|
|
||||||
# 缓冲和分析
|
# 缓冲 & 探测优化
|
||||||
"-thread_queue_size", "1024",
|
"-thread_queue_size", "4096",
|
||||||
"-analyzeduration", analyzeduration,
|
"-analyzeduration", "10000000",
|
||||||
"-probesize", probesize,
|
"-probesize", "10000000",
|
||||||
"-fflags", "+discardcorrupt",
|
|
||||||
|
|
||||||
# 输入
|
# 输入流
|
||||||
"-i", input_url,
|
"-re", "-i", input_url,
|
||||||
|
|
||||||
# 强制视频转码成 YouTube 最兼容的格式
|
# 容错标志:防止丢包、断音直接崩溃
|
||||||
|
"-fflags", "+genpts+discardcorrupt+nobuffer",
|
||||||
|
"-use_wallclock_as_timestamps", "1",
|
||||||
|
|
||||||
|
# 视频编码
|
||||||
"-c:v", "libx264",
|
"-c:v", "libx264",
|
||||||
"-preset", "veryfast",
|
"-preset", "veryfast",
|
||||||
"-pix_fmt", "yuv420p",
|
"-tune", "zerolatency",
|
||||||
"-profile:v", "high",
|
"-profile:v", "high",
|
||||||
"-level", "4.1",
|
"-level:v", "4.1",
|
||||||
"-r", "30", # 固定帧率
|
"-pix_fmt", "yuv420p",
|
||||||
"-g", "60", # GOP=2s
|
"-r", "30",
|
||||||
|
"-g", "60",
|
||||||
"-b:v", "5000k",
|
"-b:v", "5000k",
|
||||||
"-maxrate", "5000k",
|
"-maxrate", "5000k",
|
||||||
"-bufsize", "10000k",
|
"-bufsize", "10000k",
|
||||||
|
"-vf", "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2",
|
||||||
|
|
||||||
# 音频转码成 AAC
|
# 音频编码
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-b:a", "128k",
|
"-b:a", "128k",
|
||||||
"-ar", "44100",
|
"-ar", "44100",
|
||||||
"-ac", "2",
|
"-ac", "2",
|
||||||
|
|
||||||
# 避免缓冲过长导致 "no data"
|
# 输出到 YouTube
|
||||||
"-muxdelay", "0",
|
"-f", "flv", out_url,
|
||||||
"-shortest",
|
"-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
|
||||||
|
"-avioflags", "direct",
|
||||||
"-f", "flv", out_url
|
"-rtbufsize", "100M",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# 平台需要自定义 header(如 referer/origin)
|
||||||
|
if extra_headers:
|
||||||
|
# 插到 -user_agent 后、-thread_queue_size 前
|
||||||
|
i1 = base.index("-user_agent") + 2
|
||||||
|
i2 = base.index("-thread_queue_size")
|
||||||
|
base[i1:i1] = ["-headers", extra_headers]
|
||||||
|
|
||||||
|
# 代理
|
||||||
if proxy_addr:
|
if proxy_addr:
|
||||||
base = ["ffmpeg", "-http_proxy", proxy_addr] + base[1:]
|
base = ["ffmpeg", "-http_proxy", proxy_addr] + base[1:]
|
||||||
|
|
||||||
|
# 打印命令便于排障
|
||||||
|
print("ffmpeg cmd:", " ".join(base if len(" ".join(base)) < 500 else base[:30] + ["..."]))
|
||||||
|
|
||||||
logger.info(f"启动推流:{input_url} -> YouTube")
|
logger.info(f"启动推流:{input_url} -> YouTube")
|
||||||
p = subprocess.Popen(base, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
p = subprocess.Popen(base, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1)
|
||||||
|
|
||||||
|
# —— 启动 watchdog:若 20s 没有任何输出,判定卡死,主循环会重启它 ——
|
||||||
|
p.last_out_ts = time.time()
|
||||||
|
|
||||||
|
def _reader():
|
||||||
|
try:
|
||||||
|
for line in iter(p.stdout.readline, b''):
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
p.last_out_ts = time.time() # 任何输出都视为“仍在工作”
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
t = threading.Thread(target=_reader, daemon=True)
|
||||||
|
t.start()
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def stop_ffmpeg(p: Optional[subprocess.Popen]):
|
def stop_ffmpeg(p: Optional[subprocess.Popen]):
|
||||||
@@ -422,12 +457,24 @@ def main():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"URL 列表热更新失败:{e}")
|
logger.warning(f"URL 列表热更新失败:{e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if current_proc and current_url:
|
if current_proc and current_url:
|
||||||
# 已在推流:检测是否仍直播 & 进程状态
|
# 已在推流:检测是否仍直播 & 进程状态
|
||||||
if current_proc.poll() is not None:
|
if current_proc.poll() is not None:
|
||||||
color.print_colored(f"[{now_str()}] ffmpeg 退出,尝试切源…", color.YELLOW)
|
color.print_colored(f"[{now_str()}] ffmpeg 进程退出,重启推流…", color.YELLOW)
|
||||||
current_proc = None
|
stop_ffmpeg(current_proc)
|
||||||
current_url = None
|
current_proc = launch_ffmpeg(real_url, out_url, headers)
|
||||||
|
current_proc.last_out_ts = time.time()
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检测 ffmpeg 是否卡住(10 秒无输出)
|
||||||
|
no_out_for = time.time() - getattr(current_proc, "last_out_ts", time.time())
|
||||||
|
if no_out_for > 20: # ✅ 改成 20 秒
|
||||||
|
color.print_colored(f"\n[{now_str()}] ffmpeg 20s 无输出,尝试重启当前源…", color.YELLOW)
|
||||||
|
stop_ffmpeg(current_proc)
|
||||||
|
current_proc = launch_ffmpeg(real_url, out_url, headers)
|
||||||
|
current_proc.last_out_ts = time.time()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 轻量检测:仍直播?
|
# 轻量检测:仍直播?
|
||||||
@@ -436,27 +483,22 @@ def main():
|
|||||||
still_live = bool(port.get("is_live"))
|
still_live = bool(port.get("is_live"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"检测当前源失败:{e}")
|
logger.error(f"检测当前源失败:{e}")
|
||||||
still_live = False
|
still_live = True # ⚠️ 出错时默认仍在播,避免误切
|
||||||
|
|
||||||
if still_live:
|
if still_live:
|
||||||
elapsed = int(time.time() - start_time)
|
elapsed = int(time.time() - start_time)
|
||||||
h, m = divmod(elapsed, 3600)
|
h, m = divmod(elapsed, 3600)
|
||||||
m, s = divmod(m, 60)
|
m, s = divmod(m, 60)
|
||||||
dur_str = f"{h:02d}:{m:02d}:{s:02d}"
|
dur_str = f"{h:02d}:{m:02d}:{s:02d}"
|
||||||
|
print(f"\r[{now_str()}] 正在转播({dur_str}):{current_platform} | {current_anchor} | {current_url}", end="")
|
||||||
print(
|
|
||||||
f"\r[{now_str()}] 正在转播({dur_str}):{current_platform} | {current_anchor} | {current_url}",
|
|
||||||
end=""
|
|
||||||
)
|
|
||||||
time.sleep(check_interval_live)
|
time.sleep(check_interval_live)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
else:
|
else:
|
||||||
color.print_colored(f"\n[{now_str()}] 当前源已停播,切换下一个…", color.YELLOW)
|
color.print_colored(f"\n[{now_str()}] 主播停播,才切换下一个源…", color.YELLOW)
|
||||||
stop_ffmpeg(current_proc)
|
stop_ffmpeg(current_proc)
|
||||||
current_proc = None
|
current_proc = None
|
||||||
current_url = None
|
current_url = None
|
||||||
# 落下去选择下一个
|
|
||||||
else:
|
else:
|
||||||
# 未在推流:从 urls 顺序找第一个 is_live 的
|
# 未在推流:从 urls 顺序找第一个 is_live 的
|
||||||
if not urls:
|
if not urls:
|
||||||
|
|||||||
Reference in New Issue
Block a user