mb-rrvideo-server 1.0.10 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCKER.md CHANGED
@@ -285,7 +285,9 @@ sudo docker compose up -d
285
285
  sudo docker exec rrvideo-server npm update -g mb-rrvideo-server
286
286
  sudo docker restart rrvideo-server
287
287
 
288
- 不需要pm2重启
288
+ #不需要pm2重启,docker重启的时候会更新掉
289
+
290
+
289
291
  **输出示例:**
290
292
  ```
291
293
  NAME IMAGE STATUS PORTS
@@ -929,8 +931,7 @@ mb-rrvideo 使用 Playwright 来渲染 rrweb 录屏数据,需要一个真实
929
931
  **方法 1:使用镜像压缩**
930
932
  ```bash
931
933
  # 导出时压缩
932
- docker save mb-rrvideo-converter:latest | gzip > mb-rrvideo-converter.tar.gz
933
-
934
+ docker save mb-rrvideo-converter:latest | gzip > /home/mb-rrvideo-converter.tar.gz
934
935
  # 压缩后约 600MB
935
936
  ls -lh mb-rrvideo-converter.tar.gz
936
937
 
package/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  # ============================================
2
- # mb-rrvideo-server Docker 镜像
2
+ # mb-rrvideo-server Docker 镜像 (精简版)
3
3
  # 视频转码服务(纯 Node.js)
4
4
  # 包含:Node.js 22 + mb-rrvideo + mb-rrvideo-server
5
5
  # ============================================
@@ -7,7 +7,6 @@
7
7
  FROM node:22-slim
8
8
 
9
9
  # 设置代理参数(可从构建参数传入,也可在此处直接定义默认值)
10
- # 如果不想每次构建都传参,可以在这里写死默认值
11
10
  ARG HTTP_PROXY="http://172.168.1.117:10809"
12
11
  ARG HTTPS_PROXY="http://172.168.1.117:10809"
13
12
 
@@ -17,19 +16,12 @@ ENV TZ=Asia/Shanghai \
17
16
  PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
18
17
  PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright
19
18
 
20
- # PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright
21
-
22
19
  # ============================================
23
- # 第一步:配置国内镜像源(加速构建)
20
+ # 合并安装系统依赖(减少镜像层)
24
21
  # ============================================
25
- # 使用阿里云 Debian 镜像源
26
22
  RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
27
- sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources
28
-
29
- # ============================================
30
- # 第三步:安装系统依赖
31
- # ============================================
32
- RUN apt-get update && apt-get install -y \
23
+ sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
24
+ apt-get update && apt-get install -y --no-install-recommends \
33
25
  # 基础工具
34
26
  curl \
35
27
  wget \
@@ -57,34 +49,28 @@ RUN apt-get update && apt-get install -y \
57
49
  # 字体支持
58
50
  fonts-liberation \
59
51
  fonts-noto-cjk \
52
+ # 清理缓存
53
+ && apt-get clean \
60
54
  && rm -rf /var/lib/apt/lists/*
61
55
 
62
56
  # ============================================
63
- # 第三步:配置 npm 国内镜像源(加速安装)
57
+ # 安装 npm 包与浏览器(合并层以减少体积)
64
58
  # ============================================
65
- RUN npm config set registry https://registry.npmmirror.com
66
-
67
- # ============================================
68
- # 第五步:全局安装 Node.js 包
69
- # 使用 --verbose 查看详细日志
70
- # 并显式添加 --ignore-scripts 参数以跳过所有 postinstall 脚本
71
- # ============================================
72
- RUN npm install -g --verbose --ignore-scripts \
59
+ RUN npm config set registry https://registry.npmmirror.com && \
60
+ npm install -g --verbose --ignore-scripts \
73
61
  pm2 \
74
62
  mb-rrvideo \
75
63
  mb-rrvideo-server \
76
- && npm cache clean --force
77
-
78
- # ============================================
79
- # 第六步:安装 Playwright 浏览器(Chromium)
80
- # 使用构建参数中的代理(只在此步骤生效)
81
- # ============================================
82
- RUN export HTTP_PROXY=$HTTP_PROXY && \
64
+ && npm cache clean --force && \
65
+ # 立即安装浏览器并清理
66
+ export HTTP_PROXY=$HTTP_PROXY && \
83
67
  export HTTPS_PROXY=$HTTPS_PROXY && \
84
- npx playwright install chromium
68
+ npx playwright install chromium && \
69
+ # 删除 playwright 下载的 zip 包缓存(瘦身关键)
70
+ rm -rf /root/.cache/ms-playwright/*/chromium-*.zip
85
71
 
