tools_batch_files 1.0.34 → 1.0.36
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/package.json +2 -1
- package/src/audioFn/audioBatch.js +106 -30
- package/src/videoFn/videoBatch.js +696 -0
- package/utils/logger.js +8 -4
- package/utils/settleFiles.js +2 -3
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "tools_batch_files",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.36",
|
4
4
|
"description": "批处理视频工具",
|
5
5
|
"keywords": [
|
6
6
|
"utils",
|
@@ -13,6 +13,7 @@
|
|
13
13
|
"tbf": "./index.js",
|
14
14
|
"tbfp": "./src/photoBatch.js",
|
15
15
|
"tbfa": "./src/audioFn/audioBatch.js",
|
16
|
+
"tbfv": "./src/videoFn/videoBatch.js",
|
16
17
|
"tbfb": "./src/photoBatchBack.js",
|
17
18
|
"tbfre": "./src/removeFailVideo.js"
|
18
19
|
},
|
@@ -29,6 +29,12 @@ const queueCount = 10;
|
|
29
29
|
//起始任务下标
|
30
30
|
let taskIndex = 0;
|
31
31
|
|
32
|
+
//已完成数量
|
33
|
+
let completeCount = 0;
|
34
|
+
|
35
|
+
//错误文件重试次数
|
36
|
+
let retryError = 0;
|
37
|
+
|
32
38
|
//执行目录
|
33
39
|
const exeDir = __dirname;
|
34
40
|
//当前工作目录
|
@@ -48,7 +54,13 @@ const ZIP_FILES_DIR = "zip";
|
|
48
54
|
//拷贝 - 存放上传失败的文件
|
49
55
|
const Error_Files_Dir = "failed_audio";
|
50
56
|
//水印
|
51
|
-
const watermarkAudioPath = path.join(
|
57
|
+
const watermarkAudioPath = path.join(
|
58
|
+
__dirname,
|
59
|
+
"..",
|
60
|
+
"..",
|
61
|
+
"vocal_print",
|
62
|
+
"mz.mp3"
|
63
|
+
);
|
52
64
|
const longWatermarkAudioPath = path.join(
|
53
65
|
__dirname,
|
54
66
|
"..",
|
@@ -77,7 +89,7 @@ const watermarkAudio = (indexFilePath, originFilePath, fileName, duration) => {
|
|
77
89
|
`[1:a]atrim=0:duration=${duration},volume=1.5[a1]`, // 对声纹音频流进行截取操作,并设置音量,重命名为a1
|
78
90
|
`[a0][a1]amix=inputs=2[a]`, // 合并两个音频流为一个输出流,重命名为a
|
79
91
|
`[a]volume=3.0`, // 设置输出音频的音量
|
80
|
-
]
|
92
|
+
];
|
81
93
|
|
82
94
|
if (duration <= 5) {
|
83
95
|
//直接合并,放在末尾,下次补上
|
@@ -108,7 +120,7 @@ const watermarkAudio = (indexFilePath, originFilePath, fileName, duration) => {
|
|
108
120
|
.input(originFilePath)
|
109
121
|
.input(vocalPath) // 声纹音频文件
|
110
122
|
|
111
|
-
.complexFilter(complexFilter)
|
123
|
+
.complexFilter(complexFilter.join(";"))
|
112
124
|
.output(outputFile)
|
113
125
|
.on("error", (err) => {
|
114
126
|
logger("添加声纹出错: " + err);
|
@@ -272,8 +284,8 @@ const postData = (dataParams, indexFilePath, index) => {
|
|
272
284
|
logger(`第${index}条等待接口返回结果……`);
|
273
285
|
|
274
286
|
// return axios.post("http://192.168.102.61:9999/upload/sound", formData, {
|
275
|
-
// return axios.post("http://
|
276
|
-
return axios.post("http://
|
287
|
+
// return axios.post("http://192.168.101.149:9999/upload/sound", formData, {
|
288
|
+
return axios.post("http://127.0.0.1:9999/upload/sound", formData, {
|
277
289
|
headers: {
|
278
290
|
"Content-Type": "multipart/form-data",
|
279
291
|
},
|
@@ -289,7 +301,8 @@ async function postDataWithRetry(
|
|
289
301
|
indexFilePath,
|
290
302
|
index,
|
291
303
|
fileName,
|
292
|
-
hash
|
304
|
+
hash,
|
305
|
+
type
|
293
306
|
) {
|
294
307
|
let retryCount = 0; // 当前重试次数
|
295
308
|
|
@@ -300,7 +313,7 @@ async function postDataWithRetry(
|
|
300
313
|
logger("请求成功!");
|
301
314
|
logger(resData.data.code);
|
302
315
|
// 文件名和索引值
|
303
|
-
successLogger(hash, index, fileName);
|
316
|
+
successLogger(hash, index, fileName, type);
|
304
317
|
removeDirectory(indexFilePath);
|
305
318
|
return;
|
306
319
|
} else if (resData.data.code === 300) {
|
@@ -327,7 +340,7 @@ async function postDataWithRetry(
|
|
327
340
|
/**
|
328
341
|
* 任务
|
329
342
|
*/
|
330
|
-
const task = async (row, index, hash) => {
|
343
|
+
const task = async (row, index, hash, type) => {
|
331
344
|
//index文件夹 output/0
|
332
345
|
const indexFilePath = path.join(workDir, "output", index + "");
|
333
346
|
let fileName = row.fileName;
|
@@ -393,7 +406,14 @@ const task = async (row, index, hash) => {
|
|
393
406
|
await archiveZip(fileName, indexFilePath, originFilePath);
|
394
407
|
|
395
408
|
// 重试机制
|
396
|
-
await postDataWithRetry(
|
409
|
+
await postDataWithRetry(
|
410
|
+
dataParams,
|
411
|
+
indexFilePath,
|
412
|
+
index,
|
413
|
+
fileName,
|
414
|
+
hash,
|
415
|
+
type
|
416
|
+
);
|
397
417
|
|
398
418
|
logger(
|
399
419
|
`----------------------------------------第${index}条结束---------------------------------end`
|
@@ -414,35 +434,41 @@ const task = async (row, index, hash) => {
|
|
414
434
|
}
|
415
435
|
};
|
416
436
|
|
417
|
-
const run = (index, data, hash) => {
|
437
|
+
const run = (index, data, hash, type) => {
|
418
438
|
try {
|
419
439
|
logger(
|
420
440
|
`run-------------------------------------第${index}条开始------------------------------------`
|
421
441
|
);
|
422
442
|
const row = data[index];
|
423
443
|
|
424
|
-
if (
|
444
|
+
if (completeCount >= data.length) {
|
425
445
|
taskIndex = 0;
|
426
446
|
logger(
|
427
447
|
index +
|
428
448
|
"=========================当前任务,最后一条已结束!========================="
|
429
449
|
);
|
430
450
|
|
451
|
+
successLogger(hash, index, "done");
|
452
|
+
|
431
453
|
// 是否存在错误列表? 存在的话,继续遍历
|
432
|
-
const
|
433
|
-
if (
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
logger("跑干了!");
|
454
|
+
const status = runErrorList(taskIndex, hash);
|
455
|
+
if (!status) {
|
456
|
+
logger(
|
457
|
+
"》》》》》》》》》》音频批量任务任务结束《《《《《《《《《《" + hash
|
458
|
+
);
|
438
459
|
}
|
439
460
|
|
440
461
|
return;
|
441
462
|
}
|
442
463
|
|
443
|
-
|
464
|
+
if (!row) {
|
465
|
+
return;
|
466
|
+
}
|
467
|
+
|
468
|
+
task(row, index, hash, type)
|
444
469
|
.then(() => {
|
445
470
|
taskIndex++;
|
471
|
+
completeCount++;
|
446
472
|
run(taskIndex, data, hash);
|
447
473
|
})
|
448
474
|
.catch((err) => {
|
@@ -450,15 +476,52 @@ const run = (index, data, hash) => {
|
|
450
476
|
});
|
451
477
|
} catch (error) {
|
452
478
|
logger("捕获错误!" + error);
|
479
|
+
completeCount++;
|
453
480
|
taskIndex++;
|
454
481
|
run(taskIndex, data, hash);
|
455
482
|
}
|
456
483
|
};
|
457
484
|
|
458
|
-
const queue = (
|
459
|
-
|
460
|
-
|
485
|
+
const queue = (index, jsonData, hash, type) => {
|
486
|
+
const rest = jsonData.length - index;
|
487
|
+
const count = queueCount > rest ? rest : queueCount;
|
488
|
+
const queueList = [];
|
489
|
+
completeCount = index;
|
490
|
+
|
491
|
+
for (let i = 0; i < count; i++) {
|
492
|
+
queueList.push(run(i + index, jsonData, hash, type));
|
461
493
|
}
|
494
|
+
|
495
|
+
taskIndex += count - 1;
|
496
|
+
return Promise.all(queueList);
|
497
|
+
};
|
498
|
+
|
499
|
+
//yunxing cuowu wenjian
|
500
|
+
const runErrorList = (index, hash, parentHash) => {
|
501
|
+
const fileHash = parentHash || hash;
|
502
|
+
const errorExcelPath = path.join(workDir, `error${fileHash}.xlsx`);
|
503
|
+
|
504
|
+
if (retryError > 5) {
|
505
|
+
console.log("出现无法解析的错误");
|
506
|
+
return false;
|
507
|
+
}
|
508
|
+
|
509
|
+
taskIndex = index;
|
510
|
+
let taskHash = hash;
|
511
|
+
//新的运行任务-重新赋予新的任务id
|
512
|
+
if (index === 0) {
|
513
|
+
taskHash = generateUniqueHash().slice(0, 8);
|
514
|
+
}
|
515
|
+
|
516
|
+
if (isExist(errorExcelPath)) {
|
517
|
+
retryError++;
|
518
|
+
|
519
|
+
logger("开始运行错误列表");
|
520
|
+
jsonData = readExcel(errorExcelPath); // 数据来源于error excel
|
521
|
+
queue(index, jsonData, taskHash, fileHash);
|
522
|
+
return true;
|
523
|
+
}
|
524
|
+
return false;
|
462
525
|
};
|
463
526
|
|
464
527
|
const main = () => {
|
@@ -473,18 +536,31 @@ const main = () => {
|
|
473
536
|
|
474
537
|
const successPath = path.join(workDir, "success.txt");
|
475
538
|
if (isExist(successPath)) {
|
476
|
-
const { loghash, lastItemIndex } =
|
477
|
-
|
478
|
-
logger("成功日志存在,异常中断或者跑完了。");
|
539
|
+
const { loghash, lastItemIndex, fileName, parentHash } =
|
540
|
+
readTxt(successPath);
|
479
541
|
|
480
542
|
hash = loghash;
|
481
|
-
taskIndex = lastItemIndex + 1; // lastItemIndex为最后一项,所有要+1
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
543
|
+
taskIndex = parseInt(lastItemIndex) + 1; // lastItemIndex为最后一项,所有要+1
|
544
|
+
|
545
|
+
//当前任务已经结束
|
546
|
+
if (fileName === "done") {
|
547
|
+
//找寻是否存在未上传的错误文件列表
|
548
|
+
const status = runErrorList(0, hash);
|
549
|
+
if (!status) {
|
550
|
+
logger(
|
551
|
+
"》》》》》》》》》》音频批量任务任务结束《《《《《《《《《《" + hash
|
552
|
+
);
|
553
|
+
}
|
486
554
|
} else {
|
487
|
-
|
555
|
+
//当前任务异常中断
|
556
|
+
|
557
|
+
//中断的任务类型为错误任务
|
558
|
+
if (parentHash) {
|
559
|
+
runErrorList(taskIndex, hash, parentHash);
|
560
|
+
} else {
|
561
|
+
jsonData = readExcel(excelDir);
|
562
|
+
queue(taskIndex, jsonData, hash);
|
563
|
+
}
|
488
564
|
}
|
489
565
|
} else {
|
490
566
|
hash = generateUniqueHash().slice(0, 8);
|
@@ -0,0 +1,696 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
const generateUniqueHash = require("../../utils/index");
|
3
|
+
const {
|
4
|
+
disposeError,
|
5
|
+
readExcel,
|
6
|
+
logger,
|
7
|
+
successLogger,
|
8
|
+
} = require("../../utils/logger");
|
9
|
+
|
10
|
+
const {
|
11
|
+
findFileInDir,
|
12
|
+
logFileSize,
|
13
|
+
ensureDirSync,
|
14
|
+
isExist,
|
15
|
+
readTxt,
|
16
|
+
removeDirectory,
|
17
|
+
} = require("../../utils/settleFiles");
|
18
|
+
const ffmpeg = require("fluent-ffmpeg");
|
19
|
+
const fs = require("fs");
|
20
|
+
const path = require("path");
|
21
|
+
const fs_asnyc = require("fs").promises;
|
22
|
+
const archiver = require("archiver");
|
23
|
+
const axios = require("axios");
|
24
|
+
const FormData = require("form-data");
|
25
|
+
|
26
|
+
//并发数量
|
27
|
+
const queueCount = 10;
|
28
|
+
|
29
|
+
//起始任务下标
|
30
|
+
let taskIndex = 0;
|
31
|
+
|
32
|
+
//已完成数量
|
33
|
+
let completeCount = 0;
|
34
|
+
|
35
|
+
//错误文件重试次数
|
36
|
+
let retryError = 0;
|
37
|
+
|
38
|
+
//执行目录
|
39
|
+
const exeDir = __dirname;
|
40
|
+
//当前工作目录
|
41
|
+
const workDir = process.cwd();
|
42
|
+
|
43
|
+
//图片水印
|
44
|
+
const watermarkImage = path.join(exeDir, "imgs", "miz-watermark.png");
|
45
|
+
|
46
|
+
// 表格目录,从excel文件夹中查找
|
47
|
+
const excelFiles = fs
|
48
|
+
.readdirSync(path.join(workDir, "excel"))
|
49
|
+
.find((file) => file.endsWith(".xlsx"));
|
50
|
+
const excelDir = path.join(workDir, "excel", excelFiles);
|
51
|
+
|
52
|
+
// 截图数量
|
53
|
+
const SCREENSHOT_COUNT = 5;
|
54
|
+
const ORIGIN_FILE_DIR = "files";
|
55
|
+
const ZIP_FILES_DIR = "zip";
|
56
|
+
const ZIP_VIDEO_DIR_400 = "small_video";
|
57
|
+
const SCREENSHOT_DIR = "screenshots";
|
58
|
+
const ZIP_SCREENSHOT_DIR = "screenshot_watermark";
|
59
|
+
const ZIP_WATERMARK_VIDEO_DIR = "zip_watermark_video";
|
60
|
+
const SOURCE_VIDEO_DIR = "source_video";
|
61
|
+
const ZIP_VIDEO_DIR = "zip_video";
|
62
|
+
|
63
|
+
const maxRetries = 1; // 最大重试次数
|
64
|
+
|
65
|
+
/**
|
66
|
+
* 压缩视频
|
67
|
+
* @param {*} fileName 文件名
|
68
|
+
* @param {*} outputFileDir 产物文件夹
|
69
|
+
* @param {*} inputFilePath 源文件路径
|
70
|
+
* @returns Promise
|
71
|
+
*/
|
72
|
+
const compressVideo = (fileName, outputFileDir, inputFilePath) => {
|
73
|
+
const outputFilePath = path.join(
|
74
|
+
outputFileDir,
|
75
|
+
ZIP_VIDEO_DIR,
|
76
|
+
`zip-${fileName}`
|
77
|
+
);
|
78
|
+
const comand = ffmpeg(inputFilePath);
|
79
|
+
|
80
|
+
ensureDirSync(path.dirname(outputFilePath));
|
81
|
+
return new Promise((resolve, reject) => {
|
82
|
+
comand
|
83
|
+
.videoCodec("libx264")
|
84
|
+
.size("1280x?")
|
85
|
+
// .size("1280x720")
|
86
|
+
|
87
|
+
.output(outputFilePath)
|
88
|
+
|
89
|
+
.on("start", () => {
|
90
|
+
logger("视频开始压缩……");
|
91
|
+
})
|
92
|
+
.on("end", () => {
|
93
|
+
logger("视频压缩完成!");
|
94
|
+
resolve(outputFilePath);
|
95
|
+
})
|
96
|
+
.on("error", (err) => {
|
97
|
+
logger("视频压缩出错:" + err);
|
98
|
+
reject(err);
|
99
|
+
})
|
100
|
+
.run();
|
101
|
+
});
|
102
|
+
};
|
103
|
+
|
104
|
+
/**
|
105
|
+
* 压缩400p视频
|
106
|
+
*/
|
107
|
+
const compressVideo400p = (fileName, outputFileDir, inputFilePath) => {
|
108
|
+
const outputFilePath = path.join(
|
109
|
+
outputFileDir,
|
110
|
+
ZIP_VIDEO_DIR_400,
|
111
|
+
`zip-${fileName}`
|
112
|
+
);
|
113
|
+
const comand = ffmpeg(inputFilePath);
|
114
|
+
|
115
|
+
ensureDirSync(path.dirname(outputFilePath));
|
116
|
+
return new Promise((resolve, reject) => {
|
117
|
+
comand
|
118
|
+
.seekInput(0)
|
119
|
+
.inputOptions("-t 10")
|
120
|
+
.videoCodec("libx264")
|
121
|
+
.size("400x?")
|
122
|
+
|
123
|
+
.output(outputFilePath)
|
124
|
+
|
125
|
+
.on("start", () => {
|
126
|
+
logger("400p视频开始压缩……");
|
127
|
+
})
|
128
|
+
.on("end", () => {
|
129
|
+
logger("400p视频压缩完成!");
|
130
|
+
resolve(outputFilePath);
|
131
|
+
})
|
132
|
+
.on("error", (err) => {
|
133
|
+
logger("视频压缩出错:", err);
|
134
|
+
reject(err);
|
135
|
+
})
|
136
|
+
.run();
|
137
|
+
});
|
138
|
+
};
|
139
|
+
|
140
|
+
/**
|
141
|
+
* 生成5张截图
|
142
|
+
*/
|
143
|
+
const get5Screenshots = (fileName, outputFilePath, inputFilePath) => {
|
144
|
+
const folderPath = path.join(outputFilePath, SCREENSHOT_DIR); // 构建完整的目录路径
|
145
|
+
|
146
|
+
ensureDirSync(folderPath);
|
147
|
+
return new Promise((resolve, reject) => {
|
148
|
+
const screenshotsCommand = ffmpeg(inputFilePath);
|
149
|
+
|
150
|
+
screenshotsCommand
|
151
|
+
.on("start", () => {
|
152
|
+
logger("开始从视频中截图……");
|
153
|
+
})
|
154
|
+
.on("end", (stdout, stderr) => {
|
155
|
+
logger(inputFilePath + "截图完成!!!");
|
156
|
+
resolve();
|
157
|
+
})
|
158
|
+
.on("error", (err) => {
|
159
|
+
logger(inputFilePath + "截图出错:", err);
|
160
|
+
reject(err);
|
161
|
+
})
|
162
|
+
|
163
|
+
.screenshots({
|
164
|
+
timemarks: [0, "25%", "50%", "75%", "90%"],
|
165
|
+
folder: folderPath,
|
166
|
+
filename: `screenshots-%i.png`,
|
167
|
+
size: "840x?",
|
168
|
+
});
|
169
|
+
});
|
170
|
+
};
|
171
|
+
|
172
|
+
/**
|
173
|
+
* 视频打水印
|
174
|
+
*/
|
175
|
+
const watermarkVideo = (fileName, outputFileDir) => {
|
176
|
+
//用压缩后的视频作为输入源,增加效率
|
177
|
+
const inputFilePath = path.join(
|
178
|
+
outputFileDir,
|
179
|
+
ZIP_VIDEO_DIR,
|
180
|
+
`zip-${fileName}`
|
181
|
+
);
|
182
|
+
const outputFilePath = path.join(
|
183
|
+
outputFileDir,
|
184
|
+
ZIP_WATERMARK_VIDEO_DIR,
|
185
|
+
`zip-watermark-${fileName}`
|
186
|
+
);
|
187
|
+
const watermarkCommand = ffmpeg(inputFilePath);
|
188
|
+
|
189
|
+
ensureDirSync(path.dirname(outputFilePath));
|
190
|
+
|
191
|
+
return new Promise((resolve, reject) => {
|
192
|
+
watermarkCommand
|
193
|
+
.input(watermarkImage) // 添加水印图片作为第二个输入
|
194
|
+
|
195
|
+
.complexFilter([
|
196
|
+
"[0:v][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2",
|
197
|
+
])
|
198
|
+
.output(outputFilePath)
|
199
|
+
.on("start", () => {
|
200
|
+
logger("视频开始打水印……");
|
201
|
+
})
|
202
|
+
.on("end", () => {
|
203
|
+
logger("视频打水印完成!");
|
204
|
+
resolve(outputFilePath);
|
205
|
+
})
|
206
|
+
.on("error", (err) => {
|
207
|
+
logger("视频打水印出错:", err);
|
208
|
+
reject(err);
|
209
|
+
})
|
210
|
+
.run();
|
211
|
+
});
|
212
|
+
};
|
213
|
+
|
214
|
+
/**
|
215
|
+
* 截图水印,根据截图打水印
|
216
|
+
*/
|
217
|
+
const watermarkScreenshots = (indexFilePath) => {
|
218
|
+
//存放水印截图的文件夹
|
219
|
+
const inputDir = path.join(indexFilePath, SCREENSHOT_DIR);
|
220
|
+
const outputDir = path.join(indexFilePath, ZIP_SCREENSHOT_DIR);
|
221
|
+
ensureDirSync(outputDir);
|
222
|
+
|
223
|
+
return new Promise((resolve, reject) => {
|
224
|
+
fs.readdir(inputDir, (err, files) => {
|
225
|
+
if (err) {
|
226
|
+
logger("读取截图文件夹出错: " + err);
|
227
|
+
return;
|
228
|
+
} else {
|
229
|
+
files.forEach((file, index) => {
|
230
|
+
if (path.extname(file) === ".png") {
|
231
|
+
const inputFile = path.join(inputDir, file);
|
232
|
+
const outputFile = path.join(outputDir, file);
|
233
|
+
|
234
|
+
const watermarkScreenshotsComand = ffmpeg(inputFile);
|
235
|
+
watermarkScreenshotsComand
|
236
|
+
.input(watermarkImage)
|
237
|
+
.complexFilter([
|
238
|
+
"[0:v][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2",
|
239
|
+
])
|
240
|
+
.output(outputFile)
|
241
|
+
.on("error", (err) => {
|
242
|
+
logger("截图添加水印出错: ", err);
|
243
|
+
reject();
|
244
|
+
})
|
245
|
+
.on("end", () => {
|
246
|
+
logger("截图水印添加完成: " + outputFile);
|
247
|
+
if (index + 1 === SCREENSHOT_COUNT) {
|
248
|
+
resolve();
|
249
|
+
}
|
250
|
+
})
|
251
|
+
.run();
|
252
|
+
} else {
|
253
|
+
logger("截图不是png文件,无法添加图片水印!");
|
254
|
+
reject();
|
255
|
+
}
|
256
|
+
});
|
257
|
+
}
|
258
|
+
});
|
259
|
+
});
|
260
|
+
};
|
261
|
+
|
262
|
+
/**
|
263
|
+
* 打包物料
|
264
|
+
*/
|
265
|
+
const archiveZip = (fileName, inputPath, originFilePath) => {
|
266
|
+
const zipDir = path.join(inputPath, "zip");
|
267
|
+
const timestamp = new Date().getTime();
|
268
|
+
|
269
|
+
ensureDirSync(zipDir);
|
270
|
+
// create a file to stream archive data to.
|
271
|
+
const zipStream = fs.createWriteStream(
|
272
|
+
path.join(zipDir, `package${timestamp}.zip`)
|
273
|
+
);
|
274
|
+
const archive = archiver("zip", {
|
275
|
+
zlib: { level: 9 }, // Sets the compression level.
|
276
|
+
});
|
277
|
+
|
278
|
+
return new Promise((resolve, reject) => {
|
279
|
+
// listen for all archive data to be written
|
280
|
+
// 'close' event is fired only when a file descriptor is involved
|
281
|
+
zipStream.on("close", function () {
|
282
|
+
logger("压缩数据:" + archive.pointer() + " total bytes");
|
283
|
+
logger(
|
284
|
+
"完成归档archiver has been finalized and the output file descriptor has closed."
|
285
|
+
);
|
286
|
+
resolve();
|
287
|
+
});
|
288
|
+
|
289
|
+
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
290
|
+
archive.on("warning", function (err) {
|
291
|
+
if (err.code === "ENOENT") {
|
292
|
+
logger("压缩-warning:" + err);
|
293
|
+
} else {
|
294
|
+
// throw error
|
295
|
+
throw err;
|
296
|
+
}
|
297
|
+
});
|
298
|
+
|
299
|
+
// good practice to catch this error explicitly
|
300
|
+
archive.on("error", function (err) {
|
301
|
+
logger("压缩失败!" + err);
|
302
|
+
reject();
|
303
|
+
});
|
304
|
+
|
305
|
+
archive.pipe(zipStream);
|
306
|
+
|
307
|
+
const directories = [
|
308
|
+
ZIP_VIDEO_DIR,
|
309
|
+
SCREENSHOT_DIR,
|
310
|
+
ZIP_WATERMARK_VIDEO_DIR,
|
311
|
+
ZIP_SCREENSHOT_DIR,
|
312
|
+
ZIP_VIDEO_DIR_400,
|
313
|
+
];
|
314
|
+
|
315
|
+
directories.forEach((dir) => {
|
316
|
+
const dirPath = path.join(inputPath, dir);
|
317
|
+
archive.directory(dirPath, dir);
|
318
|
+
});
|
319
|
+
|
320
|
+
archive.file(originFilePath, {
|
321
|
+
name: path.join(SOURCE_VIDEO_DIR, fileName),
|
322
|
+
});
|
323
|
+
// 完成归档
|
324
|
+
archive.finalize();
|
325
|
+
});
|
326
|
+
};
|
327
|
+
|
328
|
+
/**
|
329
|
+
* 获取 元数据
|
330
|
+
*/
|
331
|
+
const getMetadata = async (
|
332
|
+
indexFilePath,
|
333
|
+
originFilePath,
|
334
|
+
{ title, keyword, anotherId }
|
335
|
+
) => {
|
336
|
+
//第一张截图
|
337
|
+
const photoInputPath = path.join(
|
338
|
+
indexFilePath,
|
339
|
+
SCREENSHOT_DIR,
|
340
|
+
"screenshots-1.png"
|
341
|
+
);
|
342
|
+
|
343
|
+
const videoMetadataComand = ffmpeg(originFilePath);
|
344
|
+
const photoMetadataComand = ffmpeg(photoInputPath);
|
345
|
+
const metaDataParams = {
|
346
|
+
userid: 10876358,
|
347
|
+
username: "樊建",
|
348
|
+
pixel_width: "",
|
349
|
+
pixel_height: "",
|
350
|
+
size: "",
|
351
|
+
duration: "",
|
352
|
+
video_id: anotherId,
|
353
|
+
title,
|
354
|
+
keyword,
|
355
|
+
pr: 0,
|
356
|
+
format: "mp4",
|
357
|
+
category_id: 4,
|
358
|
+
demand_kind: 23,
|
359
|
+
source_from: 74,
|
360
|
+
plate_id: 5,
|
361
|
+
tag_id: 158,
|
362
|
+
is_government: 0,
|
363
|
+
preview_width: "",
|
364
|
+
preview_height: "",
|
365
|
+
};
|
366
|
+
await new Promise((resolve, reject) => {
|
367
|
+
videoMetadataComand.ffprobe(function (err, metadata) {
|
368
|
+
const videoStream = metadata.streams.find(
|
369
|
+
(s) => s.codec_type === "video"
|
370
|
+
);
|
371
|
+
const formatStream = metadata.format;
|
372
|
+
|
373
|
+
metaDataParams.pixel_width = videoStream.width;
|
374
|
+
metaDataParams.pixel_height = videoStream.height;
|
375
|
+
metaDataParams.duration = videoStream.duration;
|
376
|
+
metaDataParams.size = formatStream.size;
|
377
|
+
resolve();
|
378
|
+
});
|
379
|
+
});
|
380
|
+
|
381
|
+
await new Promise((resolve, reject) => {
|
382
|
+
photoMetadataComand.ffprobe(function (err, metadata) {
|
383
|
+
const photoStream = metadata.streams.find(
|
384
|
+
(s) => s.codec_type === "video"
|
385
|
+
);
|
386
|
+
|
387
|
+
metaDataParams.preview_width = photoStream.width;
|
388
|
+
metaDataParams.preview_height = photoStream.height;
|
389
|
+
resolve();
|
390
|
+
});
|
391
|
+
});
|
392
|
+
|
393
|
+
return metaDataParams;
|
394
|
+
};
|
395
|
+
|
396
|
+
/**
|
397
|
+
* 接口
|
398
|
+
*/
|
399
|
+
const postData = (dataParams, indexFilePath, index) => {
|
400
|
+
const formData = new FormData();
|
401
|
+
|
402
|
+
const zipFiles = fs
|
403
|
+
.readdirSync(path.join(indexFilePath, ZIP_FILES_DIR))
|
404
|
+
.find((file) => file.endsWith(".zip"));
|
405
|
+
|
406
|
+
const packageZip = path.join(indexFilePath, ZIP_FILES_DIR, zipFiles);
|
407
|
+
|
408
|
+
formData.append("file", fs.createReadStream(packageZip));
|
409
|
+
for (const key in dataParams) {
|
410
|
+
if (Object.hasOwnProperty.call(dataParams, key)) {
|
411
|
+
const value = dataParams[key];
|
412
|
+
formData.append(key, value);
|
413
|
+
}
|
414
|
+
}
|
415
|
+
|
416
|
+
logger(`第${index}条等待接口返回结果……`);
|
417
|
+
|
418
|
+
// return axios.post("http://192.168.102.61:9999/upload/sound", formData, {
|
419
|
+
// return axios.post("http://192.168.101.149:9999/upload/sound", formData, {
|
420
|
+
return axios.post("http://127.0.0.1:9999/upload/video", formData, {
|
421
|
+
headers: {
|
422
|
+
"Content-Type": "multipart/form-data",
|
423
|
+
},
|
424
|
+
timeout: 300000,
|
425
|
+
});
|
426
|
+
};
|
427
|
+
|
428
|
+
/**
|
429
|
+
* 接口重试机制
|
430
|
+
*/
|
431
|
+
async function postDataWithRetry(
|
432
|
+
dataParams,
|
433
|
+
indexFilePath,
|
434
|
+
index,
|
435
|
+
fileName,
|
436
|
+
hash,
|
437
|
+
type
|
438
|
+
) {
|
439
|
+
let retryCount = 0; // 当前重试次数
|
440
|
+
|
441
|
+
while (retryCount < maxRetries) {
|
442
|
+
try {
|
443
|
+
const resData = await postData(dataParams, indexFilePath, index);
|
444
|
+
if (resData.data.code === 200) {
|
445
|
+
logger("请求成功!");
|
446
|
+
logger(resData.data.code);
|
447
|
+
// 文件名和索引值
|
448
|
+
successLogger(hash, index, fileName, type);
|
449
|
+
removeDirectory(indexFilePath);
|
450
|
+
return;
|
451
|
+
} else if (resData.data.code === 300) {
|
452
|
+
// 重复上传,不捕获此错误
|
453
|
+
logger(resData.data.code);
|
454
|
+
logger(`第${index}条文件-${index}重复上传!`);
|
455
|
+
removeDirectory(indexFilePath);
|
456
|
+
return;
|
457
|
+
} else {
|
458
|
+
logger(`请求失败,重试中... (${retryCount + 1}/${maxRetries})`);
|
459
|
+
logger(`请求code!==200: ${resData.data.code}${resData.data.msg}`);
|
460
|
+
// 延时等待一段时间后再进行重试
|
461
|
+
await new Promise((resolve) => setTimeout(resolve, 100)); // 等待0.1秒
|
462
|
+
retryCount++;
|
463
|
+
}
|
464
|
+
} catch (error) {
|
465
|
+
throw new Error("重试机制错误!" + error);
|
466
|
+
}
|
467
|
+
}
|
468
|
+
// 如果达到最大重试次数仍然失败,则抛出异常
|
469
|
+
throw new Error("请求失败,重试次数已达到上限!");
|
470
|
+
}
|
471
|
+
|
472
|
+
/**
|
473
|
+
* 任务
|
474
|
+
*/
|
475
|
+
const task = async (row, index, hash, type) => {
|
476
|
+
//index文件夹 output/0
|
477
|
+
const indexFilePath = path.join(workDir, "output", index + "");
|
478
|
+
let fileName = row.fileName;
|
479
|
+
const rowFileName = row.fileName;
|
480
|
+
const title = row.title;
|
481
|
+
const keyword = row.keyword;
|
482
|
+
/**
|
483
|
+
* 唯一标识符id,仅错误excel中有 anotherI d 列
|
484
|
+
*/
|
485
|
+
const anotherId = row.anotherId ? row.anotherId : row.fileName;
|
486
|
+
|
487
|
+
try {
|
488
|
+
logger(
|
489
|
+
"**************************" + row.fileName + "**************************"
|
490
|
+
);
|
491
|
+
|
492
|
+
// if (!fileName.includes(".")) {
|
493
|
+
// fileName = row.fileName + ".mp44444"; //findFileInDir后期返回两个 文件路径和 文件名
|
494
|
+
// }
|
495
|
+
|
496
|
+
const getPathStartTime = new Date();
|
497
|
+
// 源音频文件夹路径
|
498
|
+
const originFilePath = await findFileInDir(
|
499
|
+
path.join(workDir, ORIGIN_FILE_DIR),
|
500
|
+
fileName
|
501
|
+
);
|
502
|
+
|
503
|
+
const getPathEndTime = new Date();
|
504
|
+
const timeInSeconds1 = ((getPathEndTime - getPathStartTime) / 1000).toFixed(
|
505
|
+
2
|
506
|
+
);
|
507
|
+
|
508
|
+
logger(`第${index}条Path路径搜索时间:${timeInSeconds1}秒`);
|
509
|
+
logger(`-------------------${new Date()}------------------`);
|
510
|
+
|
511
|
+
logger("原文件路径:" + originFilePath);
|
512
|
+
logger(`第${index}条原文件路径:${originFilePath}`);
|
513
|
+
|
514
|
+
if (!originFilePath) {
|
515
|
+
logger(`第${index}条音频文件 ${fileName} 不存在`);
|
516
|
+
return;
|
517
|
+
}
|
518
|
+
|
519
|
+
await fs_asnyc.access(originFilePath, fs_asnyc.constants.F_OK);
|
520
|
+
logFileSize(originFilePath, index);
|
521
|
+
await compressVideo(fileName, indexFilePath, originFilePath);
|
522
|
+
await compressVideo400p(fileName, indexFilePath, originFilePath);
|
523
|
+
await get5Screenshots(fileName, indexFilePath, originFilePath);
|
524
|
+
await watermarkVideo(fileName, indexFilePath);
|
525
|
+
await watermarkScreenshots(indexFilePath);
|
526
|
+
await archiveZip(fileName, indexFilePath, originFilePath);
|
527
|
+
|
528
|
+
const dataParams = await getMetadata(indexFilePath, originFilePath, {
|
529
|
+
title,
|
530
|
+
keyword,
|
531
|
+
anotherId,
|
532
|
+
});
|
533
|
+
|
534
|
+
// 重试机制
|
535
|
+
await postDataWithRetry(
|
536
|
+
dataParams,
|
537
|
+
indexFilePath,
|
538
|
+
index,
|
539
|
+
fileName,
|
540
|
+
hash,
|
541
|
+
type
|
542
|
+
);
|
543
|
+
|
544
|
+
logger(
|
545
|
+
`----------------------------------------第${index}条结束---------------------------------end`
|
546
|
+
);
|
547
|
+
} catch (error) {
|
548
|
+
// 可以约定code,来表示不同的错误信息
|
549
|
+
if (error.code === "ENOENT") {
|
550
|
+
logger(`音频文件 ${fileName} 不存在`);
|
551
|
+
} else {
|
552
|
+
logger("音频任务失败(最外层catch):" + error);
|
553
|
+
}
|
554
|
+
removeDirectory(indexFilePath);
|
555
|
+
disposeError(hash, { fileName, keyword, title, anotherId });
|
556
|
+
}
|
557
|
+
};
|
558
|
+
|
559
|
+
const run = (index, data, hash, type) => {
|
560
|
+
try {
|
561
|
+
logger(
|
562
|
+
`run-------------------------------------第${index}条开始------------------------------------`
|
563
|
+
);
|
564
|
+
const row = data[index];
|
565
|
+
|
566
|
+
if (completeCount >= data.length) {
|
567
|
+
taskIndex = 0;
|
568
|
+
logger(
|
569
|
+
index +
|
570
|
+
"=========================当前任务,最后一条已结束!========================="
|
571
|
+
);
|
572
|
+
|
573
|
+
successLogger(hash, index, "done");
|
574
|
+
|
575
|
+
// 是否存在错误列表? 存在的话,继续遍历
|
576
|
+
const status = runErrorList(taskIndex, hash);
|
577
|
+
if (!status) {
|
578
|
+
logger(
|
579
|
+
"》》》》》》》》》》音频批量任务任务结束《《《《《《《《《《" + hash
|
580
|
+
);
|
581
|
+
}
|
582
|
+
|
583
|
+
return;
|
584
|
+
}
|
585
|
+
|
586
|
+
if (!row) {
|
587
|
+
return;
|
588
|
+
}
|
589
|
+
|
590
|
+
task(row, index, hash, type)
|
591
|
+
.then(() => {
|
592
|
+
taskIndex++;
|
593
|
+
completeCount++;
|
594
|
+
run(taskIndex, data, hash);
|
595
|
+
})
|
596
|
+
.catch((err) => {
|
597
|
+
throw new Error("task失败:" + err);
|
598
|
+
});
|
599
|
+
} catch (error) {
|
600
|
+
logger("捕获错误!" + error);
|
601
|
+
completeCount++;
|
602
|
+
taskIndex++;
|
603
|
+
run(taskIndex, data, hash);
|
604
|
+
}
|
605
|
+
};
|
606
|
+
|
607
|
+
const queue = (index, jsonData, hash, type) => {
|
608
|
+
const rest = jsonData.length - index;
|
609
|
+
const count = queueCount > rest ? rest : queueCount;
|
610
|
+
const queueList = [];
|
611
|
+
completeCount = index;
|
612
|
+
|
613
|
+
for (let i = 0; i < count; i++) {
|
614
|
+
queueList.push(run(i + index, jsonData, hash, type));
|
615
|
+
}
|
616
|
+
|
617
|
+
taskIndex += count - 1;
|
618
|
+
return Promise.all(queueList);
|
619
|
+
};
|
620
|
+
|
621
|
+
//yunxing cuowu wenjian
|
622
|
+
const runErrorList = (index, hash, parentHash) => {
|
623
|
+
const fileHash = parentHash || hash;
|
624
|
+
const errorExcelPath = path.join(workDir, `error${fileHash}.xlsx`);
|
625
|
+
|
626
|
+
if (retryError > 5) {
|
627
|
+
console.log("出现无法解析的错误");
|
628
|
+
return false;
|
629
|
+
}
|
630
|
+
|
631
|
+
taskIndex = index;
|
632
|
+
let taskHash = hash;
|
633
|
+
//新的运行任务-重新赋予新的任务id
|
634
|
+
if (index === 0) {
|
635
|
+
taskHash = generateUniqueHash().slice(0, 8);
|
636
|
+
}
|
637
|
+
|
638
|
+
if (isExist(errorExcelPath)) {
|
639
|
+
retryError++;
|
640
|
+
|
641
|
+
logger("开始运行错误列表");
|
642
|
+
jsonData = readExcel(errorExcelPath); // 数据来源于error excel
|
643
|
+
queue(index, jsonData, taskHash, fileHash);
|
644
|
+
return true;
|
645
|
+
}
|
646
|
+
return false;
|
647
|
+
};
|
648
|
+
|
649
|
+
const main = () => {
|
650
|
+
//当前任务hash
|
651
|
+
let hash = "";
|
652
|
+
let jsonData = [];
|
653
|
+
logger("》》》》》》》》》》视频批量任务任务开始《《《《《《《《《《" + hash);
|
654
|
+
logger("当前目录: " + workDir + ";工作目录: " + exeDir);
|
655
|
+
|
656
|
+
// 读取success.txt,判断是否是 异常中断,
|
657
|
+
// 并根据success日志获取最后一行的hash,找到对应的error_hash_excel
|
658
|
+
|
659
|
+
const successPath = path.join(workDir, "success.txt");
|
660
|
+
if (isExist(successPath)) {
|
661
|
+
const { loghash, lastItemIndex, fileName, parentHash } =
|
662
|
+
readTxt(successPath);
|
663
|
+
|
664
|
+
hash = loghash;
|
665
|
+
taskIndex = parseInt(lastItemIndex) + 1; // lastItemIndex为最后一项,所有要+1
|
666
|
+
|
667
|
+
//当前任务已经结束
|
668
|
+
if (fileName === "done") {
|
669
|
+
//找寻是否存在未上传的错误文件列表
|
670
|
+
const status = runErrorList(0, hash);
|
671
|
+
if (!status) {
|
672
|
+
logger(
|
673
|
+
"》》》》》》》》》》音频批量任务任务结束《《《《《《《《《《" + hash
|
674
|
+
);
|
675
|
+
}
|
676
|
+
} else {
|
677
|
+
//当前任务异常中断
|
678
|
+
|
679
|
+
//中断的任务类型为错误任务
|
680
|
+
if (parentHash) {
|
681
|
+
runErrorList(taskIndex, hash, parentHash);
|
682
|
+
} else {
|
683
|
+
jsonData = readExcel(excelDir);
|
684
|
+
queue(taskIndex, jsonData, hash);
|
685
|
+
}
|
686
|
+
}
|
687
|
+
} else {
|
688
|
+
hash = generateUniqueHash().slice(0, 8);
|
689
|
+
logger("成功日志不存在,开始新任务");
|
690
|
+
|
691
|
+
jsonData = readExcel(excelDir);
|
692
|
+
queue(taskIndex, jsonData, hash);
|
693
|
+
}
|
694
|
+
};
|
695
|
+
|
696
|
+
main();
|
package/utils/logger.js
CHANGED
@@ -17,11 +17,15 @@ const logger = (log) => {
|
|
17
17
|
* 成功日志
|
18
18
|
* @param {*} 日志内容 hash index fileName
|
19
19
|
*/
|
20
|
-
const successLogger = (hash, index, fileName) => {
|
20
|
+
const successLogger = (hash, index, fileName, type = "") => {
|
21
21
|
console.log(fileName);
|
22
|
-
fs.writeFileSync(
|
23
|
-
|
24
|
-
|
22
|
+
fs.writeFileSync(
|
23
|
+
"success.txt",
|
24
|
+
hash + " " + index + " " + fileName + " " + type + "\n",
|
25
|
+
{
|
26
|
+
flag: "a",
|
27
|
+
}
|
28
|
+
);
|
25
29
|
};
|
26
30
|
|
27
31
|
//错误日志- xlsx
|
package/utils/settleFiles.js
CHANGED
@@ -86,9 +86,8 @@ function readTxt(path) {
|
|
86
86
|
|
87
87
|
const lastItem = filteredArray[filteredArray.length - 1];
|
88
88
|
|
89
|
-
const loghash = lastItem.split(" ")
|
90
|
-
|
91
|
-
return { loghash, lastItemIndex };
|
89
|
+
const [loghash, lastItemIndex, fileName, parentHash] = lastItem.split(" ");
|
90
|
+
return { loghash, lastItemIndex, fileName, parentHash };
|
92
91
|
} catch (error) {
|
93
92
|
console.error("readErrorTxt函数出错:", error);
|
94
93
|
return {};
|