# FSD M10 · 导演与融合

LLMS 索引： [llms.txt](/llms.txt)

---

> [← 总则](/qmtv/fsd-qmtv/00-%E6%80%BB%E5%88%99/) ｜ [附录 →](/qmtv/fsd-qmtv/99-%E9%99%84%E5%BD%95/) ｜ 上游：[PRD §4 M10](/qmtv/product/prd-qmtv-v1/)
> 实现落点：`backend/internal/domain/director/`（model/split/repo/service/service_assets/export 单包多文件）+ `cmd/server/director_handlers.go`（28 handler）+ **generation 侧双表通路三锚点**（model.go:56-69 ShotView.Tbl / service_video.go:191-229 DirectorShotVideo / repo.go:261-283 LinkShotVideo）+ `cmd/server/routes.go:203-238`（挂载）。事实基准=Go 代码；冻结版 `qmtv/server/director.js`（11）/`fusion.js`（17）仅差异对拍参照。
> 域内要点：**双表通路**——本域写 `director_shots`（M2 拆解台是 `shots` 表勿混），生成链经 `ShotView.Tbl` 显式表名打回；扣费面六处（三处硬编码照抄）；ZIP 导入导出；wire 全蛇形+派生视图尾插键；对账口径：routes.go:209/:223 注释「11+17」系**冻结源文件口径**（#1/#2 冻结在 fusion.js:221/:227 却排导演注释块），物理分组=导演 13+融合 15；PRD §4 M10 已登记差异 ①~⑮（prd-qmtv-v1.md:208）+v1.12 变更行三件（二十续 A1 form-tag/A2 空数组/A3 episodes 404 文案）引用不重立。ID 前缀 dpr_/dep_/dir_（tasks 侧 task_id='dir_'+project_id 约定=PRD ⑩）。

### M10 导演与融合

28 端点全量。四零域反转：本域扣费面六处为全站最密（extract 总额扣+单价退/generate-image/fusion 硬编码 3/convert 硬编码 30/remix/资产生成）；split 双阈值 1200·800 合参（PRD ①）；审片入口在 M11（:266-267）share 在 M6 组（:115，冻结 index.js:574）——同前缀勿误收。

#### F10.01 项目列表与创建（director3d GET/POST）

> 对应 PRD：§4 M10 · GET/POST /api/director3d ｜ 状态：实现 ｜ 实现落点：director_handlers.go:21-52、service.go:110-137、repo.go（projects 段）｜ UI：Director3DView 项目列表 ｜ 场景挂钩：E2E-DIR-01（**待实现**，清单 :21——建项目→拆分集→分镜图生成）

##### 1 功能定义

导演工作台项目面：列表（带分集预览）与创建（即时双阈值拆分集）。

##### 2 触发条件与前置状态

登录态；create 前置 script_text 必填 400「剧本文本必填」（model.go:161，fusion.js:229——**创建端点落在 fusion.js 冻结源**，对账口径注记见册尾）。

##### 3 输入与校验

`{name, style_id, face_style, script_text, kind}`（handlers :36-42；style_id 非空透传无白名单——vs face_style 白名单 service.go:130-132，quirk）；name/style_id 截断照冻结 fusion.js:232。

##### 4 处理流程

create：INSERT dpr_ 行 → `SplitEpisodes(script, 800)`（**fusion 阈值**；director resplit 用 1200——两处刻意不同源勿统一，split.go:98-111 合参）逐集 INSERT dep_（标题缺省「第N集」）。

##### 5 输出与结果状态

列表=ProjectRow+episodes 尾插（service.go:110-113）；create 返回 `{id, episodes: n}` 形态（照冻结 :227-236）。

##### 6 状态流转

director_projects 无状态列；分集数创建即定（resplit 才重切）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| script_text 空 | 400 | 「剧本文本必填」 |
| 无「第X集」标记 | 按长度 800 块切分（chunkBlocks，split.go:103-105） | — |

##### 8 权限与数据规则

`authed + amw`；列表按 user_id 属主过滤；org_shared 列随行下发（M6 共享面消费）。

#### F10.02 项目详情/删除/重切分（:id GET/DELETE、resplit POST）

> 对应 PRD：§4 M10 · GET/DELETE /api/director3d/:id、POST /api/director3d/:id/resplit ｜ 状态：实现 ｜ 实现落点：director_handlers.go:80-112、service.go:139-183、repo.go:158-184 ｜ UI：Director3DView ｜ 场景挂钩：E2E-DIR-01（拆分集段）

