Files
MediaCrawler/mac-agent-os-main/PLANS/STUDIO_REDESIGN.md
T
butubb 2112c1a870
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
feat(monitor): 抖音 Web 接口客户端 —— 绕开爬虫子进程,直接发 HTTP
移植自 mac-agent-os 的 mediacrawler_adapter:不起子进程、不开页面,用浏览器里那份
登录态直接调抖音 Web 接口。产物键名照抄 store/douyin,所以 ingest 那条链路一个字不用改。

**目前能用的(真环境实测,非推断)**:

    profile/other : 200, 7075 字节  —— 博主主页指标(粉丝/获赞/作品数/昵称)
    aweme/detail  : 200, 45425 字节 —— 单条作品详情(含点赞/评论/收藏/分享)

**目前不能用的:作品列表 `aweme/post`。** 两个互相独立的原因:
  1. 这个接口被抖音单独升级成了真校验:不带 x-tt-argus 回 403「Uifid Not Found」,
     带上 dummy 值回 200 + **空 body**。也就是说「头在不在」骗得过,「真校验」过不了。
     同一套头打 profile/other 和 aweme/detail 都是通的 —— 抖音是挑着接口加保护的,
     挑中的恰好是「批量拉作品列表」这个最敏感的动作。
  2. 改走页面截获也不行:CDP 浏览器打开博主主页会落到「验证码中间页」(当天大量探测的
     代价,过几小时要重测)。

  所以现在的边界是:**已知作品的指标刷新能做,自动发现新作品做不了**。

**排查中控住变量后得到的两条事实**(都写进注释了):
  · `Accept` / `Accept-Language` / `Referer` 才是主页接口能返回真数据的原因 —— 只有
    UA+client hints+Cookie 时是 200 但仅 121 字节的空壳,补上这三个头变 7074 字节。
    (我先前猜的 sec-ch-ua 不是关键。)
  · 因此 UA 与 client hints 必须**成套地取自同一个浏览器**,所以 BrowserIdentity 一次
    从 CDP 取齐 cookie + UA + hints,而不是各自写死。

「200 + 空 body 必须当场报错」也是刻意写死的:放过去它会在下游变成「这个博主没作品」,
把一次失败伪装成一条正常结果 —— 爬虫那条路正是这么栽的,还被翻译成「账号被封」。

测试 +11:cookie 解析、请求头成套性(含 uifid 缺失/回退)、产物键名与 store 对齐、
以及 _get 的三条失败路径(空 body / 403 带网关原话 / 正常返回)。
2026-10-10 17:12:25 +08:00

