capability-matrix.md 12 KB

能力现状矩阵与替代方案

本文件是 skill-mv 能力边界的唯一事实来源,与 lib/providers.mjs 保持同步。 所有结论均为 2026-10-13 在网关 https://api.fmode.cn/v1 实测得到。 状态随时可能变化 —— 复测命令:

> fmode-mv providers          # 轻量探活(查模型清单 + 路由真实性,不产生费用)
> fmode-mv providers --deep   # 深度探活(会真实提交任务,可能计费)
> ```

---

## 一、总览

| 环节 | 端点 | 现状 | 证据 |
|---|---|---|---|
| 作曲填词 / 分镜 / 口播稿 | `POST /v1/chat/completions` | ✅ **可用** | 实测产出完整歌词与分镜 JSON |
| 场景 / 人物关键帧 | `POST /v1/images/generations` | ✅ **可用** | 实测 `gpt-image-2.5-sunburst` 返回 b64_json,1792×1024 / 3MB |
| 剪辑 / 合成 | 本地 ffmpeg | ✅ **可用** | 实测出片 25.2s / 1280×720 / 4.6MB,含烧字幕与响度归一化 |
| AI 音乐 | `POST /suno/submit/music` + `POST /suno/fetch` | ⚠️ **端点存在,渠道未开通** | `POST` 返回 `503 {"code":"server_error","message":"Task request failed"}` |
| 图生视频 | `POST /v1/video/generations` | ⚠️ **无可用渠道** | `503 {"code":"model_not_found","message":"No available channel for model doubao-seedance-1-0-pro ..."}` |
| 唇形驱动 | — | ⚠️ **平台无此路由** | 网关返回前端兜底页(`HTTP 200` + `text/html`) |
| TTS 口播配音 | `/v1/audio/speech` | ⚠️ **平台无此路由** | 同上;本地 edge-tts 在**部分云环境被 403 拦截**(本容器实测),需备外部接口 |
| 音轨分离 | 本地 | ◐ **ffmpeg 近似 / demucs 可选** | 单声道素材无法分离;立体声可做相位近似 |

**汇总:2 条平台通道可用(chat / image)+ 本地剪辑可用。**

---

## 二、⚠️ 一个必须知道的探测陷阱

网关**根域对未知路径返回 SPA 的前端 `index.html`**,状态码是 **`200`**,`Content-Type: text/html`。

这意味着:**只看 HTTP 状态码会把不存在的端点误判为"可用"**。

早期版本的探活就踩了这个坑(`/lipsync/generations`、`/audio/speech` 都显示 `HTTP 200` → 误报可用)。
现已修正,`probeEndpoint()` 会做三重判断:

js // 1. Content-Type / 响应体是 HTML → 端点不存在 if (ct.includes('text/html') || /^\s*<(doctype html|html)/i.test(text)) → 不存在 // 2. 405 → 路由存在(方法不对) if (status === 405) → 存在 // 3. 明确的渠道错误 → 端点存在但渠道未开通 if (/no available channel|model_not_found|task request failed/i.test(msg)) → 存在但不可用


**如果你自己写探活脚本,务必带上这个判断**,否则会得出「TTS 可用」的错误结论。

---

## 三、逐项替代方案

### AI 音乐 ⚠️

**症状**:`POST https://api.fmode.cn/suno/submit/music` → `503 {"code":"server_error","message":"Task request failed"}`
(端点路由存在 —— `GET` 该路径返回 `405 Method Not Allowed`)

| 方案 | 操作 | 适用 |
|---|---|---|
| A. 等平台开通 | 无 | 通道开通后 `fmode-mv music --from work/song.json` **无需改代码**即可用 |
| B. 自备音乐 | `fmode-mv song "主题" --music your-song.mp3` | **当前最实际**。用自己的编曲/购买的音乐 |
| C. 对接外部 | `export FEME_MUSIC_ENDPOINT=https://your-host/suno/submit/music` | 自建或采购了 Suno 兼容服务 |

**设计保证**:`lib/providers.mjs` 的 `music` provider 端点与模型名**不在代码里写死**,
全部可被 `FEME_MUSIC_ENDPOINT` / `FEME_MUSIC_MODEL` 覆盖。
平台开通或换服务商,**只需改环境变量**。

---

### 图生视频 ⚠️

**症状**:`POST /v1/video/generations`
→ `503 {"code":"model_not_found","message":"No available channel for model doubao-seedance-1-0-pro under group default (distributor)"}`

模型名**在**网关 `/v1/models` 清单里,但**没有可用渠道**(这是"模型在列 ≠ 能调用"的典型)。

