# EditMind API

Base URL: `http://localhost:8080`（本地）/ 部署后替换为实际域名

---

## 通用响应格式

所有接口统一返回以下结构：

```json
{
  "ok": true,
  "data": { ... },
  "error": null
}
```

失败时：

```json
{
  "ok": false,
  "data": null,
  "error": {
    "code": "INVALID_REQUEST",
    "message": "错误详情"
  }
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | boolean | `true` 成功，`false` 失败 |
| `data` | object \| null | 成功时的业务数据，失败时为 `null` |
| `error` | object \| null | 失败时的错误信息，成功时为 `null` |
| `error.code` | string | `INVALID_REQUEST`（入参错误）/ `INTERNAL_ERROR`（服务端错误）|
| `error.message` | string | 错误描述 |

---

## GET /health

健康检查。

**响应**

```json
{ "status": "ok" }
```

---

## POST /storyboard/plan

**适用场景**：解说词 / 旁白文稿 → 素材库检索用分镜（配合素材搜索使用）

### 请求体

```json
{
  "text": "文稿内容",
  "max_sections": 3,
  "transcript": {
    "transcript_id": "tr_xxx",
    "language": "zh",
    "duration_ms": 120000,
    "segments": [
      {
        "segment_index": 1,
        "text": "第一句字幕",
        "start_ms": 0,
        "end_ms": 2800
      }
    ]
  }
}
```

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `text` | string | 是 | 文稿正文，不能为空 |
| `max_sections` | integer | 否 | 最多处理的分段数，默认处理全文 |
| `transcript` | object | 否 | 带时间戳字幕；提供后会优先按字幕节拍规划分镜 |

### 响应 `data` 结构

```json
{
  "plan": {
    "sections": [ Section ]
  }
}
```

#### Section

| 字段 | 类型 | 说明 |
|---|---|---|
| `section_index` | integer | 段落序号，从 1 开始 |
| `title` | string | 段落标题 |
| `source_text` | string | 本段对应的原文 |
| `segments` | Segment[] | 视觉节拍列表 |

#### Segment

| 字段 | 类型 | 说明 |
|---|---|---|
| `segment_index` | integer | 节拍序号，从 1 开始 |
| `source_text` | string | 本节拍对应的原文片段 |
| `intent` | string | 本节拍的叙事意图 |
| `start_ms` | integer \| null | 本节拍起始时间戳（毫秒） |
| `end_ms` | integer \| null | 本节拍结束时间戳（毫秒） |
| `shots` | Shot[] | 分镜列表 |

#### Shot

| 字段 | 类型 | 说明 |
|---|---|---|
| `shot_index` | integer | 镜头序号，从 1 开始 |
| `purpose` | string | 镜头用途：`establishing` / `action` / `reaction` / `detail` / `transition` / `atmosphere` |
| `brief` | string | 画面描述（可见内容：主体、动作、情绪、环境、机位） |
| `people` | string[] | 画面中出现的人物名，无人物时为空数组 |
| `mood` | string[] | 情绪关键词 |
| `actions` | string[] | 可见动作描述 |
| `camera` | string \| null | 景别 / 运动 / 角度，如 `"近景，固定"` |
| `duration_hint` | number \| null | 建议时长（秒） |

### 示例

**请求**

```bash
curl -X POST http://localhost:8080/storyboard/plan \
  -H "Content-Type: application/json" \
  -d '{"text": "875年，唐朝蝗灾，百姓苦不堪言。"}'