##### 1 功能定义

项目详情（含分集树）、删除（级联两删序）、重切分（1200 阈值重跑）。

##### 2 触发条件与前置状态

属主校验：非本人 403「forbidden」（model.go:146）；不存在 404「not found」（:145）——**文案分叉**：『episode not found』系 extract/shots/convert 三处共享哨兵默认值（model.go:147），本面经 A3 重映射不直出。

##### 3 输入与校验

resplit 前置项目存在+属主；script_text 不接受（重切用**库内原 script**——冻结同形）。

##### 4 处理流程

- 详情：项目+分集树（episodes 尾插）+style_name JOIN（projectWithStyle service.go:212-215）。
- 删除：按 episode IN 子查询级联删 shots→episodes→项目（repo.go:158-166）——**与 resplit 两删序不同**（resplit 按 project_id 直删 :182-184），quirk 照抄冻结 :85-88 vs :95-96。
- resplit：删旧分集+分镜（按 project 直删）→ `SplitEpisodes(script, 1200)` 重插（service.go:165-176；**1200 阈值**——PRD ①）。

##### 5 输出与结果状态

详情=ProjectWithEpisodes；删除/重切恒 `true`（无 404 面——quirk：删除查在先恒 ok service.go:149-157）。

##### 6 状态流转

分集/分镜随 resplit 整体重建（旧 shots 含 video_id 一并清除——重建语义照冻结）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 非本人项目 | 403 | 「forbidden」 |
| 项目不存在 | 404 | 「not found」 |
| resplit 后为空文 | 单分集兜底（chunkBlocks 全文一块） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；级联删除顺序固定禁事务（中途失败半态=冻结同形）。

#### F10.03 分集详情（episodes/:eid GET）

> 对应 PRD：§4 M10 · GET /api/director3d/episodes/:eid ｜ 状态：实现 ｜ 实现落点：director_handlers.go:113-123、service.go:186-232、repo.go（episode JOIN 段）｜ UI：Director3DView L3 分集工作台 ｜ 场景挂钩：E2E-DIR-01（工作台装载）

##### 1 功能定义

分集工作台装载面：分集行+全部分镜（含图/视频产物 URL）+资产三组，一响应全量。

##### 2 触发条件与前置状态

**URL 无 :id 项目段**——属主按 `ep.project_id → projects.user_id` JOIN 判：非本人 403「forbidden」（service.go:231；冻结 director.js:117 逐字）；不存在 404「**not found**」（:222-226 本路由将共享哨兵 ErrEpisodeNotFound **重映射**为 ErrNotFound——二十续 A3：冻结 :116 此面专属 'not found'；'episode not found' 仅 extract/shots/convert 三处直出）。

##### 3 输入与校验

路径 :eid 无格式校验；无分页（全量分镜一次下发）。

##### 4 处理流程

episodes 行 → shots 按 slot_no 序（每镜 slots 槽位组 service.go:267-278）→ assets 按 kind 三组 characters/scenes/props（:280-285）→ 图/视频经 imageFilePath/videoFilePath 解析 URL（export.go:137-152，store.Read 存在性守卫）。

##### 5 输出与结果状态

`{episode, shots:[{...shot, fields, slots, image_url?, video_url?}], assets:{characters, scenes, props}}` 键形全蛇形。

##### 6 状态流转

零写面；shot.status 消费面（processing/video_done 等由 F10.06/07 写入）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 分集不存在 | 404 | 「not found」（A3 重映射 ErrEpisodeNotFound→ErrNotFound；'episode not found' 仅 extract/shots/convert 三处直出） |
| 跨项目访问他人分集 | 403 | 「forbidden」（属主按 project 判） |

##### 8 权限与数据规则

`authed + amw`；assets 三组按 kind 聚合（本域 task_id='dir_'+project_id 约定隔离——PRD ⑩，M6 projectRank 依赖此形态）。

#### F10.04 资产提取（extract-assets POST）

> 对应 PRD：§4 M10 · POST /api/director3d/:id/episodes/:eid/extract-assets ｜ 状态：实现 ｜ 实现落点：director_handlers.go:124-140、service.go:453-490、repo.go（assets 段）｜ UI：Director3DView「提取资产」｜ 场景挂钩：E2E-DIR-01（集成实测 5 目标——含锚点前残余段 quirk）

##### 1 功能定义

从分集文本提取角色/场景/道具三组资产并逐张生成立绘占位图：**总额一次扣+逐张失败退单价 continue**（PRD ⑨）。

