Metadata-Version: 2.5
Name: pixhub
Version: 0.1.7
Summary: 本地优先的生图,音频，模型统一访问路由 (CLI + MCP + Server)
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: mcp>=2.0
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: http
Requires-Dist: fastapi>=0.115; extra == 'http'
Requires-Dist: uvicorn>=0.32; extra == 'http'
Description-Content-Type: text/markdown

# pixhub

本地优先的生图、音频、视频模型统一访问路由。对外 CLI + MCP 接口稳定，对内把阿里云万相、商汤 SenseNova、腾讯混元、本地 ComfyUI 等收编成可插拔 Provider。

## 设计

- **中立协议**：Pydantic schema（ImageGen / VideoGen 两套），超集 + extra_params 兜底
- **Provider Adapter**：每厂商一个模块，`@provider` 装饰器注册，热插拔
- **本地优先**：结果默认落盘，SQLite 任务库持久化，重启可续轮询
- **Router**：alias → 规范名，顺序 fallback（local → cloud）

## 快速开始

```bash
```
# 安装
uv tool install pixhub
# 更新
uv tool upgrade pixhub
```

> **前置依赖**：视频剪辑（`pixhub edit merge/trim`、MCP `edit_video`）基于本机 `ffmpeg`/`ffprobe`，需要先安装并加入 PATH（https://ffmpeg.org ；Windows 可用 gyan.dev 完整版）。未安装时命令会给出可读提示，其余生图/音频功能不受影响。

默认只装 CLI + MCP 出口；HTTP API 是可选出口（见「部署」），默认不启动，需要时才加 `--extra http`。

## 使用

```bash
# 列出可用模型
pixhub models

# 生成图像（结果落盘到 ~/Pictures/pixhub，同参数自动命中缓存）
pixhub image "一只猫" --model wanx-turbo
pixhub image "一只猫" --model wanx-turbo --no-cache   # 强制重新生成
pixhub image "一只猫" --model wanx-turbo --output ./out  # 临时覆盖本次落盘目录

# 生成视频（提交并等待）
pixhub video "一只猫跑过草地" --model agnes-video
pixhub video "一只猫跑过草地" --model agnes-video --output ./clips  # 临时覆盖落盘目录（含封面）

# 只提交不等待，任务后台轮询 / 断点续查
pixhub video "一只猫跑过草地" --model agnes-video --submit
pixhub resume                                    # 恢复全部 pending 任务
pixhub resume <task_id>                          # 恢复单个

# 图生图 / 多图合成（本地图片自动转 Data URI 上传，远程 URL 用 --image-url）
pixhub image "改成赛博朋克风格" --model agnes-image-flash --image ref.png

# 视频关键帧动画（2 张起）/ 图生视频（单张输入用 --image-url）
pixhub video "镜头缓慢推进" --model agnes-video-v20 --keyframe a.png b.png

# 视频轻量剪辑（基于 ffmpeg，需本机已安装 ffmpeg/ffprobe）：合并片段 / 按区间裁剪
# （均可用 --scale/--fps 统一参数；未装 ffmpeg 时会提示安装）
pixhub edit merge clip1.mp4 clip2.mp4 -o 成片.mp4
pixhub edit merge a.mp4 b.mp4 c.mp4 -o all.mp4 --force-reencode   # 强制重编码统一
pixhub edit trim 输入.mp4 -o 片段.mp4 --start 0:10 --end 1:30     # 精确重编码裁剪
pixhub edit trim 输入.mp4 -o 片段.mp4 --start 5 --end 15 --copy   # stream copy（快，但起止取最近关键帧）

# 语音识别（音频 → 文本，本地文件或 --audio-url）
pixhub asr speech.wav --model qwen-asr-flash
pixhub asr --audio-url https://example.com/a.mp3 --model qwen-asr-flash --format mp3 \
  --language zh,en --vocabulary "通义千问:5"

