This commit is contained in:
eric
2025-10-18 22:28:06 +08:00
parent 019f68a91a
commit 5d0134b86e
5 changed files with 2351 additions and 26 deletions

89
main.py
View File

@@ -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,