##### 2 触发条件与前置状态

属主双校验：项目+分集（**eid↔project 无关联校验例外面**——本端点校验关联，PRD ⑥ 注convert 例外）；无可提取资产 400「没有可提取的资产」（model.go:148）。

##### 3 输入与校验

无 body；目标数由 splitScenes 解析决定。

##### 4 处理流程

`DeleteAssetsByTask` **先清后插**（service.go:468——重提取抹掉含导入的全部项目资产，quirk 照抄冻结 :153）→ 总额 `ImageCost×N` 一次 TryDeduct「导演工作台资产提取 ×N（-M）」（:461-464，taskKey='dir_'+pid）→ 逐张 Generate 失败：域内 Refund 唯一口退单价（nil claim 无防重面）+`continue`（:474-480，文案「资产图提取失败自动退回（+单价）：{err≤80}」）→ 风格**前缀**拼接 prompt（service.go:466，vs 资产生成后缀拼接——两源刻意不同，service_assets.go:109-117 注）。

##### 5 输出与结果状态

`{created: n, assets:{characters, scenes, props}}`（同 F10.03 三组形态）。

##### 6 状态流转

assets 行即插即生成（无队列）——与 F10.11 队列制不同源。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 零目标 | 400（扣费前校验） | 「没有可提取的资产」 |
| 单张生成失败 | 退单价+跳过，**不整单失败**（continue） | 台账「资产图提取失败自动退回（+10）：…」 |
| 「道具：XX」行 | 「道具」命中 character 正则（charColonRe `[\p{Han}]{2,3}[：:]` service.go:26 无排除——PRD ③） | — |
| 「第一集」锚点前残余段 | 亦成场景目标（lookaheadSplit 残余段入 raw，split.go:40-42——PRD ②） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；退款唯一口在域内 Refund（nil claim——extract 无认领制，与视频 ClaimVideoRefund 不同制）。

#### F10.05 分镜生成与编辑（shots POST、shots/:id PUT）

> 对应 PRD：§4 M10 · POST /api/director3d/:id/episodes/:eid/shots、PUT /api/director3d/shots/:id ｜ 状态：实现 ｜ 实现落点：director_handlers.go:141-166、service.go:235-405、repo.go:235-244 ｜ UI：Director3DView L3 分镜卡 ｜ 场景挂钩：E2E-DIR-01（分镜编辑段）

##### 1 功能定义

分镜批量生成（从场景草稿落 director_shots）与单镜编辑（image_prompt/duration 等槽位）。

##### 2 触发条件与前置状态

生成前置：eid↔project **无关联校验**（service.go:309 注「逐字」PRD ⑥——仅校验各自存在+项目属主）；编辑前置：分镜属主（经 JOIN director_projects）。

##### 3 输入与校验

生成 body `{scope:[]}` 按场景 no 过滤（handlers :128-131；空=全部）；编辑 body `ShotInput` 蛇形（service.go:392-400）；duration clamp 1-15s（videoCost 同参）。

##### 4 处理流程

生成：SplitScenes 草稿（有标记 title=首行 slice40；无标记 rune>10 空行块+「第N场：未命名场景」，split.go:113-137）→ `InsertShot` **15 列 15?**（repo.go:235-244——冻结字面 0 错位修后语义，PRD ④：resolution=草稿值、attempts=0）→ 分镜槽位组插入。
编辑：COALESCE 部分更新+video_id 不动（video 产物编辑不清）。

##### 5 输出与结果状态

生成 `{created: n}`；编辑恒 `true`；响应行键形蛇形（ShotRow model.go）。

##### 6 状态流转

status 初始 'draft'（InsertShot 修后语义）→ F10.06 图生成 'processing'→（图成）→ F10.07 视频链 'video_done'。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| eid 与 :id 项目不匹配 | **仍执行**（无关联校验，PRD ⑥ quirk） | — |
| 编辑非本人分镜 | 404/403 | DirectorShotForUser 查无「not found」 |
| 空场景文本 | 零分镜插入，created=0 | — |

##### 8 权限与数据规则

`authed + amw` 属主面；**本域写 director_shots**（M2 拆解台写 shots——双表通路归属标注，总则 §1 词条）。

#### F10.06 分镜图生成（generate-image POST）

> 对应 PRD：§4 M10 · POST /api/director3d/shots/:id/generate-image ｜ 状态：实现 ｜ 实现落点：director_handlers.go:167-181、service.go:407-425 ｜ UI：Director3DView 分镜卡「生成图」｜ 场景挂钩：E2E-DIR-01（分镜图生成段）