86
72
  # ============================================
87
- # 第七步:创建应用目录
73
+ # 创建应用目录与配置
88
74
  # ============================================
89
75
  RUN mkdir -p /app/config \
90
76
  && mkdir -p /app/logs \
@@ -94,23 +80,11 @@ RUN mkdir -p /app/config \
94
80
 
95
81
  WORKDIR /app
96
82
 
97
- # ============================================
98
- # 第八步:复制 PM2 配置文件
99
- # ============================================
100
83
  COPY ecosystem.docker.config.js /app/ecosystem.config.js
101
84
 
102
- # ============================================
103
- # 第九步:暴露端口
104
- # ============================================
105
85
  EXPOSE 24203
106
86
 
107
- # ============================================
108
- # 第十步:健康检查
109
- # ============================================
110
87
  HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
111
88
  CMD curl -f http://localhost:24203/health || exit 1
112
89
 
113
- # ============================================
114
- # 第十一步:启动服务
115
- # ============================================
116
90
  CMD ["pm2-runtime", "start", "ecosystem.config.js"]
@@ -12,7 +12,7 @@ services:
12
12
 
13
13
  volumes:
14
14
  # 配置文件(必须)
15
- - ./config.json:/app/config/config.json:ro
15
+ - ./config.json:/app/config/config.json
16
16
  # 日志目录
17
17
  - ./logs:/app/logs
18
18
  # 临时文件目录
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mb-rrvideo-server",
3
- "version": "1.0.10",
3
+ "version": "1.0.13",
4
4
  "description": "视频转码服务 - 接收可回溯机请求,执行转码/合并,上传MinIO/本地存储",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -138,9 +138,13 @@ function startServer() {
138
138
  }
139
139
 
140
140
  server.listen(PORT, HOST, () => {
141
- console.log(`rrvideo-server v${version} listening on ${HOST}:${PORT}`);
142
- console.log(`Storage type: ${config.get('storage.type')}`);
143
- console.log(`Log level: ${config.get('log.level')}`);
141
+ const startMsg = `rrvideo-server v${version} listening on ${HOST}:${PORT}\n` +
142
+ `Storage type: ${config.get('storage.type')}\n` +
143
+ `Log level: ${config.get('log.level')}`;
144
+
145
+ console.log(startMsg);
146
+ // 记录到系统日志文件,确保日志目录被创建
147
+ logger.info(startMsg, 'system', 'system');
144
148
  });
145
149
  }
146
150
 
package/src/logger.js CHANGED
@@ -14,7 +14,22 @@ class Logger {
14
14
  this.buffers = new Map(); // fileKey -> { logs: [], timer: null }
15
15
  this.flushInterval = config.get('log.flush_interval', 2000); // 默认2秒
16
16
  this.bufferMaxSize = config.get('log.buffer_max_size', 500); // 默认500条
17
- this.baseDir = path.join(__dirname, '../logs');
17
+
18
+ // 优先使用配置的日志目录,或者环境变量 LOG_DIR
19
+ // 适配 Docker 环境:如果 /app/logs 存在,则优先使用(防止全局安装时路径跑偏)
20
+ if (process.env.LOG_DIR) {
21
+ this.baseDir = process.env.LOG_DIR;
22
+ } else if (config.get('log.dir')) {
23
+ this.baseDir = config.get('log.dir');
24
+ } else {
25
+ // 自动检测 Docker 环境
26
+ const dockerLogDir = '/app/logs';
27
+ if (fs.existsSync(dockerLogDir)) {
28
+ this.baseDir = dockerLogDir;
29
+ } else {
30
+ this.baseDir = path.join(__dirname, '../logs');
31
+ }
32
+ }
18
33
  }
19
34
 
