tools_batch_files 1.0.38 → 1.0.40
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 -3
- package/src/audioFn/audioExample.js +403 -0
- package/src/photoFn/photoBatch.js +3 -3
- package/src/queue.js +1 -0
- package/src/videoFn/videoBatch.js +48 -21
- package/utils/index.js +0 -0
- package/utils/logger.js +40 -3
- package/index.js +0 -618
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "tools_batch_files",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.40",
|
4
4
|
"description": "批处理视频工具",
|
5
5
|
"keywords": [
|
6
6
|
"utils",
|
@@ -10,10 +10,10 @@
|
|
10
10
|
],
|
11
11
|
"main": "index.js",
|
12
12
|
"bin": {
|
13
|
-
"tbf": "./index.js",
|
14
13
|
"tbfp": "./src/photoBatch.js",
|
15
14
|
"tbfps": "./src/photoFn/photoBatch.js",
|
16
15
|
"tbfa": "./src/audioFn/audioBatch.js",
|
16
|
+
"tbfae": "./src/audioFn/audioExample.js",
|
17
17
|
"tbfv": "./src/videoFn/videoBatch.js",
|
18
18
|
"tbfre": "./src/removeFailVideo.js"
|
19
19
|
},
|
@@ -34,7 +34,6 @@
|
|
34
34
|
"xlsx": "^0.18.5"
|
35
35
|
},
|
36
36
|
"files": [
|
37
|
-
"index.js",
|
38
37
|
"imgs",
|
39
38
|
"vocal_print",
|
40
39
|
"点我批量上传视频.bat",
|
@@ -0,0 +1,403 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
const { logger } = require("../../utils/logger");
|
3
|
+
const {
|
4
|
+
findFileInDir,
|
5
|
+
logFileSize,
|
6
|
+
ensureDirSync,
|
7
|
+
removeDirectory,
|
8
|
+
} = require("../../utils/settleFiles");
|
9
|
+
const ffmpeg = require("fluent-ffmpeg");
|
10
|
+
const fs = require("fs");
|
11
|
+
const path = require("path");
|
12
|
+
const fs_asnyc = require("fs").promises;
|
13
|
+
const archiver = require("archiver");
|
14
|
+
const axios = require("axios");
|
15
|
+
const FormData = require("form-data");
|
16
|
+
const { Queue } = require("./queue");
|
17
|
+
|
18
|
+
const ORIGIN_FILE_DIR = "audio";
|
19
|
+
const WM_AUDIO_DIR = "watermark_audio";
|
20
|
+
const WAV_AUDIO_DIR = "wav_audio";
|
21
|
+
const SOURCE_AUDIO_DIR = "source_audio";
|
22
|
+
const ZIP_FILES_DIR = "zip";
|
23
|
+
//水印
|
24
|
+
const watermarkAudioPath = path.join(
|
25
|
+
__dirname,
|
26
|
+
"..",
|
27
|
+
"..",
|
28
|
+
"vocal_print",
|
29
|
+
"mz.mp3"
|
30
|
+
);
|
31
|
+
const longWatermarkAudioPath = path.join(
|
32
|
+
__dirname,
|
33
|
+
"..",
|
34
|
+
"..",
|
35
|
+
"vocal_print",
|
36
|
+
"800a.mp3"
|
37
|
+
);
|
38
|
+
|
39
|
+
const maxRetries = 1; // 最大重试次数
|
40
|
+
|
41
|
+
/**
|
42
|
+
* 声纹
|
43
|
+
*/
|
44
|
+
const watermarkAudio = (indexFilePath, originFilePath, fileName, duration) => {
|
45
|
+
//存放声纹文件夹
|
46
|
+
const outputDir = path.join(indexFilePath, WM_AUDIO_DIR);
|
47
|
+
const outputFile = path.join(outputDir, fileName);
|
48
|
+
let vocalPath = "";
|
49
|
+
|
50
|
+
ensureDirSync(outputDir);
|
51
|
+
logger("duration" + duration);
|
52
|
+
|
53
|
+
// 确保输出时长与源音频保持一致,方案改为截取声纹音频了。
|
54
|
+
const complexFilter = [
|
55
|
+
`[0:a]volume=1[a0]`, // 设置原音频音量为1,重命名为a0
|
56
|
+
`[1:a]atrim=0:duration=${duration},volume=1.5[a1]`, // 对声纹音频流进行截取操作,并设置音量,重命名为a1
|
57
|
+
`[a0][a1]amix=inputs=2[a]`, // 合并两个音频流为一个输出流,重命名为a
|
58
|
+
`[a]volume=3.0`, // 设置输出音频的音量
|
59
|
+
];
|
60
|
+
|
61
|
+
if (duration <= 5) {
|
62
|
+
//直接合并,放在末尾,下次补上
|
63
|
+
vocalPath = watermarkAudioPath;
|
64
|
+
} else if (duration > 5 && duration < 30) {
|
65
|
+
// 5s开始/ 每隔10s出现一次
|
66
|
+
vocalPath = watermarkAudioPath;
|
67
|
+
|
68
|
+
const totalCount = Math.floor((duration + 5) / 10);
|
69
|
+
for (let i = 1; i <= totalCount; i++) {
|
70
|
+
if (i === 1) {
|
71
|
+
complexFilter.push(`[1:a]adelay=5000|2000,volume=2.0[a${i}]`);
|
72
|
+
} else {
|
73
|
+
complexFilter.push(
|
74
|
+
`[1:a]adelay=${5000 + 10000 * (i - 1)}|2000,volume=2.0[a${i}]`
|
75
|
+
);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
} else {
|
79
|
+
//30以上 10s开始 每隔10s出现一次
|
80
|
+
vocalPath = longWatermarkAudioPath;
|
81
|
+
}
|
82
|
+
|
83
|
+
return new Promise((resolve, reject) => {
|
84
|
+
const watermarkCommand = ffmpeg();
|
85
|
+
// 打声纹
|
86
|
+
watermarkCommand
|
87
|
+
.input(originFilePath)
|
88
|
+
.input(vocalPath) // 声纹音频文件
|
89
|
+
|
90
|
+
.complexFilter(complexFilter.join(";"))
|
91
|
+
.output(outputFile)
|
92
|
+
.on("error", (err) => {
|
93
|
+
logger("添加声纹出错: " + err);
|
94
|
+
reject(err);
|
95
|
+
})
|
96
|
+
.on("end", () => {
|
97
|
+
logger("添加声纹完成: " + outputFile);
|
98
|
+
resolve();
|
99
|
+
})
|
100
|
+
.run();
|
101
|
+
});
|
102
|
+
};
|
103
|
+
|
104
|
+
/**
|
105
|
+
* 生成wav
|
106
|
+
*/
|
107
|
+
const wavAudio = (indexFilePath, originFilePath, fileName) => {
|
108
|
+
//存放wav文件夹
|
109
|
+
const outputDir = path.join(indexFilePath, WAV_AUDIO_DIR);
|
110
|
+
ensureDirSync(outputDir);
|
111
|
+
|
112
|
+
const outputFile = path.join(outputDir, fileName.replace(".mp3", ".wav"));
|
113
|
+
|
114
|
+
return new Promise((resolve, reject) => {
|
115
|
+
const watermarkCommand = ffmpeg(originFilePath);
|
116
|
+
watermarkCommand
|
117
|
+
.output(outputFile)
|
118
|
+
.on("error", (err) => {
|
119
|
+
logger("生成wav出错: " + err);
|
120
|
+
reject();
|
121
|
+
})
|
122
|
+
.on("end", () => {
|
123
|
+
logger("wav文件生成完成: " + outputFile);
|
124
|
+
resolve();
|
125
|
+
})
|
126
|
+
.run();
|
127
|
+
});
|
128
|
+
};
|
129
|
+
|
130
|
+
/**
|
131
|
+
* 打包物料
|
132
|
+
*/
|
133
|
+
const archiveZip = (fileName, inputPath, originFilePath) => {
|
134
|
+
const zipDir = path.join(inputPath, "zip");
|
135
|
+
const timestamp = new Date().getTime();
|
136
|
+
|
137
|
+
ensureDirSync(zipDir);
|
138
|
+
const zipStream = fs.createWriteStream(
|
139
|
+
path.join(zipDir, `package${timestamp}.zip`)
|
140
|
+
);
|
141
|
+
const archive = archiver("zip", {
|
142
|
+
zlib: { level: 9 },
|
143
|
+
});
|
144
|
+
|
145
|
+
return new Promise((resolve, reject) => {
|
146
|
+
zipStream.on("close", function () {
|
147
|
+
logger("压缩数据:" + archive.pointer() + " total bytes");
|
148
|
+
logger(
|
149
|
+
"完成归档archiver has been finalized and the output file descriptor has closed."
|
150
|
+
);
|
151
|
+
resolve();
|
152
|
+
});
|
153
|
+
|
154
|
+
archive.on("warning", function (err) {
|
155
|
+
if (err.code === "ENOENT") {
|
156
|
+
logger("压缩-warning:" + err);
|
157
|
+
} else {
|
158
|
+
throw err;
|
159
|
+
}
|
160
|
+
});
|
161
|
+
|
162
|
+
archive.on("error", function (err) {
|
163
|
+
logger("压缩失败!" + err);
|
164
|
+
reject();
|
165
|
+
});
|
166
|
+
|
167
|
+
archive.pipe(zipStream);
|
168
|
+
const directories = [WAV_AUDIO_DIR, WM_AUDIO_DIR];
|
169
|
+
|
170
|
+
directories.forEach((dir) => {
|
171
|
+
const dirPath = path.join(inputPath, dir);
|
172
|
+
archive.directory(dirPath, dir);
|
173
|
+
});
|
174
|
+
|
175
|
+
archive.file(originFilePath, {
|
176
|
+
name: path.join(SOURCE_AUDIO_DIR, fileName),
|
177
|
+
});
|
178
|
+
// 完成归档
|
179
|
+
archive.finalize();
|
180
|
+
});
|
181
|
+
};
|
182
|
+
|
183
|
+
/**
|
184
|
+
* 获取 元数据
|
185
|
+
*/
|
186
|
+
const getMetadata = async (originFilePath, { title, keyword, anotherId }) => {
|
187
|
+
//源音频数据
|
188
|
+
const photoMetadataComand = ffmpeg(originFilePath);
|
189
|
+
|
190
|
+
const metaDataParams = {
|
191
|
+
userid: 192375294,
|
192
|
+
username: "张杰",
|
193
|
+
title,
|
194
|
+
keyword,
|
195
|
+
en_keyword: "",
|
196
|
+
en_title: "",
|
197
|
+
size: "",
|
198
|
+
pr: 0,
|
199
|
+
sampling: "", // 采样率
|
200
|
+
duration: "",
|
201
|
+
tag_ids: "3",
|
202
|
+
source_from: "sound_1",
|
203
|
+
anotherId,
|
204
|
+
};
|
205
|
+
|
206
|
+
await new Promise((resolve, reject) => {
|
207
|
+
photoMetadataComand.ffprobe(function (err, metadata) {
|
208
|
+
if (metadata) {
|
209
|
+
const formatStream = metadata.format;
|
210
|
+
const audioStream = metadata.streams.find(
|
211
|
+
(s) => s.codec_type === "audio"
|
212
|
+
);
|
213
|
+
|
214
|
+
metaDataParams.size = formatStream.size;
|
215
|
+
metaDataParams.duration = audioStream.duration;
|
216
|
+
metaDataParams.sampling = audioStream.sample_rate;
|
217
|
+
|
218
|
+
resolve();
|
219
|
+
} else {
|
220
|
+
reject(err);
|
221
|
+
}
|
222
|
+
});
|
223
|
+
});
|
224
|
+
|
225
|
+
return metaDataParams;
|
226
|
+
};
|
227
|
+
|
228
|
+
/**
|
229
|
+
* 接口
|
230
|
+
*/
|
231
|
+
const postData = (dataParams, indexFilePath, index) => {
|
232
|
+
const formData = new FormData();
|
233
|
+
|
234
|
+
const zipFiles = fs
|
235
|
+
.readdirSync(path.join(indexFilePath, ZIP_FILES_DIR))
|
236
|
+
.find((file) => file.endsWith(".zip"));
|
237
|
+
|
238
|
+
const packageZip = path.join(indexFilePath, ZIP_FILES_DIR, zipFiles);
|
239
|
+
|
240
|
+
formData.append("file", fs.createReadStream(packageZip));
|
241
|
+
for (const key in dataParams) {
|
242
|
+
if (Object.hasOwnProperty.call(dataParams, key)) {
|
243
|
+
const value = dataParams[key];
|
244
|
+
formData.append(key, value);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
|
248
|
+
logger(`第${index}条等待接口返回结果……`);
|
249
|
+
|
250
|
+
// return axios.post("http://192.168.102.61:9999/upload/sound", formData, {
|
251
|
+
// return axios.post("http://192.168.101.149:9999/upload/sound", formData, {
|
252
|
+
return axios.post("http://example/upload/sound", formData, {
|
253
|
+
headers: {
|
254
|
+
"Content-Type": "multipart/form-data",
|
255
|
+
},
|
256
|
+
timeout: 300000,
|
257
|
+
});
|
258
|
+
};
|
259
|
+
|
260
|
+
/**
|
261
|
+
* 接口重试机制
|
262
|
+
*/
|
263
|
+
async function postDataWithRetry(dataParams, indexFilePath, index) {
|
264
|
+
let retryCount = 0; // 当前重试次数
|
265
|
+
|
266
|
+
while (retryCount < maxRetries) {
|
267
|
+
try {
|
268
|
+
const resData = await postData(dataParams, indexFilePath, index);
|
269
|
+
if (resData.data.code === 200) {
|
270
|
+
logger("请求成功!");
|
271
|
+
logger(resData.data.code);
|
272
|
+
|
273
|
+
removeDirectory(indexFilePath);
|
274
|
+
return;
|
275
|
+
} else if (resData.data.code === 300) {
|
276
|
+
// 重复上传,不捕获此错误
|
277
|
+
logger(resData.data.code);
|
278
|
+
logger(`第${index}条文件-${index}重复上传!`);
|
279
|
+
removeDirectory(indexFilePath);
|
280
|
+
return;
|
281
|
+
} else {
|
282
|
+
logger(`请求失败,重试中... (${retryCount + 1}/${maxRetries})`);
|
283
|
+
logger(`请求code!==200: ${resData.data.code}${resData.data.msg}`);
|
284
|
+
// 延时等待一段时间后再进行重试
|
285
|
+
await new Promise((resolve) => setTimeout(resolve, 100)); // 等待0.1秒
|
286
|
+
retryCount++;
|
287
|
+
}
|
288
|
+
} catch (error) {
|
289
|
+
throw new Error("重试机制错误!" + error);
|
290
|
+
}
|
291
|
+
}
|
292
|
+
// 如果达到最大重试次数仍然失败,则抛出异常
|
293
|
+
throw new Error("请求失败,重试次数已达到上限!");
|
294
|
+
}
|
295
|
+
|
296
|
+
//当前工作目录
|
297
|
+
const workDir = process.cwd();
|
298
|
+
|
299
|
+
const audioQueue = new Queue({
|
300
|
+
//最大并发数量
|
301
|
+
queueMaxCount: 10,
|
302
|
+
//excel 主键-非必填-无主键时自动生成
|
303
|
+
primaryKey: "",
|
304
|
+
//excel文件路径-必填
|
305
|
+
excelPath: path.join(workDir, "excel", "20240531.xlsx"),
|
306
|
+
//当前工作路径-必填
|
307
|
+
workDir: workDir,
|
308
|
+
//当前可执行任务回调-必填
|
309
|
+
onProcess: (row) => {
|
310
|
+
//..todo 执行需要压缩上传的文件所有过程。
|
311
|
+
return new Promise(async (resolve, reject) => {
|
312
|
+
//index文件夹 output/0
|
313
|
+
let fileName = row.fileName;
|
314
|
+
const anotherId = row.queue_id;
|
315
|
+
const rowFileName = row.fileName;
|
316
|
+
const title = row.title;
|
317
|
+
const keyword = row.keyword;
|
318
|
+
const indexFilePath = path.join(workDir, "output", anotherId + "");
|
319
|
+
|
320
|
+
try {
|
321
|
+
logger(
|
322
|
+
"**************************" +
|
323
|
+
row.fileName +
|
324
|
+
"**************************"
|
325
|
+
);
|
326
|
+
// Excel的列名分别为: fileName title keyword
|
327
|
+
// const keywordArr = row.keyword.split(",");
|
328
|
+
// const filteredArray = keywordArr.filter((item) => item.trim() !== "");
|
329
|
+
// const keyword = filteredArray.join(" ");
|
330
|
+
|
331
|
+
if (!fileName.includes(".")) {
|
332
|
+
fileName = row.fileName + ".mp3";
|
333
|
+
}
|
334
|
+
|
335
|
+
const getPathStartTime = new Date();
|
336
|
+
// 源音频文件夹路径
|
337
|
+
const originFilePath = await findFileInDir(
|
338
|
+
path.join(workDir, ORIGIN_FILE_DIR),
|
339
|
+
fileName
|
340
|
+
);
|
341
|
+
|
342
|
+
const getPathEndTime = new Date();
|
343
|
+
const timeInSeconds1 = (
|
344
|
+
(getPathEndTime - getPathStartTime) /
|
345
|
+
1000
|
346
|
+
).toFixed(2);
|
347
|
+
|
348
|
+
logger(`第${index}条Path路径搜索时间:${timeInSeconds1}秒`);
|
349
|
+
logger(`-------------------${new Date()}------------------`);
|
350
|
+
|
351
|
+
logger("原文件路径:" + originFilePath);
|
352
|
+
logger(`第${index}条原文件路径:${originFilePath}`);
|
353
|
+
|
354
|
+
if (!originFilePath) {
|
355
|
+
logger(`第${index}条音频文件 ${fileName} 不存在`);
|
356
|
+
return;
|
357
|
+
}
|
358
|
+
|
359
|
+
await fs_asnyc.access(originFilePath, fs_asnyc.constants.F_OK);
|
360
|
+
logFileSize(originFilePath, index);
|
361
|
+
const dataParams = await getMetadata(originFilePath, {
|
362
|
+
rowFileName,
|
363
|
+
title,
|
364
|
+
keyword,
|
365
|
+
anotherId,
|
366
|
+
});
|
367
|
+
await wavAudio(indexFilePath, originFilePath, fileName);
|
368
|
+
await watermarkAudio(
|
369
|
+
indexFilePath,
|
370
|
+
originFilePath,
|
371
|
+
fileName,
|
372
|
+
dataParams.duration
|
373
|
+
);
|
374
|
+
await archiveZip(fileName, indexFilePath, originFilePath);
|
375
|
+
|
376
|
+
// 重试机制
|
377
|
+
await postDataWithRetry(dataParams, indexFilePath, index);
|
378
|
+
|
379
|
+
logger(
|
380
|
+
`----------------------------------------第${index}条结束---------------------------------end`
|
381
|
+
);
|
382
|
+
resolve();
|
383
|
+
} catch (error) {
|
384
|
+
// 可以约定code,来表示不同的错误信息
|
385
|
+
if (error.code === "ENOENT") {
|
386
|
+
logger(`音频文件 ${fileName} 不存在`);
|
387
|
+
} else {
|
388
|
+
logger("音频任务失败(最外层catch):" + error);
|
389
|
+
}
|
390
|
+
removeDirectory(indexFilePath);
|
391
|
+
|
392
|
+
reject();
|
393
|
+
}
|
394
|
+
});
|
395
|
+
},
|
396
|
+
//当前任务全部结束-必填
|
397
|
+
onComplete: () => {
|
398
|
+
console.log("Congratulations Complete");
|
399
|
+
},
|
400
|
+
});
|
401
|
+
|
402
|
+
//开始上传任务
|
403
|
+
audioQueue.start();
|
@@ -41,7 +41,7 @@ const exeDir = __dirname;
|
|
41
41
|
const workDir = process.cwd();
|
42
42
|
|
43
43
|
//图片水印
|
44
|
-
const watermarkImage = path.join(exeDir, "imgs", "
|
44
|
+
const watermarkImage = path.join(exeDir, "..", "..", "imgs", "picWater.png");
|
45
45
|
|
46
46
|
// 表格目录,从excel文件夹中查找
|
47
47
|
const excelFiles = fs
|
@@ -278,7 +278,7 @@ const postData = (dataParams, indexFilePath, index) => {
|
|
278
278
|
|
279
279
|
// return axios.post("http://192.168.102.61:9999/upload/sound", formData, {
|
280
280
|
// return axios.post("http://192.168.101.149:9999/upload/sound", formData, {
|
281
|
-
return axios.post("http://127.0.0.1:9999/upload/
|
281
|
+
return axios.post("http://127.0.0.1:9999/upload/photo", formData, {
|
282
282
|
headers: {
|
283
283
|
"Content-Type": "multipart/form-data",
|
284
284
|
},
|
@@ -514,7 +514,7 @@ const main = () => {
|
|
514
514
|
//当前任务hash
|
515
515
|
let hash = "";
|
516
516
|
let jsonData = [];
|
517
|
-
logger("
|
517
|
+
logger("》》》》》》》》》》图片批量任务任务开始《《《《《《《《《《" + hash);
|
518
518
|
logger("当前目录: " + workDir + ";工作目录: " + exeDir);
|
519
519
|
|
520
520
|
// 读取success.txt,判断是否是 异常中断,
|
package/src/queue.js
CHANGED
@@ -41,7 +41,13 @@ const exeDir = __dirname;
|
|
41
41
|
const workDir = process.cwd();
|
42
42
|
|
43
43
|
//图片水印
|
44
|
-
const watermarkImage = path.join(
|
44
|
+
const watermarkImage = path.join(
|
45
|
+
exeDir,
|
46
|
+
"..",
|
47
|
+
"..",
|
48
|
+
"imgs",
|
49
|
+
"miz-watermark.png"
|
50
|
+
);
|
45
51
|
|
46
52
|
// 表格目录,从excel文件夹中查找
|
47
53
|
const excelFiles = fs
|
@@ -331,7 +337,16 @@ const archiveZip = (fileName, inputPath, originFilePath) => {
|
|
331
337
|
const getMetadata = async (
|
332
338
|
indexFilePath,
|
333
339
|
originFilePath,
|
334
|
-
{
|
340
|
+
{
|
341
|
+
title,
|
342
|
+
keyword,
|
343
|
+
anotherId,
|
344
|
+
category_id,
|
345
|
+
demand_kind,
|
346
|
+
tag_id,
|
347
|
+
userid,
|
348
|
+
username,
|
349
|
+
}
|
335
350
|
) => {
|
336
351
|
//第一张截图
|
337
352
|
const photoInputPath = path.join(
|
@@ -343,8 +358,8 @@ const getMetadata = async (
|
|
343
358
|
const videoMetadataComand = ffmpeg(originFilePath);
|
344
359
|
const photoMetadataComand = ffmpeg(photoInputPath);
|
345
360
|
const metaDataParams = {
|
346
|
-
userid
|
347
|
-
username
|
361
|
+
userid,
|
362
|
+
username,
|
348
363
|
pixel_width: "",
|
349
364
|
pixel_height: "",
|
350
365
|
size: "",
|
@@ -354,11 +369,11 @@ const getMetadata = async (
|
|
354
369
|
keyword,
|
355
370
|
pr: 0,
|
356
371
|
format: "mp4",
|
357
|
-
category_id
|
358
|
-
demand_kind
|
359
|
-
source_from
|
372
|
+
category_id,
|
373
|
+
demand_kind,
|
374
|
+
source_from,
|
360
375
|
plate_id: 5,
|
361
|
-
tag_id
|
376
|
+
tag_id,
|
362
377
|
is_government: 0,
|
363
378
|
preview_width: "",
|
364
379
|
preview_height: "",
|
@@ -421,7 +436,7 @@ const postData = (dataParams, indexFilePath, index) => {
|
|
421
436
|
headers: {
|
422
437
|
"Content-Type": "multipart/form-data",
|
423
438
|
},
|
424
|
-
timeout:
|
439
|
+
timeout: 1200000,
|
425
440
|
});
|
426
441
|
};
|
427
442
|
|
@@ -473,12 +488,21 @@ async function postDataWithRetry(
|
|
473
488
|
* 任务
|
474
489
|
*/
|
475
490
|
const task = async (row, index, hash, type) => {
|
491
|
+
logger("88888888888888888888888888row8888888888888888888888");
|
492
|
+
const copyRow = { ...row };
|
493
|
+
logger(copyRow);
|
494
|
+
|
476
495
|
//index文件夹 output/0
|
477
496
|
const indexFilePath = path.join(workDir, "output", index + "");
|
478
|
-
let fileName = row.fileName;
|
479
497
|
const rowFileName = row.fileName;
|
480
|
-
|
481
|
-
|
498
|
+
|
499
|
+
let fileName = row.fileName;
|
500
|
+
// const title = row.title;
|
501
|
+
// const keyword = row.keyword;
|
502
|
+
// const category_id = row.category_id;
|
503
|
+
// const demand_kind = row.demand_kind;
|
504
|
+
// const tag_id = row.tag_id;
|
505
|
+
|
482
506
|
/**
|
483
507
|
* 唯一标识符id,仅错误excel中有 anotherI d 列
|
484
508
|
*/
|
@@ -494,7 +518,7 @@ const task = async (row, index, hash, type) => {
|
|
494
518
|
// }
|
495
519
|
|
496
520
|
const getPathStartTime = new Date();
|
497
|
-
//
|
521
|
+
// 源视频文件夹路径
|
498
522
|
const originFilePath = await findFileInDir(
|
499
523
|
path.join(workDir, ORIGIN_FILE_DIR),
|
500
524
|
fileName
|
@@ -512,7 +536,7 @@ const task = async (row, index, hash, type) => {
|
|
512
536
|
logger(`第${index}条原文件路径:${originFilePath}`);
|
513
537
|
|
514
538
|
if (!originFilePath) {
|
515
|
-
logger(`第${index}
|
539
|
+
logger(`第${index}条视频文件 ${fileName} 不存在`);
|
516
540
|
return;
|
517
541
|
}
|
518
542
|
|
@@ -526,9 +550,8 @@ const task = async (row, index, hash, type) => {
|
|
526
550
|
await archiveZip(fileName, indexFilePath, originFilePath);
|
527
551
|
|
528
552
|
const dataParams = await getMetadata(indexFilePath, originFilePath, {
|
529
|
-
title,
|
530
|
-
keyword,
|
531
553
|
anotherId,
|
554
|
+
...copyRow,
|
532
555
|
});
|
533
556
|
|
534
557
|
// 重试机制
|
@@ -547,12 +570,16 @@ const task = async (row, index, hash, type) => {
|
|
547
570
|
} catch (error) {
|
548
571
|
// 可以约定code,来表示不同的错误信息
|
549
572
|
if (error.code === "ENOENT") {
|
550
|
-
logger(
|
573
|
+
logger(`视频文件 ${fileName} 不存在`);
|
551
574
|
} else {
|
552
|
-
logger("
|
575
|
+
logger("视频任务失败(最外层catch):" + error);
|
553
576
|
}
|
554
577
|
removeDirectory(indexFilePath);
|
555
|
-
disposeError(hash, {
|
578
|
+
disposeError(hash, {
|
579
|
+
fileName,
|
580
|
+
anotherId,
|
581
|
+
...copyRow,
|
582
|
+
});
|
556
583
|
}
|
557
584
|
};
|
558
585
|
|
@@ -576,7 +603,7 @@ const run = (index, data, hash, type) => {
|
|
576
603
|
const status = runErrorList(taskIndex, hash);
|
577
604
|
if (!status) {
|
578
605
|
logger(
|
579
|
-
"
|
606
|
+
"》》》》》》》》》》视频批量任务任务结束《《《《《《《《《《" + hash
|
580
607
|
);
|
581
608
|
}
|
582
609
|
|
@@ -670,7 +697,7 @@ const main = () => {
|
|
670
697
|
const status = runErrorList(0, hash);
|
671
698
|
if (!status) {
|
672
699
|
logger(
|
673
|
-
"
|
700
|
+
"》》》》》》》》》》视频批量任务任务结束《《《《《《《《《《" + hash
|
674
701
|
);
|
675
702
|
}
|
676
703
|
} else {
|
package/utils/index.js
CHANGED
File without changes
|
package/utils/logger.js
CHANGED
@@ -29,9 +29,35 @@ const successLogger = (hash, index, fileName, type = "") => {
|
|
29
29
|
};
|
30
30
|
|
31
31
|
//错误日志- xlsx
|
32
|
-
const disposeError = (
|
32
|
+
const disposeError = (
|
33
|
+
hash,
|
34
|
+
{
|
35
|
+
fileName,
|
36
|
+
keyword,
|
37
|
+
title,
|
38
|
+
anotherId,
|
39
|
+
category_id,
|
40
|
+
demand_kind,
|
41
|
+
tag_id,
|
42
|
+
userid,
|
43
|
+
username,
|
44
|
+
}
|
45
|
+
) => {
|
33
46
|
// 构建 Excel 数据
|
34
|
-
const data = [
|
47
|
+
const data = [
|
48
|
+
[
|
49
|
+
fileName,
|
50
|
+
keyword,
|
51
|
+
title,
|
52
|
+
anotherId,
|
53
|
+
category_id,
|
54
|
+
demand_kind,
|
55
|
+
tag_id,
|
56
|
+
userid,
|
57
|
+
username,
|
58
|
+
source_from
|
59
|
+
],
|
60
|
+
]; // 不包含表头
|
35
61
|
|
36
62
|
// 读取已存在的 Excel 文件
|
37
63
|
let existingData = [];
|
@@ -45,7 +71,18 @@ const disposeError = (hash, { fileName, keyword, title, anotherId }) => {
|
|
45
71
|
} catch (error) {
|
46
72
|
logger("No existing file found, creating new one.");
|
47
73
|
// 如果文件不存在,添加表头
|
48
|
-
existingData.push([
|
74
|
+
existingData.push([
|
75
|
+
"fileName",
|
76
|
+
"keyword",
|
77
|
+
"title",
|
78
|
+
"anotherId",
|
79
|
+
"category_id",
|
80
|
+
"demand_kind",
|
81
|
+
"tag_id",
|
82
|
+
"userid",
|
83
|
+
"username",
|
84
|
+
"source_from"
|
85
|
+
]);
|
49
86
|
}
|
50
87
|
|
51
88
|
// 合并已有数据和新数据
|
package/index.js
DELETED
@@ -1,618 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
const XLSX = require("xlsx");
|
3
|
-
const ffmpeg = require("fluent-ffmpeg");
|
4
|
-
const fs = require("fs");
|
5
|
-
const path = require("path");
|
6
|
-
const fs_asnyc = require("fs").promises;
|
7
|
-
const archiver = require("archiver");
|
8
|
-
const axios = require("axios");
|
9
|
-
const generateUniqueHash = require("./utils/index");
|
10
|
-
const FormData = require("form-data");
|
11
|
-
|
12
|
-
//执行目录
|
13
|
-
const exeDir = __dirname;
|
14
|
-
//当前工作目录
|
15
|
-
const workDir = process.cwd();
|
16
|
-
|
17
|
-
// 表格目录
|
18
|
-
const excelDir = path.join(workDir, "excel", "excel.xlsx");
|
19
|
-
//水印
|
20
|
-
const watermarkImage = path.join(__dirname, "imgs", "miz-watermark.png");
|
21
|
-
// 截图数量
|
22
|
-
const SCREENSHOT_COUNT = 5;
|
23
|
-
const ORIGIN_FILE_DIR = "video";
|
24
|
-
const ZIP_VIDEO_DIR = "zip_video";
|
25
|
-
const ZIP_VIDEO_DIR_400 = "small_video";
|
26
|
-
const SCREENSHOT_DIR = "screenshots";
|
27
|
-
const ZIP_WATERMARK_VIDEO_DIR = "zip_watermark_video";
|
28
|
-
const ZIP_SCREENSHOT_DIR = "screenshot_watermark";
|
29
|
-
const SOURCE_VIDEO_DIR = "source_video";
|
30
|
-
const ZIP_FILES_DIR = "zip";
|
31
|
-
|
32
|
-
//并发数量
|
33
|
-
const queueCount = 50;
|
34
|
-
|
35
|
-
//起始任务下标
|
36
|
-
let taskIndex = 2066;
|
37
|
-
|
38
|
-
/**
|
39
|
-
* 递归遍历文件夹,查找mp4文件
|
40
|
-
* @param {*} dir 文件夹路径
|
41
|
-
* @param {*} fileName 文件名称
|
42
|
-
* @returns string 匹配成功的文件路径
|
43
|
-
*/
|
44
|
-
function findFileInDir(dir, fileName) {
|
45
|
-
const files = fs.readdirSync(dir);
|
46
|
-
for (const file of files) {
|
47
|
-
const filePath = path.join(dir, file);
|
48
|
-
const stat = fs.statSync(filePath);
|
49
|
-
if (stat.isDirectory()) {
|
50
|
-
const result = findFileInDir(filePath, fileName);
|
51
|
-
if (result) {
|
52
|
-
return result;
|
53
|
-
}
|
54
|
-
} else if (
|
55
|
-
file.startsWith(fileName) &&
|
56
|
-
(file.endsWith(".mp4") || file.endsWith(".mov"))
|
57
|
-
) {
|
58
|
-
return filePath;
|
59
|
-
}
|
60
|
-
}
|
61
|
-
return null;
|
62
|
-
}
|
63
|
-
|
64
|
-
/**
|
65
|
-
* 确保目录存在,如果不存在则创建它
|
66
|
-
*/
|
67
|
-
function ensureDirSync(dirpath) {
|
68
|
-
try {
|
69
|
-
if (!fs.existsSync(dirpath)) {
|
70
|
-
fs.mkdirSync(dirpath, { recursive: true });
|
71
|
-
logger(`目录创建成功:${dirpath}`);
|
72
|
-
}
|
73
|
-
} catch (err) {
|
74
|
-
logger(`创建目录时出错:${err}`);
|
75
|
-
throw err;
|
76
|
-
}
|
77
|
-
}
|
78
|
-
|
79
|
-
/**
|
80
|
-
* 检查文件是否存在
|
81
|
-
*/
|
82
|
-
function checkFileExistence(path) {
|
83
|
-
return new Promise((resolve, reject) => {
|
84
|
-
fs.access(path, fs.constants.F_OK, (err) => {
|
85
|
-
if (!err) {
|
86
|
-
logger("文件存在,可以访问");
|
87
|
-
resolve(true);
|
88
|
-
} else {
|
89
|
-
logger(`视频文件 ${path} 不存在`);
|
90
|
-
resolve(false);
|
91
|
-
}
|
92
|
-
});
|
93
|
-
});
|
94
|
-
}
|
95
|
-
|
96
|
-
//普通日志
|
97
|
-
const logger = (log, hash, err) => {
|
98
|
-
console.log(log);
|
99
|
-
fs.writeFileSync("log.txt", log + "\n", {
|
100
|
-
flag: "a",
|
101
|
-
});
|
102
|
-
};
|
103
|
-
|
104
|
-
//错误日志
|
105
|
-
const disposeError = (fileName) => {
|
106
|
-
logger("---任务失败---" + fileName);
|
107
|
-
logger(
|
108
|
-
`******************************************************************end`
|
109
|
-
);
|
110
|
-
fs.writeFileSync("error.txt", fileName + "\n", {
|
111
|
-
flag: "a",
|
112
|
-
});
|
113
|
-
};
|
114
|
-
|
115
|
-
const readExcel = (path) => {
|
116
|
-
const workbook = XLSX.readFile(path);
|
117
|
-
|
118
|
-
// 获取第一个工作表(Sheet)
|
119
|
-
const firstSheetName = workbook.SheetNames[0];
|
120
|
-
const worksheet = workbook.Sheets[firstSheetName];
|
121
|
-
|
122
|
-
// 将工作表转换为 JSON 对象
|
123
|
-
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
124
|
-
return jsonData;
|
125
|
-
};
|
126
|
-
|
127
|
-
function logFileSize(path) {
|
128
|
-
const fileSize = fs.statSync(path).size / (1024 * 1024);
|
129
|
-
const formattedSize = fileSize.toFixed(2); // 保留两位小数
|
130
|
-
logger(`视频大小:${formattedSize}M`);
|
131
|
-
}
|
132
|
-
|
133
|
-
/**
|
134
|
-
* 压缩视频
|
135
|
-
* @param {*} fileName 文件名
|
136
|
-
* @param {*} outputFileDir 产物文件夹
|
137
|
-
* @param {*} inputFilePath 源文件路径
|
138
|
-
* @returns Promise
|
139
|
-
*/
|
140
|
-
const compressVideo = (fileName, outputFileDir, inputFilePath) => {
|
141
|
-
// const inputFilePath = path.join(workDir, "video", fileName);
|
142
|
-
const outputFilePath = path.join(
|
143
|
-
outputFileDir,
|
144
|
-
ZIP_VIDEO_DIR,
|
145
|
-
`zip-${fileName}`
|
146
|
-
);
|
147
|
-
const comand = ffmpeg(inputFilePath);
|
148
|
-
|
149
|
-
ensureDirSync(path.dirname(outputFilePath));
|
150
|
-
return new Promise((resolve, reject) => {
|
151
|
-
comand
|
152
|
-
.videoCodec("libx264")
|
153
|
-
.size("1280x?")
|
154
|
-
// .size("1280x720")
|
155
|
-
|
156
|
-
.output(outputFilePath)
|
157
|
-
|
158
|
-
.on("start", () => {
|
159
|
-
logger("视频开始压缩……");
|
160
|
-
})
|
161
|
-
.on("end", () => {
|
162
|
-
logger("视频压缩完成!");
|
163
|
-
resolve(outputFilePath);
|
164
|
-
})
|
165
|
-
.on("error", (err) => {
|
166
|
-
logger("视频压缩出错:" + err);
|
167
|
-
reject(err);
|
168
|
-
})
|
169
|
-
.run();
|
170
|
-
});
|
171
|
-
};
|
172
|
-
|
173
|
-
/**
|
174
|
-
* 压缩400p视频
|
175
|
-
*/
|
176
|
-
const compressVideo400p = (fileName, outputFileDir, inputFilePath) => {
|
177
|
-
const outputFilePath = path.join(
|
178
|
-
outputFileDir,
|
179
|
-
ZIP_VIDEO_DIR_400,
|
180
|
-
`zip-${fileName}`
|
181
|
-
);
|
182
|
-
const comand = ffmpeg(inputFilePath);
|
183
|
-
|
184
|
-
ensureDirSync(path.dirname(outputFilePath));
|
185
|
-
return new Promise((resolve, reject) => {
|
186
|
-
comand
|
187
|
-
.seekInput(0)
|
188
|
-
.inputOptions("-t 10")
|
189
|
-
.videoCodec("libx264")
|
190
|
-
.size("400x?")
|
191
|
-
|
192
|
-
.output(outputFilePath)
|
193
|
-
|
194
|
-
.on("start", () => {
|
195
|
-
logger("400p视频开始压缩……");
|
196
|
-
})
|
197
|
-
.on("end", () => {
|
198
|
-
logger("400p视频压缩完成!");
|
199
|
-
resolve(outputFilePath);
|
200
|
-
})
|
201
|
-
.on("error", (err) => {
|
202
|
-
logger("视频压缩出错:", err);
|
203
|
-
reject(err);
|
204
|
-
})
|
205
|
-
.run();
|
206
|
-
});
|
207
|
-
};
|
208
|
-
|
209
|
-
/**
|
210
|
-
* 生成5张截图
|
211
|
-
*/
|
212
|
-
const get5Screenshots = (fileName, outputFilePath, inputFilePath) => {
|
213
|
-
const folderPath = path.join(outputFilePath, SCREENSHOT_DIR); // 构建完整的目录路径
|
214
|
-
|
215
|
-
ensureDirSync(folderPath);
|
216
|
-
return new Promise((resolve, reject) => {
|
217
|
-
const screenshotsCommand = ffmpeg(inputFilePath);
|
218
|
-
|
219
|
-
screenshotsCommand
|
220
|
-
.on("start", () => {
|
221
|
-
logger("开始从视频中截图……");
|
222
|
-
})
|
223
|
-
.on("end", (stdout, stderr) => {
|
224
|
-
logger(inputFilePath + "截图完成!!!");
|
225
|
-
resolve();
|
226
|
-
})
|
227
|
-
.on("error", (err) => {
|
228
|
-
logger(inputFilePath + "截图出错:", err);
|
229
|
-
reject(err);
|
230
|
-
})
|
231
|
-
|
232
|
-
.screenshots({
|
233
|
-
timemarks: [0, "25%", "50%", "75%", "90%"],
|
234
|
-
folder: folderPath,
|
235
|
-
filename: `screenshots-%i.png`,
|
236
|
-
size: "840x?",
|
237
|
-
});
|
238
|
-
});
|
239
|
-
};
|
240
|
-
|
241
|
-
/**
|
242
|
-
* 视频打水印
|
243
|
-
*/
|
244
|
-
const watermarkVideo = (fileName, outputFileDir) => {
|
245
|
-
//用压缩后的视频作为输入源,增加效率
|
246
|
-
const inputFilePath = path.join(
|
247
|
-
outputFileDir,
|
248
|
-
ZIP_VIDEO_DIR,
|
249
|
-
`zip-${fileName}`
|
250
|
-
);
|
251
|
-
const outputFilePath = path.join(
|
252
|
-
outputFileDir,
|
253
|
-
ZIP_WATERMARK_VIDEO_DIR,
|
254
|
-
`zip-watermark-${fileName}`
|
255
|
-
);
|
256
|
-
const watermarkCommand = ffmpeg(inputFilePath);
|
257
|
-
|
258
|
-
ensureDirSync(path.dirname(outputFilePath));
|
259
|
-
|
260
|
-
return new Promise((resolve, reject) => {
|
261
|
-
watermarkCommand
|
262
|
-
.input(watermarkImage) // 添加水印图片作为第二个输入
|
263
|
-
|
264
|
-
.complexFilter([
|
265
|
-
"[0:v][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2",
|
266
|
-
])
|
267
|
-
.output(outputFilePath)
|
268
|
-
.on("start", () => {
|
269
|
-
logger("视频开始打水印……");
|
270
|
-
})
|
271
|
-
.on("end", () => {
|
272
|
-
logger("视频打水印完成!");
|
273
|
-
resolve(outputFilePath);
|
274
|
-
})
|
275
|
-
.on("error", (err) => {
|
276
|
-
logger("视频打水印出错:", err);
|
277
|
-
reject(err);
|
278
|
-
})
|
279
|
-
.run();
|
280
|
-
});
|
281
|
-
};
|
282
|
-
|
283
|
-
/**
|
284
|
-
* 截图水印,根据截图打水印
|
285
|
-
*/
|
286
|
-
const watermarkScreenshots = (indexFilePath) => {
|
287
|
-
//存放水印截图的文件夹
|
288
|
-
const inputDir = path.join(indexFilePath, SCREENSHOT_DIR);
|
289
|
-
const outputDir = path.join(indexFilePath, ZIP_SCREENSHOT_DIR);
|
290
|
-
ensureDirSync(outputDir);
|
291
|
-
|
292
|
-
return new Promise((resolve, reject) => {
|
293
|
-
fs.readdir(inputDir, (err, files) => {
|
294
|
-
if (err) {
|
295
|
-
logger("读取截图文件夹出错: " + err);
|
296
|
-
return;
|
297
|
-
} else {
|
298
|
-
files.forEach((file, index) => {
|
299
|
-
if (path.extname(file) === ".png") {
|
300
|
-
const inputFile = path.join(inputDir, file);
|
301
|
-
const outputFile = path.join(outputDir, file);
|
302
|
-
|
303
|
-
const watermarkScreenshotsComand = ffmpeg(inputFile);
|
304
|
-
watermarkScreenshotsComand
|
305
|
-
.input(watermarkImage)
|
306
|
-
.complexFilter([
|
307
|
-
"[0:v][1:v]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2",
|
308
|
-
])
|
309
|
-
.output(outputFile)
|
310
|
-
.on("error", (err) => {
|
311
|
-
logger("截图添加水印出错: ", err);
|
312
|
-
reject();
|
313
|
-
})
|
314
|
-
.on("end", () => {
|
315
|
-
logger("截图水印添加完成: " + outputFile);
|
316
|
-
if (index + 1 === SCREENSHOT_COUNT) {
|
317
|
-
resolve();
|
318
|
-
}
|
319
|
-
})
|
320
|
-
.run();
|
321
|
-
} else {
|
322
|
-
logger("截图不是png文件,无法添加图片水印!");
|
323
|
-
reject();
|
324
|
-
}
|
325
|
-
});
|
326
|
-
}
|
327
|
-
});
|
328
|
-
});
|
329
|
-
};
|
330
|
-
|
331
|
-
/**
|
332
|
-
* 打包物料
|
333
|
-
*/
|
334
|
-
const archiveZip = (fileName, inputPath, originFilePath) => {
|
335
|
-
const zipDir = path.join(inputPath, "zip");
|
336
|
-
const timestamp = new Date().getTime();
|
337
|
-
|
338
|
-
ensureDirSync(zipDir);
|
339
|
-
// create a file to stream archive data to.
|
340
|
-
const zipStream = fs.createWriteStream(
|
341
|
-
path.join(zipDir, `package${timestamp}.zip`)
|
342
|
-
);
|
343
|
-
const archive = archiver("zip", {
|
344
|
-
zlib: { level: 9 }, // Sets the compression level.
|
345
|
-
});
|
346
|
-
|
347
|
-
return new Promise((resolve, reject) => {
|
348
|
-
// listen for all archive data to be written
|
349
|
-
// 'close' event is fired only when a file descriptor is involved
|
350
|
-
zipStream.on("close", function () {
|
351
|
-
logger("压缩数据:" + archive.pointer() + " total bytes");
|
352
|
-
logger(
|
353
|
-
"完成归档archiver has been finalized and the output file descriptor has closed."
|
354
|
-
);
|
355
|
-
resolve();
|
356
|
-
});
|
357
|
-
|
358
|
-
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
359
|
-
archive.on("warning", function (err) {
|
360
|
-
if (err.code === "ENOENT") {
|
361
|
-
logger("压缩-warning:" + err);
|
362
|
-
} else {
|
363
|
-
// throw error
|
364
|
-
throw err;
|
365
|
-
}
|
366
|
-
});
|
367
|
-
|
368
|
-
// good practice to catch this error explicitly
|
369
|
-
archive.on("error", function (err) {
|
370
|
-
logger("压缩失败!" + err);
|
371
|
-
reject();
|
372
|
-
});
|
373
|
-
|
374
|
-
archive.pipe(zipStream);
|
375
|
-
|
376
|
-
const directories = [
|
377
|
-
ZIP_VIDEO_DIR,
|
378
|
-
SCREENSHOT_DIR,
|
379
|
-
ZIP_WATERMARK_VIDEO_DIR,
|
380
|
-
ZIP_SCREENSHOT_DIR,
|
381
|
-
ZIP_VIDEO_DIR_400,
|
382
|
-
];
|
383
|
-
|
384
|
-
directories.forEach((dir) => {
|
385
|
-
const dirPath = path.join(inputPath, dir);
|
386
|
-
archive.directory(dirPath, dir);
|
387
|
-
});
|
388
|
-
|
389
|
-
archive.file(originFilePath, {
|
390
|
-
name: path.join(SOURCE_VIDEO_DIR, fileName),
|
391
|
-
});
|
392
|
-
// 完成归档
|
393
|
-
archive.finalize();
|
394
|
-
});
|
395
|
-
};
|
396
|
-
|
397
|
-
/**
|
398
|
-
* 获取 元数据
|
399
|
-
*/
|
400
|
-
const getMetadata = async (
|
401
|
-
fileName,
|
402
|
-
indexFilePath,
|
403
|
-
originFilePath,
|
404
|
-
{ rowFileName, title, keyword }
|
405
|
-
) => {
|
406
|
-
//第一张截图
|
407
|
-
const photoInputPath = path.join(
|
408
|
-
indexFilePath,
|
409
|
-
SCREENSHOT_DIR,
|
410
|
-
"screenshots-1.png"
|
411
|
-
);
|
412
|
-
|
413
|
-
const videoMetadataComand = ffmpeg(originFilePath);
|
414
|
-
const photoMetadataComand = ffmpeg(photoInputPath);
|
415
|
-
const metaDataParams = {
|
416
|
-
userid: 1003,
|
417
|
-
username: "美好景象",
|
418
|
-
pixel_width: "",
|
419
|
-
pixel_height: "",
|
420
|
-
size: "",
|
421
|
-
duration: "",
|
422
|
-
video_id: rowFileName,
|
423
|
-
title,
|
424
|
-
keyword,
|
425
|
-
pr: 100,
|
426
|
-
format: "mp4",
|
427
|
-
category_id: 4,
|
428
|
-
demand_kind: 23,
|
429
|
-
source_from: 71,
|
430
|
-
plate_id: 5,
|
431
|
-
tag_id: 158,
|
432
|
-
is_government: 0,
|
433
|
-
preview_width: "",
|
434
|
-
preview_height: "",
|
435
|
-
};
|
436
|
-
await new Promise((resolve, reject) => {
|
437
|
-
videoMetadataComand.ffprobe(function (err, metadata) {
|
438
|
-
const videoStream = metadata.streams.find(
|
439
|
-
(s) => s.codec_type === "video"
|
440
|
-
);
|
441
|
-
const formatStream = metadata.format;
|
442
|
-
|
443
|
-
metaDataParams.pixel_width = videoStream.width;
|
444
|
-
metaDataParams.pixel_height = videoStream.height;
|
445
|
-
metaDataParams.duration = videoStream.duration;
|
446
|
-
metaDataParams.size = formatStream.size;
|
447
|
-
resolve();
|
448
|
-
});
|
449
|
-
});
|
450
|
-
|
451
|
-
await new Promise((resolve, reject) => {
|
452
|
-
photoMetadataComand.ffprobe(function (err, metadata) {
|
453
|
-
const photoStream = metadata.streams.find(
|
454
|
-
(s) => s.codec_type === "video"
|
455
|
-
);
|
456
|
-
|
457
|
-
metaDataParams.preview_width = photoStream.width;
|
458
|
-
metaDataParams.preview_height = photoStream.height;
|
459
|
-
resolve();
|
460
|
-
});
|
461
|
-
});
|
462
|
-
|
463
|
-
return metaDataParams;
|
464
|
-
};
|
465
|
-
|
466
|
-
/**
|
467
|
-
* 接口
|
468
|
-
*/
|
469
|
-
const postData = (dataParams, indexFilePath) => {
|
470
|
-
const formData = new FormData();
|
471
|
-
//风险:确保文件夹中只要一个zip文件,(因为时间戳,老的文件不会被覆盖)
|
472
|
-
const zipFiles = fs
|
473
|
-
.readdirSync(path.join(indexFilePath, ZIP_FILES_DIR))
|
474
|
-
.find((file) => file.endsWith(".zip"));
|
475
|
-
|
476
|
-
const packageZip = path.join(indexFilePath, ZIP_FILES_DIR, zipFiles);
|
477
|
-
|
478
|
-
formData.append("file", fs.createReadStream(packageZip));
|
479
|
-
for (const key in dataParams) {
|
480
|
-
if (Object.hasOwnProperty.call(dataParams, key)) {
|
481
|
-
const value = dataParams[key];
|
482
|
-
formData.append(key, value);
|
483
|
-
}
|
484
|
-
}
|
485
|
-
|
486
|
-
logger("等待接口返回结果……");
|
487
|
-
|
488
|
-
return axios.post("http://127.0.0.1:9999/upload/video", formData, {
|
489
|
-
headers: {
|
490
|
-
"Content-Type": "multipart/form-data",
|
491
|
-
},
|
492
|
-
timeout: 600000,
|
493
|
-
});
|
494
|
-
};
|
495
|
-
|
496
|
-
/**
|
497
|
-
* 任务
|
498
|
-
*/
|
499
|
-
const task = async (row, index) => {
|
500
|
-
logger(
|
501
|
-
"**************************" + row.fileName + "**************************"
|
502
|
-
);
|
503
|
-
// Excel的列名分别为: fileName title keyword
|
504
|
-
// 表格中文件名无后缀,遂手动添加写死为mp4
|
505
|
-
let fileName = row.fileName;
|
506
|
-
const rowFileName = row.fileName;
|
507
|
-
const title = row.title;
|
508
|
-
const keywordArr = row.keyword.split(",");
|
509
|
-
const filteredArray = keywordArr.filter((item) => item.trim() !== "");
|
510
|
-
const keyword = filteredArray.join(" ");
|
511
|
-
|
512
|
-
if (!fileName.includes(".")) {
|
513
|
-
fileName = row.fileName + ".mov";
|
514
|
-
}
|
515
|
-
|
516
|
-
// 源视频文件路径
|
517
|
-
const originFilePath = findFileInDir(
|
518
|
-
path.join(workDir, ORIGIN_FILE_DIR),
|
519
|
-
fileName
|
520
|
-
);
|
521
|
-
|
522
|
-
if (!originFilePath) {
|
523
|
-
logger(`视频文件 ${fileName} 不存在`);
|
524
|
-
return;
|
525
|
-
}
|
526
|
-
logger("源视频文件路径");
|
527
|
-
logger(originFilePath);
|
528
|
-
|
529
|
-
//index文件夹 output/0
|
530
|
-
const indexFilePath = path.join(workDir, "output", index + "");
|
531
|
-
|
532
|
-
try {
|
533
|
-
await fs_asnyc.access(originFilePath, fs_asnyc.constants.F_OK);
|
534
|
-
logFileSize(originFilePath);
|
535
|
-
await compressVideo(fileName, indexFilePath, originFilePath);
|
536
|
-
await compressVideo400p(fileName, indexFilePath, originFilePath);
|
537
|
-
await get5Screenshots(fileName, indexFilePath, originFilePath);
|
538
|
-
await watermarkVideo(fileName, indexFilePath);
|
539
|
-
await watermarkScreenshots(indexFilePath);
|
540
|
-
await archiveZip(fileName, indexFilePath, originFilePath);
|
541
|
-
const dataParams = await getMetadata(
|
542
|
-
fileName,
|
543
|
-
indexFilePath,
|
544
|
-
originFilePath,
|
545
|
-
{
|
546
|
-
rowFileName,
|
547
|
-
title,
|
548
|
-
keyword,
|
549
|
-
}
|
550
|
-
);
|
551
|
-
|
552
|
-
const resData = await postData(dataParams, indexFilePath);
|
553
|
-
if (resData.data.code === 200) {
|
554
|
-
logger("请求成功!");
|
555
|
-
logger(resData.data.code);
|
556
|
-
logger(resData.data);
|
557
|
-
//删除文件夹
|
558
|
-
fs.rmdirSync(indexFilePath, { recursive: true });
|
559
|
-
} else {
|
560
|
-
throw new Error("请求失败!" + resData.data.msg);
|
561
|
-
}
|
562
|
-
logger(resData.data);
|
563
|
-
logger(
|
564
|
-
`----------------------------------------第${index}条结束---------------------------------end`
|
565
|
-
);
|
566
|
-
} catch (error) {
|
567
|
-
// 可以约定code,来表示不同的错误信息
|
568
|
-
if (error.code === "ENOENT") {
|
569
|
-
logger(`视频文件 ${fileName} 不存在`);
|
570
|
-
} else {
|
571
|
-
logger("视频处理失败:");
|
572
|
-
logger(error);
|
573
|
-
}
|
574
|
-
logger("catch日志*********************************");
|
575
|
-
disposeError(fileName);
|
576
|
-
fs.rmdirSync(indexFilePath, { recursive: true });
|
577
|
-
}
|
578
|
-
};
|
579
|
-
|
580
|
-
/**
|
581
|
-
* 解析Excel数据
|
582
|
-
*/
|
583
|
-
const transitionExcelToJSON = () => {
|
584
|
-
//当前任务hash
|
585
|
-
const hashOrigin = generateUniqueHash();
|
586
|
-
const hash = hashOrigin.slice(0, 8);
|
587
|
-
logger("任务开始---" + hash);
|
588
|
-
logger("当前目录: " + workDir);
|
589
|
-
logger("工作目录: " + __dirname);
|
590
|
-
|
591
|
-
const jsonData = readExcel(excelDir);
|
592
|
-
|
593
|
-
const run = (index) => {
|
594
|
-
const row = jsonData[index];
|
595
|
-
if (!row) {
|
596
|
-
return;
|
597
|
-
}
|
598
|
-
logger(
|
599
|
-
`----------------------------------------第${index}条开始------------------------------------`
|
600
|
-
);
|
601
|
-
logger(`-------------------${new Date()}------------------`);
|
602
|
-
task(row, index)
|
603
|
-
.then(() => {
|
604
|
-
taskIndex++;
|
605
|
-
run(taskIndex);
|
606
|
-
})
|
607
|
-
.catch(() => {
|
608
|
-
taskIndex++;
|
609
|
-
run(taskIndex);
|
610
|
-
});
|
611
|
-
};
|
612
|
-
|
613
|
-
for (let i = 0; i < queueCount; i++) {
|
614
|
-
run(i + taskIndex);
|
615
|
-
}
|
616
|
-
};
|
617
|
-
|
618
|
-
transitionExcelToJSON();
|