##### 1 功能定义

单镜立绘/Storyboard 图生成：ImageCost 动态价扣费→generation.Generate（Mock SVG 或真实供应商）→images(kind=storyboard)+image_histories 落链。

##### 2 触发条件与前置状态

分镜属主（DirectorShotForUser JOIN——**ShotView.Tbl='director_shots'** 显式表名，generation/repo.go:261-275）；余额不足 402。

##### 3 输入与校验

无 body（prompt 由分镜 image_prompt 组装+风格**前缀**）；provider 侧 size 白名单/4000 截断（generation/provider.go 通用守卫）。

##### 4 处理流程

TryDeduct ImageCost「导演分镜图生成（-{cost}）」（service.go:410-413）→ `gen.Generate` → 成功：images 行+`InsertImageHistory`（**ih_ 前缀**——`ids.PrefixImageHistory` director/repo.go:319 消费，与 task 域 si_ **双轨并存**，DIF-29-4 三十期勘正口径）+分镜回填 image_id；失败：RefundGenError 502 信封+域内退款（:418-420，「导演分镜图生成失败自动退回（+{cost}）：{err}」）。

##### 5 输出与结果状态

`{image_id, url, cost}`（url=PathOf 落库口径）。

##### 6 状态流转

shot.status='processing'（图链）→ 成功后回可生成视频态；重复生成追加 image_histories（历史链不截断）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 余额不足 | 402 | 统一信封 |
| provider 失败 | 502+已退款 | 「导演分镜图生成失败自动退回（+10）：…」 |
| Mock 分支 | SVG 字节级移植产物（mockassets）同步 done | — |

##### 8 权限与数据规则

`authed + amw` 属主面；扣费 type='image'；images.kind='storyboard' 与 M2 拆解图同表隔离（task_id 前缀 dir_）。

#### F10.07 分镜视频生成（generate-video POST）——双表通路

> 对应 PRD：§4 M10 · POST /api/director3d/shots/:id/generate-video ｜ 状态：实现 ｜ 实现落点：director_handlers.go:182-201、**generation/service_video.go:191-229（DirectorShotVideo）**、repo.go:274-283（LinkShotVideo）｜ UI：Director3DView 分镜卡「生成视频」｜ 场景挂钩：E2E-DIR-01（video_id 回填断言面）

##### 1 功能定义

单镜视频生成（V2 动态价形态）：经 generation 域双表通路打 director_shots——**ShotView.Tbl 显式字段是冻结 pseudoShot._tbl 鸭子字段的 Go 显式化**（PRD ⑤）。

##### 2 触发条件与前置状态

分镜属主（DirectorShotForUser JOIN director_projects，Tbl='director_shots' generation/repo.go:273）；余额不足 402。

##### 3 输入与校验

无 body；duration/ratio/resolution 取自分镜行（resolution 空回「1080p」COALESCE，repo.go:266）；videoCost(secs) clamp 1-15s（service_video.go:201-213）。

##### 4 处理流程

TryDeduct videoCost「导演分镜视频 {secs}s（-{cost}）」（V2 计费形态）→ SmartVideo 编排（poll/SSE video 事件照 M3 四帧）→ 成功 `LinkShotVideo(tbl='director_shots',…)`：UPDATE video_id+status='video_done'+attempts+1（repo.go:274-283 **按 Tbl 动态表名**）；失败退款「导演分镜视频失败自动退回（+{cost}）」（Mock desc 参数化——PRD ⑮ 后半）。

##### 5 输出与结果状态

`{video_id, url, cost}`；分镜 video_url 随 F10.03 详情解析。

##### 6 状态流转

status→'video_done'（终态；取消走 M3 cancel 面 taskId='dir_'+pid 隔离）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| Link 失败 | videoFailFast 退款+500（service_video.go:316-318——DIF-29-22 failFast 口径） | — |
| Mock 分支 | **按 Tbl 打 director_shots 回填**（冻结 Mock 分支 UPDATE shots 硬编码致导演分镜不回填——Go 按意图语义修，PRD ⑤ 登记） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；台账 type='video'；**本节是全站唯一跨域写表通路**（generation 域函数写 director 域表——经 Tbl 参数化而非 import，域边界守卫形态）。

#### F10.08 项目 ZIP 导出（:id/export GET）

