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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tools_batch_files",
3
- "version": "1.0.34",
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(__dirname, "..", "vocal_print", "mz.mp3");
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
- ].join(";");
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://127.0.0.1:9999/upload/sound", formData, {
276
- return axios.post("http://192.168.101.149:9999/upload/sound", formData, {
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(dataParams, indexFilePath, index, fileName, hash);
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 (!row) {
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 errorExcelPath = path.join(workDir, `error${hash}.xlsx`);
433
- if (isExist(errorExcelPath)) {
434
- const jsonData2 = readExcel(errorExcelPath);
435
- queue(taskIndex, jsonData2, hash);
436
- } else {
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
- task(row, index, hash)
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 = (taskIndex, jsonData, hash) => {
459
- for (let i = 0; i < queueCount; i++) {
460
- run(i + taskIndex, jsonData, hash);
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 } = readTxt(successPath);
477
- const errorExcelPath = path.join(workDir, `error${loghash}.xlsx`);
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
- if (isExist(errorExcelPath)) {
484
- jsonData = readExcel(errorExcelPath); // 数据来源于error excel
485
- queue(taskIndex, jsonData, hash);
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
- logger("跑干了!");
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("success.txt", hash + " " + index + " " + fileName + "\n", {
23
- flag: "a",
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
@@ -86,9 +86,8 @@ function readTxt(path) {
86
86
 
87
87
  const lastItem = filteredArray[filteredArray.length - 1];
88
88
 
89
- const loghash = lastItem.split(" ")[0];
90
- const lastItemIndex = parseInt(lastItem.split(" ")[1]);
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 {};