# 语音合成（文本 → 音频，默认落盘，URL 仅 24h 有效）
pixhub tts "你好，我是通义千问。" --model qwen-tts-plus --voice longanhuan_v3.6
pixhub tts "Hello!" --model qwen-tts-plus --format mp3 --sample-rate 48000
# 不传 --voice 时用 models.yaml 里该模型的 voice 配置（见下）

# 查看任务历史 / 详情（task_id 支持完整 ID 或短 ID 前缀查询）
pixhub tasks
pixhub tasks --json        # 完整 task_id 的 JSON 输出，便于复制/脚本处理
pixhub task <task_id>
pixhub task <短ID前缀>      # 前缀唯一时自动命中；前缀不唯一会列出候选
pixhub task <task_id> --json   # 单任务完整 JSON（含 created_at/updated_at/响应体）

# 查看版本号（自动跟踪 pyproject.toml）
pixhub version

# 密钥与配置体检（密钥来源、缺失项、配置警告）
pixhub config
```

### MCP server（stdio）

```jsonc
// Claude Desktop / Zed / 其它 MCP 客户端配置
{
  "mcpServers": {
    "pixhub": {
      "command": "pixhub-mcp"
    }
  }
}
```

工具集（新增模型只改 `models.yaml`，不碰这里）：`list_models`、`generate_image`、`generate_video`（**默认同步阻塞到成功/失败才返回**；`wait=false` 提交即返回 task_id 后台续轮询）、`edit_video`（视频轻量剪辑：`action=merge` 合并片段 / `action=trim` 按时间裁剪，基于本地 ffmpeg，等待期间同样上报进度）、`transcribe_audio`（语音识别）、`synthesize_speech`（语音合成）、`reload_config`（改配置后手动刷新）、`get_task`（支持短 ID 前缀）、`cancel_task`、`list_tasks`、`resume`。`generate_image`/`generate_video` 均支持 `output_dir` 参数临时覆盖本次落盘目录；所有工具的文件路径支持相对路径，**按 MCP 进程工作目录（即本项目/当前目录）解析为绝对路径**。`generate_video`/`edit_video`（wait=true）等待期间会按 MCP progress 协议实时上报状态（客户端支持时显示，如 Zed；未请求进度则静默跳过）。自定义配置：`PIXHUB_CONFIG` 指向 models.yaml，`PIXHUB_DATA_DIR` 覆盖数据目录，日志级别用 `PIXHUB_LOG_LEVEL`（默认 INFO）。

### fallback 链

`models.yaml` 里 `fallback: [...]` 配置备用模型，主 provider 失败/超时自动顺延：

```yaml
models:
  wanx-turbo:
    provider: dashscope
    model: wanx2.1-t2i-turbo
    fallback: [sensenova-u15]   # 图像：商汤兜底
  agnes-video-v20:
    provider: agnes
    model: agnes-video-v2.0
    fallback: [wanx-t2v-turbo]  # 视频：万相兜底
```

语音模型（`kind: asr` / `kind: tts`）同样支持 fallback，如给 `qwen-tts-plus` 配 `fallback: [qwen-tts-flash]`。

TTs 模型可在条目里配置默认音色与可选音色清单（调用方不传 `--voice` 时用 `voice`）：

```yaml
models:
  qwen-tts-plus:
    provider: dashscope
    model: qwen-audio-3.0-tts-plus
    kind: tts
    voice: longanhuan_v3.6                    # 默认音色
    voices: [longanhuan_v3.6, longanyang, longshange_v3]  # 可选音色清单（提示用）
```

## 部署

CLI / MCP / HTTP 三种出口共享同一套核心（fallback、缓存、轮询、落盘）、同一个 `models.yaml` 和同一个 SQLite 任务库，选一种即可，混用不冲突。其中 CLI 和 MCP 开箱即用；**HTTP 是可选出口，默认不启动**：

```mermaid
graph LR
    CLI[pixhub CLI] --> CORE[核心层<br/>fallback + 缓存 + 轮询 + 落盘]
    MCP[MCP server stdio] --> CORE
    HTTP[HTTP API :8668] --> CORE
    CORE --> DB[(SQLite 任务库)]
    CORE --> OUT[(输出目录)]
