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 +4 -3
- package/Dockerfile +16 -42
- package/docker-compose.yml +1 -1
- package/package.json +1 -1
- package/src/index.js +7 -3
- package/src/logger.js +16 -1
- package/src/routes/convert.js +24 -0
- package/src/routes/merge.js +11 -2
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
|
-
|
|
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
|
-
#
|
|
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"]
|
package/docker-compose.yml
CHANGED
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -138,9 +138,13 @@ function startServer() {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
server.listen(PORT, HOST, () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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) {
|
package/src/routes/convert.js
CHANGED
|
@@ -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
|
// 转码失败
|
package/src/routes/merge.js
CHANGED
|
@@ -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
|
-
|
|
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';
|