Files
meijiaka-zy/docs/material-matching-impl-plan-v3.md
T
小鱼开发 4e06f4abe2 feat: 空镜素材配置后端化,视频生成流程重构
- 后端: 空镜素材迁移到 config/materials.json,duration从文件名_{N}s_自动解析
- 后端: 新增 POST /api/v1/materials/match 接口,后端做关键词匹配
- 前端: VideoGeneration 空镜匹配改为调用后端接口
- 前端: 人物出镜素材改为本地文件选择器直接选取,不走素材库
- 前端: 视频生成流程简化,移除Vidu对口型和七牛云上传
- Rust: 视频合成支持从随机起始时间截取人物素材片段
- Rust: 修复ffprobe参数错误(添加-show_entries format=duration)
2026-04-22 18:49:20 +08:00

11 KiB
Raw Blame History

视频生成实现方案 v3(Vidu 对口型版)

状态:待审阅


一、流程概述

Step 页面 产出
1 ScriptCreation 分镜列表(含 scene + duration
2 VoiceDubbing 配音音频(已存七牛云)
3 VideoGeneration 最终视频(Vidu 对口型后)
4 SubtitleBurning 字幕压制
5 CoverDesign 封面
6 VideoComposite 最终成品(如需要额外合成)

二、Step 3 完整流程

┌─────────────────────────────────────────────────────────────┐
│ Step 3: 视频生成                                             │
│                                                             │
│ 1. 选择形象素材(本地 avatar_materials                     │
│    └─ 上传 / 选择已有素材                                    │
│                                                             │
│ 2. 匹配空镜素材(七牛云)                                    │
│    └─ 根据 scene 关键词匹配                                  │
│    └─ 检查时长 >= 分镜 duration                              │
│                                                             │
│ 3. FFmpeg 裁剪                                               │
│    └─ segment:从形象素材中截取 duration 秒                  │
│    └─ empty_shot:从空镜素材中截取 duration 秒               │
│    └─ 输出:clip_001.mp4, clip_002.mp4, ...                  │
│                                                             │
│ 4. FFmpeg 拼接(移除音频)                                   │
│    └─ concat 所有 clip → raw_video.mp4(无声)               │
│                                                             │
│ 5. 上传临时视频                                              │
│    └─ raw_video.mp4 → 七牛云临时 URL                        │
│                                                             │
│ 6. Vidu 对口型                                               │
│    └─ POST /ent/v2/lip-sync                                 │
│       { video_url: 临时URL, audio_url: Step2音频URL }       │
│    └─ 返回 task_id                                          │
│                                                             │
│ 7. 轮询等待                                                  │
│    └─ GET /ent/v2/tasks/{task_id}/creations                 │
│    └─ state = "success" 时获取 video_url                    │
│                                                             │
│ 8. 下载最终视频                                              │
│    └─ 下载到本地 products/{project_id}/                     │
│                                                             │
│ 9. 上传永久保存                                              │
│    └─ 上传七牛云 videos/{project_id}/final.mp4              │
│                                                             │
│ 10. 更新项目状态                                             │
│     └─ segment.videoPath = 本地路径                         │
│     └─ segment.videoUrl = 七牛云永久 URL                    │
└─────────────────────────────────────────────────────────────┘

三、素材体系

3.1 人物出镜素材(本地)

存储位置~/Documents/Meijiaka-zj/avatar_materials/

索引~/Documents/Meijiaka-zj/avatar_materials_index.json

{
  "materials": [
    {
      "id": "avt_001",
      "name": "男主播正面",
      "filename": "host_male.mp4",
      "duration": 15.2,
      "uploadedAt": "2026-04-22T10:00:00Z"
    }
  ]
}

时长获取:上传后用 ffprobe 提取。

3.2 空镜素材(七牛云)

URL 格式https://media.liche.cn/meijiaka-zj/material/{slug}/{slug}_{序号}_{时长}s.mp4

映射表tauri-app/src/constants/materialUrls.ts

export interface MaterialInfo {
  url: string;
  duration: number;
}

export const MATERIAL_URLS: Record<string, MaterialInfo[]> = {
  "ceiling": [
    { url: "https://media.liche.cn/meijiaka-zj/material/ceiling/ceiling_001_5s.mp4", duration: 5 },
  ],
  "plumbing": [
    { url: "https://media.liche.cn/meijiaka-zj/material/plumbing/plumbing_153_4s.mp4", duration: 4 },
  ],
  "paint": [
    { url: "https://media.liche.cn/meijiaka-zj/material/paint/paint_001_5s.mp4", duration: 5 },
  ],
  "final": [
    { url: "https://media.liche.cn/meijiaka-zj/material/final/final_001_7s.mp4", duration: 7 },
  ],
};

export const KEYWORD_MAP: Record<string, string> = {
  "吊顶": "ceiling",
  "天花": "ceiling",
  "水电": "plumbing",
  "水管": "plumbing",
  "油漆": "paint",
  "涂料": "paint",
  "刷漆": "paint",
  "完工": "final",
  "竣工": "final",
  "交付": "final",
};

四、匹配规则(含时长检查)

4.1 人物出镜素材选择

用户手动选择一个本地素材作为"当前形象"。

系统检查:

let maxSegmentDuration = max(所有 segment 分镜的 duration);
if 形象素材.duration < maxSegmentDuration:
    警告:形象素材时长不足

4.2 空镜素材匹配

function matchMaterial(scene: string, requiredDuration: number): MaterialInfo | null {
  for (const [keyword, slug] of Object.entries(KEYWORD_MAP)) {
    if (scene.includes(keyword)) {
      const candidates = MATERIAL_URLS[slug]?.filter(m => m.duration >= requiredDuration);
      if (candidates && candidates.length > 0) {
        return candidates[Math.floor(Math.random() * candidates.length)];
      }
    }
  }
  return null;
}

五、裁剪与拼接

5.1 裁剪

每个分镜从素材中截取 duration 秒:

场景 素材时长 截取方式
等于 5s 从头截取 0-5s
大于 8s 随机从 [0, 3s] 开始截取
ffmpeg -ss {start} -t {duration} -i {input} -c:v libx264 -preset fast -an -y {output}

-an 确保输出无音频。

5.2 拼接(无声)

# clips.txt
file 'clip_001.mp4'
file 'clip_002.mp4'
...

# 拼接,无音频
ffmpeg -f concat -safe 0 -i clips.txt -c copy -an raw_video.mp4

5.3 上传临时视频

使用七牛云 SDK 上传 raw_video.mp4,获取临时访问 URL。

5.4 Vidu 对口型

POST https://api.vidu.cn/ent/v2/lip-sync
{
  "video_url": "https://media.liche.cn/tmp/raw_video_xxx.mp4",
  "audio_url": "https://media.liche.cn/audios/project_xxx.mp3"
}

响应{ task_id: "xxx", state: "created" }

轮询

GET https://api.vidu.cn/ent/v2/tasks/{task_id}/creations

成功响应

{
  "state": "success",
  "creations": [{ "url": "https://.../final.mp4" }]
}

5.5 下载与保存

  1. 下载 Vidu 返回的 url 到本地
  2. 本地路径:~/Documents/Meijiaka-zj/products/{project_id}/final.mp4
  3. 上传七牛云:videos/{project_id}/final.mp4
  4. 更新项目数据

六、Step 3 页面交互

┌─────────────────────────────────────────┐
│ Step 3: 视频生成                         │
│                                         │
│ ┌─ 人物形象 ──────────────────────────┐ │
│ │ [上传] 或 选择已有                   │ │
│ │ ● 男主播正面 (15.2s)               │ │
│ └──────────────────────────────────────┘ │
│                                         │
│ ┌─ 空镜匹配 ──────────────────────────┐ │
│ │ 分镜2:瓦工贴墙砖 (5s)              │ │
│ │   ✓ ceiling_001 (5s)               │ │
│ │                                     │ │
│ │ 分镜4:防水施工 (4s)                │ │
│ │   ✗ 素材不足 (plumbing 只有 4s?)   │ │
│ │   [手动选择本地文件]                │ │
│ └──────────────────────────────────────┘ │
│                                         │
│ [生成视频]                               │
│                                         │
│ 进度:                                   │
│   裁剪中...                              │
│   拼接中...                              │
│   上传中...                              │
│   Vidu 处理中... (task_id: xxx)         │
│   下载中...                              │
│   完成 ✓                                 │
└─────────────────────────────────────────┘

七、实施清单

Phase 1:人物出镜素材库

# 文件 内容
1 src-tauri/src/commands/avatar_material.rs IPC:上传、列表、删除
2 src-tauri/src/storage/avatar_material.rs 索引读写、ffprobe 提取时长
3 src/api/modules/avatarMaterial.ts 前端 API 封装
4 VideoGeneration.tsx 形象上传 + 选择组件

Phase 2:空镜素材匹配

# 文件 内容
5 src/constants/materialUrls.ts 映射表(按实际 URL 格式)
6 VideoGeneration.tsx 自动匹配逻辑

Phase 3Vidu 合成流水线

# 文件 内容
7 src-tauri/src/ffmpeg_cmd.rs 新增:批量裁剪 + 无声拼接
8 python-api/app/services/vidu_service.py Vidu API 封装(对口型 + 轮询)
9 python-api/app/api/v1/vidu.py 后端路由:提交对口型、查询状态
10 src-tauri/src/commands/video_generate.rs IPC:生成视频主流程
11 VideoGeneration.tsx [生成视频] 按钮 + 进度显示

八、确认事项

  • 人物出镜素材本地管理(上传/选择/时长检查)
  • 空镜素材七牛云映射(关键词 → slug → URL)
  • FFmpeg 裁剪 + 无声拼接
  • 拼接后视频上传七牛云临时 URL
  • Vidu 对口型 API(提交 + 轮询)
  • 最终视频下载到本地 products/ + 上传七牛云永久保存
  • Step 2 音频已在七牛云,直接取 URL

确认后我开始写。