> 对应 PRD：§4 M10 · GET /api/director3d/:id/export ｜ 状态：实现 ｜ 实现落点：director_handlers.go:202-213、export.go:26-162 ｜ UI：Director3DView「导出项目」｜ 场景挂钩：无（E2E-EXP-01 为 M4 面）

##### 1 功能定义

项目全量打包下载：六段 zip（project.json/episodes/*.txt/shots/EPxx.json/shots_all.json/images/EP{i}_shot{j}.png/videos/…svg）。

##### 2 触发条件与前置状态

项目属主（403/404 同 F10.02）；素材缺失静默跳过（store.Read 失败=冻结 fs.existsSync 守卫，export.go:118-133）。

##### 3 输入与校验

无 body；文件名净化 `[\\/:*?"<>|]→_`（export.go:22）+slice20。

##### 4 处理流程

`zip.NewWriter` 逐段 addZipEntry（:153-161）：project.json（style 尾插+style_id 键保留 :38-49）→ episodes 文本 → shots 两形态 JSON → appendShotMedia 逐镜产物（store.Read，:118-133）→ 文件名 `director-{pid前8}.zip`（:86）→ `zipRespond` 二进制直出（application/zip+Content-Disposition）。

##### 5 输出与结果状态

zip 字节流（PK 魔数——集成锚 integration_director_test.go:204-210 断言 :204 前缀）。

##### 6 状态流转

零写面。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 分镜产物文件缺失 | 跳过该条目（zip 不中断） | — |
| 空项目 | 仅 project.json+空 episodes 段 | — |

##### 8 权限与数据规则

`authed + amw` 属主面；导出含全部素材字节（无脱敏）。

#### F10.09 项目 ZIP 导入（director3d/import POST）

> 对应 PRD：§4 M10 · POST /api/director3d/import（up20）｜ 状态：实现 ｜ 实现落点：director_handlers.go:53-79、export.go:164-234 ｜ UI：Director3DView「导入项目」｜ 场景挂钩：无

##### 1 功能定义

v1 格式 ZIP 导入（project.json+episodes/*.txt）重建项目。

##### 2 触发条件与前置状态

缺 ZIP 400「缺少 ZIP 文件」（model.go:158）；解析失败 400「ZIP 解析失败：{e.message}」（:159，前缀拼接）；限额 20MB（importZipLimit handlers :18+路由 up20，PRD ⑭）。

##### 3 输入与校验

multipart `file` 字段；meta 必需 project.json（缺省兜底形态 export.go:192-234）；episodes 字典序排列。

##### 4 处理流程

zip.NewReader（内存）→ project.json 解析（meta 缺省兜底）→ episodes/*.txt 按**文件名字典序**重建（标题「{原名}（导入）」后缀）→ SplitEpisodes 文本再切（1200 阈值——director.js:268 导入源）。

##### 5 输出与结果状态

`{id, episodes: n}`（同 create 形态）。

##### 6 状态流转

新项目 enabled 语义同 create；旧项目不受扰。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 缺文件 | 400 | 「缺少 ZIP 文件」 |
| 非 ZIP/损坏 | 400 | 「ZIP 解析失败：…」 |
| 无 project.json | meta 缺省兜底继续（quirk） | — |

##### 8 权限与数据规则

`authed + amw`；导入产物属主=当前用户；**不导入素材字节**（v1 仅文本结构——images/videos 不在包规格）。

#### F10.10 资产 CRUD 与公共池导入（assets 四件）

> 对应 PRD：§4 M10 · GET/POST /api/director3d/:id/assets、DELETE /api/director3d/assets/:id、POST /api/director3d/:id/assets/import ｜ 状态：实现 ｜ 实现落点：director_handlers.go:214-243/:300-327、service_assets.go:44-118/:417-435 ｜ UI：Director3DView L1 资产面（公共池 `['asset-library']` 同 key）｜ 场景挂钩：无

##### 1 功能定义

项目资产三组读写+资产库（asset_library 公共池）导入。

##### 2 触发条件与前置状态

GET/POST 前置项目属主；DELETE 查在先恒 ok（service_assets.go:74-82）；import 前置库资产存在 400「资产库资产不存在」（model.go:152）。

##### 3 输入与校验

POST `{name, style_id, face_style, script_text, kind}`（handlers :36-42 复用）——name/kind 必填 400「名称与类型必填」（model.go:149）；风格**后缀**拼接（service_assets.go:109-117 注「与 extract 前缀两源刻意不同」）。

##### 4 处理流程

import：asset_library 行复制为项目 assets（task_id='dir_'+pid，公共池隔离靠前缀）；DELETE 单行（级联 image 引用不动——image_id 保留孤儿兼容）。

##### 5 输出与结果状态

列表=AssetRow+groups 三组尾插（service_assets.go:48-51 status 派生）；AssetRow.StyleID/FaceStyle `json:"-"` 不出 wire（model.go:81-82）。

##### 6 状态流转

queue_status 由 F10.12 写；status 派生（image_id 有无）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 删除不存在资产 | 200 恒 true（查在先不拦截——quirk） | — |
| 队列候选谓词 | `image_id=''` 代冻结 `IS NULL`（repo.go:451——MySQL NOT NULL DEFAULT '' 方言适配，PRD ⑮ 同族） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；公共池 asset_library 全站共享读（导入才落项目域）。

#### F10.11 资产生成（generate + batch-generate POST）

> 对应 PRD：§4 M10 · POST /api/director3d/assets/:id/generate、POST /api/director3d/:id/assets/batch-generate ｜ 状态：实现 ｜ 实现落点：director_handlers.go:244-271、service_assets.go:120-198 ｜ UI：Director3DView 资产卡「生成」｜ 场景挂钩：无

##### 1 功能定义

单个/批量资产立绘生成（ImageCost 计价）；批量**逐笔容错**（失败回 queued 不整单失败）。

##### 2 触发条件与前置状态

已生成资产入队 400「该资产已生成，无需入队」（model.go:150）；batch：无 ids 且无待生成 400「没有待生成的资产」（:151）/有 ids 但全已生成 400「所选资产均已生成」（:150——**双哨兵分叉** service_assets.go:163-167，二十续 A2 空数组方言收口）。

##### 3 输入与校验

generate 无 body；batch `{ids?:[]}`——空数组=全量待生成（A2：MySQL `IN ()` 500→Go 收口语义）。

##### 4 处理流程

单发：TryDeduct「资产生成：{name}（-{cost}）」→Generate→回填；失败退「资产生成：{name}失败自动退回（+{cost}）：{err}」。批量：逐笔同上+失败 `queue_status` 回 queued 继续（:185-198）。

##### 5 输出与结果状态

generate `{image_id, url, cost}`；batch `{generated: n, failed: m}`（failed=剩余数语义）。

##### 6 状态流转

queued→生成中→done（image_id 回填）/失败回 queued（可重试）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| batch 空数组 | 全量待生成语义（A2 收口） | — |
| 单笔失败 | 退费+回 queued+continue | — |
| mid-batch 余额耗尽 | 后续笔 402 整批中断（已生成不回滚） | — |

##### 8 权限与数据规则

`authed + amw` 属主面（batch ids 逐个校验属主）；扣费 type='image'。

#### F10.12 待生成队列（queue 二件）

> 对应 PRD：§4 M10 · POST /api/director3d/assets/:id/queue、GET /api/director3d/:id/queue ｜ 状态：实现 ｜ 实现落点：director_handlers.go:272-299、service_assets.go:200-246、repo.go:18-26/:451 ｜ UI：Director3DView 队列面板 ｜ 场景挂钩：无

##### 1 功能定义

资产入队/队列视图（持久化 queue_status）——批量排程前置面。

##### 2 触发条件与前置状态

入队前置：已生成 400「该资产已生成，无需入队」；action 归一（非 unqueue 一律按 queue，service_assets.go:223-225 quirk 照抄 fusion.js:105）。

##### 3 输入与校验

`{action}` 任意字符串（归一语义）；视图无分页。

##### 4 处理流程

入队：queue_status='queued'；出队：=''/置空；视图：QueueItemRow 七键投影（repo.go:18-26）双键排序。启动恢复由 recovery.requeueAssets 承接（service_assets.go:211-212 注——重启丢队列态回补）。

##### 5 输出与结果状态

入队/出队恒 `true`；视图 queued 列表。

##### 6 状态流转

queued ⇄ 空（unqueue）；done 由 F10.11 写。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| action 未知值 | 按 queue 处理（归一 quirk） | — |
| 重启后 queued 行 | recovery 回补入队 | — |

##### 8 权限与数据规则

`authed + amw` 属主面；零扣费（入队不扣，生成才扣）。

#### F10.13 融合生图与图片改创（fusion 二件 + remix）

> 对应 PRD：§4 M10 · POST/GET /api/director3d/:id/fusion、POST /api/director3d/:id/remix ｜ 状态：实现 ｜ 实现落点：director_handlers.go:328-371、service_assets.go:249-383 ｜ UI：Director3DView 融合 tab ｜ 场景挂钩：无

##### 1 功能定义

双图融合生图（cost=3 **硬编码**）+融合历史列表+单图改创（remix，ImageCost 计价）。

##### 2 触发条件与前置状态

fusion：画面描述必填 400「请填写画面描述」（model.go:154）；remix：原图存在 400「原图不存在」（:156）+改创说明必填「请填写改创说明」（:157）。

##### 3 输入与校验

fusion `{image_id_a, image_id_b, description, scene_id?}`——scene_id 空串→**NULL 指针**（sceneIDPtr service_assets.go:509-514，`scene_id || null` 冻结同形）；remix `{image_id, instructions}`。

##### 4 处理流程

fusion：`const cost = 3` TryDeduct「融合生图（-3）」（service_assets.go:260 注「非 ImageCost——差异登记 PRD ⑦」）→ 双图 prompt 组合 → images(kind=fusion) → fusion_items 行（响应带 cost :310）。remix：ImageCost「图片改创（-{cost}）」（:366-369）→ images(kind=remix)。

##### 5 输出与结果状态

fusion create `{id, url, cost}`；list=融合历史（LIMIT 面）；remix `{image_id, url, cost}`。

##### 6 状态流转

零状态列（同步完成）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 描述/说明空 | 400（扣费前） | 「请填写画面描述」/「请填写改创说明」 |
| 原图不存在 | 400 | 「原图不存在」 |
| fusion cost 调价 | **不随 settings**（硬编码 3——PRD ⑦，与 ImageCost 双轨） | — |

##### 8 权限与数据规则

`authed + amw` 属主面（image_id 引用校验属主）；fusion_items.scene_id 可空外键语义（NULL=非场景绑定）。

#### F10.14 参考视频（ref-videos 三件）

> 对应 PRD：§4 M10 · GET/POST /api/director3d/:id/ref-videos、DELETE /api/director3d/ref-videos/:id ｜ 状态：实现 ｜ 实现落点：director_handlers.go:372-425、service_assets.go:385-435 ｜ UI：Director3DView 参考视频区 ｜ 场景挂钩：无

##### 1 功能定义

项目参考视频上传（60MB 新档位）/列表/删除。

##### 2 触发条件与前置状态

上传前置文件存在 400「缺少视频文件」（model.go:158 族）+扩展名白名单 400「仅支持 mp4/webm/mov」（:159）；限额 60MB（refVideoLimit handlers :19+路由 `up60` routes.go:208 **新档位**——PRD ⑭；handler 单文件双保险 500「File too large」:391-393）。

##### 3 输入与校验

multipart `file`+`name`/`note` **form tag**（handlers :403-407——json tag 恒不命中，二十续 A1 修复锚）；name/note 可空。

##### 4 处理流程

store.Put 大文件流 → ref_videos 行（rv_ 族）→ 列表蛇形投影；DELETE 恒 ok 不查行数（service_assets.go:430-435 quirk 照抄 fusion.js:215-218）。

##### 5 输出与结果状态

上传 `{id, url, name}`；列表 rv 行数组；删除恒 `true`。

##### 6 状态流转

零状态（上传即可用）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 扩展名非三白名单 | 400 | 「仅支持 mp4/webm/mov」 |
| 超 60MB | 路由 413/handler 500 双层 | 「File too large」（handler 层） |
| 删除不存在 | 200 恒 true（quirk） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；素材字节落 MinIO/local（files 目录），wire 出 url=PathOf 口径。

#### F10.15 一键转剧（convert/:eid POST）

> 对应 PRD：§4 M10 · POST /api/director3d/:id/convert/:eid ｜ 状态：实现 ｜ 实现落点：director_handlers.go:426-436、service_assets.go:440-481 ｜ UI：Director3DView「一键转剧」｜ 场景挂钩：无

##### 1 功能定义

分集文本一键转剧：flat=30 **硬编码**扣费→Mock 视频即弃（**不落 videos 表**）。

##### 2 触发条件与前置状态

**eid↔project 有关联校验**（`WHERE id AND project_id`，service_assets.go:451-453——PRD ⑥ 的 convert 例外面：convert 是唯一校验关联的端点）；余额不足 402。

##### 3 输入与校验

无 body；空场次→**意图语义**：ep.title 单场（:471-481——冻结 :262-267 fallback 对象调 .split 恒 TypeError 500，Go 按意图修，PRD ⑧ 登记）。

##### 4 处理流程

`const flat = 30` TryDeduct `type='video'`「一键转剧《{ep.title}》（-30）」**taskID=裸 p.ID**（:454-456——无 dir_ 前缀，PRD ⑦；集成断言 integration_director_test.go:229/:235 钉死）→ MakeVideo 占位 SVG 落盘 → **不落 videos 表**（service_assets.go:440-441 注——与 M3 视频链不同源，产物一次性）。

##### 5 输出与结果状态

`{url, cost}`（url 一次性产物链接）。

##### 6 状态流转

无任务行无轮询（同步伪完成形态——M4 同款语义，status 无从查证）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| eid 不属该项目 | 404（关联校验——convert 例外 PRD ⑥） | — |
| 空场次 | 单场兜底（意图语义 PRD ⑧） | — |
| 硬编码 30 | **不随 settings**（PRD ⑦） | — |

##### 8 权限与数据规则

`authed + amw` 属主面；台账 type='video' task_id=裸 pid（credit-records 按 taskId 聚合时与 M3 视频同族——m04 对账注记关联面）。

---

## 册尾端点覆盖对账（28/28）

对账 `backend/cmd/server/routes.go:203-238` 亲数 28 条 ↔ 本册 F10.01~F10.15。**口径脚注**：routes.go:209/:223 注释「导演面 11（director.js）/融合面 17（fusion.js）」系**冻结源文件归属**计数（#1/#2 冻结在 fusion.js:221/:227 却排导演注释块内）；物理分组=导演功能 13+融合功能 15，28 总数自洽。非本域同前缀：share（M6 组 :115，冻结 index.js:574——m06 F6.05）/review（M11 :266-267）勿误收。

| # | 端点（routes.go:line） | 节 | # | 端点（routes.go:line） | 节 |
|---|---|---|---|---|---|
| 1 | GET /director3d（:210） | F10.01 | 15 | POST /director3d/:id/assets（:225） | F10.10 |
| 2 | POST /director3d（:211） | F10.01 | 16 | POST /director3d/assets/:id/generate（:226） | F10.11 |
| 3 | POST /director3d/import（:212） | F10.09 | 17 | POST /director3d/:id/assets/batch-generate（:227） | F10.11 |
| 4 | DELETE /director3d/:id（:213） | F10.02 | 18 | POST /director3d/assets/:id/queue（:228） | F10.12 |
| 5 | GET /director3d/:id（:214） | F10.02 | 19 | GET /director3d/:id/queue（:229） | F10.12 |
| 6 | POST /director3d/:id/resplit（:215） | F10.02 | 20 | DELETE /director3d/assets/:id（:230） | F10.10 |
| 7 | GET /director3d/episodes/:eid（:216） | F10.03 | 21 | POST /director3d/:id/assets/import（:231） | F10.10 |
| 8 | POST /director3d/:id/episodes/:eid/extract-assets（:217） | F10.04 | 22 | POST /director3d/:id/fusion（:232） | F10.13 |
| 9 | POST /director3d/:id/episodes/:eid/shots（:218） | F10.05 | 23 | GET /director3d/:id/fusion（:233） | F10.13 |
| 10 | PUT /director3d/shots/:id（:219） | F10.05 | 24 | POST /director3d/:id/remix（:234） | F10.13 |
| 11 | POST /director3d/shots/:id/generate-image（:220） | F10.06 | 25 | GET /director3d/:id/ref-videos（:235） | F10.14 |
| 12 | POST /director3d/shots/:id/generate-video（:221） | F10.07 | 26 | POST /director3d/:id/ref-videos（:236） | F10.14 |
| 13 | GET /director3d/:id/export（:222） | F10.08 | 27 | DELETE /director3d/ref-videos/:id（:237） | F10.14 |
| 14 | GET /director3d/:id/assets（:224） | F10.10 | 28 | POST /director3d/:id/convert/:eid（:238） | F10.15 |

差异纪律：PRD §4 M10 ①~⑮ 全引用不重立；二十续 A1/A2/A3 引 PRD v1.12 变更行；quirk（双方一致 as-is）节内登记不立 DIF——**本册零新立 DIF**（差异面全在 PRD 已登记集合；image_id='' 谓词引 ⑮ 族）。

---

反链：

- [QMTV 功能规格说明书（FSD）v1.0 · 总则](/qmtv/fsd-qmtv/00-%E6%80%BB%E5%88%99/)
- [FSD 附录 · PRD 对照 / 代码域索引 / 差异台账](/qmtv/fsd-qmtv/99-%E9%99%84%E5%BD%95/)
