Metadata-Version: 2.5
Name: video-assembler-mcp
Version: 1.0.0
Summary: MCP toolset that assembles narrated short videos from your own clips: Edge-TTS voiceover, burned-in subtitles, ffmpeg timeline. No stock-footage service required.
Project-URL: Homepage, https://modelscope.cn/mcp/servers
Project-URL: Repository, https://github.com/
Author: aqiu
License: MIT
License-File: LICENSE
Keywords: edge-tts,ffmpeg,mcp,model-context-protocol,shorts,subtitles,tts,video
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Requires-Dist: edge-tts>=7.0.0
Requires-Dist: imageio-ffmpeg>=0.5.1
Provides-Extra: fetch
Requires-Dist: httpx>=0.27; extra == 'fetch'
Description-Content-Type: text/markdown

# video-assembler-mcp

> MCP 工具集：把你自己的素材 + Edge-TTS 旁白 + 烧入字幕，组装成短视频。
> 不依赖任何素材搜索服务，不依赖外部引擎进程，`pip install` 完就能跑。

## 它做什么 / 不做什么

| 做 | 不做 |
|---|---|
| 按分镜脚本拼时间轴、合成旁白、烧字幕、出 mp4 | ❌ 不联网搜素材（不带 Pexels/Pixabay 等） |
| 素材可以是本地文件或公网直链 | ❌ 不接本地 TTS 服务（不带 GPT-SoVITS） |
| 中文按像素折行（libass 不折中文，这里自己算） | ❌ 不需要另起 HTTP 引擎进程 |
| 输出路径白名单校验 | ❌ 不写任何机器专属绝对路径 |

设计定位很明确：**创作智能在调用方（agent），本包只负责「把给定的东西变成帧」。**
素材从哪来是 agent 的事（它有自己的搜索工具），本包不替它决定画面内容。

## 安装

```bash
pip install video-assembler-mcp
# 或直接用 uvx 免安装运行
uvx video-assembler-mcp
```

依赖只有两个：`edge-tts`（旁白合成）和 `imageio-ffmpeg`（随包带 ffmpeg 二进制）。

### 关于 ffmpeg

按以下顺序自动定位，一般无需任何配置：

1. 环境变量 `VAM_FFMPEG` / `VAM_FFPROBE`
2. 系统 `PATH` 里的 `ffmpeg` / `ffprobe`
3. `imageio-ffmpeg` 随包附带的二进制（兜底，保证装完即用）

实测 `imageio-ffmpeg` 附带的 gyan.dev essentials 构建带 `--enable-libass` +
libfreetype，所以 ASS 字幕烧制可用。

## MCP 客户端配置

```json
{
  "mcpServers": {
    "video-assembler": {
      "command": "uvx",
      "args": ["video-assembler-mcp@latest"],
      "env": {
        "VAM_OUTPUT_DIR": "/tmp/vam_output"
      }
    }
  }
}
```

Windows 上把 `VAM_OUTPUT_DIR` 换成 `D:\\vam_output` 这类路径。

## 工具

| 工具 | 用途 |
|---|---|
| `assemble_video` | 渲染成片（唯一入口）。默认异步返 `job_id`，小任务自动同步 |
| `assemble_status` | 查任务状态（`queued/running/done/failed` + `step`） |
| `assemble_tts` | 单独合成一段配音（试音色 / 只要音频） |
| `assemble_voices` | 列中文音色（含性别与风格） |
| `assemble_health` | 自检：ffmpeg、libass、字体、输出白名单 |
| `assemble_spec` | 读协议全文 + 默认值 + 上限 |
| `assemble_jobs` | 列最近任务 |

## 分镜协议

最少只需要 `title` + `scenes[{text, local_path|url}]`：

```json
{
  "title": "demo",
  "resolution": "1080x1920",
  "transition": "fade",
  "tts": { "voice": "zh-CN-YunxiNeural", "rate": "+10%" },
  "subtitle": { "font_size": 66, "margin_v": 290 },
  "scenes": [
    { "text": "第一段旁白，十五到四十五字口语最佳。", "subtitle": "开场",
      "local_path": "D:/clips/a.mp4", "trim": { "start": 0, "end": 8.5 } },
    { "text": "第二段旁白。", "subtitle": "第二幕",
      "url": "https://example.com/b.jpg", "kind": "image", "min_sec": 3 }
  ]
}
```

### 字段

