mediac 1.3.5 → 1.5.1

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.
@@ -102,7 +102,7 @@ const handler = async function cmdCompress(argv) {
102
102
  const purgeSource = argv.purge || false;
103
103
  log.show(`${logTag} input:`, root);
104
104
 
105
- const RE_THUMB = /Z4K|M4K|feature|web|thumb$/i;
105
+ const RE_THUMB = /Z4K|P4K|M4K|feature|web|thumb$/i;
106
106
  const walkOpts = {
107
107
  needStats: true,
108
108
  entryFilter: (f) =>
@@ -0,0 +1,252 @@
1
+ /*
2
+ * File: cmd_fixname.js
3
+ * Created: 2024-04-05 14:04:04
4
+ * Modified: 2024-04-05 14:04:35
5
+ * Author: mcxiaoke (github@mcxiaoke.com)
6
+ * License: Apache License 2.0
7
+ */
8
+ import chalk from 'chalk';
9
+ import { sify } from 'chinese-conv';
10
+ import fs from 'fs-extra';
11
+ import inquirer from "inquirer";
12
+ import { cpus } from "os";
13
+ import pMap from 'p-map';
14
+ import path from "path";
15
+ import * as core from '../lib/core.js';
16
+ import { asyncFilter } from '../lib/core.js';
17
+ import * as log from '../lib/debug.js';
18
+ import * as enc from '../lib/encoding.js';
19
+ import * as mf from '../lib/file.js';
20
+ import * as helper from '../lib/helper.js';
21
+ import { renameFiles } from "./cmd_shared.js";
22
+
23
+ export { aliases, builder, command, describe, handler };
24
+ const command = "fixname <input> [output]"
25
+ const aliases = ["fn", "fxn"]
26
+ const describe = 'Fix filenames (fix messy, clean, convert tc to sc)'
27
+
28
+ const builder = function addOptions(ya, helpOrVersionSet) {
29
+ return ya// 仅处理符合指定条件的文件,包含文件名规则
30
+ .option("include", {
31
+ alias: "I",
32
+ type: "string",
33
+ description: "include filename patterns ",
34
+ })
35
+ // 仅处理不符合指定条件的文件,例外文件名规则
36
+ .option("exclude", {
37
+ alias: "E",
38
+ type: "string",
39
+ description: "exclude filename patterns ",
40
+ })
41
+ // 清理文件名中的特殊字符和非法字符
42
+ .option("clean", {
43
+ alias: "c",
44
+ type: "boolean",
45
+ description: "remove special chars in filename",
46
+ })
47
+ // 使用正则表达式替换文件名中的特定字符,比如问号
48
+ // 如果数组只有一项,就是替换这一项为空白,即删除模式字符串
49
+ // 如果有两项,就是替换第一项匹配的字符串为第二项指定的字符
50
+ .option("replace", {
51
+ type: "array",
52
+ description: "replace regex pattern in filename [from,to]",
53
+ })
54
+ // 修复文件名乱码
55
+ .option("encoding", {
56
+ alias: "e",
57
+ type: "boolean",
58
+ description: "fix filename with messy chars",
59
+ })
60
+ // 繁体转简体
61
+ .option("tcsc", {
62
+ alias: "t",
63
+ type: "boolean",
64
+ description: "convert Chinese from TC to SC",
65
+ })
66
+ // 确认执行所有系统操作,非测试模式,如删除和重命名和移动操作
67
+ .option("doit", {
68
+ alias: "d",
69
+ type: "boolean",
70
+ default: false,
71
+ description: "execute os operations in real mode, not dry run",
72
+ })
73
+ }
74
+
75
+ const handler = async function cmdFixName(argv) {
76
+ const testMode = !argv.doit;
77
+ const logTag = "cmdFixName";
78
+ log.info(logTag, argv);
79
+ const root = path.resolve(argv.input);
80
+ if (!root || !(await fs.pathExists(root))) {
81
+ throw new Error(`Invalid Input: ${root}`);
82
+ }
83
+ if (!testMode) {
84
+ log.fileLog(`Root: ${root}`, logTag);
85
+ log.fileLog(`Argv: ${JSON.stringify(argv)}`, logTag);
86
+ }
87
+ const startMs = Date.now();
88
+ log.show(logTag, `Input: ${root}`);
89
+ if (!(argv.clean || argv.encoding || argv.tcsc || argv.remove)) {
90
+ log.error(`Error: replace|clean|encoding|tcsc,at least one is required`);
91
+ throw new Error(`replace|clean|encoding|tcsc,at least one is required`);
92
+ }
93
+ let files = await mf.walk(root, {
94
+ needStats: true,
95
+ entryFilter: (entry) =>
96
+ entry.stats.isFile() &&
97
+ entry.stats.size > 1024
98
+ });
99
+ log.show(logTag, `Total ${files.length} files found in ${helper.humanTime(startMs)}`);
100
+ if (argv.include?.length >= 3) {
101
+ // 处理include规则
102
+ const pattern = new RegExp(argv.include, "gi");
103
+ log.showRed(pattern)
104
+
105
+ files = await asyncFilter(files, x => x.path.match(pattern));
106
+ log.show(logTag, `Total ${files.length} files left after include rules`);
107
+ } else if (argv.exclude?.length >= 3) {
108
+ // 处理exclude规则
109
+ const pattern = new RegExp(argv.exclude, "gi");
110
+ log.showRed(pattern)
111
+ files = await asyncFilter(files, x => !x.path.match(pattern));
112
+ log.show(logTag, `Total ${files.length} files left after exclude rules`);
113
+ }
114
+ files = files.map((f, i) => {
115
+ return {
116
+ ...f,
117
+ index: i,
118
+ argv: argv,
119
+ total: files.length,
120
+ }
121
+ })
122
+ const fCount = files.length;
123
+ let tasks = await pMap(files, fixFileName, { concurrency: cpus().length * 4 })
124
+ tasks = tasks.filter(f => f?.outName)
125
+ const tCount = tasks.length;
126
+ log.showYellow(
127
+ logTag, `Total ${fCount - tCount} files are skipped.`
128
+ );
129
+ if (tasks.length > 0) {
130
+ log.showGreen(
131
+ logTag,
132
+ `Total ${tasks.length} media files ready to rename`
133
+ );
134
+ } else {
135
+ log.showYellow(
136
+ logTag,
137
+ `Nothing to do, abort.`);
138
+ return;
139
+ }
140
+ log.show(logTag, argv);
141
+ testMode && log.showYellow("++++++++++ TEST MODE (DRY RUN) ++++++++++")
142
+ const answer = await inquirer.prompt([
143
+ {
144
+ type: "confirm",
145
+ name: "yes",
146
+ default: false,
147
+ message: chalk.bold.red(
148
+ `Are you sure to rename these ${tasks.length} files?`
149
+ ),
150
+ },
151
+ ]);
152
+ if (answer.yes) {
153
+ if (testMode) {
154
+ log.showYellow(logTag, `All ${tasks.length} files, BUT NO file renamed in TEST MODE.`);
155
+ }
156
+ else {
157
+ const results = await renameFiles(tasks);
158
+ log.showGreen(logTag, `All ${results.length} file were renamed.`);
159
+ }
160
+ } else {
161
+ log.showYellow(logTag, "Will do nothing, aborted by user.");
162
+ }
163
+ }
164
+
165
+ let badCount = 0;
166
+ // 重复文件名Set,检测重复,防止覆盖
167
+ const nameDuplicateSet = new Set();
168
+ async function fixFileName(f) {
169
+ const logTag = `FixName`;
170
+ const argv = f.argv;
171
+ const ipx = f.index;
172
+ const oldPath = f.path;
173
+ const [oldDir, base, ext] = helper.pathSplit(oldPath);
174
+ const oldDirName = path.basename(oldDir);
175
+ const strPath = path.resolve(f.path).split(path.sep).join(' ')
176
+ let oldBase = base;
177
+ let newDir = oldDir;
178
+ if (argv.replace?.[0]?.length > 0) {
179
+ const rFrom = argv.replace[0];
180
+ const rTo = argv.replace[1] || "";
181
+ // 执行文件名字符替换操作
182
+ // 按照正则表达式替换指定字符
183
+ // 如果rTo为空则等于删除字符
184
+ oldBase = oldBase.replaceAll(rFrom, rTo);
185
+ oldBase = oldBase.replaceAll(new RegExp(rFrom, "gu"), rTo);
186
+ }
187
+ if (argv.encoding) {
188
+ // 执行文件路径乱码修复操作
189
+ // 对路径进行中日韩文字编码修复
190
+ let [fs, ft] = enc.fixCJKEnc(oldBase);
191
+ oldBase = fs.trim();
192
+ // 将目录路径分割,并对每个部分进行编码修复
193
+ const dirNamesFixed = oldDir.split(path.sep).map(s => {
194
+ let [rs, rt] = enc.fixCJKEnc(s)
195
+ return rs.trim();
196
+ });
197
+ // 重新组合修复后的目录路径
198
+ newDir = path.join(...dirNamesFixed);
199
+ if (core.isUNCPath(oldDir)) {
200
+ newDir = "\\\\" + newDir;
201
+ }
202
+ // 显示有乱码的文件路径
203
+ if (enc.hasBadUnicode(strPath)) {
204
+ log.showGray(logTag, `BadEnc:${++badCount}`, oldPath)
205
+ log.fileLog(`BadEnc: ${ipx} <${oldPath}>`, logTag);
206
+ }
207
+ }
208
+ if (argv.clean) {
209
+ // 执行净化文件名操作
210
+ oldBase = oldBase;
211
+ }
212
+ if (argv.tcsc) {
213
+ // 执行繁体转简体操作
214
+ oldBase = sify(oldBase)
215
+ }
216
+ // 生成修复后的新路径,包括旧基础路径和文件扩展名
217
+ const newName = `${oldBase}${ext}`
218
+ const newPath = path.join(newDir, newName);
219
+ if (newPath === oldPath) {
220
+ log.info(logTag, `Ignore Same: ${ipx} ${helper.pathShort(newPath)}`);
221
+ f.skipped = true;
222
+ }
223
+ else if (await fs.pathExists(newPath)) {
224
+ log.info(logTag, `Ignore Exists: ${ipx} ${helper.pathShort(newPath)}`);
225
+ f.skipped = true;
226
+ }
227
+ else if (nameDuplicateSet.has(newPath)) {
228
+ log.info(logTag, `Ignore Dup: ${ipx} ${helper.pathShort(newPath)}`);
229
+ f.skipped = true;
230
+ }
231
+
232
+ if (f.skipped) {
233
+ // log.fileLog(`Skip: ${ipx} ${oldPath}`, logTag);
234
+ // log.showGray(logTag, `Skip: ${ipx} ${oldPath}`);
235
+ } else {
236
+ if (enc.hasBadUnicode(newDir)) {
237
+ log.showGray(logTag, `BadEncFR:${++badCount}`, oldPath)
238
+ log.show(logTag, `BadEncTO:${++badCount}`, newPath)
239
+ log.fileLog(`BadEncFR: ${ipx} <${oldPath}>`, logTag);
240
+ log.fileLog(`BadEncTO: ${ipx} <${newPath}>`, logTag);
241
+ } else {
242
+ f.skipped = false;
243
+ f.outName = newName;
244
+ f.outPath = newPath;
245
+ log.show(logTag, `FR: ${ipx} ${oldPath}`);
246
+ log.showGreen(logTag, `TO: ${ipx} ${newPath}`);
247
+ log.fileLog(`Add: ${ipx} <${oldPath}> [FR]`, logTag);
248
+ log.fileLog(`Add: ${ipx} <${newPath}> [TO]`, logTag);
249
+ return f;
250
+ }
251
+ }
252
+ }
package/cmd/cmd_prefix.js CHANGED
@@ -11,12 +11,11 @@ import chalk from 'chalk';
11
11
  import { sify } from 'chinese-conv';
12
12
  import fs from 'fs-extra';
13
13
  import inquirer from "inquirer";
14
+ import { cpus } from "os";
15
+ import pMap from 'p-map';
14
16
  import path from "path";
15
-
16
-
17
17
  import { asyncFilter } from '../lib/core.js';
18
18
  import * as log from '../lib/debug.js';
19
- import { fixCJKEnc } from '../lib/encoding.js';
20
19
  import * as mf from '../lib/file.js';
21
20
  import * as helper from '../lib/helper.js';
22
21
  import { renameFiles } from "./cmd_shared.js";
@@ -26,8 +25,6 @@ const MODE_DIR = "dirname";
26
25
  const MODE_PREFIX = "prefix";
27
26
  const MODE_MEDIA = "media";
28
27
  const MODE_CLEAN = 'clean';
29
- const MODE_TC2SC = "tc2sc"; // 繁体转简体
30
- const MODE_FIXENC = "fixenc"; // 乱码还原
31
28
 
32
29
  const NAME_LENGTH = 32;
33
30
 
@@ -67,7 +64,7 @@ const builder = function addOptions(ya, helpOrVersionSet) {
67
64
  type: "string",
68
65
  default: MODE_AUTO,
69
66
  description: "filename prefix mode for output ",
70
- choices: [MODE_AUTO, MODE_DIR, MODE_PREFIX, MODE_MEDIA, MODE_CLEAN, MODE_TC2SC, MODE_FIXENC],
67
+ choices: [MODE_AUTO, MODE_DIR, MODE_PREFIX, MODE_MEDIA, MODE_CLEAN],
71
68
  })
72
69
  .option("auto", {
73
70
  type: "boolean",
@@ -93,14 +90,6 @@ const builder = function addOptions(ya, helpOrVersionSet) {
93
90
  type: "boolean",
94
91
  description: "mode clean only",
95
92
  })
96
- .option("tc-to-sc", {
97
- type: "boolean",
98
- description: "mode tc to sc",
99
- })
100
- .option("fix-encoding", {
101
- type: "boolean",
102
- description: "mode fix encoding messy chars",
103
- })
104
93
  // 清理文件名中的特殊字符和非法字符
105
94
  .option("clean", {
106
95
  alias: "c",
@@ -158,7 +147,7 @@ const reImageName = /更新|合集|画师|图片|视频|插画|视图|作品|订
158
147
  // \p{ASCII} ASCII字符
159
148
  // \uFE10-\uFE1F 中文全角标点
160
149
  // \uFF01-\uFF11 中文全角标点
161
- const reNonChars = /[^\p{Unified_Ideograph}\p{P}\p{sc=Hira}0-z]/ugi;
150
+ const reNonChars = /[^\p{Unified_Ideograph}\p{sc=Hira}\p{sc=Kana}\w]/ugi;
162
151
  // 匹配空白字符和特殊字符
163
152
  // https://www.unicode.org/charts/PDF/U3000.pdf
164
153
  // https://www.asciitable.com/
@@ -177,7 +166,7 @@ const reMediaDirName = /^图片|视频|电影|电视剧|Image|Video|Thumbs$/gi;
177
166
  // https://github.com/fujaru/aromanize-js
178
167
  // https://www.npmjs.com/package/aromanize
179
168
  // https://www.npmjs.com/package/@lazy-cjk/japanese
180
- function cleanAlbumName(nameString, sep, filename) {
169
+ function cleanFileName(nameString, sep, filename, keepNumber = false) {
181
170
  let nameStr = nameString;
182
171
  // 去掉方括号 [xxx] 的内容
183
172
  // nameStr = nameStr.replaceAll(/\[.+?\]/gi, "");
@@ -186,19 +175,21 @@ function cleanAlbumName(nameString, sep, filename) {
186
175
  // 去掉视频说明文字
187
176
  nameStr = nameStr.replaceAll(reVideoName, "");
188
177
  // 去掉日期字符串
189
- nameStr = nameStr.replaceAll(/\d+年\d+月/gi, "");
190
- nameStr = nameStr.replaceAll(/\d{4}-\d{2}-\d{2}/gi, "");
178
+ if (!keepNumber) {
179
+ nameStr = nameStr.replaceAll(/\d+年\d+月/ugi, "");
180
+ nameStr = nameStr.replaceAll(/\d{4}-\d{2}-\d{2}/ugi, "");
181
+ }
191
182
  // 去掉 [100P5V 2.25GB] No.46 这种图片集说明
192
- nameStr = nameStr.replaceAll(/\[\d+P.*(\d+V)?.*?\]/gi, "");
193
- nameStr = nameStr.replaceAll(/No\.\d+|\d+\.?\d+GB?|\d+P|\d+V|NO\.(\d+)/gi, "$1");
183
+ nameStr = nameStr.replaceAll(/\[\d+P.*(\d+V)?.*?\]/ugi, "");
184
+ nameStr = nameStr.replaceAll(/No\.\d+|\d+\.?\d+GB?|\d+P|\d+V|NO\.(\d+)/ugi, "$1");
194
185
  if (helper.isImageFile(filename)) {
195
186
  // 去掉 2024.03.22 这种格式的日期
196
- nameStr = nameStr.replaceAll(/\d{4}\.\d{2}\.\d{2}/gi, "");
187
+ nameStr = nameStr.replaceAll(/\d{4}\.\d{2}\.\d{2}/ugi, "");
197
188
  }
198
189
  // 去掉中文标点特殊符号
199
- nameStr = nameStr.replaceAll(/[\u3000-\u303F\uFE10-\uFE1F\uFF01-\uFF11]/gi, "");
190
+ nameStr = nameStr.replaceAll(/[\u3000-\u303F\uFE10-\uFE2F]/ugi, "");
200
191
  // () [] {} <> . - 改为下划线
201
- nameStr = nameStr.replaceAll(/[\(\)\[\]{}<>\.\-]/gi, sep);
192
+ nameStr = nameStr.replaceAll(/[\(\)\[\]{}<>\.\-]/ugi, sep);
202
193
  // 日文转罗马字母
203
194
  // nameStr = hepburn.fromKana(nameStr);
204
195
  // nameStr = wanakana.toRomaji(nameStr);
@@ -232,19 +223,16 @@ function parseNameMode(argv) {
232
223
  if (argv.dirname) { mode = MODE_DIR; }
233
224
  if (argv.media) { mode = MODE_MEDIA; }
234
225
  if (argv.cleanOnly) { mode = MODE_CLEAN; }
235
- if (argv.tcToSc) { mode = MODE_TC2SC; }
236
- if (argv.fixEncoding) { mode = MODE_FIXENC; }
237
226
  return mode;
238
227
  }
239
228
 
240
229
  // 重复文件名Set,检测重复,防止覆盖
241
230
  const nameDuplicateSet = new Set();
242
- function createNewNameByMode(f, argv) {
231
+ async function createNewNameByMode(f) {
232
+ const argv = f.argv;
243
233
  const mode = parseNameMode(argv);
244
234
  const nameLength = (mode === MODE_MEDIA
245
- || mode === MODE_CLEAN
246
- || mode === MODE_TC2SC
247
- || mode === MODE_FIXENC) ?
235
+ || mode === MODE_CLEAN) ?
248
236
  200 : argv.length || NAME_LENGTH;
249
237
  const nameSlice = nameLength * -1;
250
238
  const [dir, base, ext] = helper.pathSplit(f.path);
@@ -263,8 +251,6 @@ function createNewNameByMode(f, argv) {
263
251
  let oldBase = base;
264
252
  switch (mode) {
265
253
  case MODE_CLEAN:
266
- case MODE_TC2SC:
267
- case MODE_FIXENC:
268
254
  {
269
255
  sep = ".";
270
256
  prefix = "";
@@ -310,35 +296,21 @@ function createNewNameByMode(f, argv) {
310
296
  break;
311
297
  default:
312
298
  throw new Error(`Invalid mode: ${mode} ${argv.mode}`)
313
- break;
314
299
  }
315
300
 
316
- if (mode !== MODE_CLEAN && mode !== MODE_TC2SC && mode !== MODE_FIXENC) {
301
+ if (mode !== MODE_CLEAN) {
317
302
  // 无有效前缀,报错退出
318
303
  if (!prefix || prefix.length == 0) {
319
304
  log.warn(logTag, `Invalid Prefix: ${helper.pathShort(f.path)} ${mode}`);
320
305
  throw new Error(`No prefix supplied!`);
321
306
  }
322
307
  }
308
+ log.show(prefix)
323
309
  let newPathFixed = null;
324
- // 此模式仅执行简繁转换,不进行其它操作
325
- if (mode === MODE_TC2SC) {
326
- oldBase = sify(oldBase);
327
- } else if (mode == MODE_FIXENC) {
328
- // 当模式为MODE_FIXENC时,对文件路径进行特定的编码修复处理
329
- // 对旧基础路径进行中日韩文字编码修复
330
- oldBase = fixCJKEnc(oldBase);
331
- // 将目录路径分割,并对每个部分进行编码修复
332
- const dirNamesFixed = dir.split(path.sep).map(s => fixCJKEnc(s));
333
- // 重新组合修复后的目录路径
334
- const dirFixed = path.join(...dirNamesFixed);
335
- // 生成修复后的新路径,包括旧基础路径和文件扩展名
336
- newPathFixed = path.join(dirFixed, `${oldBase}${ext}`);
337
- }
338
310
  // 是否净化文件名,去掉各种特殊字符
339
- else if (argv.clean || mode === MODE_CLEAN) {
340
- prefix = cleanAlbumName(prefix, sep, oldName);
341
- oldBase = cleanAlbumName(oldBase, sep, oldName);
311
+ if (argv.clean || mode === MODE_CLEAN) {
312
+ prefix = cleanFileName(prefix, sep, oldName, false);
313
+ oldBase = cleanFileName(oldBase, sep, oldName, true);
342
314
  }
343
315
  // 不添加重复前缀
344
316
  if (oldBase.includes(prefix)) {
@@ -346,26 +318,23 @@ function createNewNameByMode(f, argv) {
346
318
  prefix = "";
347
319
  }
348
320
  let fullBase = prefix.length > 0 ? (prefix + sep + oldBase) : oldBase;
349
- log.showGray(base)
350
- log.showGray(fullBase)
351
- if (mode !== MODE_TC2SC && mode !== MODE_FIXENC) {
352
- // 去除首位空白和特殊字符
353
- fullBase = fullBase.replaceAll(reStripUglyChars, "");
354
- // 多余空白和字符替换为一个字符 _或.
355
- fullBase = fullBase.replaceAll(reUglyChars, sep);
356
- // 去掉重复词组,如目录名和人名
357
- fullBase = Array.from(new Set(fullBase.split(sep))).join(sep)
358
- fullBase = unicodeStrLength(fullBase) > nameLength ? fullBase.slice(nameSlice) : fullBase;
359
- // 再次去掉首位的特殊字符和空白字符
360
- fullBase = fullBase.replaceAll(reStripUglyChars, "");
361
- }
321
+ // 去除首位空白和特殊字符
322
+ fullBase = fullBase.replaceAll(reStripUglyChars, "");
323
+ // 多余空白和字符替换为一个字符 _或.
324
+ fullBase = fullBase.replaceAll(reUglyChars, sep);
325
+ // 去掉重复词组,如目录名和人名
326
+ fullBase = Array.from(new Set(fullBase.split(sep))).join(sep)
327
+ fullBase = unicodeStrLength(fullBase) > nameLength ? fullBase.slice(nameSlice) : fullBase;
328
+ // 再次去掉首位的特殊字符和空白字符
329
+ fullBase = fullBase.replaceAll(reStripUglyChars, "");
330
+
362
331
  const newName = `${fullBase}${ext}`;
363
332
  const newPath = newPathFixed ?? path.join(dir, newName);
364
333
  if (newPath === f.path) {
365
334
  log.info(logTag, `Same: ${ipx} ${helper.pathShort(newPath)}`);
366
335
  f.skipped = true;
367
336
  }
368
- else if (fs.existsSync(newPath)) {
337
+ else if (await fs.pathExists(newPath)) {
369
338
  log.info(logTag, `Exists: ${ipx} ${helper.pathShort(newPath)}`);
370
339
  f.skipped = true;
371
340
  }
@@ -375,12 +344,15 @@ function createNewNameByMode(f, argv) {
375
344
  }
376
345
  nameDuplicateSet.add(newPath);
377
346
  if (f.skipped) {
378
- log.fileLog(`Skip: ${ipx} ${f.path}`, logTag);
347
+ // log.fileLog(`Skip: ${ipx} ${f.path}`, logTag);
348
+ // log.showGray(logTag, `Skip: ${ipx} ${f.path}`);
379
349
  } else {
380
350
  f.outName = newName;
381
351
  f.outPath = newPath;
382
- log.show(logTag, `${ipx} ${chalk.cyan(helper.pathShort(f.path, 36))} => ${chalk.green(newName)}`);
383
- log.fileLog(`Prepare: ${ipx} <${f.path}> => ${newName}`, logTag);
352
+ log.show(logTag, `${ipx} ${f.path}`);
353
+ log.showGreen(logTag, `${ipx} ${newPath}`);
354
+ log.fileLog(`Prepare: ${ipx} <${f.path}> [FROM]`, logTag);
355
+ log.fileLog(`Prepare: ${ipx} <${newPath}> [TOTO]`, logTag);
384
356
  }
385
357
  return f;
386
358
  }
@@ -434,12 +406,17 @@ const handler = async function cmdPrefix(argv) {
434
406
  files = files.map((f, i) => {
435
407
  return {
436
408
  ...f,
409
+ argv: argv,
437
410
  index: i,
438
411
  total: files.length,
439
412
  }
440
413
  })
441
414
  const fCount = files.length;
442
- const tasks = files.map(f => createNewNameByMode(f, argv)).filter(f => f?.outName)
415
+ //const tasks = files.map(f => createNewNameByMode(f, argv)).filter(f => f?.outName)
416
+
417
+ let tasks = await pMap(files, createNewNameByMode, { concurrency: cpus().length * 4 })
418
+ tasks = tasks.filter(f => f?.outName)
419
+
443
420
  const tCount = tasks.length;
444
421
  log.showYellow(
445
422
  logTag, `Total ${fCount - tCount} files are skipped.`
package/cmd/cmd_shared.js CHANGED
@@ -29,6 +29,7 @@ async function renameOneFile(f) {
29
29
  log.showYellow("Rename", "ignore", f.path);
30
30
  return;
31
31
  }
32
+ log.showGray(`Source: ${f.path}`);
32
33
  try {
33
34
  // 确保输出目录已存在,如果不存在则创建
34
35
  const outDir = path.dirname(outPath);