From 63056170cc122336ddee96ca9734a7d66ca9ed1f Mon Sep 17 00:00:00 2001 From: cc <94575594@qq.com> Date: Thu, 16 Apr 2026 14:45:38 +0800 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20=E7=AE=80=E5=8C=96=E5=92=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=83=A8=E7=BD=B2=E6=96=87=E6=A1=A3=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E4=B8=8E=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 精简环境准备部分,去除后端和 FFmpeg 安装说明,聚焦前端依赖安装 - 调整关键配置说明,删除后端相关代理和跨域配置,保留前端代理重点 - 优化部署方案,移除复杂的后端服务部署和代理设置,仅保留纯前端部署示例 - 更新部署验证清单,聚焦前端功能和路由验证 - 精简常见问题,去除后端服务相关故障信息 - 删除后端服务、Docker、性能监控等章节,文档更聚焦轻量部署 - 更新页面路由清单,保持核心页面及功能介绍 --- DEPLOY.md | 462 +------- server/README.md | 73 -- server/index.js | 33 - server/package-lock.json | 1020 ---------------- server/package.json | 16 - server/routes/video.js | 284 ----- src/config/index.js | 72 -- src/router/index.js | 6 - src/utils/videoComposer.js | 139 --- src/views/HomePage.vue | 8 - src/views/QuestionExplanation.vue | 1847 ----------------------------- vite.config.js | 5 - 12 files changed, 31 insertions(+), 3934 deletions(-) delete mode 100644 server/README.md delete mode 100644 server/index.js delete mode 100644 server/package-lock.json delete mode 100644 server/package.json delete mode 100644 server/routes/video.js delete mode 100644 src/utils/videoComposer.js delete mode 100644 src/views/QuestionExplanation.vue diff --git a/DEPLOY.md b/DEPLOY.md index d064fa5..559fbf6 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -3,12 +3,8 @@ ## 项目概述 - **项目名称**:AI 英语学习辅助平台 -- **技术栈**: - - 前端:Vue 3 + Vite + Vue Router(History 模式) - - 后端:Express.js + FFmpeg(视频合成服务) -- **项目结构**: - - 前端:纯静态文件(HTML / CSS / JS),可部署到任意静态托管服务 - - 后端:Node.js 服务,需要 FFmpeg 环境,提供视频合成 API +- **技术栈**:Vue 3 + Vite + Vue Router(History 模式) +- **项目结构**:纯前端静态文件(HTML / CSS / JS),可部署到任意静态托管服务 --- @@ -18,61 +14,21 @@ | 工具 | 版本要求 | 用途 | |------|----------|------| -| Node.js | >= 18.x(推荐 20.x LTS) | 前端构建 & 后端服务运行 | +| Node.js | >= 18.x(推荐 20.x LTS) | 前端构建 | | npm | >= 9.x | 包管理器 | -| FFmpeg | 最新稳定版 | 后端视频合成(仅后端服务需要) | ```bash # 检查 Node.js 和 npm 版本 node -v npm -v - -# 检查 FFmpeg 是否已安装(后端服务需要) -ffmpeg -version ``` -### 1.2 安装 FFmpeg(后端服务必需) - -#### Windows - -1. 下载 FFmpeg: https://www.gyan.dev/ffmpeg/builds/ -2. 解压到 `C:\ffmpeg` -3. 添加 `C:\ffmpeg\bin` 到系统环境变量 PATH -4. 验证安装: - ```bash - ffmpeg -version - ``` - -#### macOS - -```bash -# 使用 Homebrew 安装 -brew install ffmpeg - -# 验证安装 -ffmpeg -version -``` - -#### Linux (Ubuntu/Debian) - -```bash -# 安装 FFmpeg -sudo apt update && sudo apt install ffmpeg - -# 验证安装 -ffmpeg -version -``` - -### 1.3 安装依赖 +### 1.2 安装依赖 ```bash # 安装前端依赖 cd AI_Demo npm install - -# 安装后端依赖 -cd server -npm install ``` --- @@ -96,7 +52,7 @@ npm install ### 2.2 跨域响应头要求(重要) -项目使用了 `@ffmpeg/ffmpeg`(WebAssembly),需要服务器返回以下响应头,否则视频讲解功能将无法运行: +项目使用了 `@ffmpeg/ffmpeg`(WebAssembly),需要服务器返回以下响应头: ``` Cross-Origin-Opener-Policy: same-origin @@ -111,14 +67,11 @@ Cross-Origin-Embedder-Policy: require-corp | 前端请求路径前缀 | 代理目标 | 用途 | 备注 | |-----------------|----------|------|------| -| `/api/video` | 后端服务(默认 http://localhost:3001) | 视频合成 | **无需跨域代理**,直接访问后端服务 | | `/tts-api` | `https://openspeech.bytedance.com` | 豆包 TTS 语音合成 | 需要反向代理 | | `/ark-api` | `https://ark.cn-beijing.volces.com` | 火山引擎 Ark 大模型 | 需要反向代理 | | `/dashscope-api` | `https://dashscope.aliyuncs.com` | 阿里云百炼 | 需要反向代理 | | `/asr-ws` | `wss://openspeech.bytedance.com` | 豆包 ASR WebSocket | 需要反向代理+注入鉴权 Header | -> **后端服务说明**:`/api/video` 接口由本项目的后端服务(`server/`)提供,不需要跨域代理,但需要确保后端服务正常运行。 - > **ASR WebSocket 特殊说明**:`/asr-ws` 代理需要在代理层注入以下鉴权 Header(浏览器原生 WebSocket 不支持自定义 Header): > - `X-Api-App-Key` > - `X-Api-Access-Key` @@ -152,22 +105,6 @@ dist/ npm run preview ``` -### 3.2 后端服务准备 - -后端服务无需构建,直接使用源码运行即可。确保: - -1. 已安装 FFmpeg(见 1.2 节) -2. 已安装依赖: - ```bash - cd server - npm install - ``` -3. 测试服务是否正常: - ```bash - npm start - ``` - 访问 http://localhost:3001/health 应返回 `{"status":"ok"}` - --- ## 四、部署方案 @@ -194,7 +131,7 @@ server { root /var/www/ai-demo; index index.html; - # 跨域响应头(FFmpeg WASM 必需) + # 跨域响应头 add_header Cross-Origin-Opener-Policy "same-origin" always; add_header Cross-Origin-Embedder-Policy "require-corp" always; @@ -203,26 +140,6 @@ server { try_files $uri $uri/ /index.html; } - # 后端视频合成服务代理 - location /api/video/ { - proxy_pass http://localhost:3001/api/video/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # 增大请求体大小限制(视频合成可能需要) - client_max_body_size 100m; - - # 增加超时时间(视频合成耗时较长) - proxy_connect_timeout 300; - proxy_send_timeout 300; - proxy_read_timeout 300; - } - # 代理:豆包 TTS location /tts-api/ { proxy_pass https://openspeech.bytedance.com/; @@ -282,34 +199,7 @@ nginx -t systemctl reload nginx ``` -#### 4.A.4 启动后端服务 - -后端服务需要使用进程管理器(如 PM2)保持持续运行: - -```bash -# 安装 PM2(如果未安装) -npm install -g pm2 - -# 进入后端目录 -cd /path/to/AI_Demo/server - -# 启动后端服务 -pm2 start index.js --name ai-demo-server - -# 查看服务状态 -pm2 status - -# 查看日志 -pm2 logs ai-demo-server - -# 设置开机自启 -pm2 startup -pm2 save -``` - -后端服务将在 `http://localhost:3001` 启动,Nginx 会将 `/api/video` 请求代理到该端口。 - -#### 4.A.5 配置 HTTPS(推荐) +#### 4.A.4 配置 HTTPS(推荐) ```bash # 使用 Certbot 申请免费 SSL 证书 @@ -319,32 +209,9 @@ certbot --nginx -d your-domain.com --- -### 方案 B:Node.js + Express 部署(前后端分离) +### 方案 B:Node.js + Express 部署 -适用于需要在 Node.js 环境中自定义响应头的场景,前后端独立部署。 - -#### 4.B.1 后端服务部署 - -后端服务已在 `server/` 目录中,需要独立运行: - -```bash -# 进入后端目录 -cd server - -# 安装依赖 -npm install - -# 使用 PM2 启动服务 -pm2 start index.js --name ai-demo-backend - -# 设置开机自启 -pm2 startup -pm2 save -``` - -后端服务将在 `http://localhost:3001` 启动。 - -#### 4.B.2 前端静态服务器 +适用于需要在 Node.js 环境中自定义响应头的场景。 创建前端静态服务器文件 `frontend-server.js`(项目根目录): @@ -359,20 +226,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) const app = express() const PORT = process.env.PORT || 3000 -// 全局注入 COOP/COEP 响应头(FFmpeg WASM 必需) -app.use((req, res, next) => { - res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') - res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') - next() -}) - -// 后端视频合成服务代理 -app.use('/api/video', createProxyMiddleware({ - target: 'http://localhost:3001', - changeOrigin: true, - timeout: 300000, // 5分钟超时 -})) - // 第三方 API 代理 app.use('/tts-api', createProxyMiddleware({ target: 'https://openspeech.bytedance.com', @@ -417,7 +270,6 @@ app.get('*', (req, res) => { app.listen(PORT, () => { console.log(`Frontend server running at http://localhost:${PORT}`) - console.log(`Backend API: http://localhost:3001`) }) ``` @@ -441,16 +293,14 @@ pm2 status pm2 save ``` -现在有两个服务运行: -- **前端服务**:`http://localhost:3000`(提供静态文件和代理) -- **后端服务**:`http://localhost:3001`(视频合成 API) +前端服务运行在 `http://localhost:3000`(提供静态文件和代理) --- ### 方案 C:EdgeOne Pages / Vercel / Netlify(纯静态托管) > **注意**:此类平台**不支持服务端代理**,`/tts-api`、`/ark-api`、`/dashscope-api`、`/asr-ws` 等接口将因跨域或缺少鉴权 Header 而失败。 -> 建议仅用于演示或配合独立后端服务使用。 +> 建议仅用于演示。 #### 部署步骤(以 Vercel 为例) @@ -478,16 +328,7 @@ pm2 save ## 五、部署后验证清单 -### 5.1 后端服务验证 - -| 验证项 | 检查方法 | 预期结果 | -|--------|----------|----------| -| 后端服务运行 | `pm2 status` 或 `ps aux \| grep node` | 显示 ai-demo-server 进程 | -| 健康检查接口 | `curl http://localhost:3001/health` | 返回 `{"status":"ok"}` | -| FFmpeg 可用 | `curl http://localhost:3001/api/video/health` | 返回 `{"status":"ok","message":"视频合成服务正常运行"}` | -| 后端日志无错误 | `pm2 logs ai-demo-server` | 无报错信息 | - -### 5.2 前端服务验证 +### 5.1 部署验证 | 验证项 | 检查方法 | |--------|----------| @@ -500,18 +341,11 @@ pm2 save | 试题分析 | 进入试题分析页,上传图片或输入题目 | | 单词拼写练习 | 进入单词拼写页,测试拼写检测功能 | -### 5.3 视频合成功能验证 - -| 验证项 | 检查方法 | -|--------|----------| -| 前端可访问后端 | 浏览器 Network 标签查看 `/api/video/health` 请求 | -| 视频合成接口 | 在试题分析页生成视频讲解,检查是否能正常下载 | - --- ## 六、常见问题排查 -### Q1:视频讲解页面报错 `SharedArrayBuffer is not defined` +### Q1:页面报错 `SharedArrayBuffer is not defined` **原因**:服务器未返回 `Cross-Origin-Opener-Policy: same-origin` 和 `Cross-Origin-Embedder-Policy: require-corp` 响应头。 **解决**:参考第二节 2.2,在 Nginx 或 Node.js 服务器中添加对应响应头。 @@ -531,31 +365,9 @@ pm2 save **原因**:代理未注入豆包 ASR 鉴权 Header,或 WebSocket 升级配置缺失。 **解决**:确认 Nginx 配置中包含 `proxy_set_header Upgrade $http_upgrade;` 及三个 `X-Api-*` Header。 -### Q5:视频合成失败或接口 504 超时 +### Q5:构建时内存不足 -**原因**:视频合成耗时较长,默认代理超时时间不足,或后端服务未启动。 -**解决**: -1. 检查后端服务是否运行:`pm2 status` 或 `curl http://localhost:3001/health` -2. 检查 FFmpeg 是否安装:`ffmpeg -version` -3. 增加 Nginx 超时时间(已在配置中设置为 300 秒) -4. 查看后端日志:`pm2 logs ai-demo-server` - -### Q6:后端服务启动失败:`FFmpeg not found` - -**原因**:系统未安装 FFmpeg 或未添加到环境变量。 -**解决**:参考第一节 1.2,安装 FFmpeg 并配置环境变量。 - -### Q7:视频合成返回 `Internal server error` - -**原因**:可能是 FFmpeg 处理错误、临时目录权限问题或资源下载失败。 -**解决**: -1. 查看后端详细日志:`pm2 logs ai-demo-server` -2. 检查 `server/temp/` 目录权限:`ls -la server/temp` -3. 测试后端健康检查:`curl http://localhost:3001/api/video/health` - -### Q8:构建时内存不足 - -**原因**:项目包含 FFmpeg WASM,构建产物较大。 +**原因**:项目依赖较多,构建产物较大。 **解决**: ```bash @@ -579,7 +391,6 @@ NODE_OPTIONS=--max-old-space-size=4096 npm run build | `/question-generator` | 题目生成 | 自动生成练习题 | | `/question-variant` | 题目变体 | 生成相似题目变体 | | `/audio-to-text` | 语音转文字 | 音频转文本功能 | -| `/question-explanation` | 视频讲解 | 视频合成讲解(使用后端服务)| --- @@ -598,30 +409,11 @@ NODE_OPTIONS=--max-old-space-size=4096 npm run build | marked | ^17.0.5 | | pako | ^2.1.0 | -### 后端依赖(server/package.json) - -| 依赖包 | 版本 | 用途 | -|--------|------|------| -| express | ^4.21.0 | Web 服务器框架 | -| cors | ^2.8.5 | 跨域中间件 | -| fluent-ffmpeg | ^2.1.3 | FFmpeg Node.js 封装 | -| axios | ^1.7.0 | HTTP 客户端 | -| uuid | ^10.0.0 | UUID 生成 | - ---- - ## 九、目录结构参考 ``` AI_Demo/ ├── dist/ # 前端构建产物(部署此目录) -├── server/ # 后端服务 -│ ├── routes/ # 路由模块 -│ │ └── video.js # 视频合成 API -│ ├── temp/ # 临时文件目录(自动创建) -│ ├── index.js # 后端服务入口 -│ ├── package.json # 后端依赖配置 -│ └── README.md # 后端服务文档 ├── src/ # 前端源码 │ ├── config/index.js # API 密钥配置(部署前确认) │ ├── router/index.js # 路由定义 @@ -635,8 +427,7 @@ AI_Demo/ │ │ ├── ProblemSolving.vue # 解题指导 │ │ ├── QuestionGenerator.vue # 题目生成 │ │ ├── QuestionVariant.vue # 题目变体 -│ │ ├── AudioToText.vue # 语音转文字 -│ │ └── QuestionExplanation.vue # 视频讲解 +│ │ └── AudioToText.vue # 语音转文字 │ ├── components/ # 公共组件 │ ├── assets/ # 静态资源 │ └── MainLayout.vue # 布局组件 @@ -647,217 +438,42 @@ AI_Demo/ --- -## 十、后端服务详细说明 +## 十、Docker 部署(可选) -### 10.1 服务架构 - -后端服务基于 Express.js 构建,主要提供视频合成功能: - -- **技术栈**:Express.js + Fluent-FFmpeg -- **默认端口**:3001 -- **主要功能**:接收图片和音频 URL,合成 MP4 视频文件 - -### 10.2 API 接口 - -#### POST `/api/video/compose` - -合成视频接口,将多组图片和音频合成为视频。 - -**请求体示例**: -```json -{ - "slides": [ - { - "imageUrl": "https://example.com/image1.png", - "audioUrl": "https://example.com/audio1.mp3" - }, - { - "imageUrl": "https://example.com/image2.png", - "audioUrl": "https://example.com/audio2.mp3" - } - ], - "width": 1920, - "height": 1080 -} -``` - -**参数说明**: -- `slides`(必需):幻灯片数组,每个元素包含: - - `imageUrl`:图片 URL(支持 HTTP/HTTPS URL 或 base64 data URI) - - `audioUrl`:音频 URL(MP3 格式) -- `width`(可选):视频宽度,默认 1920 -- `height`(可选):视频高度,默认 1080 - -**响应**: -- Content-Type: `video/mp4` -- 返回合成后的 MP4 视频文件 - -#### GET `/api/video/health` - -健康检查接口,检查 FFmpeg 服务状态。 - -**响应示例**: -```json -{ - "status": "ok", - "message": "视频合成服务正常运行", - "formats": 300 -} -``` - -#### GET `/health` - -服务健康检查接口。 - -**响应示例**: -```json -{ - "status": "ok", - "timestamp": "2024-01-01T12:00:00.000Z" -} -``` - -### 10.3 环境变量配置 - -后端服务支持以下环境变量: - -| 环境变量 | 默认值 | 说明 | -|---------|--------|------| -| `PORT` | 3001 | 服务监听端口 | - -使用示例: -```bash -PORT=3002 pm2 start index.js --name ai-demo-server -``` - -### 10.4 临时文件管理 - -- 后端服务在 `server/temp/` 目录下存储临时文件 -- 每个视频合成任务会创建独立的子目录(使用 UUID 命名) -- 任务完成后,临时文件会自动清理 -- 如遇异常中断,可手动清理 `temp/` 目录 - -### 10.5 性能与资源要求 - -**建议配置**: -- **CPU**:2 核以上(视频合成需要较多 CPU 资源) -- **内存**:2GB 以上 -- **磁盘**:10GB 以上可用空间(临时文件存储) - -**并发处理**: -- 单个视频合成任务处理时间:约 10-60 秒(取决于片段数量和时长) -- 建议使用队列机制处理高并发请求(当前版本为同步处理) - -### 10.6 日志查看 - -使用 PM2 查看后端服务日志: -```bash -# 查看实时日志 -pm2 logs ai-demo-server - -# 查看最近 100 行日志 -pm2 logs ai-demo-server --lines 100 - -# 清空日志 -pm2 flush ai-demo-server -``` - -日志包含: -- 服务启动信息 -- FFmpeg 命令执行日志 -- 视频合成进度 -- 错误信息 - -### 10.7 监控与告警 - -建议监控以下指标: -- 服务进程状态(PM2 监控) -- 端口 3001 响应状态(健康检查) -- 磁盘空间(`temp/` 目录) -- CPU 和内存使用率 - ---- - -## 十一、Docker 部署(可选) - -### 11.1 后端服务 Dockerfile - -已提供 Dockerfile 位于 `Dockerfiles/backend.Dockerfile`(如需创建): +### 10.1 构建镜像 ```dockerfile FROM node:20-alpine -# 安装 FFmpeg -RUN apk add --no-cache ffmpeg - WORKDIR /app # 复制依赖文件 -COPY server/package*.json ./ +COPY package*.json ./ RUN npm ci --only=production # 复制源码 -COPY server/ ./ +COPY . ./ -# 创建临时目录 -RUN mkdir -p temp +# 构建 +RUN npm run build -EXPOSE 3001 +EXPOSE 80 -CMD ["node", "index.js"] -``` - -### 11.2 Docker Compose 部署 - -创建 `docker-compose.yml`: - -```yaml -version: '3.8' - -services: - backend: - build: - context: . - dockerfile: Dockerfiles/backend.Dockerfile - ports: - - "3001:3001" - volumes: - - ./server/temp:/app/temp - restart: unless-stopped - environment: - - PORT=3001 - - frontend: - image: nginx:alpine - ports: - - "80:80" - volumes: - - ./dist:/usr/share/nginx/html - - ./nginx.conf:/etc/nginx/conf.d/default.conf - depends_on: - - backend - restart: unless-stopped -``` - -启动: -```bash -docker-compose up -d +CMD ["nginx", "-g", "daemon off;"] ``` --- -## 十二、安全建议 +## 十一、安全建议 1. **HTTPS 强制**:生产环境务必启用 HTTPS 2. **API 密钥保护**:不要将 API 密钥提交到代码仓库 -3. **文件上传限制**:限制视频合成的图片和音频大小 -4. **频率限制**:对 `/api/video/compose` 接口添加速率限制 -5. **临时文件清理**:定期检查并清理异常终止的临时文件 -6. **日志审计**:记录所有视频合成请求的来源和处理结果 +3. **输入验证**:对用户输入进行验证和过滤 +4. **日志审计**:记录关键操作和错误信息 --- -## 十三、更新与维护 +## 十二、更新与维护 ### 更新前端 @@ -868,24 +484,8 @@ npm run build # 重新部署 dist/ 目录 ``` -### 更新后端 - -```bash -cd server -git pull -npm install -pm2 restart ai-demo-server -``` - -### 查看服务状态 - -```bash -pm2 status -pm2 logs -``` - --- -**文档版本**:v2.0 -**更新日期**:2024-01-01 -**更新内容**:新增后端视频合成服务部署说明 +**文档版本**:v2.1 +**更新日期**:2025-04-16 +**更新内容**:移除视频讲解功能及相关后端服务 diff --git a/server/README.md b/server/README.md deleted file mode 100644 index ed3ca38..0000000 --- a/server/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# AI Demo - 后端服务 - -视频合成后端服务,使用 FFmpeg 处理视频合成任务。 - -## 前置要求 - -- Node.js 18+ -- **FFmpeg** - 需要安装到系统环境变量中 - -### 安装 FFmpeg - -#### Windows -1. 下载 FFmpeg: https://www.gyan.dev/ffmpeg/builds/ -2. 解压到 `C:\ffmpeg` -3. 添加 `C:\ffmpeg\bin` 到系统环境变量 PATH - -#### macOS -```bash -brew install ffmpeg -``` - -#### Linux -```bash -sudo apt update && sudo apt install ffmpeg -``` - -## 安装依赖 - -```bash -cd server -npm install -``` - -## 启动服务 - -```bash -npm run dev # 开发模式(自动重启) -npm start # 生产模式 -``` - -服务将在 http://localhost:3001 启动。 - -## API 接口 - -### POST /api/video/compose - -合成视频 - -**请求体:** -```json -{ - "slides": [ - { - "imageUrl": "https://example.com/image1.png", - "audioUrl": "https://example.com/audio1.mp3" - } - ], - "width": 1920, - "height": 1080 -} -``` - -**响应:** `video/mp4` 格式的视频文件 - -### GET /api/video/health - -检查视频服务状态 - -## 前端配置 - -前端 Vite 开发服务器会自动代理 `/api` 请求到后端服务。 - -确保后端服务运行在 `http://localhost:3001`。 diff --git a/server/index.js b/server/index.js deleted file mode 100644 index dbca74c..0000000 --- a/server/index.js +++ /dev/null @@ -1,33 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import videoRoutes from './routes/video.js'; - -const app = express(); -const PORT = process.env.PORT || 3001; - -// 中间件 -app.use(cors()); -app.use(express.json({ limit: '50mb' })); -app.use(express.urlencoded({ extended: true, limit: '50mb' })); - -// 路由 -app.use('/api/video', videoRoutes); - -// 健康检查 -app.get('/health', (req, res) => { - res.json({ status: 'ok', timestamp: new Date().toISOString() }); -}); - -// 错误处理 -app.use((err, req, res, next) => { - console.error('Server error:', err); - res.status(500).json({ - error: 'Internal server error', - message: err.message - }); -}); - -app.listen(PORT, () => { - console.log(`🚀 Server running on http://localhost:${PORT}`); - console.log(`📁 Video API: http://localhost:${PORT}/api/video`); -}); diff --git a/server/package-lock.json b/server/package-lock.json deleted file mode 100644 index 9a98b4b..0000000 --- a/server/package-lock.json +++ /dev/null @@ -1,1020 +0,0 @@ -{ - "name": "ai-demo-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "ai-demo-server", - "version": "1.0.0", - "dependencies": { - "axios": "^1.7.0", - "cors": "^2.8.5", - "express": "^4.21.0", - "fluent-ffmpeg": "^2.1.3", - "uuid": "^10.0.0" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async": { - "version": "0.2.10", - "resolved": "https://registry.npmmirror.com/async/-/async-0.2.10.tgz", - "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.14.0", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.14.0.tgz", - "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmmirror.com/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fluent-ffmpeg": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", - "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "dependencies": { - "async": "^0.2.9", - "which": "^1.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.13", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz", - "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", - "license": "MIT" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmmirror.com/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - } - } -} diff --git a/server/package.json b/server/package.json deleted file mode 100644 index f1b653a..0000000 --- a/server/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "ai-demo-server", - "version": "1.0.0", - "type": "module", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "dependencies": { - "express": "^4.21.0", - "cors": "^2.8.5", - "fluent-ffmpeg": "^2.1.3", - "axios": "^1.7.0", - "uuid": "^10.0.0" - } -} diff --git a/server/routes/video.js b/server/routes/video.js deleted file mode 100644 index f0289f5..0000000 --- a/server/routes/video.js +++ /dev/null @@ -1,284 +0,0 @@ -import express from 'express'; -import ffmpeg from 'fluent-ffmpeg'; -import axios from 'axios'; -import { v4 as uuidv4 } from 'uuid'; -import { promises as fs } from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const router = express.Router(); - -// 临时文件目录 -const TEMP_DIR = path.join(__dirname, '../temp'); - -// 确保临时目录存在 -async function ensureTempDir() { - try { - await fs.mkdir(TEMP_DIR, { recursive: true }); - } catch (err) { - console.error('创建临时目录失败:', err); - } -} - -ensureTempDir(); - -/** - * 下载资源到本地 - */ -async function downloadResource(url, outputPath) { - // 处理 base64 图片 (data:image/svg+xml,...) - if (url.startsWith('data:')) { - const matches = url.match(/^data:image\/(\w+);base64,(.+)$/); - if (matches) { - const buffer = Buffer.from(matches[2], 'base64'); - await fs.writeFile(outputPath, buffer); - return outputPath; - } - // 处理 URL 编码的 SVG - if (url.startsWith('data:image/svg+xml,')) { - const svgContent = decodeURIComponent(url.replace('data:image/svg+xml,', '')); - await fs.writeFile(outputPath, svgContent); - return outputPath; - } - throw new Error('不支持的 base64 格式'); - } - - // 处理普通 HTTP URL - const response = await axios({ - method: 'GET', - url: url, - responseType: 'arraybuffer', - timeout: 60000, - }); - - await fs.writeFile(outputPath, response.data); - return outputPath; -} - -/** - * 获取音频时长 - */ -function getAudioDuration(audioPath) { - return new Promise((resolve, reject) => { - ffmpeg.ffprobe(audioPath, (err, metadata) => { - if (err) { - reject(err); - return; - } - const duration = metadata.format.duration; - resolve(duration); - }); - }); -} - -/** - * 合成单个视频片段 - */ -async function composeSegment(imagePath, audioPath, outputPath, width, height) { - const audioDuration = await getAudioDuration(audioPath); - - return new Promise((resolve, reject) => { - ffmpeg() - .input(imagePath) - .loop(1) - .input(audioPath) - .outputOptions([ - '-tune stillimage', - '-c:a aac', - '-b:a 192k', - '-pix_fmt yuv420p', - '-shortest' - ]) - .videoFilter(`scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`) - .duration(audioDuration + 0.5) - .output(outputPath) - .on('start', (commandLine) => { - console.log('FFmpeg 命令:', commandLine); - }) - .on('end', () => resolve(outputPath)) - .on('error', (err, stdout, stderr) => { - console.error('FFmpeg 错误输出:', stderr); - reject(err); - }) - .run(); - }); -} - -/** - * 合并多个视频片段 - */ -async function mergeSegments(segmentPaths, outputPath) { - const listPath = path.join(TEMP_DIR, `concat_${uuidv4()}.txt`); - // Windows 路径需要转义反斜杠或使用正斜杠 - const listContent = segmentPaths.map(p => { - // 将反斜杠替换为正斜杠,FFmpeg 更兼容 - const normalizedPath = p.replace(/\\/g, '/'); - return `file '${normalizedPath}'`; - }).join('\n'); - - await fs.writeFile(listPath, listContent); - console.log('合并列表内容:\n', listContent); - - return new Promise((resolve, reject) => { - ffmpeg() - .input(listPath) - .inputOptions(['-f concat', '-safe 0']) - .outputOptions(['-c copy']) - .output(outputPath) - .on('start', (commandLine) => { - console.log('FFmpeg 合并命令:', commandLine); - }) - .on('end', async () => { - // 清理列表文件 - try { - await fs.unlink(listPath); - } catch (e) { - console.error('清理列表文件失败:', e); - } - resolve(outputPath); - }) - .on('error', (err, stdout, stderr) => { - console.error('FFmpeg 合并错误:', stderr); - reject(err); - }) - .run(); - }); -} - -/** - * POST /api/video/compose - * 合成视频 - * - * Body: { - * slides: [{ imageUrl, audioUrl }], - * width: 1920, - * height: 1080 - * } - */ -router.post('/compose', async (req, res) => { - const { slides, width = 1920, height = 1080 } = req.body; - - if (!slides || !Array.isArray(slides) || slides.length === 0) { - return res.status(400).json({ error: '缺少 slides 参数或格式错误' }); - } - - const taskId = uuidv4(); - const taskDir = path.join(TEMP_DIR, taskId); - - console.log(`[${taskId}] 开始处理视频合成任务,共 ${slides.length} 个片段`); - - try { - // 创建任务目录 - await fs.mkdir(taskDir, { recursive: true }); - - const segmentPaths = []; - - // 逐个处理片段 - for (let i = 0; i < slides.length; i++) { - const slide = slides[i]; - console.log(`[${taskId}] 处理第 ${i + 1}/${slides.length} 个片段`); - - if (!slide.imageUrl || !slide.audioUrl) { - console.warn(`[${taskId}] 第 ${i + 1} 个片段缺少 imageUrl 或 audioUrl,跳过`); - continue; - } - - // 下载图片和音频 - const imagePath = path.join(taskDir, `image_${i}.png`); - const audioPath = path.join(taskDir, `audio_${i}.mp3`); - const segmentPath = path.join(taskDir, `segment_${i}.mp4`); - - console.log(`[${taskId}] 下载图片: ${slide.imageUrl.substring(0, 50)}...`); - console.log(`[${taskId}] 下载音频: ${slide.audioUrl.substring(0, 50)}...`); - - await downloadResource(slide.imageUrl, imagePath); - await downloadResource(slide.audioUrl, audioPath); - - // 检查文件是否成功创建 - try { - const imageStats = await fs.stat(imagePath); - const audioStats = await fs.stat(audioPath); - console.log(`[${taskId}] 图片大小: ${imageStats.size} bytes, 音频大小: ${audioStats.size} bytes`); - } catch (e) { - console.error(`[${taskId}] 文件检查失败:`, e); - } - - // 合成片段 - await composeSegment(imagePath, audioPath, segmentPath, width, height); - segmentPaths.push(segmentPath); - - console.log(`[${taskId}] 第 ${i + 1} 个片段合成完成`); - } - - if (segmentPaths.length === 0) { - throw new Error('没有有效的片段可以合并'); - } - - // 合并所有片段 - console.log(`[${taskId}] 开始合并 ${segmentPaths.length} 个片段`); - const finalPath = path.join(taskDir, 'output.mp4'); - await mergeSegments(segmentPaths, finalPath); - - console.log(`[${taskId}] 视频合成完成: ${finalPath}`); - - // 返回视频文件 - res.setHeader('Content-Type', 'video/mp4'); - res.setHeader('Content-Disposition', 'attachment; filename="question-explanation.mp4"'); - - const videoBuffer = await fs.readFile(finalPath); - res.send(videoBuffer); - - // 清理临时文件(延迟删除,确保响应已发送) - setTimeout(async () => { - try { - await fs.rm(taskDir, { recursive: true, force: true }); - console.log(`[${taskId}] 临时文件已清理`); - } catch (e) { - console.error(`[${taskId}] 清理临时文件失败:`, e); - } - }, 1000); - - } catch (error) { - console.error(`[${taskId}] 视频合成失败:`, error); - - // 清理临时文件 - try { - await fs.rm(taskDir, { recursive: true, force: true }); - } catch (e) { - // 忽略清理错误 - } - - res.status(500).json({ - error: '视频合成失败', - message: error.message - }); - } -}); - -/** - * GET /api/video/health - * 检查视频服务状态 - */ -router.get('/health', (req, res) => { - // 检查 ffmpeg 是否可用 - ffmpeg.getAvailableFormats((err, formats) => { - if (err) { - return res.status(500).json({ - status: 'error', - message: 'FFmpeg 不可用', - error: err.message - }); - } - - res.json({ - status: 'ok', - message: '视频合成服务正常运行', - formats: formats ? Object.keys(formats).length : 'unknown' - }); - }); -}); - -export default router; diff --git a/src/config/index.js b/src/config/index.js index 9882338..2f2e266 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -227,75 +227,3 @@ export const AUDIO_TEXT_OPTIMIZE_PROMPT = `你是一位专业的文字编辑和 - 保持原文语言(中文/英文/日语) - 如果原文已经很好,只需做必要的格式调整`; -// ── 试题讲解生成配置 ── -// 文本分析模型(用于分析试题复杂度、拆分讲解点、生成讲解文本和图片提示词) -export const QUESTION_EXPLANATION_ANALYSIS_URL = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"; -export const QUESTION_EXPLANATION_ANALYSIS_MODEL = "doubao-seed-2-0-lite-260215"; -export const QUESTION_EXPLANATION_ANALYSIS_TEMPERATURE = 0.7; -export const QUESTION_EXPLANATION_ANALYSIS_MAX_TOKENS = 4096; - -// 试题分析 System Prompt -export const QUESTION_EXPLANATION_ANALYSIS_PROMPT = `你是一位经验丰富的教学专家,擅长将复杂的试题拆解为清晰的讲解步骤。 - -【任务目标】 -分析试题复杂度,将其拆分为2-8个讲解点,并为每个讲解点生成详细的讲解文本和对应的图片生成提示词。 - -【分析流程】 -1. 评估试题复杂度: - - 简单题(单一知识点):生成2-3个讲解点 - - 中等题(多个知识点):生成4-5个讲解点 - - 复杂题(综合应用):生成6-8个讲解点 - -2. 拆分讲解点:按照解题逻辑顺序拆分,确保每个讲解点聚焦一个核心概念或步骤 - -3. 为每个讲解点生成: - - 标题:简洁明了,概括该步骤的核心内容 - - 讲解文本:详细的解释说明,语言通俗易懂,适合语音朗读(控制在80-150字之间) - - 图片提示词:用于生成配套讲解图片,要求视觉化呈现该步骤的核心内容 - -【输出格式 - 严格按JSON格式输出】 -必须只输出纯JSON,不要包含任何其他文字、说明或markdown标记。格式如下: -{"complexity":"简单|中等|复杂","totalPoints":数字,"explanationPoints":[{"order":1,"title":"讲解点标题","explanationText":"详细的讲解文本内容...","imagePrompt":"清晰、具体的图片生成提示词,包含视觉元素描述..."}]} - -【重要规则】 -1. 【最高优先级】只输出JSON,不要有任何其他文字 -2. 必须严格按JSON格式输出,所有字符串值必须用双引号包裹 -3. 字符串内的双引号必须转义为 \\" -4. 字符串内的换行符必须转义为 \\n -5. 讲解文本要口语化,适合语音朗读,避免过于书面化 -6. 图片提示词要具体、形象,便于AI生成直观的教学图示 -7. 讲解点之间要有逻辑连贯性,形成完整的解题思路 -8. 确保JSON格式正确,所有引号、逗号、括号匹配 - -【示例输出】 -{"complexity":"简单","totalPoints":2,"explanationPoints":[{"order":1,"title":"理解题目","explanationText":"首先我们来看这道题目,题目要求我们...","imagePrompt":"一个清晰的题目展示卡片..."},{"order":2,"title":"解题思路","explanationText":"接下来我们分析解题思路...","imagePrompt":"解题步骤流程图..."}]}`; - -// 图片生成模型配置(阿里云百炼) -export const QUESTION_EXPLANATION_IMAGE_URL = "/dashscope-api/api/v1/services/aigc/multimodal-generation/generation"; -export const QUESTION_EXPLANATION_IMAGE_MODEL = "qwen-image-2.0-pro"; // 千问文生图模型,擅长文字渲染 -export const QUESTION_EXPLANATION_IMAGE_SIZE = "1920*1080"; // 16:9 比例 - -// 图片统一风格描述(确保所有生成的图片风格一致) -export const QUESTION_EXPLANATION_IMAGE_STYLE = `【统一视觉风格要求】 -1. 背景:使用深蓝色渐变背景(从深蓝色#1e293b到更深的蓝黑色#0f172a),营造专业学术氛围 -2. 配色方案:主色调为深蓝色系,辅助色为紫色和蓝色(色值范围:紫色#8b5cf6到蓝色#3b82f6),强调色使用白色和淡蓝色 -3. 字体样式:所有文字使用清晰易读的无衬线字体,标题文字使用白色或淡蓝色,重点内容可用亮色标注 -4. 图形元素:使用简洁的几何图形、流程图、示意图等,线条清晰,颜色协调 -5. 整体风格:现代简约、专业严谨的教学图示风格,避免过于花哨或复杂的装饰 -6. 布局:采用居中对称或左右分栏布局,保持视觉平衡 -7. 光影效果:适当使用柔和的光晕效果或渐变叠加,增强层次感 -8. 所有图片必须严格遵循以上风格要求,确保整个讲解系列的视觉连贯性和专业性`; - -// 语音合成模型配置(阿里云百炼) -export const QUESTION_EXPLANATION_TTS_URL = "/dashscope-api/api/v1/services/aigc/multimodal-generation/generation"; -export const QUESTION_EXPLANATION_TTS_MODEL = "qwen3-tts-flash"; -export const QUESTION_EXPLANATION_TTS_VOICE = "Cherry"; // 芊悦:阳光积极的小姐姐音色 -export const QUESTION_EXPLANATION_TTS_LANGUAGE = "zh-CN"; -export const QUESTION_EXPLANATION_TTS_FORMAT = "mp3"; -export const QUESTION_EXPLANATION_TTS_SAMPLE_RATE = 24000; - -// ── 视频合成配置 ── -export const VIDEO_WIDTH = 1920; -export const VIDEO_HEIGHT = 1080; -export const VIDEO_OUTPUT_FORMAT = "mp4"; -export const VIDEO_FILENAME_PREFIX = "试题讲解_"; diff --git a/src/router/index.js b/src/router/index.js index 06e0a15..9718521 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -9,7 +9,6 @@ import ProblemSolving from '../views/ProblemSolving.vue' import QuestionGenerator from '../views/QuestionGenerator.vue' import QuestionVariant from '../views/QuestionVariant.vue' import AudioToText from '../views/AudioToText.vue' -import QuestionExplanation from '../views/QuestionExplanation.vue' import SpeakingEvaluation from '../views/SpeakingEvaluation.vue' const router = createRouter({ @@ -65,11 +64,6 @@ const router = createRouter({ name: 'audio-to-text', component: AudioToText }, - { - path: '/question-explanation', - name: 'question-explanation', - component: QuestionExplanation - }, { path: '/speaking-evaluation', name: 'speaking-evaluation', diff --git a/src/utils/videoComposer.js b/src/utils/videoComposer.js deleted file mode 100644 index b49472d..0000000 --- a/src/utils/videoComposer.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 视频合成模块 - 后端 FFmpeg 方案 - */ - -// 后端 API 地址 -const API_BASE = '/api/video'; - -/** - * 合成视频 - 调用后端 API - * @param {Array} slides 讲解点数组,每个包含 imageUrl 和 audioUrl - * @param {Object} options 配置选项 - * @param {Function} onProgress 进度回调 { stage, current, total, message } - * @returns {Promise} 视频Blob - */ -export async function composeVideo(slides, options = {}, onProgress = null) { - const { - width = 1920, - height = 1080, - } = options; - - // 过滤有效的 slide,并使用原始完整 URL - const validSlides = slides - .filter(s => (s.originalImageUrl || s.imageUrl) && (s.originalAudioUrl || s.audioUrl)) - .map(s => ({ - // 优先使用原始完整 URL,否则使用当前 URL - imageUrl: s.originalImageUrl || s.imageUrl, - audioUrl: s.originalAudioUrl || s.audioUrl, - })); - - if (validSlides.length === 0) { - throw new Error('没有有效的讲解内容'); - } - - // 检查 URL 格式 - const invalidUrls = validSlides.filter(s => - !s.imageUrl.startsWith('http') && !s.imageUrl.startsWith('data:') - ); - if (invalidUrls.length > 0) { - console.warn('部分 URL 格式不正确:', invalidUrls); - } - - // 阶段1: 准备请求 - if (onProgress) { - onProgress({ - stage: 'preparing', - current: 0, - total: validSlides.length, - message: '正在准备视频合成请求...', - }); - } - - try { - // 阶段2: 发送请求到后端 - if (onProgress) { - onProgress({ - stage: 'composing', - current: 1, - total: validSlides.length, - message: `服务器正在合成视频 (${validSlides.length} 个片段)...`, - }); - } - - const response = await fetch(`${API_BASE}/compose`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - slides: validSlides, - width, - height, - }), - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ message: '请求失败' })); - throw new Error(error.message || `服务器错误: ${response.status}`); - } - - // 阶段3: 接收视频 - if (onProgress) { - onProgress({ - stage: 'merging', - current: validSlides.length, - total: validSlides.length, - message: '正在接收合成后的视频...', - }); - } - - const videoBlob = await response.blob(); - - if (onProgress) { - onProgress({ - stage: 'completed', - current: 1, - total: 1, - message: '视频合成完成!', - }); - } - - return videoBlob; - } catch (error) { - console.error('视频合成失败:', error); - throw new Error(`视频合成失败: ${error.message}`); - } -} - -/** - * 下载视频 - * @param {Blob} blob 视频Blob - * @param {string} filename 文件名 - */ -export function downloadVideo(blob, filename = 'question-explanation.mp4') { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); -} - -/** - * 创建预览URL - * @param {Blob} blob 视频Blob - * @returns {string} Blob URL - */ -export function createPreviewUrl(blob) { - return URL.createObjectURL(blob); -} - -/** - * 释放预览URL - * @param {string} url Blob URL - */ -export function revokePreviewUrl(url) { - URL.revokeObjectURL(url); -} diff --git a/src/views/HomePage.vue b/src/views/HomePage.vue index 3b0ad00..569bbaf 100644 --- a/src/views/HomePage.vue +++ b/src/views/HomePage.vue @@ -85,14 +85,6 @@ const features = ref([ icon: "file-audio", route: "/audio-to-text", }, - { - id: 11, - title: "AI试题讲解生成", - desc: "智能分析试题复杂度,自动生成图文并茂的幻灯片式讲解,每张图片配语音讲解,让解题过程清晰易懂。", - class: "card-11", - icon: "presentation", - route: "/question-explanation", - }, { id: 12, title: "听读评测", diff --git a/src/views/QuestionExplanation.vue b/src/views/QuestionExplanation.vue deleted file mode 100644 index 22e235f..0000000 --- a/src/views/QuestionExplanation.vue +++ /dev/null @@ -1,1847 +0,0 @@ - - - - - diff --git a/vite.config.js b/vite.config.js index a5e6c7b..e87b20f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -18,11 +18,6 @@ export default defineConfig({ server: { host: '0.0.0.0', proxy: { - // 后端 API 代理 - '/api': { - target: 'http://localhost:3001', - changeOrigin: true, - }, '/tts-api': { target: 'https://openspeech.bytedance.com', changeOrigin: true,