tools_batch_files 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/imgs/picWater.png +0 -0
- package/index.js +5 -0
- package/package.json +3 -2
- package/src/photoBatch.js +436 -0
Binary file
|
package/index.js
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "tools_batch_files",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.13",
|
4
4
|
"description": "批处理视频工具",
|
5
5
|
"keywords": [
|
6
6
|
"utils",
|
@@ -10,7 +10,8 @@
|
|
10
10
|
],
|
11
11
|
"main": "index.js",
|
12
12
|
"bin": {
|
13
|
-
"tbf": "./index.js"
|
13
|
+
"tbf": "./index.js",
|
14
|
+
"tbfp": "./src/photoBatch.js"
|
14
15
|
},
|
15
16
|
"scripts": {
|
16
17
|
"test": "echo \"Error: no test specified\" && exit 1",
|
@@ -0,0 +1,436 @@
|
|
1
|
+
const XLSX = require("xlsx");
|
2
|
+
const ffmpeg = require("fluent-ffmpeg");
|
3
|
+
const fs = require("fs");
|
4
|
+
const path = require("path");
|
5
|
+
const fs_asnyc = require("fs").promises;
|
6
|
+
const archiver = require("archiver");
|
7
|
+
const axios = require("axios");
|
8
|
+
const generateUniqueHash = require("../utils/index");
|
9
|
+
const FormData = require("form-data");
|
10
|
+
|
11
|
+
//执行目录
|
12
|
+
const exeDir = __dirname;
|
13
|
+
//当前工作目录
|
14
|
+
const workDir = process.cwd();
|
15
|
+
|
16
|
+
// 表格目录
|
17
|
+
const excelDir = path.join(workDir, "excel", "excel.xlsx");
|
18
|
+
//水印
|
19
|
+
const watermarkImage = path.join(__dirname, "..", "imgs", "picWater.png");
|
20
|
+
|
21
|
+
// 截图数量
|
22
|
+
const SCREENSHOT_COUNT = 5;
|
23
|
+
const ORIGIN_FILE_DIR = "photos";
|
24
|
+
const PHOTO_PREVIEW_DIR = "preview_photo";
|
25
|
+
const ZIP_VIDEO_DIR = "zip_video";
|
26
|
+
const ZIP_VIDEO_DIR_400 = "small_video";
|
27
|
+
const SCREENSHOT_DIR = "preview_photo";
|
28
|
+
const SCREENSHOT_WATERMARK_DIR = "screenshots_watermark";
|
29
|
+
const ZIP_WATERMARK_VIDEO_DIR = "zip_watermark_video";
|
30
|
+
const ZIP_SCREENSHOT_WM_DIR = "preview_watermark_photo";
|
31
|
+
const SOURCE_PHOTO_DIR = "source_photo";
|
32
|
+
const ZIP_FILES_DIR = "zip";
|
33
|
+
|
34
|
+
const expandedName = ".jpg";
|
35
|
+
|
36
|
+
//并发数量
|
37
|
+
const queueCount = 1;
|
38
|
+
|
39
|
+
//起始任务下标
|
40
|
+
let taskIndex = 0;
|
41
|
+
|
42
|
+
const readExcel = (path) => {
|
43
|
+
const workbook = XLSX.readFile(path);
|
44
|
+
|
45
|
+
// 获取第一个工作表(Sheet)
|
46
|
+
const firstSheetName = workbook.SheetNames[0];
|
47
|
+
const worksheet = workbook.Sheets[firstSheetName];
|
48
|
+
|
49
|
+
// 将工作表转换为 JSON 对象
|
50
|
+
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
51
|
+
return jsonData;
|
52
|
+
};
|
53
|
+
|
54
|
+
function logFileSize(path) {
|
55
|
+
const fileSize = fs.statSync(path).size / (1024 * 1024);
|
56
|
+
const formattedSize = fileSize.toFixed(2); // 保留两位小数
|
57
|
+
logger(`图片大小:${formattedSize}M`);
|
58
|
+
}
|
59
|
+
|
60
|
+
//普通日志
|
61
|
+
const logger = (log, hash, err) => {
|
62
|
+
console.log(log);
|
63
|
+
fs.writeFileSync("log.txt", log + "\n", {
|
64
|
+
flag: "a",
|
65
|
+
});
|
66
|
+
};
|
67
|
+
|
68
|
+
/**
|
69
|
+
* 递归遍历文件夹,查找文件
|
70
|
+
* @param {*} dir 文件夹路径
|
71
|
+
* @param {*} fileName 文件名称
|
72
|
+
* @returns string 匹配成功的文件路径
|
73
|
+
*/
|
74
|
+
function findFileInDir(dir, fileName) {
|
75
|
+
const files = fs.readdirSync(dir);
|
76
|
+
for (const file of files) {
|
77
|
+
const filePath = path.join(dir, file);
|
78
|
+
const stat = fs.statSync(filePath);
|
79
|
+
if (stat.isDirectory()) {
|
80
|
+
const result = findFileInDir(filePath, fileName);
|
81
|
+
if (result) {
|
82
|
+
return result;
|
83
|
+
}
|
84
|
+
} else if (file.startsWith(fileName) && file.endsWith(expandedName)) {
|
85
|
+
return filePath;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
return null;
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* 生成预览图 indexFilePath: output/0
|
93
|
+
* originFilePath E:\workspace\图片测试\photos\abc222.png
|
94
|
+
*/
|
95
|
+
const PriviewPhoto = (indexFilePath, originFilePath, fileName) => {
|
96
|
+
//存放预览图图的文件夹
|
97
|
+
const outputDir = path.join(indexFilePath, SCREENSHOT_DIR);
|
98
|
+
ensureDirSync(outputDir);
|
99
|
+
|
100
|
+
const outputFile = path.join(outputDir, fileName);
|
101
|
+
|
102
|
+
return new Promise((resolve, reject) => {
|
103
|
+
const watermarkScreenshotsComand = ffmpeg(originFilePath);
|
104
|
+
watermarkScreenshotsComand
|
105
|
+
.size("1280x?")
|
106
|
+
.output(outputFile)
|
107
|
+
.on("error", (err) => {
|
108
|
+
logger("截图预览图出错: " + err);
|
109
|
+
reject();
|
110
|
+
})
|
111
|
+
.on("end", () => {
|
112
|
+
logger("预览图完成: " + outputFile);
|
113
|
+
resolve();
|
114
|
+
})
|
115
|
+
.run();
|
116
|
+
});
|
117
|
+
};
|
118
|
+
|
119
|
+
/**
|
120
|
+
* 生成水印预览图
|
121
|
+
*/
|
122
|
+
const watermarkPriviewPhoto = (indexFilePath, originFilePath, fileName) => {
|
123
|
+
//存放水印截图的文件夹
|
124
|
+
const outputDir = path.join(indexFilePath, ZIP_SCREENSHOT_WM_DIR);
|
125
|
+
const inputDir = path.join(indexFilePath, SCREENSHOT_DIR, fileName);
|
126
|
+
ensureDirSync(outputDir);
|
127
|
+
|
128
|
+
const outputFile = path.join(outputDir, fileName);
|
129
|
+
|
130
|
+
return new Promise((resolve, reject) => {
|
131
|
+
const watermarkScreenshotsComand = ffmpeg(inputDir);
|
132
|
+
watermarkScreenshotsComand
|
133
|
+
.input(watermarkImage)
|
134
|
+
.complexFilter("overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2")
|
135
|
+
.output(outputFile)
|
136
|
+
.on("error", (err) => {
|
137
|
+
logger("截图添加水印出错: " + err);
|
138
|
+
reject();
|
139
|
+
})
|
140
|
+
.on("end", () => {
|
141
|
+
logger("截图水印添加完成: " + outputFile);
|
142
|
+
resolve();
|
143
|
+
})
|
144
|
+
.run();
|
145
|
+
});
|
146
|
+
};
|
147
|
+
|
148
|
+
/**
|
149
|
+
* 确保目录存在,如果不存在则创建它
|
150
|
+
*/
|
151
|
+
function ensureDirSync(dirpath) {
|
152
|
+
try {
|
153
|
+
if (!fs.existsSync(dirpath)) {
|
154
|
+
fs.mkdirSync(dirpath, { recursive: true });
|
155
|
+
logger(`目录创建成功:${dirpath}`);
|
156
|
+
}
|
157
|
+
} catch (err) {
|
158
|
+
logger(`创建目录时出错:${err}`);
|
159
|
+
throw err;
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
/**
|
164
|
+
* 打包物料
|
165
|
+
*/
|
166
|
+
const archiveZip = (fileName, inputPath, originFilePath) => {
|
167
|
+
const zipDir = path.join(inputPath, "zip");
|
168
|
+
const timestamp = new Date().getTime();
|
169
|
+
|
170
|
+
ensureDirSync(zipDir);
|
171
|
+
const zipStream = fs.createWriteStream(
|
172
|
+
path.join(zipDir, `package${timestamp}.zip`)
|
173
|
+
);
|
174
|
+
const archive = archiver("zip", {
|
175
|
+
zlib: { level: 9 },
|
176
|
+
});
|
177
|
+
|
178
|
+
return new Promise((resolve, reject) => {
|
179
|
+
zipStream.on("close", function () {
|
180
|
+
logger("压缩数据:" + archive.pointer() + " total bytes");
|
181
|
+
logger(
|
182
|
+
"完成归档archiver has been finalized and the output file descriptor has closed."
|
183
|
+
);
|
184
|
+
resolve();
|
185
|
+
});
|
186
|
+
|
187
|
+
archive.on("warning", function (err) {
|
188
|
+
if (err.code === "ENOENT") {
|
189
|
+
logger("压缩-warning:" + err);
|
190
|
+
} else {
|
191
|
+
throw err;
|
192
|
+
}
|
193
|
+
});
|
194
|
+
|
195
|
+
archive.on("error", function (err) {
|
196
|
+
logger("压缩失败!" + err);
|
197
|
+
reject();
|
198
|
+
});
|
199
|
+
|
200
|
+
archive.pipe(zipStream);
|
201
|
+
const directories = [ZIP_SCREENSHOT_WM_DIR, SCREENSHOT_DIR];
|
202
|
+
|
203
|
+
directories.forEach((dir) => {
|
204
|
+
const dirPath = path.join(inputPath, dir);
|
205
|
+
archive.directory(dirPath, dir);
|
206
|
+
});
|
207
|
+
|
208
|
+
archive.file(originFilePath, {
|
209
|
+
name: path.join(SOURCE_PHOTO_DIR, fileName),
|
210
|
+
});
|
211
|
+
// 完成归档
|
212
|
+
archive.finalize();
|
213
|
+
});
|
214
|
+
};
|
215
|
+
|
216
|
+
/**
|
217
|
+
* 获取 元数据
|
218
|
+
*/
|
219
|
+
const getMetadata = async (
|
220
|
+
fileName,
|
221
|
+
indexFilePath,
|
222
|
+
originFilePath,
|
223
|
+
{ rowFileName, title, keyword }
|
224
|
+
) => {
|
225
|
+
//源图片数据
|
226
|
+
const photoMetadataComand = ffmpeg(originFilePath);
|
227
|
+
//预览图数据
|
228
|
+
const photoInputPath = path.join(indexFilePath, SCREENSHOT_DIR, fileName);
|
229
|
+
const photoPreviewMetadataComand = ffmpeg(photoInputPath);
|
230
|
+
|
231
|
+
const metaDataParams = {
|
232
|
+
photo_id: rowFileName,
|
233
|
+
width: "",
|
234
|
+
height: "",
|
235
|
+
format: "",
|
236
|
+
size: "",
|
237
|
+
pr: 160,
|
238
|
+
dpi: 0,
|
239
|
+
is_government: 0,
|
240
|
+
common_width: "",
|
241
|
+
common_height: "",
|
242
|
+
dujia: 0,
|
243
|
+
xiaoxiang: 0,
|
244
|
+
title,
|
245
|
+
keyword,
|
246
|
+
en_keyword: "",
|
247
|
+
// preview_path: "",
|
248
|
+
// markImage_path: "",
|
249
|
+
// photo_path: "",
|
250
|
+
// common_image_path: "",
|
251
|
+
// en_keyword: "",
|
252
|
+
plate_id: 3,
|
253
|
+
transform_plate_id: 3,
|
254
|
+
tag_id: 47,
|
255
|
+
userid: 1003,
|
256
|
+
username: "美好景象",
|
257
|
+
demand_kind: 3,
|
258
|
+
source_from: 71,
|
259
|
+
file: "",
|
260
|
+
};
|
261
|
+
|
262
|
+
await new Promise((resolve, reject) => {
|
263
|
+
photoMetadataComand.ffprobe(function (err, metadata) {
|
264
|
+
console.log("metadata");
|
265
|
+
console.log(JSON.stringify(metadata));
|
266
|
+
|
267
|
+
const formatStream = metadata.format;
|
268
|
+
const photoStream = metadata.streams.find(
|
269
|
+
(s) => s.codec_type === "video"
|
270
|
+
);
|
271
|
+
|
272
|
+
metaDataParams.width = photoStream.width;
|
273
|
+
metaDataParams.height = photoStream.height;
|
274
|
+
// metaDataParams.format = formatStream.format_name;
|
275
|
+
metaDataParams.format = "jpg";
|
276
|
+
metaDataParams.size = formatStream.size;
|
277
|
+
|
278
|
+
resolve();
|
279
|
+
});
|
280
|
+
});
|
281
|
+
await new Promise((resolve, reject) => {
|
282
|
+
photoPreviewMetadataComand.ffprobe(function (err, metadata) {
|
283
|
+
const photoStream = metadata.streams.find(
|
284
|
+
(s) => s.codec_type === "video"
|
285
|
+
);
|
286
|
+
|
287
|
+
metaDataParams.common_width = photoStream.width;
|
288
|
+
metaDataParams.common_height = photoStream.height;
|
289
|
+
resolve();
|
290
|
+
});
|
291
|
+
});
|
292
|
+
|
293
|
+
return metaDataParams;
|
294
|
+
};
|
295
|
+
|
296
|
+
/**
|
297
|
+
* 接口
|
298
|
+
*/
|
299
|
+
const postData = (dataParams, indexFilePath) => {
|
300
|
+
const formData = new FormData();
|
301
|
+
|
302
|
+
const zipFiles = fs
|
303
|
+
.readdirSync(path.join(indexFilePath, ZIP_FILES_DIR))
|
304
|
+
.find((file) => file.endsWith(".zip"));
|
305
|
+
|
306
|
+
const packageZip = path.join(indexFilePath, ZIP_FILES_DIR, zipFiles);
|
307
|
+
|
308
|
+
formData.append("file", fs.createReadStream(packageZip));
|
309
|
+
for (const key in dataParams) {
|
310
|
+
if (Object.hasOwnProperty.call(dataParams, key)) {
|
311
|
+
const value = dataParams[key];
|
312
|
+
formData.append(key, value);
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
316
|
+
logger("等待接口返回结果……");
|
317
|
+
|
318
|
+
return axios.post("http://192.168.101.149:9999/upload/photo", formData, {
|
319
|
+
headers: {
|
320
|
+
"Content-Type": "multipart/form-data",
|
321
|
+
},
|
322
|
+
});
|
323
|
+
};
|
324
|
+
|
325
|
+
/**
|
326
|
+
* 任务
|
327
|
+
*/
|
328
|
+
const task = async (row, index) => {
|
329
|
+
logger(
|
330
|
+
"**************************" + row.fileName + "**************************"
|
331
|
+
);
|
332
|
+
// Excel的列名分别为: fileName title keyword
|
333
|
+
// 表格中文件名无后缀,遂手动添加写死为mp4
|
334
|
+
let fileName = row.fileName;
|
335
|
+
const rowFileName = row.fileName;
|
336
|
+
const title = row.title;
|
337
|
+
const keyword = row.keyword;
|
338
|
+
|
339
|
+
if (!fileName.includes(".")) {
|
340
|
+
fileName = row.fileName + expandedName;
|
341
|
+
}
|
342
|
+
|
343
|
+
// 源图片文件夹路径
|
344
|
+
const originFilePath = findFileInDir(
|
345
|
+
path.join(workDir, ORIGIN_FILE_DIR),
|
346
|
+
fileName
|
347
|
+
);
|
348
|
+
console.log("originFilePath:" + originFilePath);
|
349
|
+
console.log("fileName:" + fileName);
|
350
|
+
|
351
|
+
if (!originFilePath) {
|
352
|
+
logger(`图片文件 ${fileName} 不存在`);
|
353
|
+
return;
|
354
|
+
}
|
355
|
+
|
356
|
+
//index文件夹 output/0
|
357
|
+
const indexFilePath = path.join(workDir, "output", index + "");
|
358
|
+
|
359
|
+
try {
|
360
|
+
await fs_asnyc.access(originFilePath, fs_asnyc.constants.F_OK);
|
361
|
+
logFileSize(originFilePath);
|
362
|
+
await PriviewPhoto(indexFilePath, originFilePath, fileName);
|
363
|
+
await watermarkPriviewPhoto(indexFilePath, originFilePath, fileName);
|
364
|
+
await archiveZip(fileName, indexFilePath, originFilePath);
|
365
|
+
const dataParams = await getMetadata(
|
366
|
+
fileName,
|
367
|
+
indexFilePath,
|
368
|
+
originFilePath,
|
369
|
+
{
|
370
|
+
rowFileName,
|
371
|
+
title,
|
372
|
+
keyword,
|
373
|
+
}
|
374
|
+
);
|
375
|
+
console.log("dataParams参数");
|
376
|
+
console.log(dataParams);
|
377
|
+
|
378
|
+
const resData = await postData(dataParams, indexFilePath);
|
379
|
+
logger(resData.data);
|
380
|
+
//删除文件夹
|
381
|
+
fs.rmdirSync(indexFilePath, { recursive: true });
|
382
|
+
|
383
|
+
logger(
|
384
|
+
`----------------------------------------第${index}条结束---------------------------------end`
|
385
|
+
);
|
386
|
+
} catch (error) {
|
387
|
+
// 可以约定code,来表示不同的错误信息
|
388
|
+
if (error.code === "ENOENT") {
|
389
|
+
logger(`图片文件 ${fileName} 不存在`);
|
390
|
+
} else {
|
391
|
+
logger("图片任务处理失败:" + error);
|
392
|
+
logger(error);
|
393
|
+
}
|
394
|
+
disposeError(hash, fileName);
|
395
|
+
}
|
396
|
+
};
|
397
|
+
|
398
|
+
/**
|
399
|
+
* 解析Excel数据
|
400
|
+
*/
|
401
|
+
const transitionExcelToJSON = () => {
|
402
|
+
//当前任务hash
|
403
|
+
const hashOrigin = generateUniqueHash();
|
404
|
+
const hash = hashOrigin.slice(0, 8);
|
405
|
+
logger("图片批量任务任务开始---" + hash);
|
406
|
+
logger("当前目录: " + workDir);
|
407
|
+
logger("工作目录: " + __dirname);
|
408
|
+
|
409
|
+
const jsonData = readExcel(excelDir);
|
410
|
+
|
411
|
+
const run = (index) => {
|
412
|
+
const row = jsonData[index];
|
413
|
+
if (!row) {
|
414
|
+
return;
|
415
|
+
}
|
416
|
+
logger(
|
417
|
+
`----------------------------------------第${index}条开始------------------------------------`
|
418
|
+
);
|
419
|
+
logger(`-------------------${new Date()}------------------`);
|
420
|
+
task(row, index)
|
421
|
+
.then(() => {
|
422
|
+
taskIndex++;
|
423
|
+
run(taskIndex);
|
424
|
+
})
|
425
|
+
.catch(() => {
|
426
|
+
taskIndex++;
|
427
|
+
run(taskIndex);
|
428
|
+
});
|
429
|
+
};
|
430
|
+
|
431
|
+
for (let i = 0; i < queueCount; i++) {
|
432
|
+
run(i);
|
433
|
+
}
|
434
|
+
};
|
435
|
+
|
436
|
+
transitionExcelToJSON();
|