| 字段 | 说明 |
|---|---|
| `title` | 必填。也用于文件名（非 ASCII 会被过滤） |
| `resolution` | `1080x1920` 竖屏（默认）/ `1920x1080` 横屏 / `1080x1080` 方形 |
| `fps` | 默认 30 |
| `transition` | `cut` 硬切（默认）/ `fade` 每场景开头 0.45s 黑场淡入 |
| `tts.voice` | edge ShortName；`assemble_voices` 查全部 |
| `tts.rate` / `pitch` / `volume` | 如 `+18%` / `-10%` / `+0Hz` / `+0%` |
| `subtitle.font_size` | 字号像素，竖屏建议 50–80 |
| `subtitle.margin_v` | 字幕离底边像素，竖屏建议 260–320（避开平台底部 UI） |
| `bgm` | 背景音乐路径，自动压到 12% 音量 |
| `output_path` | 成片落点（目录或 `.mp4` 全路径），须在输出白名单内 |
| `scenes[].text` | 必填。旁白，TTS 的唯一输入 |
| `scenes[].subtitle` | 屏幕字幕，不给就用 `text` |
| `scenes[].local_path` | 本地素材（与 `url` 互斥） |
| `scenes[].url` | 公网直链素材（与 `local_path` 互斥） |
| `scenes[].trim` | `{start, end}` 秒，仅视频 |
| `scenes[].kind` | `video` / `image`，不给就按后缀猜 |
| `scenes[].min_sec` | 场景最短时长 |

### 规则

- `scenes` 顺序 = 成片顺序，第 i 个场景铺第 i 份素材，无隐式映射。
- 场景时长 = `clamp(max(min_sec, 旁白时长 + 0.35s), 1.2s, 20s)`。
- **素材失败即报错**：`local_path` 缺文件、`url` 下载失败都会立刻返回错误并带场景序号。
  不存在「降级成纯色」的静默行为 —— 你给的素材是精确指定的，换掉等于偷偷改需求。
- 素材比场景短会自动补足（视频复制末帧 / 图片缓慢推拉），比场景长按 `trim` 截断。
- 中文长句自动按像素折行。

## 环境变量

| 变量 | 默认 | 说明 |
|---|---|---|
| `VAM_OUTPUT_DIR` | `./vam_output` | 输出根目录 |
| `VAM_ALLOWED_OUTPUT_DIRS` | 空 | 额外允许写入的目录，逗号分隔。空 = 只允许 `VAM_OUTPUT_DIR` |
| `VAM_EDGE_VOICE` | `zh-CN-YunxiNeural` | 默认音色 |
| `VAM_EDGE_RATE` | `+6%` | 默认语速 |
| `VAM_DEFAULT_RESOLUTION` | `1080x1920` | 默认分辨率 |
| `VAM_SUBTITLE_FONT_SIZE` | `66` | 默认字号 |
| `VAM_SUBTITLE_MARGIN_V` | `290` | 默认字幕底边距 |
| `VAM_FONT_FALLBACKS` | 内置链 | 逗号分隔的字体降级链 |
| `VAM_FFMPEG` / `VAM_FFPROBE` | 自动 | 指定二进制路径 |
| `VAM_MEDIA_CACHE_DIR` | `<output>/.media_cache` | URL 素材缓存 |
| `VAM_TTS_CACHE_DIR` | `<output>/.tts_cache` | 音频缓存 |
| `VAM_DOWNLOAD_MAX_MB` | `200` | 单素材下载体积上限 |

### 输出路径权限

写到哪里必须由调用方知情，所以做了两层：

1. **引擎侧**：`resolve_output_target()` 把路径 resolve 后判断是否落在白名单内，
   越界直接报错，**不静默改写**到别的目录。
2. **MCP 客户端侧**：你所用客户端（如 CowAgent / Claude Desktop）的工具级权限，
   可以按 agent 或分组禁用 `assemble_*`。

## 字体

ASS 里写的是字体名，字形由系统提供。按 `VAM_FONT_FALLBACKS` 顺序探测：

`Microsoft YaHei`（Windows）→ `Noto Sans CJK SC`（Linux 容器）→
`WenQuanYi Zen Hei` → `Source Han Sans SC` → `PingFang SC`（macOS）→ `DejaVu Sans`

**Linux 容器里通常没有中文字体**，需要先装：

```bash
apt-get install -y fonts-noto-cjk   # Debian/Ubuntu
```

否则中文会渲染成方块（tofu）。`assemble_health` 的 `subtitle_font` 字段
会告诉你实际选中了哪个字体。

## 命令行

```bash
video-assembler-mcp            # 前台跑 MCP stdio 服务（主用法）
video-assembler-mcp --health   # 打印自检信息
video-assembler-mcp --demo     # 渲染一个内置演示片验证端到端
```

## Python 库用法

MCP 不是唯一入口，也可以直接当库调：

```python
from video_assembler import assemble, load_config

cfg = load_config()
manifest = assemble({
    "title": "hello",
    "scenes": [{"text": "你好世界。", "local_path": "D:/clips/a.mp4"}],
}, cfg)
print(manifest["video"])
```

## 许可

MIT