| 方案 | 操作 | 适用 |
|---|---|---|
| A. 平台开通 | 无 | 开通后 `fmode-mv video --from work/storyboard.json --mode provider --force` 一键替换 |
| B. 换已开通模型 | `export FEME_VIDEO_MODEL=<可用模型名>` | 若网关接入其它视频模型 |
| C. **静帧 + Ken Burns 兜底** | 默认 `--mode auto` 自动启用 | **当前默认行为** |

**兜底实现**(`lib/ffmpeg.mjs` 的 `kenBurns()`):
在 2 倍画布上做 `zoompan` 缓慢推拉,按镜头序号交替推近/拉远,输出 1280×720 @30fps。

**兜底产物的诚实标记**(这是本技能的设计底线):
- 文件名带 **`.fallback`**:`shot-01.fallback.mp4`
- `work/clips/clips.json` 里 `mode: "still"`、`fallback: true`
- 命令输出明确打印:

! 注意:本次所有片段都是「静帧 + 运镜」兜底产物,不是 AI 图生视频结果。

!提示:N 段为兜底运镜(非真 AI 图生视频)
- `out/manifest.json` 记录 `fallbackCount`,成片报告里再提示一次

**绝不把兜底产物说成 AI 生成视频。**

---

### 唇形驱动 ⚠️

**症状**:网关**没有** `/lipsync/generations` 路由 —— 返回 SPA 兜底页(`HTTP 200` + `text/html`)。

| 方案 | 操作 | 观感 |
|---|---|---|
| C. 纯空镜 + 字幕 | 分镜里不标 `lipSync` | 纯音乐 MV 最常见做法,零风险 ✅ |
| B. 表情微动镜头 | 演唱镜头走图生视频(表情/头部微动),不严格对口型 | 可接受 |
| **A. 自建服务** | 部 Wav2Lip / SadTalker / LatentSync → `export FEME_LIPSYNC_ENDPOINT=https://your-host/lipsync` | 最好 |

**自建服务的接口约定**(技能按此调用):

http POST Content-Type: application/json Authorization: Bearer

{ "image": "<人脸图片 URL 或 data:image/png;base64,...>", "audio": "<音频 URL>", "model": "" }


期望响应:任何包含视频 URL 的结构(技能会递归扫描 `video_url` / `url` 等字段),例如:

json { "data": { "video_url": "https://cdn.example.com/out.mp4" } }


**降低依赖的建议**:
- 分镜里 `lipSync: true` 的镜头**全片 ≤ 4 个**(产线 B ≤ 2 个)。
- `talking`/`perf` 镜头用中景而非正脸特写,口型不匹配不易察觉。

---

### TTS 口播配音 ⚠️

**症状**:网关**没有** `/v1/audio/speech` 路由 —— 同样返回 SPA 兜底页。

| 方案 | 操作 | 说明 |
|---|---|---|
| **A. 本地 edge-tts** | `pip install edge-tts` | **免费**,中文音色多,技能**自动探测**。⚠️ 但**部分云环境会失败**:调用 `wss://speech.platform.bing.com` 返回 **403 HandshakeError**(本容器 2026-10-13 实测 edge-tts 7.2.8 最新版亦被拦)—— 命中则该方案不可用,改走 B |
| B. 对接外部 | `export FEME_TTS_ENDPOINT=https://your-host/v1/audio/speech` | OpenAI 兼容接口。**云环境推荐**;DashScope/百炼 `cosyvoice`、火山、MiniMax 均可自建转发 |
| C. 自备配音 | `--voice my.mp3` | 真人录音也行 |

**探测优先级**(`tts.probe()`):**本地 edge-tts 优先**,其次才探网关。
安装后 `fmode-mv tts --probe` 返回:

状态 可用 提供方 edge-tts(local)


**常用中文音色**:

| 音色 | 特征 |
|---|---|
| `zh-CN-XiaoxiaoNeural` | 女声,自然亲和(默认) |
| `zh-CN-YunxiNeural` | 男声,年轻 |
| `zh-CN-YunjianNeural` | 男声,沉稳 |
| `zh-CN-XiaoyiNeural` | 女声,活泼 |

---

### 音轨分离 ◐

**不是"未开通",是"精度分级"**:

| 级别 | 依赖 | 效果 | 命令 |
|---|---|---|---|
| 高质量 | `demucs`(`pip install demucs`) | 真·分离,人声/伴奏/鼓/贝斯分轨 | `fmode-mv separate x.mp3 --method demucs` |
| **兜底** | 仅 ffmpeg | **近似**:侧信号取伴奏、中置取人声 | `fmode-mv separate x.mp3`(默认) |