```

**响应**

```json
{
  "ok": true,
  "data": {
    "plan": {
      "sections": [
        {
          "section_index": 1,
          "title": "晚唐乱象",
          "source_text": "875年，唐朝蝗灾，百姓苦不堪言。",
          "segments": [
            {
              "segment_index": 1,
              "source_text": "875年，唐朝蝗灾，百姓苦不堪言。",
              "intent": "展现灾年百姓困苦",
              "shots": [
                {
                  "shot_index": 1,
                  "purpose": "establishing",
                  "brief": "远景，荒芜农田上蝗虫漫天，百姓面黄肌瘦",
                  "people": [],
                  "mood": ["绝望", "困苦"],
                  "actions": ["百姓望着农田叹气"],
                  "camera": "固定远景",
                  "duration_hint": 5
                }
              ]
            }
          ]
        }
      ]
    }
  },
  "error": null
}
```

---

## POST /broll/plan

**适用场景**：解说词 / 旁白文稿 → B-roll 配图方案（适合纪录片、讲解、Vlog）

### 请求体

```json
{
  "text": "解说词文本",
  "style": "documentary"
}
```

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `text` | string | 是 | 解说词正文，不能为空 |
| `style` | string | 否 | 内容风格：`documentary` / `explainer` / `review` / `educational` |

### 响应 `data` 结构

```json
{
  "plan": {
    "clips": [ Clip ]
  }
}
```

#### Clip

| 字段 | 类型 | 说明 |
|---|---|---|
| `clip_index` | integer | 片段序号，从 1 开始 |
| `source_text` | string | 本片段对应的解说词原文 |
| `duration_hint` | number \| null | 建议时长（秒），与解说词朗读时长对应 |
| `shots` | BrollShot[] | B-roll 镜头列表，每个片段 1-2 个 |

#### BrollShot

| 字段 | 类型 | 说明 |
|---|---|---|
| `shot_index` | integer | 镜头序号，从 1 开始 |
| `brief` | string | 画面描述（与输入文本同语言） |
| `shot_type` | string | 镜头类型：`literal`（直接呈现）/ `detail`（细节特写）/ `metaphor`（象征性画面）/ `establishing`（环境建立）/ `abstract`（质感氛围） |
| `keywords` | string[] | 素材搜索关键词（英文），适用于 Getty / Pexels / Shutterstock |
| `flow_note` | string \| null | 与上一镜头的衔接说明，第一个镜头为 `null` |

---

## POST /director/breakdown

**适用场景**：小说 / 故事 / 剧本文本 → 导演分场 + 专业分镜表

### 请求体

```json
{
  "text": "故事文本",
  "genre": "历史"
}
```

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `text` | string | 是 | 故事或剧本正文，不能为空 |
| `genre` | string | 否 | 题材风格，如 `历史`、`悬疑`、`爱情`，用于调整拍摄风格 |

### 响应 `data` 结构

```json
{
  "breakdown": {
    "overview": Overview,
    "scenes": [ Scene ]
  }
}
```

#### Overview

| 字段 | 类型 | 说明 |
|---|---|---|
| `dramatic_arc` | string | 整体戏剧弧线与叙事结构分析 |
| `visual_approach` | string | 推荐的视觉语言与风格方向 |
| `tone` | string | 整体情感基调，如 `压抑`、`紧张`、`抒情` |

#### Scene

| 字段 | 类型 | 说明 |
|---|---|---|
| `scene_index` | integer | 场景序号，从 1 开始 |
| `heading` | string | 场景标题，标准格式：`INT./EXT. 地点 - 时段` |
| `source_text` | string | 本场景对应的原文 |
| `dramatic_purpose` | string | 本场景在故事中的戏剧功能 |
| `emotional_beat` | string | 本场景内的情绪走向 |
| `characters` | string[] | 本场景出现的人物 |
| `director_note` | string \| null | 导演意图：需要捕捉的感受与注意事项 |
| `shots` | DirectorShot[] | 分镜列表 |

#### DirectorShot

| 字段 | 类型 | 说明 |
|---|---|---|
| `shot_index` | integer | 镜头序号，从 1 开始 |
| `label` | string | 镜头编号，如 `1A`、`1B`、`2A` |
| `type` | string | 镜头类型：`master` / `single` / `OTS` / `insert` / `cutaway` / `POV` / `two-shot` |
| `size` | string | 景别：`EWS`（极远）/ `WS`（远）/ `MS`（中）/ `MCU`（中近）/ `CU`（近）/ `ECU`（极近） |
| `movement` | string | 机位运动：`static` / `push in` / `pull out` / `pan` / `tilt` / `track` / `dolly` / `handheld` / `crane` |
| `brief` | string | 画面内容：主体、构图、视觉核心 |
| `action` | string \| null | 人物或主体在本镜头内的动作 |
| `duration_hint` | number \| null | 建议时长（秒） |
| `transition_out` | string \| null | 出镜转场方式：`cut` / `dissolve` / `fade to black` / `smash cut` / `match cut` / `null` |