20
35
  ensureDir(dirPath) {
@@ -167,6 +167,7 @@ function deriveRecordId(taskItem, rootParams) {
167
167
  */
168
168
  async function handleConvertV1(inputData, res, logFileName) {
169
169
  const actionName = 'handleConvertV1';
170
+ let createdTempDir = null; // 记录创建的临时目录
170
171
 
171
172
  try {
172
173
  let input = inputData.input;
@@ -198,6 +199,7 @@ async function handleConvertV1(inputData, res, logFileName) {
198
199
  const basePath = config.get('storage.local.temp_dir') || config.get('storage.local.base_path') || './';
199
200
  const tempDir = path.join(basePath, `__temp_convert_${recordId}`);
200
201
  ensureDir(tempDir);
202
+ createdTempDir = tempDir; // 记录临时目录路径
201
203
 
202
204
  const tempFile = 'record.data';
203
205
  const tempFilePath = path.join(tempDir, tempFile);
@@ -322,6 +324,16 @@ async function handleConvertV1(inputData, res, logFileName) {
322
324
  if (tempConfigFile) {
323
325
  cleanupTempConfigFile(tempConfigFile);
324
326
  }
327
+
328
+ // 清理临时目录
329
+ if (createdTempDir) {
330
+ try {
331
+ fs.rmSync(createdTempDir, { recursive: true, force: true });
332
+ logger.info(`[${actionName}] 临时目录已删除: ${createdTempDir}`, logFileName, 'convert');
333
+ } catch (e) {
334
+ logger.error(`[${actionName}] 删除临时目录失败: ${e.message}`, logFileName, 'convert');
335
+ }
336
+ }
325
337
 
326
338
  if (exitCode !== 0) {
327
339
  // 转码失败
@@ -432,6 +444,7 @@ async function handleConvertV1(inputData, res, logFileName) {
432
444
  */
433
445
  async function handleConvertV2(inputData, res, logFileName) {
434
446
  const actionName = 'handleConvertV2';
447
+ const createdTempDirs = []; // 记录创建的临时目录列表
435
448
 
436
449
  try {
437
450
  const recordId = inputData.record_id || inputData.record_no || 'unknown';
@@ -508,6 +521,7 @@ async function handleConvertV2(inputData, res, logFileName) {
508
521
  const basePath = config.get('storage.local.temp_dir') || config.get('storage.local.base_path') || './';
509
522
  const tempDir = path.join(basePath, `__temp_convert_${recordId}_${recordPageId}`);
510
523
  ensureDir(tempDir);
524
+ createdTempDirs.push(tempDir); // 记录临时目录路径
511
525
 
512
526
  const tempFile = `record_${recordPageId}.data`;
513
527
  const tempFilePath = path.join(tempDir, tempFile);
@@ -664,6 +678,16 @@ async function handleConvertV2(inputData, res, logFileName) {
664
678
  if (tempConfigFile) {
665
679
  cleanupTempConfigFile(tempConfigFile);
666
680
  }
681
+
682
+ // 清理所有临时目录
683
+ for (const dir of createdTempDirs) {
684
+ try {
685
+ fs.rmSync(dir, { recursive: true, force: true });
686
+ logger.info(`[${actionName}] 临时目录已删除: ${dir}`, logFileName, 'convert');
687
+ } catch (e) {
688
+ logger.error(`[${actionName}] 删除临时目录失败: ${e.message}`, logFileName, 'convert');
689
+ }
690
+ }
667
691
 
668
692
  if (exitCode !== 0) {
669
693
  // 转码失败
@@ -43,9 +43,18 @@ async function handleMerge(inputData, res, logFileName) {
43
43
  }
44
44
 
45
45
  const taskId = mergeId;
46
- const videos = Array.isArray(inputData.videos) ? inputData.videos : [];
46
+ const videos = (Array.isArray(inputData.videos) ? inputData.videos : [])
47
+ .filter(v => v && typeof v === 'string' && v.trim())
48
+ .map(v => v.trim());
47
49
  // 兼容 output 和 output_name 两种参数名
48
- const output = inputData.output || inputData.output_name || '';
50
+ let output = (inputData.output || inputData.output_name || '').trim();
51
+
52
+ // 如果 output 只是文件名(不是绝对路径),则拼接到配置的视频目录
53
+ if (output && !path.isAbsolute(output)) {
54
+ const videoDir = config.get('storage.local.video_dir') || path.join(__dirname, '../../Video');
55
+ output = path.join(videoDir, output);
56
+ }
57
+
49
58
  const format = inputData.format || 'mp4';
50
59
  const method = inputData.method || 'concat';
51
60
  const autoMode = inputData.auto_mode || 'smart';