```

### HTTP 出口（serve，可选）

HTTP API 默认不启动，仅在需要给同机其他进程 / 远程调用时才启用（不装 `http` 依赖时 `pixhub serve` 会直接报缺依赖提示）：

```bash
uv sync --extra http                          # 仓库内开发
uv tool install -e ".[http]"                  # 全局 CLI 带上 HTTP（不带则 serve 会报缺依赖）
```

启动与自测：

```bash
pixhub serve --host 127.0.0.1 --port 8668     # 默认只监听本机
curl http://127.0.0.1:8668/health             # 健康检查
```

端点一览（详见 `src/pixhub/http_server.py`）：

| 方法与路径 | 说明 |
|---|---|
| `GET /health` | 健康检查 |
| `GET /models` | 模型列表 |
| `POST /images` | 生图（同步等待） |
| `POST /videos` | 生视频，默认同步等待完成；`wait=false` 提交即返回 task_id 后台续轮询 |
| `POST /audio/asr` | 语音识别（audio_path / audio_url，同步） |
| `POST /audio/tts` | 语音合成（text + voice，同步，默认落盘） |
| `GET /tasks?limit=&status=` | 任务历史 |
| `GET /tasks/{task_id}` | 任务详情（pending 顺带刷新远端；支持短 ID 前缀） |
| `POST /tasks/{task_id}/cancel` | 取消 |
| `POST /resume` | 续轮询全部 pending |
| `POST /reload` | 改 models.yaml 后重载配置（无需重启，CLI 用 `pixhub reload` 触发） |

```bash
curl -X POST http://127.0.0.1:8668/images -H "Content-Type: application/json" \
  -d '{"prompt": "一只猫", "model": "wanx-turbo"}'
curl -X POST http://127.0.0.1:8668/videos -H "Content-Type: application/json" \
  -d '{"prompt": "一只猫跑过草地", "model": "agnes-video"}'                     # wait 默认 true（同步）
curl -X POST http://127.0.0.1:8668/videos -H "Content-Type: application/json" \
  -d '{"prompt": "一只猫跑过草地", "model": "agnes-video", "wait": false}'    # 提交即返回 task_id
curl -X POST http://127.0.0.1:8668/audio/asr -H "Content-Type: application/json" \
  -d '{"audio_url": "https://example.com/a.wav", "model": "qwen-asr-flash"}'
curl -X POST http://127.0.0.1:8668/audio/tts -H "Content-Type: application/json" \
  -d '{"text": "你好", "model": "qwen-tts-plus", "voice": "longanhuan_v3.6"}'
