's'
This commit is contained in:
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,
|
||||
|
||||
Reference in New Issue
Block a user