tools_batch_files 1.0.36 → 1.0.37
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/batchTash.js +44 -0
- package/src/photoFn/photoBatch.js +560 -0
- package/src/queue.js +519 -0
- package/src/videoFn/videoBatch.js +696 -696
@@ -1,696 +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 =
|
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();
|
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 = 5;
|
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();
|