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.
- package/cmd/cmd_compress.js +1 -1
- package/cmd/cmd_fixname.js +252 -0
- package/cmd/cmd_prefix.js +45 -68
- package/cmd/cmd_shared.js +1 -0
- package/cmd/cmd_zipu.js +246 -0
- package/lib/core.js +5 -0
- package/lib/debug.js +18 -4
- package/lib/encoding.js +182 -34
- package/lib/file.js +1 -1
- package/lib/helper.js +11 -1
- package/lib/messy_hanzi.txt +1 -0
- package/lib/unicode.js +23 -10
- package/lib/unicode_data.js +21 -5
- package/lib/unicode_data.json +6 -0
- package/lib/words.json +64570 -0
- package/package.json +11 -1
- package/scripts/fix_messy.js +84 -0
- package/scripts/media_cli.js +10 -3
- package/scripts/path_test.js +14 -0
- package/scripts/unicode_test.js +70 -60
- package/scripts/zip_test.js +82 -0
package/cmd/cmd_compress.js
CHANGED
|
@@ -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
|
|
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{
|
|
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
|
|
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
|
-
|
|
190
|
-
|
|
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)?.*?\]/
|
|
193
|
-
nameStr = nameStr.replaceAll(/No\.\d+|\d+\.?\d+GB?|\d+P|\d+V|NO\.(\d+)/
|
|
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}/
|
|
187
|
+
nameStr = nameStr.replaceAll(/\d{4}\.\d{2}\.\d{2}/ugi, "");
|
|
197
188
|
}
|
|
198
189
|
// 去掉中文标点特殊符号
|
|
199
|
-
nameStr = nameStr.replaceAll(/[\u3000-\u303F\uFE10-\
|
|
190
|
+
nameStr = nameStr.replaceAll(/[\u3000-\u303F\uFE10-\uFE2F]/ugi, "");
|
|
200
191
|
// () [] {} <> . - 改为下划线
|
|
201
|
-
nameStr = nameStr.replaceAll(/[\(\)\[\]{}<>\.\-]/
|
|
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
|
|
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
|
|
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
|
-
|
|
340
|
-
prefix =
|
|
341
|
-
oldBase =
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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.
|
|
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} ${
|
|
383
|
-
log.
|
|
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.`
|