修复微软edge-tts报403的bug,优化UI界面
This commit is contained in:
1 parent
e39f9025ce
commit
b081bba33c
6 files changed
+1242
-1170
No files matched your search
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""修复 edge-tts 的 403 问题(微软 2026-10 更换 Edge Read Aloud 端点)。
|
||||
|
||||
【现象】
|
||||
合成语音时报:
|
||||
aiohttp.client_exceptions.WSServerHandshakeError: 403, message='Invalid response status'
|
||||
同时伴随这些特征(可用来确认不是自己的环境问题):
|
||||
- `edge_tts --list-voices`(普通 HTTPS)仍然正常返回
|
||||
- 系统时钟已 NTP 同步,偏差在几秒内
|
||||
- edge-tts 已经是最新版(PyPI 上的 7.2.8)
|
||||
- 换网络 / 换机器同样 403
|
||||
|
||||
【原因】
|
||||
微软把 Edge Read Aloud 的「合成」端点从旧地址
|
||||
speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1
|
||||
迁到了新地址
|
||||
api.msedgeservices.com/tts/cognitiveservices/websocket/v1
|
||||
旧端点在 2026-10-10 起于多数地区开始返回 403,2026-10-12 起全球(含美国)
|
||||
全线 403。而 edge-tts 7.2.8(2025-12 发布)尚未跟进。
|
||||
上游讨论:https://github.com/rany2/edge-tts/issues/490
|
||||
上游修复:https://github.com/rany2/edge-tts/pull/491 (未合并)
|
||||
|
||||
【做法】
|
||||
把 edge_tts/constants.py 中的 WSS_URL 指向新端点。新端点同样接受旧 token,
|
||||
参数名改为 Ocp-Apim-Subscription-Key;Sec-MS-GEC / Sec-MS-GEC-Version
|
||||
继续沿用原来的 v1 形式即可,无需 v2 token。
|
||||
|
||||
【用法】
|
||||
python3 patch_edge_tts_403.py # 打补丁
|
||||
python3 patch_edge_tts_403.py --check # 只看当前状态,不改动
|
||||
python3 patch_edge_tts_403.py --verify # 打补丁后实际合成一段音频来验证
|
||||
python3 patch_edge_tts_403.py --revert # 还原成 edge-tts 的原始端点
|
||||
|
||||
【注意】
|
||||
重新安装或升级 edge-tts 会覆盖 constants.py,补丁随之丢失,重跑本脚本即可。
|
||||
新端点对同一 IP 有限流(429),脚本验证时会自动重试几次。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
# 补丁块的起始标记。用它来界定「补丁插入的内容」,
|
||||
# 这样 --revert 能把注释和赋值语句一起清干净,逐字节还原成 edge-tts 原文件。
|
||||
MARKER = "# edge-tts-403-fix (PA_horn_speaker)"
|
||||
|
||||
ORIGINAL_WSS_URL = 'WSS_URL = f"wss://{BASE_URL}/edge/v1?TrustedClientToken={TRUSTED_CLIENT_TOKEN}"\n'
|
||||
|
||||
PATCHED_WSS_URL = "\n".join([
|
||||
MARKER,
|
||||
"# 2026-10 起微软把 Edge Read Aloud 合成从旧端点 speech.platform.bing.com",
|
||||
"# 迁到了 api.msedgeservices.com(旧端点在多数地区返回 403)。",
|
||||
"# 新端点同样接受旧 token,参数名为 Ocp-Apim-Subscription-Key;",
|
||||
"# Sec-MS-GEC / Sec-MS-GEC-Version 仍沿用旧的 v1 形式即可。",
|
||||
"# 语音列表接口仍由旧端点提供,故 VOICE_LIST 保持不变。",
|
||||
"WSS_URL = (",
|
||||
' "wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1"',
|
||||
' f"?Ocp-Apim-Subscription-Key={TRUSTED_CLIENT_TOKEN}"',
|
||||
")",
|
||||
"",
|
||||
])
|
||||
|
||||
# 匹配「可选的补丁块」+「WSS_URL 赋值语句」(赋值可能是单行或跨行括号形式),
|
||||
# 直到下一行的 VOICE_LIST 定义为止。
|
||||
WSS_BLOCK = re.compile(
|
||||
r"(?:^" + re.escape(MARKER) + r"\n(?:^#[^\n]*\n)*)?"
|
||||
r"^WSS_URL = .*?\n(?=^VOICE_LIST = )",
|
||||
re.S | re.M,
|
||||
)
|
||||
|
||||
|
||||
def find_constants_path():
|
||||
"""定位已安装的 edge_tts/constants.py。"""
|
||||
spec = importlib.util.find_spec("edge_tts")
|
||||
if spec is None or not spec.origin:
|
||||
raise SystemExit("找不到 edge_tts,请先安装:pip install edge-tts")
|
||||
return os.path.join(os.path.dirname(spec.origin), "constants.py")
|
||||
|
||||
|
||||
def read(path):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def is_patched(text):
|
||||
return "api.msedgeservices.com" in text
|
||||
|
||||
|
||||
def do_check(path):
|
||||
text = read(path)
|
||||
if not WSS_BLOCK.search(text):
|
||||
raise SystemExit("未能在 %s 中定位 WSS_URL,edge-tts 结构可能已变化" % path)
|
||||
print("edge-tts constants.py:%s" % path)
|
||||
print("状态:%s" % ("已打补丁(指向新端点)" if is_patched(text) else "未打补丁(仍是旧端点,会 403)"))
|
||||
return 0
|
||||
|
||||
|
||||
def do_patch(path, revert=False):
|
||||
text = read(path)
|
||||
if not WSS_BLOCK.search(text):
|
||||
raise SystemExit("未能在 %s 中定位 WSS_URL,edge-tts 结构可能已变化" % path)
|
||||
|
||||
patched = is_patched(text)
|
||||
if revert:
|
||||
if not patched:
|
||||
print("当前已是 edge-tts 的原始端点,无需还原。")
|
||||
return 0
|
||||
replacement = ORIGINAL_WSS_URL
|
||||
else:
|
||||
if patched:
|
||||
print("当前已指向新端点,无需重复打补丁。")
|
||||
return 0
|
||||
replacement = PATCHED_WSS_URL
|
||||
|
||||
backup = path + ".bak"
|
||||
if not os.path.exists(backup):
|
||||
shutil.copy2(path, backup)
|
||||
print("已备份原文件到 %s" % backup)
|
||||
|
||||
new_text = WSS_BLOCK.sub(lambda _: replacement, text, count=1)
|
||||
with open(path, "w", encoding="utf-8", newline="") as f:
|
||||
f.write(new_text)
|
||||
|
||||
print("已%s:%s" % ("还原为原始端点" if revert else "指向新端点", path))
|
||||
if not revert:
|
||||
print("下一步可用 `python3 %s --verify` 实际合成一段音频确认。" % os.path.basename(__file__))
|
||||
return 0
|
||||
|
||||
|
||||
def do_verify(attempts=3):
|
||||
"""实际合成一段音频,确认真能拿到数据(而不只是改了个变量)。"""
|
||||
out = os.path.join(tempfile.gettempdir(), "edge_tts_verify.mp3")
|
||||
for i in range(1, attempts + 1):
|
||||
if os.path.exists(out):
|
||||
os.remove(out)
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-m", "edge_tts",
|
||||
"--voice", "zh-CN-XiaoxiaoNeural",
|
||||
"--text", "测试广播,今天水果特价。",
|
||||
"--write-media", out],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if proc.returncode == 0 and os.path.exists(out) and os.path.getsize(out) > 0:
|
||||
print("验证通过:成功合成 %d 字节音频 -> %s" % (os.path.getsize(out), out))
|
||||
return 0
|
||||
tail = (proc.stderr or "").strip().splitlines()
|
||||
print("第 %d/%d 次失败:%s" % (i, attempts, tail[-1] if tail else "无输出"))
|
||||
if i < attempts:
|
||||
time.sleep(2 * i)
|
||||
print("验证失败。若错误仍是 403,说明端点又变了,请查看 "
|
||||
"https://github.com/rany2/edge-tts/issues 上的最新讨论。")
|
||||
print("若错误是 429,说明只是被限流,稍后重试即可。")
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="修复 edge-tts 的 403(微软 2026-10 更换 Edge Read Aloud 端点)")
|
||||
ap.add_argument("--check", action="store_true", help="只检查当前状态,不改动文件")
|
||||
ap.add_argument("--revert", action="store_true", help="还原为 edge-tts 的原始端点")
|
||||
ap.add_argument("--verify", action="store_true", help="实际合成一段音频来验证")
|
||||
ap.add_argument("--constants", metavar="PATH",
|
||||
help="手动指定 constants.py 路径(默认自动定位已安装的 edge_tts)")
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.check:
|
||||
return do_check(args.constants or find_constants_path())
|
||||
|
||||
if args.verify:
|
||||
return do_verify()
|
||||
|
||||
path = args.constants or find_constants_path()
|
||||
return do_patch(path, revert=args.revert)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user