289 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 导演工作室 · 重新设计方案
> 2026-10-10 · 基于**实测盘查**(非推测)
> 目标:止住"越改越乱",把现有能力重新收进一个**边界清晰**的结构里
---
## 一、现状诊断(全部实测数据)
### 1.1 规模
| 项 | 数量 | 说明 |
|---|---|---|
| 前端视图 | **85 个** | 视频工厂相关 44 个 |
| 后端路由 | **7 文件 / 145 端点 / 7206 行** | ave(33) · ave_project(30) · ave_keyframe(25) · ave_assets(33) · ave_scene(5) · ave_creatives(16) · ave_docs(3) |
| `ave-director.js` | **112 KB** | 单文件注册 **26 个 `window._dir*` 函数** |
| 项目对象顶层键 | **19 个** | 其中 3 个是从没写过的死键 |
| 运行目录 | **15 个** | 含 5 个测试残留 |
### 1.2 五类具体问题(都对齐到了文件)
**① 空壳页面 4 个**(内容是"功能开发中,敬请期待")
```
ave-script.js 409 B 脚本生成
ave-render.js 409 B 渲染任务
ave-templates.js 406 B 模板
ave-materials.js 409 B 素材库
```
**② 新旧两套流程并存 —— 6 个老流程页在菜单里,但跑不通**
```
ave-flow-short-drama.js 744 B ← 菜单「🎭 短剧创作」
ave-flow-dub.js 647 B ← 菜单「🎙️ 基础口播」
ave-flow-beat.js 579 B ← 菜单「🥁 卡点视频」
ave-flow-narrative.js 668 B ← 菜单「📖 角色叙事」
ave-flow-digital-human.js 611 B ← 菜单「👤 数字人口播」
ave-flow-hybrid.js 564 B ← 菜单「🎞️ 口播+混剪」
```
它们自己的注释写着:**"TTS / 素材 / BGM / 字幕的执行实现仍在接入中(运行时会明确跳过)"**
→ 而工作室那条链路**是通的** → 两套并存,用户不知道点哪个
**③ 工作室内部职责重叠(用户已经混了)**
```
🌍 设定 vs 📋 创意卡 —— 都在"想清楚这个故事"
🖼 关键帧设计 vs 🖼 关键帧 —— 用户明确问过"这两个有什么区别"
```
实测:用户上次说的"关键帧工作台里面是空的",我查了两轮才确认他指的是哪个页面。
**④ 单文件职责超载**
`ave-director.js`(112 KB)一个文件里塞了:
```
<!-- ② 全局设置 --> 导演系统 / 风格 / 演员表 / 场景 / seed
<!-- 📐 全片设定 --> 空间/角色/镜头语言(原故事板并入)
<!-- ③ 按场次生成 --> 段落列表 + 场记板 + 生成
<!-- ④ 分镜表 --> 镜头卡片编辑
<!-- 出片进度 --> 渲染任务轮询
```
**⑤ 数据源分散 + 死键**
```
projects/{pid}/project.json ← 项目对象(19 键,其中 3 键为空)
storyboards/{pid}.json ← 分镜(不在 project.json)
keyframes/{pid}/{sid}/ ← 关键帧图
keyframe_videos/{pid}/{sid}/ ← 视频片段
```
`project.json` 里的 `keyframes` / `videos` / `assets` **三个键从没被写过**(空)。
实测 `_enrich()` 每次读项目时**临时合并**前三个 → 说明它们本该在项目对象里。
---
## 二、根因(为什么会"越改越乱")
三条,都不是技术问题:
1. **加功能时"就地扩"** —— 没有"这一层该管什么"的边界。分镜页越塞越多(演员表、锚点、全片设定、出片设置),因为"顺手就放这了"。
2. **新旧并存不删** —— 怕删坏,就留着旧的。结果路由/菜单/文档里同时存在两套,用户选错就在所难免。
3. **数据"能跑就行"** —— 写在哪方便就写哪。分镜写 `storyboards/`、关键帧写 `keyframes/`,导致"上游带入"读不到(这是之前修过的真 bug)。
---
## 三、新架构
### 3.1 三条主线(先分清楚"这是哪一类东西")
```
📦 原料 🎬 生产 📊 交付
(可复用资产) (做一条片子) (看结果)
角色/场景/道具 导演工作室 生产记录
音色/BGM/音效 (唯一主流程) 费用统计
产品/服装/素材 产物中心
```
**判断规则**:
- 能被**多条片子复用** → 进「原料」
- 属于**某一条片子**的 → 进「生产」
- 是**结果/统计** → 进「交付」
### 3.2 生产主线:工作室 9 阶段 → **7 阶段**
**合并的关键点**:`🖼 关键帧设计` + `🖼 关键帧` → **一个页面**(左设计右图)
```
① 💡 创意 ┐
② 🌍 设定 ├→ ③ 📋 创意卡 → ④ 📝 剧本 → ⑤ 🎬 分镜 → ⑥ 🖼 关键帧 → ⑦ 🎞 视频 → ⑧ 🎙 后期
┘
(素材层) (汇聚层) (执行层)
```
**为什么保留 ①②(不合并)**:
- `💡 创意` = **这个故事的种子**(候选 → 选一句)
- `🌍 设定` = **世界的规则**(可跨故事复用,比如"肛肠科病房"这个世界的规则)
- `📋 创意卡` = **两者合起来 → 这个故事的具体构想**
- 职责不同,但**界面上必须写明各自管什么**(现在的毛病是没写)
**为什么合并 ⑥**:
- 用户已经混了。而且"设计"(决定拆几张)和"生成"(出图)本来就是**同一步的两半** —— 设计完立刻要看图对不对。
### 3.3 每阶段一张「职责卡」(界面顶部固定显示)
这是**治乱的核心**:每个页面顶部明确写三行 —— **我从哪来 / 我产出什么 / 下一步用它做什么**。
| # | 阶段 | 输入(从哪来) | 产出 | 下一步用它做什么 |
|---|---|---|---|---|
| ① | 💡 创意 | 参数选型 | 选中的 1 句话 | 扩写成创意卡 |
| ② | 🌍 设定 | — | 世界规则(可复用) | 创意卡 + 剧本的世界观依据 |
| ③ | 📋 创意卡 | 创意 + 设定 | 故事卡(12 字段) | 剧本的**最高依据** |
| ④ | 📝 剧本 | 创意卡 | 按**段落**的剧本 | 分镜按段落做 |
| ⑤ | 🎬 分镜 | 剧本段落 + 导演/风格/演员 | 镜头(Shot) | 关键帧按镜头做 |
| ⑥ | 🖼 关键帧 | 镜头 + 角色/场景资产 | 帧设计(A/B/C)+ 帧图 | 视频的首/尾帧 |
| ⑦ | 🎞 视频 | 关键帧(选首尾)+ 引擎 | 视频片段(可多段) | 后期拼接 |
| ⑧ | 🎙 后期 | 采用的片段 | 成片(拼接/字幕/混音) | 交付 |
### 3.4 菜单重排
**现状 → 目标**
```
🎬 视频工厂 · 资源库(16 项) → 📦 原料(12 项)
摘掉:素材库(空壳)
保留:创意库 / 角色库 / 角色生成器 / 场景生成器 / 场景库 /
真实场地 / 道具库 / 产品库 / 服装库 / 音色库 / BGM / 音效
说明:环境音库、特效预设 暂藏在「更多」里(当前为空,用不上)
🎬 视频工厂 · 流程(12 项) → 🎬 生产(3 项)
★ 导演工作室(唯一主流程)
🔄 人物置换(独立工具,不属主流程)
🔀 流程画布(高级功能,折叠在「高级」下)
隐藏:6 个 ave-flow-*(老流程,跑不通)+ 新建流程(与流程画布重复)
🎬 视频工厂 · 能力中心(4 项) → 🧩 配置(4 项,不变)
🎬 视频工厂 · 运营(2+ 项) → 📊 交付(3 项)
生产记录 / 费用统计 / 产物中心
```
**净减**:`16+12+4+2 = 34 项` → `12+3+4+3 = 22 项`(-35%)
### 3.5 拆掉 112 KB 的巨物
`ave-director.js` 拆成 3 个视图(各管一件事):
```
views/studio-shots-config.js ← 导演系统 / 风格 / 演员表 / 场景 / 锚点(配置)
views/studio-shots-generate.js ← 段落列表(场记板)/ 生成 / 全片设定(动作)
views/studio-shots-table.js ← 分镜表(结果,可编辑)
```
用户在一个页面里**从上到下**走,但代码是三块,各自可独立改。
---
## 四、数据模型(单一真相源)
### 4.1 原则
> **一条片子的全部数据,只在一个文件里。** 媒体文件(图/视频/音频)仍各自落盘,但**路径登记在项目对象里**。
### 4.2 目标结构
**⚠️ 权衡说明(我先自我修正)**:
我最初想把 `shots`(含关键帧和视频的全部版本记录)塞进 `project.json`。
**但那样会让它膨胀到几百 KB** —— 20 个镜头 × 每镜 3 帧 × 每帧多版本,
每次改一个字都要重写整个文件,性能和并发都会出问题。
**所以改成:单一真相源 = 单一目录,而不是单一文件。**
```
projects/{pid}/ ← ★ 一个项目 = 一个目录(这就是"真相源")
│
├ project.json ← 轻量(< 20 KB):元数据 + 各阶段摘要
│ { "project_id","title","stage","created_at","updated_at",
│ "creative":{...}, // 💡 创意(选中的候选)
│ "setting":{...}, // 🌍 设定
│ "creative_card":{...}, // 📋 创意卡
│ "script":{...}, // 📝 剧本(段落)
│ "film_settings":{...}, // 📐 全片设定
│ "post":{...}, // 🎙 后期(时间线 + 成片路径)
│ "cast":[...], // 演员表
│ "history":[...] } // 变更记录
│
├ shots.json ← 重数据:分镜 + 关键帧 + 视频(可单独读写)
│ [ { "shot_id","paragraph_no","visual","duration","cast_slots", ...,
│ "keyframes": { "plan":{A/B/C 提示词},
│ "frames":[{seq,role,versions:[{v,file,adopted}]}] },
│ "videos": [ {segment, versions:[{v,file,adopted,has_voice}]} ] } ]
│
└ media/ ← 媒体文件(或继续用现目录,只登记路径)
keyframes/shot_01/v1.png
videos/shot_01/s1_v1.mp4
```
**为什么这样更好**:
- `project.json` 保持轻量 → 改设定/剧本不会牵动分镜
- `shots.json` 单独读写 → 生成一镜只改这个文件
- **但对外只有"一个项目目录"** → 备份/复制/迁移 = 复制目录
**取消**:
- `project.json` 里的 `keyframes` / `videos` / `assets` 三个死键 → 删除
- `storyboards/{pid}.json` **作为独立位置** → 并入 `projects/{pid}/shots.json`
(同级目录,不再是"另一个地方")
- 前端不再需要 `_enrich()` 跨目录临时合并
### 4.3 收益
- 「上游带入」天然正确(同一目录,不用跨目录捞)
- 「这个项目的全部产物」一个请求拿到
- 备份/迁移/复制项目 = 复制一个目录
- 删项目 = 删一个目录(现在要删 4 个地方)
---
## 五、迁移路径(现有内容怎么落位)
**原则:不删数据,只搬家。每一步都可回滚(先备份)。**
| 现有 | 去向 |
|---|---|
| `storyboards/{pid}.json` 的 shots | 搬进 `project.json` 的 `shots[]` |
| `keyframes/{pid}/{sid}/*.png` | 文件不动;路径登记到 `shots[i].keyframes` |
| `keyframe_videos/{pid}/{sid}/*.mp4` | 文件不动;路径登记到 `shots[i].videos` |
| `storyboard_paper` | 已是 `film_settings` 的来源(已迁移)→ 删键 |
| 6 个 `ave-flow-*` 视图 | **文件保留**(不删仓库文件),仅从菜单摘掉 |
| 4 个空壳视图 | 同上,从菜单摘掉(它们本来也不在菜单里) |
| 运行目录里的测试残留 | 移到 `_trash/` |
---
## 六、实施顺序(4 批,每批独立可验证)
**批 1 · 止血(零风险,先让界面不乱)**
- 菜单重排(摘掉老流程 + 空壳)
- 每个阶段页顶部加「职责卡」(我产出什么 / 下一步用它做什么)
- 合并 `🖼 关键帧设计` + `🖼 关键帧` 为一个页面
**批 2 · 数据归一(动后端,但只加不改)**
- `shots` 搬进 `project.json`(写入时双写,读取时优先新位置)
- 关键帧/视频的路径登记进 `shots[i]`
- 前端改读新位置(保留旧路径兜底)
**批 3 · 拆巨物**
- `ave-director.js` 拆 3 块(配置 / 生成 / 表)
- 每块独立测试
**批 4 · 清理**
- 删死键、废弃 `_enrich()`、测试残留移 `_trash/`
- 更新文档
---
## 七、待你确认的 5 件事
1. **要不要合并「关键帧设计」和「关键帧」?**(我倾向合并 —— 你已经混过一次)
2. **`ave-flow-*` 那 6 个老流程页,从菜单摘掉可以吗?**(文件保留)
3. **「环境音库」当前是空的 —— 先藏起来,还是保留在菜单里?**
4. **「流程画布」(`workflow`) 你还用吗?**(它是另一套编排方式,功能与工作室重叠)
5. **批 1 的菜单重排,要不要现在就做?**(零风险,改完立刻清爽)
---
## 附:本次盘查用到的命令(可复现)
```bash
# 视图规模
ls -la frontend/src/views/*.js | awk '{print $5, $NF}' | sort -rn
# 空壳识别
for f in views/*.js; do [ $(wc -c < $f) -lt 1000 ] && echo "$f"; done
# 后端端点分布
for f in routes/ave*.py; do echo "$(basename $f) $(grep -cE '^@router\.' $f)"; done
# 项目对象结构
python3 -c "import json;d=json.load(open('.../project.json'));print(list(d.keys()))"
```