's'
This commit is contained in:
1035
back/mult6
Normal file
1035
back/mult6
Normal file
File diff suppressed because it is too large
Load Diff
1035
back/mult7
Normal file
1035
back/mult7
Normal file
File diff suppressed because it is too large
Load Diff
@@ -158,3 +158,14 @@ mp4格式重新编码为h264 = 否
|
||||
使用代理直播的平台(逗号分隔) = tiktok, soop, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu, faceit
|
||||
额外使用代理直播的平台(逗号分隔) =
|
||||
|
||||
[监控]
|
||||
启用webui(是/否) = 是
|
||||
webui监听ip = 0.0.0.0
|
||||
webui端口 = 8787
|
||||
|
||||
[稳定性]
|
||||
ffmpeg长时间无输出阈值秒 = 600
|
||||
码率低重启比率(0-1) = 0.5
|
||||
软重启冷却秒 = 90
|
||||
url刷新间隔秒 = 300
|
||||
|
||||
|
||||
89
main.py
89
main.py
@@ -407,52 +407,89 @@ def probe_stream_url(real_url: Optional[str], timeout: float = 5.0) -> bool:
|
||||
|
||||
# =============== 解析与选源(抖音/TikTok/自定义) ===============
|
||||
# 修复点①:增强 fetch_port_info —— 重试 + 指数退避(不改签名/调用处)
|
||||
import random, json, contextlib, urllib.request, urllib.error, re, time
|
||||
|
||||
async def fetch_port_info(url: str, record_quality_code: str = "OD") -> dict:
|
||||
"""抖音/TikTok 直播解析(带防风控与 JSON fallback)"""
|
||||
proxy = proxy_addr
|
||||
delay = 1.5 # 初始退避
|
||||
for attempt in range(1, 4): # 最多 3 次
|
||||
delay = 1.2
|
||||
for attempt in range(1, 4):
|
||||
try:
|
||||
if "douyin.com/" in url:
|
||||
if "douyin.com" in url:
|
||||
# ======= 原始接口 =======
|
||||
if 'v.douyin.com' not in url and '/user/' not in url:
|
||||
j = await spider.get_douyin_stream_data(url=url, proxy_addr=proxy, cookies=dy_cookie)
|
||||
else:
|
||||
j = await spider.get_douyin_app_stream_data(url=url, proxy_addr=proxy, cookies=dy_cookie)
|
||||
port_info = await stream.get_douyin_stream_url(j, record_quality_code, proxy)
|
||||
elif url.startswith("https://www.tiktok.com/"):
|
||||
|
||||
# ======= fallback:接口空时解析 HTML 中 JSON =======
|
||||
if not port_info.get("is_live"):
|
||||
handlers = []
|
||||
if proxy:
|
||||
handlers.append(urllib.request.ProxyHandler({"http": proxy, "https": proxy}))
|
||||
opener = urllib.request.build_opener(*handlers) if handlers else urllib.request.build_opener()
|
||||
headers = {
|
||||
"User-Agent": random.choice([
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile Safari/604.1",
|
||||
]),
|
||||
"Referer": f"https://live.douyin.com/{re.sub(r'\\D','',url)}",
|
||||
"Accept": "*/*",
|
||||
}
|
||||
req = urllib.request.Request(url, headers=headers)
|
||||
with contextlib.closing(opener.open(req, timeout=6)) as resp:
|
||||
html = resp.read(512*1024).decode("utf-8", errors="ignore")
|
||||
|
||||
# 解析 roomStore JSON
|
||||
m = re.search(r'"roomStore"\s*:\s*({.*?})\s*,\s*"userStore"', html)
|
||||
if m:
|
||||
try:
|
||||
data = json.loads(m.group(1))
|
||||
stream_data = data.get("roomInfo", {}).get("stream_url", {})
|
||||
flv = (stream_data.get("flv_pull_url", {}).get("FULL_HD1")
|
||||
or stream_data.get("flv_pull_url", {}).get("HD1"))
|
||||
m3u8 = (stream_data.get("hls_pull_url", {}).get("FULL_HD1")
|
||||
or stream_data.get("hls_pull_url", {}).get("HD1"))
|
||||
if flv or m3u8:
|
||||
port_info = {"anchor_name": "JSONFallback", "is_live": True}
|
||||
if flv: port_info["flv_url"] = flv
|
||||
else: port_info["m3u8_url"] = m3u8
|
||||
CLOG.warn("⚠️ JSON fallback 成功解析流地址", worker=None)
|
||||
except Exception as e:
|
||||
CLOG.debug(f"JSON fallback 解析失败: {e}")
|
||||
|
||||
elif "tiktok.com" in url:
|
||||
if proxy:
|
||||
j = await spider.get_tiktok_stream_data(url=url, proxy_addr=proxy, cookies=tiktok_cookie)
|
||||
port_info = await stream.get_tiktok_stream_url(j, record_quality_code, proxy)
|
||||
else:
|
||||
logger.error("TikTok 需要代理,请在 config.ini 开启并配置代理。")
|
||||
port_info = {"anchor_name": "", "is_live": False}
|
||||
logger.error("TikTok 需要代理。")
|
||||
|
||||
elif (".m3u8" in url) or (".flv" in url):
|
||||
port_info = {
|
||||
"anchor_name": "自定义_" + str(uuid.uuid4())[:8],
|
||||
"is_live": True,
|
||||
}
|
||||
if url.endswith(".flv"):
|
||||
port_info["flv_url"] = url
|
||||
else:
|
||||
port_info["m3u8_url"] = url
|
||||
port_info = {"anchor_name": "自定义", "is_live": True}
|
||||
port_info["flv_url" if url.endswith(".flv") else "m3u8_url"] = url
|
||||
|
||||
else:
|
||||
port_info = {"anchor_name": "", "is_live": False}
|
||||
|
||||
# 返回前补充:若 is_live True 但未给出真实播放地址,也作为暂不可用(但不判下播)
|
||||
if port_info.get("is_live") and not select_source_url(url, port_info):
|
||||
logger.warning(f"[fetch_port_info] 在播但暂未拿到播放 URL,可能接口延迟:{url}")
|
||||
return port_info
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[fetch_port_info] 第{attempt}次解析失败: {e}")
|
||||
if attempt < 3:
|
||||
try:
|
||||
await asyncio.sleep(delay)
|
||||
except Exception:
|
||||
time.sleep(delay)
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 403:
|
||||
CLOG.warn(f"[fetch_port_info] Douyin 403 第{attempt}次延时重试")
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
logger.error(f"[fetch_port_info] 多次失败,返回空状态: {url}")
|
||||
return {"anchor_name": "", "is_live": False}
|
||||
continue
|
||||
CLOG.error(f"[fetch_port_info] HTTP {e.code}: {url}")
|
||||
break
|
||||
except Exception as e:
|
||||
CLOG.warn(f"[fetch_port_info] 异常: {e}")
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
|
||||
return {"anchor_name": "", "is_live": False}
|
||||
|
||||
# =============== 启动/停止 ffmpeg ===============
|
||||
def launch_ffmpeg(input_url: str,
|
||||
|
||||
207
out.html
Normal file
207
out.html
Normal file
@@ -0,0 +1,207 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<title>验证码中间页</title>
|
||||
<script charset="utf-8" nonce="argus-csp-token" src="https://lf-cdn-tos.bytescm.com/obj/static/sec_sdk_build/3.5.2/captcha/index.js"></script>
|
||||
<style>
|
||||
.middle_page_loading{
|
||||
width: 150px;
|
||||
height: 15px;
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.middle_page_loading span{
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
height: 100%;
|
||||
margin-right: 5px;
|
||||
background: #323332;
|
||||
-webkit-transform-origin: right bottom;
|
||||
-moz-transform-origin: right bottom;
|
||||
-ms-transform-origin: right bottom;
|
||||
-o-transform-origin: right bottom;
|
||||
transform-origin: right bottom;
|
||||
-webkit-animation: load 1s ease infinite;
|
||||
-moz-animation: load 1s ease infinite;
|
||||
-ms-animation: load 1s ease infinite;
|
||||
-o-animation: load 1s ease infinite;
|
||||
animation: load 1s ease infinite;
|
||||
}
|
||||
.middle_page_loading span:last-child{
|
||||
margin-right: 0px;
|
||||
}
|
||||
@keyframes load {
|
||||
0%{
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes load {
|
||||
0%{
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes load {
|
||||
0%{
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@-o-keyframes load {
|
||||
0%{
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
@-ms-keyframes load {
|
||||
0%{
|
||||
opacity: 1;
|
||||
}
|
||||
100%{
|
||||
opacity: 0;
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
.middle_page_loading span:nth-child(1){
|
||||
-webkit-animation-delay: 0.13s;
|
||||
-moz-animation-delay: 0.13s;
|
||||
-ms-animation-delay: 0.13s;
|
||||
-o-animation-delay: 0.13s;
|
||||
animation-delay: 0.13s;
|
||||
}
|
||||
.middle_page_loading span:nth-child(2){
|
||||
-webkit-animation-delay: 0.26s;
|
||||
-moz-animation-delay: 0.26s;
|
||||
-ms-animation-delay: 0.26s;
|
||||
-o-animation-delay: 0.26s;
|
||||
animation-delay: 0.26s;
|
||||
}
|
||||
.middle_page_loading span:nth-child(3){
|
||||
-webkit-animation-delay: 0.39s;
|
||||
-moz-animation-delay: 0.39s;
|
||||
-ms-animation-delay: 0.39s;
|
||||
-o-animation-delay: 0.39s;
|
||||
animation-delay: 0.39s;
|
||||
}
|
||||
.middle_page_loading span:nth-child(4){
|
||||
-webkit-animation-delay: 0.52s;
|
||||
-moz-animation-delay: 0.52s;
|
||||
-ms-animation-delay: 0.52s;
|
||||
-o-animation-delay: 0.52s;
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
.middle_page_loading span:nth-child(5){
|
||||
-webkit-animation-delay: 0.65s;
|
||||
-moz-animation-delay: 0.65s;
|
||||
-ms-animation-delay: 0.65s;
|
||||
-o-animation-delay: 0.65s;
|
||||
animation-delay: 0.65s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div class="middle_page_loading">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</body>
|
||||
<script nonce="argus-csp-token">
|
||||
function parseCookie() {
|
||||
var obj = document.cookie.split(';')
|
||||
.filter(function(item) {
|
||||
return !!item.trim();
|
||||
})
|
||||
.reduce(function(obj, item) {
|
||||
var pairs = item.split('=');
|
||||
var key = pairs[0] && pairs[0].trim();
|
||||
var val = pairs[1] && pairs[1].trim();
|
||||
obj[key] = val;
|
||||
return obj;
|
||||
}, {});
|
||||
return obj;
|
||||
}
|
||||
function setCookie(name, value) {
|
||||
try {
|
||||
if (window.sessionStorage) {
|
||||
if (!window.sessionStorage.getItem(name)) {
|
||||
window.sessionStorage.setItem(name, value)
|
||||
}
|
||||
}
|
||||
if (window.localStorage) {
|
||||
if (!window.localStorage.getItem(name)) {
|
||||
window.localStorage.setItem(name, value)
|
||||
}
|
||||
}
|
||||
if (!!!parseCookie()[name]) {
|
||||
document.cookie = name + "=" + value + ";";
|
||||
}
|
||||
} catch (e) { }
|
||||
}
|
||||
setCookie("__ac_referer", document.referrer || "__ac_blank");
|
||||
const options = {
|
||||
commonOptions: {
|
||||
aid: 15623,
|
||||
iid: '0',
|
||||
did: '0',
|
||||
},
|
||||
captchaOptions: {
|
||||
ele: 'root',
|
||||
hideCloseBtn: true,
|
||||
showMode: 'mask',
|
||||
successCb: function(){
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 2000)
|
||||
},
|
||||
},
|
||||
}
|
||||
const verify_data = {"code":"10000","from":"","type":"verify","version":"","region":"cn","subtype":"slide","ui_type":"","detail":"w5YhWL-cg9tHnsLcpIVjj-pqrjbnkFClfSeHUG6gEdJBKAhWs5cwR8fiY6IrrVDQVW8Ox597kD6MYeV3UsDa9bWCtz1C7xOMAyUat5oTTzqxHW8q2YRHJSXVhnPXLArIAiIdN0vPYloSMSL9Ahexn4bVXZI6G7XWsmN0yFcyXMS2B05cdwRG7EIRkZ2Fa9VQplkiIgPp2CpyApLD*rVSdiUPiJ-1YWwLAu4GC8Ux2YXFPMv07wG1XQGGjtLKigOLmnJVlY2gf7A3mKyfWkJc2mV0BZwO-l69ARJtTMc*BD*0OBGhI43FMTTC2taIMmwP7QM4gXloe-LSUE0CBG3815WdlIUdZXQP8569nfHkjbtY5g..","verify_event":"","fp":"verify_mgvprn20_kEBbdkkA_AE16_4gpX_9NAu_OepLidaY9UcL","server_sdk_env":"{\"idc\":\"lf\",\"region\":\"CN\",\"server_type\":\"whale\"}","log_id":"20251018112418B86F5C29924FAD8C3C6D","is_assist_mobile":false,"is_complex_sms":false,"identity_action":"","identity_scene":"","login_status":0,"aid":0}
|
||||
window.TTGCaptcha.init(options)
|
||||
window.TTGCaptcha.render({
|
||||
verify_data
|
||||
})
|
||||
</script>
|
||||
</html>
|
||||
Reference in New Issue
Block a user