lake-cimg 1.0.2 → 1.1.0
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/bin/cimg.js +1 -1
- package/lib/compress.js +59 -6
- package/lib/scan.js +5 -0
- package/lib/scanCodeReferences.js +1 -1
- package/package.json +1 -1
package/bin/cimg.js
CHANGED
|
@@ -298,7 +298,7 @@ program
|
|
|
298
298
|
.command("scan [input]")
|
|
299
299
|
.description("扫描图片并给出优化建议(不修改文件)")
|
|
300
300
|
.option("-r, --recursive", "递归处理子目录")
|
|
301
|
-
.option("--json", "以 JSON
|
|
301
|
+
.option("--json", "以 JSON 格式输出(适合脚本与工具消费)")
|
|
302
302
|
.action(async function scanAction(input, opts) {
|
|
303
303
|
if (!input || input.trim() === "") {
|
|
304
304
|
this.outputHelp();
|
package/lib/compress.js
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
encodeJpegBuffer,
|
|
18
18
|
} from "./sharpHelpers.js";
|
|
19
19
|
|
|
20
|
+
/** 小于此体积跳过压缩 */
|
|
21
|
+
const THRESHOLD_SKIP_FOR_AGENT = 10 * 1024;
|
|
22
|
+
|
|
20
23
|
/** Max concurrent tasks for processing images */
|
|
21
24
|
const MAX_CONCURRENCY = Math.max(1, cpus().length);
|
|
22
25
|
|
|
@@ -104,7 +107,9 @@ export async function collectFiles(inputPath, recursive = false) {
|
|
|
104
107
|
* originalHeight: number | null,
|
|
105
108
|
* width: number | null,
|
|
106
109
|
* height: number | null,
|
|
107
|
-
* format: string | null
|
|
110
|
+
* format: string | null,
|
|
111
|
+
* skipped?: true,
|
|
112
|
+
* reason?: "small" | "larger"
|
|
108
113
|
* }>}
|
|
109
114
|
*/
|
|
110
115
|
export async function processOne(inputPath, options = {}) {
|
|
@@ -119,6 +124,21 @@ export async function processOne(inputPath, options = {}) {
|
|
|
119
124
|
|
|
120
125
|
const inputBuffer = await readFile(inputPath);
|
|
121
126
|
const sizeBefore = inputBuffer.length;
|
|
127
|
+
if (sizeBefore < THRESHOLD_SKIP_FOR_AGENT) {
|
|
128
|
+
return {
|
|
129
|
+
outPath,
|
|
130
|
+
sizeBefore,
|
|
131
|
+
sizeAfter: sizeBefore,
|
|
132
|
+
originalWidth: null,
|
|
133
|
+
originalHeight: null,
|
|
134
|
+
width: null,
|
|
135
|
+
height: null,
|
|
136
|
+
format: null,
|
|
137
|
+
skipped: true,
|
|
138
|
+
reason: "small",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
let pipeline = sharp(inputBuffer, { animated: true });
|
|
123
143
|
const inputMeta = await pipeline.metadata();
|
|
124
144
|
|
|
@@ -128,8 +148,23 @@ export async function processOne(inputPath, options = {}) {
|
|
|
128
148
|
? await encodeWebpBuffer(pipeline, quality, { lossless: false })
|
|
129
149
|
: await toFormatBuffer(pipeline, ext, quality);
|
|
130
150
|
|
|
131
|
-
const outputMeta = await sharp(outputBuffer).metadata();
|
|
132
151
|
const sizeAfter = outputBuffer.length;
|
|
152
|
+
if (sizeAfter > sizeBefore) {
|
|
153
|
+
return {
|
|
154
|
+
outPath,
|
|
155
|
+
sizeBefore,
|
|
156
|
+
sizeAfter,
|
|
157
|
+
originalWidth: inputMeta.width ?? null,
|
|
158
|
+
originalHeight: inputMeta.height ?? null,
|
|
159
|
+
width: null,
|
|
160
|
+
height: null,
|
|
161
|
+
format: null,
|
|
162
|
+
skipped: true,
|
|
163
|
+
reason: "larger",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const outputMeta = await sharp(outputBuffer).metadata();
|
|
133
168
|
await mkdir(outBase, { recursive: true });
|
|
134
169
|
await writeFile(outPath, outputBuffer);
|
|
135
170
|
return {
|
|
@@ -166,7 +201,7 @@ async function toFormatBuffer(pipeline, ext, quality) {
|
|
|
166
201
|
/**
|
|
167
202
|
* Run batch compression.
|
|
168
203
|
* @param {{ inputPath: string, outDir?: string | null, size?: number | null, quality?: number, recursive?: boolean }} options
|
|
169
|
-
* @returns {Promise<{ success: number, failed: number }>}
|
|
204
|
+
* @returns {Promise<{ success: number, failed: number, skipped: number }>} counts; throws if input invalid
|
|
170
205
|
*/
|
|
171
206
|
export async function run(options) {
|
|
172
207
|
const {
|
|
@@ -208,13 +243,30 @@ export async function run(options) {
|
|
|
208
243
|
|
|
209
244
|
let success = 0;
|
|
210
245
|
let failed = 0;
|
|
246
|
+
let skipped = 0;
|
|
211
247
|
let totalBefore = 0;
|
|
212
248
|
let totalAfter = 0;
|
|
213
249
|
for (let i = 0; i < files.length; i++) {
|
|
214
250
|
const r = results[i];
|
|
215
251
|
const fp = files[i];
|
|
216
252
|
if (r.status === "fulfilled") {
|
|
217
|
-
const
|
|
253
|
+
const v = r.value;
|
|
254
|
+
if (v.skipped) {
|
|
255
|
+
skipped++;
|
|
256
|
+
if (v.reason === "small") {
|
|
257
|
+
console.log(` ⏭ ${fp}`);
|
|
258
|
+
console.log(
|
|
259
|
+
` 不处理:原图 ${formatSize(v.sizeBefore)} 小于 10KB(无需压缩)`
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
console.log(` ⏭ ${fp}`);
|
|
263
|
+
console.log(
|
|
264
|
+
` 不处理:压缩后 ${formatSize(v.sizeAfter)} 大于原图 ${formatSize(v.sizeBefore)}(已保留原图、未写出新文件)`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const { outPath, sizeBefore, sizeAfter } = v;
|
|
218
270
|
totalBefore += sizeBefore;
|
|
219
271
|
totalAfter += sizeAfter;
|
|
220
272
|
console.log(` ✅ ${fp} → ${basename(outPath)}`);
|
|
@@ -225,9 +277,10 @@ export async function run(options) {
|
|
|
225
277
|
failed++;
|
|
226
278
|
}
|
|
227
279
|
}
|
|
228
|
-
|
|
280
|
+
const skipPart = skipped > 0 ? `,跳过 ${skipped}(<10KB 或压缩后更大)` : "";
|
|
281
|
+
console.log(`\n完成:成功 ${success},失败 ${failed}${skipPart}。`);
|
|
229
282
|
if (success > 0 && totalBefore > 0) {
|
|
230
283
|
console.log(`合计:${formatCompare(totalBefore, totalAfter)}`);
|
|
231
284
|
}
|
|
232
|
-
return { success, failed };
|
|
285
|
+
return { success, failed, skipped };
|
|
233
286
|
}
|
package/lib/scan.js
CHANGED
|
@@ -7,6 +7,8 @@ import sharp from "sharp";
|
|
|
7
7
|
import { collectFiles } from "./compress.js";
|
|
8
8
|
|
|
9
9
|
const KB = 1024;
|
|
10
|
+
/** 小于此体积一般无压缩收益,建议标记为不处理 */
|
|
11
|
+
const THRESHOLD_SKIP_FOR_AGENT = 10 * KB;
|
|
10
12
|
const THRESHOLD_LARGE_NON_WEBP = 100 * KB;
|
|
11
13
|
const THRESHOLD_LARGE_WEBP = 500 * KB;
|
|
12
14
|
const MAX_EDGE = 1920;
|
|
@@ -25,6 +27,9 @@ function formatSize(bytes) {
|
|
|
25
27
|
* @returns {string}
|
|
26
28
|
*/
|
|
27
29
|
function computeSuggestion(sizeBytes, format, width, height) {
|
|
30
|
+
if (sizeBytes < THRESHOLD_SKIP_FOR_AGENT) {
|
|
31
|
+
return "不处理:原图 <10KB(体积过小,无需压缩)";
|
|
32
|
+
}
|
|
28
33
|
const fmt = (format ?? "").toLowerCase();
|
|
29
34
|
if (fmt !== "webp" && sizeBytes > THRESHOLD_LARGE_NON_WEBP) {
|
|
30
35
|
return "convert to webp";
|