**兜底的物理限制(必须如实告知用户)**:

1. **仅对立体声有效** —— 单声道素材脚本会**直接报错**,不会给你假结果。
2. **是相位近似,不是分离** —— 只对「人声居中、乐器分列左右」的老式混音有效;
   现代宽混音基本无效,人声/伴奏都会残留串扰。
3. 脚本会明确打印:`! 未检测到 demucs,使用 ffmpeg 相位兜底方案(近似分离,非真·分离)`,
   并在返回结构里带 `note` 字段。

---

## 四、provider 覆盖机制(不改代码换通道)

`lib/providers.mjs` 是唯一的通道注册表。任何 provider 的模型名/端点都可被覆盖:

### 方式 1:环境变量

bash export FEME_MUSIC_MODEL=chirp-v4-0 export FEME_MUSIC_ENDPOINT=https://your-host/suno/submit/music export FEME_VIDEO_MODEL=kling-v1 export FEME_VIDEO_ENDPOINT=https://your-host/v1/video/generations export FEME_LIPSYNC_ENDPOINT=https://your-host/lipsync export FEME_TTS_ENDPOINT=https://your-host/v1/audio/speech export FEME_TTS_VOICE=zh-CN-YunxiNeural export FEME_CHAT_MODEL=doubao-seed-2-1-pro export FEME_IMAGE_MODEL=gpt-image-2.5-sunburst


### 方式 2:身份文件的 `providers` 段

jsonc // /opt/data/fmode-identity.json { "newapi_token": "...", "providers": {

"music": { "model": "chirp-v4-0", "endpoint": "/suno/submit/music" },
"video": { "model": "kling-v1" },
"tts":   { "voice": "zh-CN-YunxiNeural" }

} }


> ⚠️ 身份文件含凭据,**不要提交进 Git**(`.gitignore` 已排除 `*identity*.json`)。

**覆盖优先级**:环境变量 > 身份文件 `providers` 段 > provider 内置默认值。

---

## 五、复测脚本

通道状态会变。建议每次开工前跑一次:

bash fmode-mv providers # 轻量:查 /v1/models + 路由真实性 + SPA 兜底判断,零费用 fmode-mv doctor # 全量自检:node/ffmpeg/凭据/demucs/各通道 fmode-mv providers --deep # 深度:真实提交任务(music 会消耗额度,image 不做 deep)


**预期输出(2026-10-13 基线)**:

chat ✅ 可用 大语言模型(作曲填词 / 分镜 / 口播稿) image ✅ 可用 文生图(场景 & 人物关键帧) music ◐ 未知 AI 音乐生成(Suno 兼容通道) video ⚠️ 未开通 图生视频(B-roll 空镜 / 人物演唱镜头) lipsync ⚠️ 未开通 唇形驱动(口型对齐人声) tts ⚠️ 未开通 语音合成(口播配音) 汇总: 2/6 通道可用


---

## 六、本技能的诚实性承诺

针对"未开通通道"这一现实,本技能遵守三条硬规则:

1. **不静默失败** —— 调用未开通通道必抛 `ProviderUnavailableError`,
   携带 `hint` 字段,打印**可操作方案**(换通道 / 对接外部 / 用现成素材)。
2. **不伪造产物** —— 绝不写入空的或占位的音频/视频文件冒充生成结果。
   兜底产物一律带 `.fallback` 后缀与 `fallback: true` 标记。
3. **不隐藏降级** —— 端到端编排的最终报告里,每一步的状态
   (`ok` / `fallback` / `unavailable` / `skipped` / `failed`)逐条列出,
   未完成项单独成段列出原因。

════════════════════════════════════════════════════════════════ 产线A · AI 单曲 MV · 执行报告 ════════════════════════════════════════════════════════════════ ✅ 作曲填词 《末班车还亮着》 ✅ 分镜脚本 6 镜 / 36s ⚠️ AI 音乐 通道未开通 ⏭️ 音轨分离 无音乐文件 ✅ 关键帧 成功 6 / 失败 0 ◐ 图生视频 Ken Burns 兜底:6 段 ⚠️ 唇形驱动 2 个待处理镜头未处理 ✅ 剪辑合成 36.0s / 4.6MB ──────────────────────────────────────────────────────────────── 成片: out/mv.mp4 未完成项: · AI 音乐: 通道未开通 · 唇形驱动: 2 个待处理镜头未处理 ════════════════════════════════════════════════════════════════ ```

这就是本技能对"诚实"的落地方式:让用户一眼看清哪些是真 AI 生成、哪些是降级替代。