适用版本:v1(接管 抖音 + 小红书 + 微信视频号 + X(Twitter))。X(Twitter) 含推文音视频与 X Space 回放(两阶段文件交付,见「X Space 回放」节)。其它平台(B 站 / YouTube / 小宇宙)不受影响,仍走原生下载器。
MediaResolverAPI 是一个独立的「短视频 URL → 无水印直链 + 元数据」解析服务, 内置 TikHub 多端点降级 + Cobalt 兜底。本项目可选地把抖音 / 小红书 / 微信视频号 / X(Twitter) 的解析外包给它,从而:
- 把易碎的 TikHub 解析逻辑集中到专用服务,本仓库退化为「下载 + 转录 + LLM」;
- 抖音改为下载完整 mp4 再由 CapsWriter 提取音轨(而非旧版直接抓
music.play_url的 mp3)—— 对套用热门 BGM 模板的口播视频,提取的是视频自带人声而非背景乐,转录更准; - 支持微信视频号(
https://weixin.qq.com/sph/<sph_code>)链接转录:resolver 只负责解析并下发解密文件头 + 微信 CDN 直链(GET /api/stream/wechat_channels/{sph_code}/direct),由本服务直连 CDN 按 Range 拼接成完整 mp4(不经 resolver 流式中转,避免跨机房 DERP 慢速); - 支持 X(Twitter,
x.com与twitter.com)音视频链接转录:resolver 解析第三方无水印直链并下发 MP4,本服务直接下载并由 CapsWriter 提取音轨转录;本服务为 ASR 场景自动选取 resolver 返回的最低码率 variants 档(省流量、提速),无需配置。
⚠️ 行为变更:开启后抖音下载体积由 mp3 增大为 mp4。长视频可能撞storage.max_download_size_mb上限,或 CapsWriter 一次性入内存的限制。短视频无影响。
| 你的情况 | 建议 |
|---|---|
| 抖音/小红书解析经常失败、想集中维护解析逻辑 | ✅ 启用 |
| 需要转录微信视频号或 X(Twitter) 链接 | ✅ 启用(视频号与 X 均依赖 MediaResolverAPI) |
| 已部署 MediaResolverAPI 服务并有 API Key | ✅ 启用 |
| 只转录 B 站/YouTube/小宇宙 | 无需启用(默认 off,不影响) |
| 没有 MediaResolverAPI 服务 | 保持 off,继续用内置 TikHub 直连(视频号与 X 不可用) |
默认 关闭。开关打开前请确认 MediaResolverAPI 服务可达。
-
一个可访问的 MediaResolverAPI 服务(自建或他人提供),拿到
base_url与X-API-Key。 -
微信视频号转录前提:
- 必须设置
downloaders.use_media_resolver: true(无原生下载器兜底); - 若配置了
security.download_url_allowlist安全下载白名单,MediaResolverAPI 服务域名/IP 须在允许列表中; - 视频号
video_url仍指向 resolver 的流式端点路径(/api/stream/wechat_channels/{sph_code}),下载器识别后改调/direct拿到解密头与 CDN 直链,再由本机 Range 续传拼接;只有调/direct时携带X-API-Key,请求微信 CDN 时不携带; - CDN 直链含时效 token,慢读可能被 CDN 掐断,下载器会重新调
/direct换链并从已下载偏移续传(最多 5 次)。
- 必须设置
-
服务健康检查:
curl http://<your-host>:<port>/health # 期望返回 {"status":"ok"}
编辑 config/config.jsonc,新增/修改两段:
Docker 注意:容器内访问宿主机服务用
host.docker.internal,不要写localhost/127.0.0.1。
配置改完无需改代码——factory 会在 use_media_resolver=true 时自动把抖音/小红书/视频号/X(Twitter)路由到
MediaResolverDownloader,并跳过旧的 DouyinDownloader / XiaohongshuDownloader。
开关打开后,正常提交抖音/小红书/视频号/X(Twitter)链接即可,无需任何额外参数:
curl -X POST http://localhost:8000/api/transcribe \
-H "Authorization: Bearer <your-auth-token>" \
-H "Content-Type: application/json" \
-d '{"url": "https://weixin.qq.com/sph/AOzokRxWHz"}'支持的链接形态:douyin.com / v.douyin.com 短链 / xiaohongshu.com / xhslink.com 短链 / xhslink.cn 短链 / weixin.qq.com/sph/<sph_code> 视频号链接 / x.com 与 twitter.com 推文链接 / X Space 回放链接。
提交 https://x.com/i/spaces/<13位ID>(含 /peek 与 query)时,解析服务不会直接给一个能立刻拉的直链,
而是给一个指向自己的下载端点 GET /api/stream/twitter_space/{id}。这是两阶段物化:
- 首次 GET 返回
202+{"status": "extracting", "retry_after": 5}——服务端后台在物化整场音频 (提取回放清单、逐段抓取、拼成单个 m4a 落盘)。断开连接不影响服务端任务。 - 客户端按
retry_after秒间隔轮询同一 URL,直到拿到200普通文件响应 (Content-Type: audio/mp4、Content-Length: <整场大小>),此时落盘的就是可直接转录的完整 m4a。
本仓库的客户端约定:
| 项 | 行为 |
|---|---|
| 202 | 按 retry_after 退避后再 GET 同一 URL;该 JSON 状态体永远不会写进媒体文件 |
| 200 | 直接落盘,字节数与 Content-Length 严格相等(不套用通用下载器的 1024 字节容差),再走音轨校验 |
| 400 / 404 | 终态:直播中/未开播 → 「这场还不能回放」;未开启回放或已过期 → 「回放不可用」(不是「无法识别的链接」) |
| 502 | 上一次后台物化失败(服务端下次 GET 会重新拉起),退避后再 GET 同一 URL;不做 force_refresh 重解析 |
| 503 | 物化队列满或磁盘水位不足,任务未被受理;在等待预算内退避重试,预算耗尽后失败可见 |
| 产物后缀 | twitter_space 平台或 media_type=audio 的解析结果固定为 m4a(不是默认 mp4) |
| 等待上限 | 默认 1800 秒(同步等待,超时抛用户可见错误「回放仍在准备」)。这是上限不是保证:服务端按约 10~30 倍速拉完全场,2 小时场约 10 分钟,队列满时会更久 |
| 占用 | 轮询期间占用一个转录 worker(concurrent.max_workers 的一格),期间不推进 ASR |
不支持:直播中/尚未开播的 Space(那是实时录制,不在本服务范围);t.co 短链包裹的 Space 链接;
本仓库不解析 HLS、不跑 yt-dlp——物化与合并全在解析服务侧完成。
解析服务的失败会被映射为明确的用户提示(终态错误不重试、直达用户):
| 场景 | 用户看到 | 是否重试 |
|---|---|---|
| 服务超时/连接拒绝/DNS | 解析服务暂不可用 | 是(退避) |
| API Key 错误/缺失(HTTP 401) | 鉴权失败(配置错误,请检查 api_key) |
否 |
| 无法识别的链接(HTTP 400) | 无法识别的链接 | 否 |
| 图文/已删除/私密等无视频内容 | 该内容无可转录视频 | 否 |
| 全部解析源失败 | 解析失败,稍后再试 | 否 |
| 服务端错误(HTTP 5xx) | 解析服务异常 | 是 |
| X Space 直播中/未开播 | 这场还不能回放(直播中或尚未开始) | 否 |
| X Space 未开启回放/已过期 | 回放不可用(未开启回放或已过期) | 否 |
| X Space 等待超上限(>1800 秒仍未就绪) | X Space 回放仍在准备中,请稍后重试 | 否 |
注:当前 MediaResolverAPI 的
error仅返回文案、无结构化error.code,因此「图文/删除」类终态 可能被笼统归为「解析失败,稍后再试」。若你维护该服务,建议为终态返回error.code以便精确区分。
MediaResolverAPI 返回的视频直链在下载前会经过 SSRF 校验(utils/url_validator.validate_url_safe),
阻止指向内网/云元数据端点(如 169.254.169.254)的恶意直链。校验不通过会终止下载并提示用户。
若启用后遇到问题,把 downloaders.use_media_resolver 改回 false 即可立即回到内置 TikHub 直连下载器,
无需回滚代码(旧下载器在迁移期保留;视频号链接在未启用时将回退至通用下载器)。
| 现象 | 排查 |
|---|---|
| 提示「鉴权失败」 | 检查 media_resolver.api_key;用 curl -H "X-API-Key: <key>" 直接打 /api/resolve 验证 |
| 提示「解析服务暂不可用」 | 检查 base_url 可达性、/health、Docker 内是否误用 localhost |
| 视频号下载失败(401) | 确认 /direct 请求发往 resolver 域名并携带了 X-API-Key |
| 视频号下载失败(502) | resolver 拉微信元数据/CDN 失败,稍后重试 |
resolver 返回的 video_url(流式端点)的 netloc 须与 media_resolver.base_url 一致,否则 X-API-Key 不会携带且可能触发 SSRF 拦截 |
核对 media_resolver.base_url 与解析结果里的主机名(含端口)是否完全一致(小写比较) |
| 抖音下载撞大小上限 | 调高 storage.max_download_size_mb,或对长视频暂时关闭开关 |
| X Space 一直 202,最后报「回放仍在准备」 | 解析服务还在后台物化(队列满/长场次会慢);重试即可,已物化完成的文件在服务端保留 72 小时 |
| X Space 报「回放不可用」 | X 只保留回放约 30 天;确认该场次开了回放且未过期 |
| 想确认走了哪个下载器 | 看日志 为URL创建下载器: ..., 类型: MediaResolverDownloader |
- 设计与实现决策记录:docs/designs/media-resolver-integration.md
- 后续 v2 规划(多平台/观测/CDN 兜底):见仓库
TODOS.md
{ // 下载器路由开关 "downloaders": { "use_media_resolver": true // 设 true 才启用;默认 false }, // MediaResolverAPI 连接配置(仅 use_media_resolver=true 时生效) "media_resolver": { "base_url": "http://host.docker.internal:8000", // 服务地址;Docker 内不能用 localhost "api_key": "your-x-api-key", // X-API-Key 鉴权 "timeout": 30, // 单次请求超时(秒) "max_retries": 2 // 解析重试次数(网络错误/5xx 退避重试) } }