feat(ffmpeg): add ffmpeg-core.wasm for video processing capabilities

This commit is contained in:
2025-11-14 19:49:37 +08:00
parent 18df10c7af
commit fb68b6a3cb
10 changed files with 1966 additions and 768 deletions

226
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,226 @@
# XSH Assistant 编码指南
## 项目概览
**xsh-assistant** 是一个基于 Nuxt 3 的 AI 驱动内容生成平台,专注于数字内容创作(微课视频、虚拟讲师、绿幕合成等)。核心特性:
- **前端框架**: Nuxt 3 + Vue 3 + TypeScript + Tailwind CSS + Radix Vue UI
- **状态管理**: Pinia带持久化
- **媒体处理**: FFmpeg WASM客户端视频处理、WebAV视频剪辑
- **API 集成**: 统一的 `useFetchWrapped` 包装器,所有请求都通过 `API_BASE` 代理(`https://service1.fenshenzhike.com/`
- **部署模式**: SPASSR=false
## 核心架构模式
### 1. Composables 设计Pinia + 自定义 Composables
**状态管理采用分层设计**
```
Pinia Stores持久化状态
├── useLoginState: 用户认证、token、个人资料
├── useHistory: AIGC 会话、聊天历史
└── useTourState: 新手引导状态
业务 Composables无状态或短生命周期
├── useFetchWrapped: API 请求包装(自动添加 token、user_id
├── useLLM: LLM API 调用Spark 模型集成)
├── useFFmpeg: FFmpeg WASM 单例管理
├── useVideoBackgroundCompositing: 视频合成(数字人+背景)
├── useVideoSubtitleEmbedding: 字幕嵌入
└── useDownload: 文件下载管理
```
**关键模式**
- **Pinia stores 必须是单一实例**,通过 `storeToRefs()` 获取响应式引用
- **API 请求必须通过 `useFetchWrapped`** 来自动处理认证头token/user_id
- **FFmpeg 采用单例模式**`useFFmpeg()` 返回全局加载的实例),避免重复初始化
### 2. API 请求模式
所有 API 请求使用统一的 `useFetchWrapped` 包装器:
```typescript
// 基础签名
useFetchWrapped<RequestType, ResponseType>(action: string, payload?: RequestType, options?: FetchOptions)
// 请求格式示例(来自 useHistory
useFetchWrapped<AuthedRequest, BaseResponse<resp.xxx>>(
'App.User_User.CheckSession', // action 作为查询参数 ?s=
{ token: loginState.token, user_id: loginState.user.id, ...payload },
{ method: 'POST' } // 默认 POST
)
```
**约定**
- **每个请求必须包含** `token``user_id`(来自 `useLoginState`
- **响应结构统一**: `BaseResponse<T>` 包含 `ret: number` 状态码和 `data: T` 数据
- **API_BASE 在 `nuxt.config.ts` 中定义**,所有请求都相对于此 URL
### 3. 媒体处理架构
#### FFmpeg 初始化流程
- **单例加载**: 首次调用 `useFFmpeg()` 时初始化,后续复用缓存的实例
- **WASM 资源加载**: 从 CDN`cdn.jsdelivr.net`)加载 FFmpeg core、wasm、worker
- **错误恢复**: 调用 `cleanupFFmpeg()` 清理资源并重置单例
#### 视频合成流程(核心用例)
```
输入: 透明通道视频 (WebM) + 背景图 (PNG/File)
1. 获取背景图尺寸
2. 计算等比缩放到 720P
3. 加载文件到 FFmpeg vFS
4. 执行 FFmpeg 滤镜链:
- 背景: scale → ${outputWidth}x${outputHeight}
- 视频: scale → ${outputWidth}x${outputHeight} (保留 alpha)
- overlay: 视频叠加到背景format=auto
5. 使用 VP9 编码(支持 alpha+ Opus 音频编码
6. 返回 Blob → 可直接上传或本地预览
```
### 4. UI 组件架构
#### 内置组件库(`components/uni/`
自定义包装组件,提供统一 API
- `UniButton`: 按钮 + loading 状态
- `UniInput`/`UniTextArea`: 表单输入
- `UniSelect`: 下拉选择
- `UniMessage`: 全局消息通知(通过 provide/inject
- `UniCopyable`: 可复制文本
**消息通知用法**
```typescript
const toast = useToast() // Radix Vue 的 Toast顶部通知
// 或从 provide 注入
const messageApi = inject('uni-message')
messageApi.success('操作成功')
messageApi.error('操作失败', 5000)
```
#### Radix Vue + Nuxt UI 集成
- 使用 Radix Vue for 基础组件button, dialog, select
- Nuxt UI 用于高级组件 + 主题管理
- **颜色方案**: primary='indigo', gray='neutral',详见 `app.config.ts`
### 5. 路由与页面结构
**目录映射**
```
pages/
├── generation.vue (导航枢纽)
└── aigc/
├── chat/index.vue (聊天页,支持多 LLM 模型)
├── draw/index.vue (绘图生成)
└── generation/
├── course.vue (微课生成)
├── green-screen.vue (绿幕视频)
├── avatar-models.vue (数字讲师)
├── materials.vue (片头片尾)
├── ppt-templates.vue (PPT 库)
└── admin/ (管理功能)
```
**导航约定**
- `/generation` → 功能导航页面
- `/aigc/chat` → 聊天/文本生成
- `/generation/course` → 视频生成工作流
- 所有生成功能都需要登录ModalAuthentication 处理)
## 开发工作流
### 启动项目
```bash
ni # 安装依赖
nr dev # 启动 http://localhost:3000
nr generate # 生产构建 (生成静态文件)
```
### 常见任务
**添加新的 API 端点**
1. 定义 Request 和 Response 类型(参考 `typings/llm.ts`
2. 在 composable 中使用 `useFetchWrapped` 调用
3. 自动包含 token/user_id来自 `useLoginState`
**添加新的视频处理功能**
1. 使用 `useFFmpeg()` 获取实例(自动初始化)
2. 写入文件到 vFS: `ffmpeg.writeFile()`
3. 执行命令:`ffmpeg.exec([...filterArgs])`
4. 清理临时文件:`ffmpeg.deleteFile()`
5. 使用 progress callback 通报处理进度
**添加新的 UI 组件**
1. 创建在 `components/` 下(自动注册)
2. 优先使用 Radix Vue + Nuxt UI已集成
3. 使用 Tailwind CSS utility classes + `@apply` 指令
4. 通过 `app.config.ts` 自定义 UI 主题
## 项目特定的约定
### 类型定义位置
- **LLM 相关**: `typings/llm.ts`ChatMessage, ChatSession, ModelTag, LLMModal
- **全局类型**: `typings/types.d.ts`BaseResponse, AuthedRequest, UserSchema
- **组件接口**: 组件目录下的 `index.d.ts`(例 `components/aigc/drawing/index.d.ts`
### 命名规范
- **Composables**: `use` 前缀(`useLoginState`, `useLLM`
- **Stores**: `use` + 功能名(`useHistory`, `useTourState`
- **组件**: PascalCase`ChatItem.vue`, `ModalAuthentication.vue`
- **工具函数**: camelCase放在 `composables/` 或各功能目录
### 响应式数据模式
- **Pinia store 返回值**: 必须通过 `storeToRefs()` 才能保持响应式
- **模板中的 ref**: 直接访问Vue 自动展开)
- **跨组件数据**: 优先使用 Pinia store带持久化
### 进度反馈与错误处理
- **长时间操作** (视频处理): 通过 callback 函数报告 progress0-100
- **错误处理**: 返回 Promise reject上层 catch 处理;可选通过 toast/message 提示
- **FFmpeg 错误**: 捕获 exitCode 非零,记录详细的 FFmpeg 输出
## 依赖与性能优化
### 关键依赖
- **@ffmpeg/ffmpeg@0.12.15**: WASM 视频处理(从 CDN 加载)
- **@webav/av-cliper**: 客户端视频剪辑库
- **markdown-it + highlight.js**: 内容渲染(支持代码高亮)
- **date-fns/dayjs**: 时间处理dayjs-nuxt 提供全局实例)
- **idb-keyval**: IndexedDB 简化操作(缓存大文件)
### Vite 优化设置
```typescript
// nuxt.config.ts 中排除以下包进行优化,避免 bundling WASM
optimizeDeps.exclude: ['@ffmpeg/ffmpeg', 'idb-keyval', '@webav/av-cliper', 'gsap', 'markdown-it']
```
### 构建排除项
Worker 格式设置为 ES Module避免 Vite 默认处理:
```typescript
vite.worker.format = 'es'
```
## 测试与调试
- **开发服务器日志**: 浏览器控制台查看 FFmpeg、API、业务日志
- **FFmpeg 调试**: `[FFmpeg]` 前缀的日志输出包含加载进度、命令执行信息
- **状态调试**: Pinia DevTools启用 `devtools: true`
- **样式调试**: Tailwind 配置在 `tailwind.config.ts`,按需自定义
## 常见陷阱与解决方案
| 问题 | 原因 | 解决方案 |
|------|------|--------|
| API 请求 401 | 缺少 token 或已过期 | 检查 `useLoginState().token`,通过 ModalAuthentication 重新登录 |
| FFmpeg 加载超时 | CDN 资源加载慢 | 检查网络,可切换到本地 `/public/assets/ffmpeg` |
| 视频输出无声音 | 滤镜链未映射音频 | 确保 FFmpeg 命令包含 `-map '1:a?'` 映射音频轨道 |
| 组件未注册 | 文件位置错误 | 确保在 `components/` 目录下,子目录自动扁平化注册 |
| Pinia 状态未持久化 | 未配置 persist 选项 | 在 store 返回语句后添加 persist 配置(参考 `useLoginState` |
## 资源链接
- [Nuxt 3 文档](https://nuxt.com/docs)
- [Pinia 文档](https://pinia.vuejs.org)
- [FFmpeg.wasm 文档](https://ffmpegwasm.netlify.app/)
- [Radix Vue](https://www.radix-vue.com/)
- [Nuxt UI 组件库](https://ui.nuxt.com/):可使用 nuxt-ui MCP 工具