'优化'
This commit is contained in:
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。
|
||||
"""
|
||||
import threading
|
||||
|
||||
user_agent = ("Mozilla/5.0 (Linux; Android 11; SM-G973U) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/87.0.4280.141 Mobile Safari/537.36")
|
||||
|
||||
rw_timeout = "15000000"
|
||||
analyzeduration = "20000000"
|
||||
probesize = "10000000"
|
||||
# —— 输入侧容错/重连/时间戳修复 ——
|
||||
rw_timeout = "15000000"
|
||||
analyzeduration = "20000000"
|
||||
probesize = "10000000"
|
||||
|
||||
base = [
|
||||
"ffmpeg", "-nostdin", "-hide_banner",
|
||||
# 超时和重连
|
||||
"-rw_timeout", rw_timeout,
|
||||
# 输入容错 & 自动重连
|
||||
"-rw_timeout", "15000000",
|
||||
"-timeout", "10000000",
|
||||
"-reconnect", "1",
|
||||
"-reconnect_streamed", "1",
|
||||
"-reconnect_at_eof", "1",
|
||||
"-reconnect_delay_max", "60",
|
||||
"-reconnect_delay_max", "30",
|
||||
|
||||
# UA + 协议
|
||||
# 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",
|
||||
"-analyzeduration", analyzeduration,
|
||||
"-probesize", probesize,
|
||||
"-fflags", "+discardcorrupt",
|
||||
# 缓冲 & 探测优化
|
||||
"-thread_queue_size", "4096",
|
||||
"-analyzeduration", "10000000",
|
||||
"-probesize", "10000000",
|
||||
|
||||
# 输入
|
||||
"-i", input_url,
|
||||
# 输入流
|
||||
"-re", "-i", input_url,
|
||||
|
||||
# 强制视频转码成 YouTube 最兼容的格式
|
||||
# 容错标志:防止丢包、断音直接崩溃
|
||||
"-fflags", "+genpts+discardcorrupt+nobuffer",
|
||||
"-use_wallclock_as_timestamps", "1",
|
||||
|
||||
# 视频编码
|
||||
"-c:v", "libx264",
|
||||
"-preset", "veryfast",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-tune", "zerolatency",
|
||||
"-profile:v", "high",
|
||||
"-level", "4.1",
|
||||
"-r", "30", # 固定帧率
|
||||
"-g", "60", # GOP=2s
|
||||
"-level:v", "4.1",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-r", "30",
|
||||
"-g", "60",
|
||||
"-b:v", "5000k",
|
||||
"-maxrate", "5000k",
|
||||
"-bufsize", "10000k",
|
||||
"-vf", "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2",
|
||||
|
||||
# 音频转码成 AAC
|
||||
# 音频编码
|
||||
"-c:a", "aac",
|
||||
"-b:a", "128k",
|
||||
"-ar", "44100",
|
||||
"-ac", "2",
|
||||
|
||||
# 避免缓冲过长导致 "no data"
|
||||
"-muxdelay", "0",
|
||||
"-shortest",
|
||||
|
||||
"-f", "flv", out_url
|
||||
# 输出到 YouTube
|
||||
"-f", "flv", out_url,
|
||||
"-fflags", "+genpts+discardcorrupt+nobuffer+flush_packets",
|
||||
"-avioflags", "direct",
|
||||
"-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:
|
||||
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")
|
||||
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
|
||||
|
||||
def stop_ffmpeg(p: Optional[subprocess.Popen]):
|
||||
@@ -422,12 +457,24 @@ def main():
|
||||
except Exception as e:
|
||||
logger.warning(f"URL 列表热更新失败:{e}")
|
||||
|
||||
|
||||
|
||||
if current_proc and current_url:
|
||||
# 已在推流:检测是否仍直播 & 进程状态
|
||||
if current_proc.poll() is not None:
|
||||
color.print_colored(f"[{now_str()}] ffmpeg 退出,尝试切源…", color.YELLOW)
|
||||
current_proc = None
|
||||
current_url = None
|
||||
color.print_colored(f"[{now_str()}] ffmpeg 进程退出,重启推流…", color.YELLOW)
|
||||
stop_ffmpeg(current_proc)
|
||||
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
|
||||
|
||||
# 轻量检测:仍直播?
|
||||
@@ -436,27 +483,22 @@ def main():
|
||||
still_live = bool(port.get("is_live"))
|
||||
except Exception as e:
|
||||
logger.error(f"检测当前源失败:{e}")
|
||||
still_live = False
|
||||
still_live = True # ⚠️ 出错时默认仍在播,避免误切
|
||||
|
||||
if still_live:
|
||||
elapsed = int(time.time() - start_time)
|
||||
h, m = divmod(elapsed, 3600)
|
||||
m, s = divmod(m, 60)
|
||||
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)
|
||||
continue
|
||||
|
||||
else:
|
||||
color.print_colored(f"\n[{now_str()}] 当前源已停播,切换下一个…", color.YELLOW)
|
||||
color.print_colored(f"\n[{now_str()}] 主播停播,才切换下一个源…", color.YELLOW)
|
||||
stop_ffmpeg(current_proc)
|
||||
current_proc = None
|
||||
current_url = None
|
||||
# 落下去选择下一个
|
||||
|
||||
else:
|
||||
# 未在推流:从 urls 顺序找第一个 is_live 的
|
||||
if not urls:
|
||||
|
||||
Reference in New Issue
Block a user