curl http://127.0.0.1:8668/tasks?limit=10
```

### 长期运行（守护进程）

任务库在 SQLite，重启不丢；服务中断后重启，`POST /resume`（或 CLI `pixhub resume`）接着轮询即可，无需额外持久化配置。

Windows（计划任务，开机启动）：

```bat
schtasks /create /tn pixhub /tr "uv run pixhub serve --host 127.0.0.1 --port 8668" /sc onlogon
:: 或用 NSSM 包成 Windows 服务
:: nssm install pixhub C:\path\to\pixhub\.venv\Scripts\pixhub.exe serve --host 127.0.0.1 --port 8668
```

Linux（systemd）：

```ini
[Unit]
Description=pixhub HTTP API
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/pixhub
ExecStart=/opt/pixhub/.venv/bin/pixhub serve --host 127.0.0.1 --port 8668
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
```

> HTTP 出口无鉴权。监听 `127.0.0.1` 只服务本机；跨机（`--host 0.0.0.0`）一定要放在可信内网或自己加网关鉴权。

### 数据与配置目录

| 内容 | 默认位置 | 覆盖方式 |
|---|---|---|
| 配置 `models.yaml` | `./models.yaml` 或 `~/.config/pixhub/models.yaml` | `PIXHUB_CONFIG` / `--config` |
| 密钥 `.env` | `~/.config/pixhub/.env`（或系统环境变量） | `pixhub init --key KEY=...` |
| SQLite 任务库 | `~/.local/share/pixhub/pixhub.db` | `PIXHUB_DATA_DIR` |
| 生成结果 | `~/Pictures/pixhub` | `models.yaml` 的 `output_dir` |

Windows 上 `~` 即 `%USERPROFILE%`。MCP / HTTP 进程通过环境变量隔离到独立数据目录时，注意共享任务库的场景（如 server 用独立目录就不会看到 CLI 的任务），默认同用户共库。

## 配置

先一键生成全局配置模板（幂等，已有配置不覆盖）：

```bash
pixhub init                        # 生成 ~/.config/pixhub/models.yaml + .env
pixhub init --key DASHSCOPE_API_KEY=sk-xxx   # 顺便写入密钥
pixhub init --force                # 覆盖重建（.env 里已填的密钥会保留）
```

三层优先级：**CLI 参数 > 配置文件 > 环境变量兜底**。

### 1. 配置文件（`models.yaml`）

放在当前目录或 `~/.config/pixhub/models.yaml`：

```yaml
providers:
  sensenova:
    api_key_env: SENSENOVA_API_KEY   # 推荐：引用环境变量
    base_url: https://custom.url/v1   # 可选：覆盖默认 URL
  agnes:
    api_key: sk-xxx                   # 明文密钥（不推荐，需保护权限）
  bailian:
    api_key_env: BAILIAN_API_KEY      # 阿里云百炼 TokenPlan 订阅专属 key（sk-sp-）
```

语音识别/合成（ASR/TTS）复用 `dashscope` provider，即 `DASHSCOPE_API_KEY`。
`bailian` provider 是阿里云百炼 TokenPlan 订阅，图像生成走 DashScope 同步协议（`POST /api/v1/services/aigc/multimodal-generation/generation`），默认 base_url 为 `https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1`；模型条目（`wan2.7-image` 系列、`qwen-image-3.0` 系列等）按你的套餐可配列表自行增删。注意：TokenPlan 的 chat 接口才是 OpenAI 兼容 `compatible-mode/v1`，图像接口在该路径返回 400 `url error`。

models:
  sensenova-u15:
    provider: sensenova
    model: sensenova-u1.5-lite
    aliases: [sensenova]
```

### 2. 环境变量（最安全）

```bash
set SENSENOVA_API_KEY=sk-xxx
set AGNES_API_KEY=sk-xxx
set DASHSCOPE_API_KEY=sk-xxx
set BAILIAN_API_KEY=sk-sp-xxx   # TokenPlan 专属 key，与按量付费 sk- 不能混用
```

Provider 无配置时自动从环境变量读取。

### 3. CLI 参数（临时覆盖）

```bash
pixhub image "猫" -m sensenova-u15 --api-key sk-xxx --base-url https://custom.url
pixhub video "猫" -m agnes-video-v20 --api-key sk-xxx
```

覆盖 `--model` 对应 provider 的配置，不进文件。


3. **配置模板同步**：新版本模型/字段会进入 `pixhub init` 生成的模板，旧配置不会被覆盖；需要新模板做参考时：

   ```bash
   pixhub init --force     # 重建模板；.env 里已填的密钥会保留
   ```

   然后手动把新增的 `models` 条目合并进自己的 `models.yaml`（新增模型不强制，按需取用）。

跨版本升级前建议备份 `~/.config/pixhub`（配置+密钥）与 `~/.local/share/pixhub`（任务库）；未来若任务库 schema 变更会提供迁移命令。
