'back2
This commit is contained in:
2367
back/back2
2367
back/back2
File diff suppressed because it is too large
Load Diff
67
main.py
67
main.py
@@ -207,11 +207,11 @@ async def fetch_port_info(url: str, record_quality_code: str = "OD") -> dict:
|
|||||||
|
|
||||||
# --------------------- ffmpeg 单进程推流 ---------------------
|
# --------------------- ffmpeg 单进程推流 ---------------------
|
||||||
def build_youtube_output() -> Tuple[str, list, list]:
|
def build_youtube_output() -> Tuple[str, list, list]:
|
||||||
"""读取 youtube.ini,返回 (out_url, v_args, a_args)"""
|
|
||||||
key = _yt_get("youtube", "key", fallback="", cast=str)
|
key = _yt_get("youtube", "key", fallback="", cast=str)
|
||||||
if not key:
|
if not key:
|
||||||
logger.error("错误: config/youtube.ini 未配置 key")
|
logger.error("错误: config/youtube.ini 未配置 key")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
secure = _yt_get("youtube", "secure", fallback=False, cast=bool)
|
secure = _yt_get("youtube", "secure", fallback=False, cast=bool)
|
||||||
ingest = _yt_get("youtube", "ingest", fallback="a", cast=str)
|
ingest = _yt_get("youtube", "ingest", fallback="a", cast=str)
|
||||||
res_str = (_yt_get("youtube", "resolution", fallback="", cast=str) or "").strip()
|
res_str = (_yt_get("youtube", "resolution", fallback="", cast=str) or "").strip()
|
||||||
@@ -223,6 +223,7 @@ def build_youtube_output() -> Tuple[str, list, list]:
|
|||||||
|
|
||||||
out_url = _build_youtube_rtmp_url(key, secure, ingest)
|
out_url = _build_youtube_rtmp_url(key, secure, ingest)
|
||||||
|
|
||||||
|
# —— 默认强制转码(最稳),如果想省 CPU 可把 -c:v libx264 改为 copy —— #
|
||||||
v_args = [
|
v_args = [
|
||||||
"-c:v", "libx264",
|
"-c:v", "libx264",
|
||||||
"-preset", "veryfast",
|
"-preset", "veryfast",
|
||||||
@@ -242,18 +243,16 @@ def build_youtube_output() -> Tuple[str, list, list]:
|
|||||||
"-ac", "2",
|
"-ac", "2",
|
||||||
]
|
]
|
||||||
|
|
||||||
# 可选缩放/留黑边
|
# 可选缩放/留黑边,保持 YT 吃得更稳
|
||||||
vf_chain = []
|
|
||||||
if res_str:
|
if res_str:
|
||||||
try:
|
try:
|
||||||
w, h = map(int, res_str.lower().split("x"))
|
w, h = map(int, res_str.lower().split("x"))
|
||||||
vf_chain.append(f"scale={w}:{h}:force_original_aspect_ratio=decrease")
|
vf = f"scale={w}:{h}:force_original_aspect_ratio=decrease,pad={w}:{h}:(ow-iw)/2:(oh-ih)/2,format=yuv420p"
|
||||||
vf_chain.append(f"pad={w}:{h}:(ow-iw)/2:(oh-ih)/2")
|
v_args = ["-vf", vf] + v_args
|
||||||
vf_chain.append("format=yuv420p")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(f"resolution '{res_str}' 非法,忽略")
|
logger.warning(f"resolution '{res_str}' 非法,忽略")
|
||||||
|
|
||||||
return out_url, v_args, a_args if not vf_chain else (out_url, ["-vf", ",".join(vf_chain)] + v_args, a_args)
|
return out_url, v_args, a_args
|
||||||
|
|
||||||
def launch_ffmpeg(input_url: str, out_url: str, extra_headers: Optional[str] = None) -> subprocess.Popen:
|
def launch_ffmpeg(input_url: str, out_url: str, extra_headers: Optional[str] = None) -> subprocess.Popen:
|
||||||
"""
|
"""
|
||||||
@@ -269,40 +268,50 @@ def launch_ffmpeg(input_url: str, out_url: str, extra_headers: Optional[str] = N
|
|||||||
|
|
||||||
base = [
|
base = [
|
||||||
"ffmpeg", "-nostdin", "-hide_banner",
|
"ffmpeg", "-nostdin", "-hide_banner",
|
||||||
|
# 超时 & 重连(必须在 -i 前面)
|
||||||
"-rw_timeout", rw_timeout,
|
"-rw_timeout", rw_timeout,
|
||||||
"-loglevel", "error",
|
"-timeout", "10000000",
|
||||||
|
"-reconnect", "1",
|
||||||
|
"-reconnect_streamed", "1",
|
||||||
|
"-reconnect_at_eof", "1",
|
||||||
|
"-reconnect_delay_max", "60",
|
||||||
|
|
||||||
|
# UA + 协议
|
||||||
"-user_agent", user_agent,
|
"-user_agent", user_agent,
|
||||||
"-protocol_whitelist", "rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy",
|
"-protocol_whitelist", "rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy",
|
||||||
|
|
||||||
|
# 缓冲 & 分析
|
||||||
"-thread_queue_size", "1024",
|
"-thread_queue_size", "1024",
|
||||||
"-analyzeduration", analyzeduration,
|
"-analyzeduration", analyzeduration,
|
||||||
"-probesize", probesize,
|
"-probesize", probesize,
|
||||||
"-fflags", "+discardcorrupt",
|
"-fflags", "+discardcorrupt",
|
||||||
|
|
||||||
|
# 输入流(放在重连参数之后)
|
||||||
"-re", "-i", input_url,
|
"-re", "-i", input_url,
|
||||||
|
|
||||||
|
# 视频/音频编解码
|
||||||
|
"-c:v", "copy", # 直接拷贝视频,省 CPU,低延迟
|
||||||
|
"-c:a", "aac", # 音频转码成 AAC(保证 YouTube 兼容)
|
||||||
|
"-ar", "44100", # 音频采样率
|
||||||
|
"-b:a", "128k", # 音频码率
|
||||||
|
"-ac", "2", # 双声道
|
||||||
"-bufsize", bufsize,
|
"-bufsize", bufsize,
|
||||||
"-sn", "-dn",
|
|
||||||
"-reconnect_delay_max", "60",
|
|
||||||
"-reconnect_streamed", "-reconnect_at_eof",
|
|
||||||
"-max_muxing_queue_size", "2048",
|
"-max_muxing_queue_size", "2048",
|
||||||
"-correct_ts_overflow", "1",
|
|
||||||
"-avoid_negative_ts", "1",
|
"-avoid_negative_ts", "1",
|
||||||
"-rtmp_live", "live",
|
"-fflags", "+genpts", # 确保时间戳连续,防止花屏
|
||||||
"-timeout", "10000000"
|
|
||||||
|
# 输出容器
|
||||||
|
"-f", "flv", out_url
|
||||||
]
|
]
|
||||||
|
|
||||||
if extra_headers:
|
if extra_headers:
|
||||||
base[ base.index("-user_agent") : base.index("-thread_queue_size") ] += ["-headers", extra_headers]
|
base[ base.index("-user_agent") : base.index("-protocol_whitelist") ] += ["-headers", extra_headers]
|
||||||
|
|
||||||
out_url2, v_args, a_args = build_youtube_output()
|
|
||||||
if out_url:
|
|
||||||
out_url2 = out_url # 允许外部传入覆盖
|
|
||||||
|
|
||||||
cmd = base + v_args + a_args + ["-f", "flv", out_url2]
|
|
||||||
if proxy_addr:
|
if proxy_addr:
|
||||||
cmd = ["ffmpeg", "-http_proxy", proxy_addr] + cmd[1:]
|
base = ["ffmpeg", "-http_proxy", proxy_addr] + base[1:]
|
||||||
|
|
||||||
logger.info(f"启动推流:{input_url} -> YouTube")
|
logger.info(f"启动推流:{input_url} -> YouTube")
|
||||||
# Windows 优雅关闭:后续喂 'q';Linux 发 SIGINT
|
p = subprocess.Popen(base, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def stop_ffmpeg(p: Optional[subprocess.Popen]):
|
def stop_ffmpeg(p: Optional[subprocess.Popen]):
|
||||||
@@ -423,9 +432,18 @@ def main():
|
|||||||
still_live = False
|
still_live = False
|
||||||
|
|
||||||
if still_live:
|
if still_live:
|
||||||
print(f"\r[{now_str()}] 正在转播:{current_platform} | {current_anchor} | {current_url}", end="")
|
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=""
|
||||||
|
)
|
||||||
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)
|
||||||
@@ -468,6 +486,7 @@ def main():
|
|||||||
out_url, _, _ = build_youtube_output()
|
out_url, _, _ = build_youtube_output()
|
||||||
current_proc = launch_ffmpeg(real_url, out_url, headers)
|
current_proc = launch_ffmpeg(real_url, out_url, headers)
|
||||||
current_url = url
|
current_url = url
|
||||||
|
start_time = time.time()
|
||||||
color.print_colored(f"[{now_str()}] 开始转播:{current_platform} | {current_anchor} | {url}", color.GREEN)
|
color.print_colored(f"[{now_str()}] 开始转播:{current_platform} | {current_anchor} | {url}", color.GREEN)
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user