lofter-lottie-opt 1.0.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.
Files changed (66) hide show
  1. package/README.md +371 -0
  2. package/dist/bundle/358.mjs +34 -0
  3. package/dist/bundle/506.mjs +301 -0
  4. package/dist/bundle/599.mjs +15335 -0
  5. package/dist/bundle/599.mjs.LICENSE.txt +22 -0
  6. package/dist/bundle/index.mjs +13631 -0
  7. package/dist/bundle/index.mjs.LICENSE.txt +13 -0
  8. package/dist/cli/index.mjs +3162 -0
  9. package/dist/esm/cli/index.d.ts +3 -0
  10. package/dist/esm/cli/index.d.ts.map +1 -0
  11. package/dist/esm/cli/validate.d.ts +3 -0
  12. package/dist/esm/cli/validate.d.ts.map +1 -0
  13. package/dist/esm/core/optimizer.d.ts +13 -0
  14. package/dist/esm/core/optimizer.d.ts.map +1 -0
  15. package/dist/esm/index.d.ts +8 -0
  16. package/dist/esm/index.d.ts.map +1 -0
  17. package/dist/esm/index.mjs +2048 -0
  18. package/dist/esm/plugins/compress-images/algorithm.d.ts +11 -0
  19. package/dist/esm/plugins/compress-images/algorithm.d.ts.map +1 -0
  20. package/dist/esm/plugins/compress-images/index.d.ts +14 -0
  21. package/dist/esm/plugins/compress-images/index.d.ts.map +1 -0
  22. package/dist/esm/plugins/compress-images/tinypng.d.ts +22 -0
  23. package/dist/esm/plugins/compress-images/tinypng.d.ts.map +1 -0
  24. package/dist/esm/plugins/compress-images/types.d.ts +106 -0
  25. package/dist/esm/plugins/compress-images/types.d.ts.map +1 -0
  26. package/dist/esm/plugins/compress-images/utils.d.ts +66 -0
  27. package/dist/esm/plugins/compress-images/utils.d.ts.map +1 -0
  28. package/dist/esm/plugins/index.d.ts +8 -0
  29. package/dist/esm/plugins/index.d.ts.map +1 -0
  30. package/dist/esm/plugins/minify-json/algorithm.d.ts +15 -0
  31. package/dist/esm/plugins/minify-json/algorithm.d.ts.map +1 -0
  32. package/dist/esm/plugins/minify-json/index.d.ts +9 -0
  33. package/dist/esm/plugins/minify-json/index.d.ts.map +1 -0
  34. package/dist/esm/plugins/minify-json/types.d.ts +55 -0
  35. package/dist/esm/plugins/minify-json/types.d.ts.map +1 -0
  36. package/dist/esm/plugins/remove-unused-assets/algorithm.d.ts +20 -0
  37. package/dist/esm/plugins/remove-unused-assets/algorithm.d.ts.map +1 -0
  38. package/dist/esm/plugins/remove-unused-assets/index.d.ts +18 -0
  39. package/dist/esm/plugins/remove-unused-assets/index.d.ts.map +1 -0
  40. package/dist/esm/plugins/remove-unused-assets/types.d.ts +52 -0
  41. package/dist/esm/plugins/remove-unused-assets/types.d.ts.map +1 -0
  42. package/dist/esm/plugins/simplify-paths/algorithm.d.ts +17 -0
  43. package/dist/esm/plugins/simplify-paths/algorithm.d.ts.map +1 -0
  44. package/dist/esm/plugins/simplify-paths/index.d.ts +16 -0
  45. package/dist/esm/plugins/simplify-paths/index.d.ts.map +1 -0
  46. package/dist/esm/plugins/simplify-paths/types.d.ts +101 -0
  47. package/dist/esm/plugins/simplify-paths/types.d.ts.map +1 -0
  48. package/dist/esm/testing/comparison-tool.d.ts +60 -0
  49. package/dist/esm/testing/comparison-tool.d.ts.map +1 -0
  50. package/dist/esm/types/index.d.ts +175 -0
  51. package/dist/esm/types/index.d.ts.map +1 -0
  52. package/dist/esm/types/plugin-common.d.ts +91 -0
  53. package/dist/esm/types/plugin-common.d.ts.map +1 -0
  54. package/dist/esm/utils/config-validator.d.ts +19 -0
  55. package/dist/esm/utils/config-validator.d.ts.map +1 -0
  56. package/dist/esm/utils/logger.d.ts +4 -0
  57. package/dist/esm/utils/logger.d.ts.map +1 -0
  58. package/dist/esm/utils/nos-client.d.ts +55 -0
  59. package/dist/esm/utils/nos-client.d.ts.map +1 -0
  60. package/dist/esm/utils/size.d.ts +5 -0
  61. package/dist/esm/utils/size.d.ts.map +1 -0
  62. package/dist/esm/validation/index.d.ts +67 -0
  63. package/dist/esm/validation/index.d.ts.map +1 -0
  64. package/dist/esm/validation/visual-validator.d.ts +61 -0
  65. package/dist/esm/validation/visual-validator.d.ts.map +1 -0
  66. package/package.json +57 -0
@@ -0,0 +1,2048 @@
1
+ import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "node:module";
2
+ import chalk from "chalk";
3
+ import node_fs from "node:fs";
4
+ import node_path from "node:path";
5
+ import node_fetch from "node-fetch";
6
+ import node_os from "node:os";
7
+ import nconf from "nconf";
8
+ var __webpack_modules__ = {
9
+ sharp: function(module) {
10
+ module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("sharp");
11
+ }
12
+ };
13
+ var __webpack_module_cache__ = {};
14
+ function __webpack_require__(moduleId) {
15
+ var cachedModule = __webpack_module_cache__[moduleId];
16
+ if (void 0 !== cachedModule) return cachedModule.exports;
17
+ var module = __webpack_module_cache__[moduleId] = {
18
+ exports: {}
19
+ };
20
+ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
21
+ return module.exports;
22
+ }
23
+ (()=>{
24
+ __webpack_require__.n = (module)=>{
25
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
26
+ __webpack_require__.d(getter, {
27
+ a: getter
28
+ });
29
+ return getter;
30
+ };
31
+ })();
32
+ (()=>{
33
+ __webpack_require__.d = (exports, definition)=>{
34
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) Object.defineProperty(exports, key, {
35
+ enumerable: true,
36
+ get: definition[key]
37
+ });
38
+ };
39
+ })();
40
+ (()=>{
41
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
42
+ })();
43
+ function createLogger(verbose = false) {
44
+ return {
45
+ debug: (message)=>{
46
+ if (verbose) console.log(chalk.gray(`[DEBUG] ${message}`));
47
+ },
48
+ info: (message)=>{
49
+ console.log(chalk.blue(`[INFO] ${message}`));
50
+ },
51
+ warn: (message)=>{
52
+ console.log(chalk.yellow(`[WARN] ${message}`));
53
+ },
54
+ error: (message)=>{
55
+ console.log(chalk.red(`[ERROR] ${message}`));
56
+ }
57
+ };
58
+ }
59
+ function createSilentLogger() {
60
+ return {
61
+ debug: ()=>{},
62
+ info: ()=>{},
63
+ warn: ()=>{},
64
+ error: ()=>{}
65
+ };
66
+ }
67
+ function calculateSize(data) {
68
+ return Buffer.byteLength(JSON.stringify(data), 'utf8');
69
+ }
70
+ function formatSize(bytes) {
71
+ if (bytes < 1024) return `${bytes} B`;
72
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
73
+ return `${(bytes / 1048576).toFixed(1)} MB`;
74
+ }
75
+ function calculateCompressionRatio(originalSize, optimizedSize) {
76
+ return (originalSize - optimizedSize) / originalSize * 100;
77
+ }
78
+ function computeReferencedAssetIds(json, opts) {
79
+ const assets = Array.isArray(json.assets) ? json.assets : [];
80
+ const rootLayers = Array.isArray(json.layers) ? json.layers : [];
81
+ const byId = new Map();
82
+ for (const a of assets)if (a && "string" == typeof a.id) byId.set(a.id, a);
83
+ const used = new Set();
84
+ const visitedPrecompIds = new Set();
85
+ const unknownRefs = new Set();
86
+ const queue = [
87
+ ...rootLayers
88
+ ];
89
+ while(queue.length > 0){
90
+ const layer = queue.shift();
91
+ const ty = layer.ty;
92
+ const refId = layer.refId;
93
+ if ((0 === ty || 2 === ty) && "string" == typeof refId && refId) if (byId.has(refId)) {
94
+ used.add(refId);
95
+ const asset = byId.get(refId);
96
+ if (Array.isArray(asset.layers) && asset.layers.length > 0) {
97
+ if (!visitedPrecompIds.has(refId)) {
98
+ visitedPrecompIds.add(refId);
99
+ for (const sub of asset.layers)queue.push(sub);
100
+ }
101
+ }
102
+ } else unknownRefs.add(refId);
103
+ }
104
+ if (opts.preserveAssetIds && opts.preserveAssetIds.length > 0) for (const id of opts.preserveAssetIds)used.add(id);
105
+ return {
106
+ used,
107
+ unknownRefs
108
+ };
109
+ }
110
+ function removeUnusedAssets(input, options = {}) {
111
+ const opts = {
112
+ preserveAssetIds: options.preserveAssetIds ?? [],
113
+ keepAssetsWithoutId: options.keepAssetsWithoutId ?? true,
114
+ keepUnreferencedPrecomps: options.keepUnreferencedPrecomps ?? false,
115
+ onLog: options.onLog ?? (()=>{})
116
+ };
117
+ const assets = Array.isArray(input.assets) ? input.assets : [];
118
+ const originalAssetCount = assets.length;
119
+ if (0 === originalAssetCount) return {
120
+ data: input,
121
+ stats: {
122
+ originalAssetCount,
123
+ keptAssetCount: 0,
124
+ removedAssetCount: 0,
125
+ unknownRefIds: [],
126
+ referencedAssetIds: [],
127
+ removedAssetIds: []
128
+ }
129
+ };
130
+ const { used, unknownRefs } = computeReferencedAssetIds(input, opts);
131
+ const byId = new Map();
132
+ for (const a of assets)if (a && "string" == typeof a.id) byId.set(a.id, a);
133
+ const preservedIdSet = new Set(opts.preserveAssetIds);
134
+ const kept = [];
135
+ const removedIds = [];
136
+ for (const a of assets){
137
+ const id = a?.id;
138
+ if ("string" == typeof id && id) {
139
+ const isPrecomp = Array.isArray(a.layers);
140
+ const isReferenced = used.has(id) || preservedIdSet.has(id);
141
+ if (isReferenced) kept.push(a);
142
+ else if (opts.keepUnreferencedPrecomps && isPrecomp) kept.push(a);
143
+ else removedIds.push(id);
144
+ } else if (opts.keepAssetsWithoutId) kept.push(a);
145
+ else removedIds.push("(no-id)");
146
+ }
147
+ const assetsWithIds = kept.filter((asset)=>'string' == typeof asset?.id);
148
+ const output = {
149
+ ...input,
150
+ assets: assetsWithIds
151
+ };
152
+ const stats = {
153
+ originalAssetCount,
154
+ keptAssetCount: kept.length,
155
+ removedAssetCount: originalAssetCount - kept.length,
156
+ unknownRefIds: Array.from(unknownRefs),
157
+ referencedAssetIds: Array.from(used),
158
+ removedAssetIds: removedIds
159
+ };
160
+ if (stats.unknownRefIds.length > 0) opts.onLog(`[remove-unused-assets] Warning: ${stats.unknownRefIds.length} unknown refId(s) found in layers: ${stats.unknownRefIds.join(", ")}`);
161
+ opts.onLog(`[remove-unused-assets] assets: ${originalAssetCount} \u{2192} ${kept.length} (removed ${stats.removedAssetCount})`);
162
+ return {
163
+ data: output,
164
+ stats
165
+ };
166
+ }
167
+ const removeUnusedAssetsPlugin = {
168
+ name: 'remove-unused-assets',
169
+ description: 'Remove assets that are not (transitively) referenced by any layers using BFS traversal',
170
+ version: '2.0.0',
171
+ defaultOptions: {
172
+ preserveAssetIds: [],
173
+ keepAssetsWithoutId: true,
174
+ keepUnreferencedPrecomps: false
175
+ },
176
+ async apply (data, options, context) {
177
+ const sizeBefore = calculateSize(data);
178
+ const pluginOptions = {
179
+ ...options,
180
+ onLog: context.logger?.info || (()=>{})
181
+ };
182
+ const result = removeUnusedAssets(data, pluginOptions);
183
+ const sizeAfter = calculateSize(result.data);
184
+ return {
185
+ data: result.data,
186
+ changed: result.stats.removedAssetCount > 0,
187
+ stats: {
188
+ itemsProcessed: result.stats.originalAssetCount,
189
+ itemsRemoved: result.stats.removedAssetCount,
190
+ sizeBefore,
191
+ sizeAfter
192
+ }
193
+ };
194
+ }
195
+ };
196
+ function add(a, b) {
197
+ return [
198
+ a[0] + b[0],
199
+ a[1] + b[1]
200
+ ];
201
+ }
202
+ function algorithm_sub(a, b) {
203
+ return [
204
+ a[0] - b[0],
205
+ a[1] - b[1]
206
+ ];
207
+ }
208
+ function mul(a, s) {
209
+ return [
210
+ a[0] * s,
211
+ a[1] * s
212
+ ];
213
+ }
214
+ function dot(a, b) {
215
+ return a[0] * b[0] + a[1] * b[1];
216
+ }
217
+ function len(a) {
218
+ return Math.hypot(a[0], a[1]);
219
+ }
220
+ function dist(a, b) {
221
+ return len(algorithm_sub(a, b));
222
+ }
223
+ function clamp01(x) {
224
+ return x < 0 ? 0 : x > 1 ? 1 : x;
225
+ }
226
+ function cubicBezier(p0, p1, p2, p3, t) {
227
+ const u = 1 - t;
228
+ const tt = t * t;
229
+ const uu = u * u;
230
+ const uuu = uu * u;
231
+ const ttt = tt * t;
232
+ const a = mul(p0, uuu);
233
+ const b = mul(p1, 3 * uu * t);
234
+ const c = mul(p2, 3 * u * tt);
235
+ const d = mul(p3, ttt);
236
+ return add(add(a, b), add(c, d));
237
+ }
238
+ function bboxOfVertices(vs) {
239
+ let minx = 1 / 0, miny = 1 / 0, maxx = -1 / 0, maxy = -1 / 0;
240
+ for (const [x, y] of vs){
241
+ if (x < minx) minx = x;
242
+ if (y < miny) miny = y;
243
+ if (x > maxx) maxx = x;
244
+ if (y > maxy) maxy = y;
245
+ }
246
+ const diag = Math.hypot(maxx - minx, maxy - miny) || 1;
247
+ return {
248
+ min: [
249
+ minx,
250
+ miny
251
+ ],
252
+ max: [
253
+ maxx,
254
+ maxy
255
+ ],
256
+ diag
257
+ };
258
+ }
259
+ function angleDeg(a, b, c) {
260
+ const v1 = algorithm_sub(a, b);
261
+ const v2 = algorithm_sub(c, b);
262
+ const l1 = len(v1) || 1;
263
+ const l2 = len(v2) || 1;
264
+ let cos = 2 * clamp01((dot(v1, v2) / (l1 * l2) + 1) / 2) - 1;
265
+ const ang = Math.acos(Math.max(-1, Math.min(1, cos)));
266
+ return 180 * ang / Math.PI;
267
+ }
268
+ function modWrap(i, n) {
269
+ return (i % n + n) % n;
270
+ }
271
+ function catmullRomHandles(v, closed, tension = 1.0) {
272
+ const n = v.length;
273
+ const Ii = new Array(n);
274
+ const Oo = new Array(n);
275
+ const get = (idx)=>{
276
+ if (closed) return v[modWrap(idx, n)];
277
+ if (idx < 0) return v[0];
278
+ if (idx >= n) return v[n - 1];
279
+ return v[idx];
280
+ };
281
+ for(let k = 0; k < n; k++){
282
+ const p0 = get(k - 1);
283
+ const p1 = get(k);
284
+ const p2 = get(k + 1);
285
+ const p3 = get(k + 2);
286
+ const b1 = add(p1, mul(algorithm_sub(p2, p0), tension / 6));
287
+ const b2 = algorithm_sub(p2, mul(algorithm_sub(p3, p1), tension / 6));
288
+ Oo[k] = algorithm_sub(b1, p1);
289
+ Ii[modWrap(k + 1, n)] = algorithm_sub(b2, p2);
290
+ }
291
+ if (!closed) {
292
+ if (!Ii[0]) Ii[0] = [
293
+ 0,
294
+ 0
295
+ ];
296
+ if (!Oo[n - 1]) Oo[n - 1] = [
297
+ 0,
298
+ 0
299
+ ];
300
+ }
301
+ for(let k = 0; k < n; k++){
302
+ if (!Ii[k]) Ii[k] = [
303
+ 0,
304
+ 0
305
+ ];
306
+ if (!Oo[k]) Oo[k] = [
307
+ 0,
308
+ 0
309
+ ];
310
+ }
311
+ return {
312
+ i: Ii,
313
+ o: Oo
314
+ };
315
+ }
316
+ function perpendicularDistance(p, a, b) {
317
+ const ab = algorithm_sub(b, a);
318
+ const t = Math.max(0, Math.min(1, dot(algorithm_sub(p, a), ab) / (dot(ab, ab) || 1)));
319
+ const proj = add(a, mul(ab, t));
320
+ return dist(p, proj);
321
+ }
322
+ function rdpIndices(points, epsilon) {
323
+ if (points.length <= 2) return points.map((_, idx)=>idx);
324
+ const keep = new Array(points.length).fill(false);
325
+ keep[0] = true;
326
+ keep[points.length - 1] = true;
327
+ function recurse(start, end) {
328
+ let maxDist = -1;
329
+ let index = -1;
330
+ const a = points[start];
331
+ const b = points[end];
332
+ for(let i = start + 1; i < end; i++){
333
+ const d = perpendicularDistance(points[i], a, b);
334
+ if (d > maxDist) {
335
+ maxDist = d;
336
+ index = i;
337
+ }
338
+ }
339
+ if (maxDist > epsilon && index >= 0) {
340
+ keep[index] = true;
341
+ recurse(start, index);
342
+ recurse(index, end);
343
+ }
344
+ }
345
+ recurse(0, points.length - 1);
346
+ const out = [];
347
+ for(let i = 0; i < keep.length; i++)if (keep[i]) out.push(i);
348
+ return out;
349
+ }
350
+ function rdpClosed(points, epsilon, mustKeep) {
351
+ if (points.length <= 2) return points.map((_, idx)=>idx);
352
+ const pts = points.slice();
353
+ pts.push(points[0]);
354
+ let kept = new Set(rdpIndices(pts, epsilon));
355
+ if (kept.has(pts.length - 1)) kept.delete(pts.length - 1);
356
+ if (mustKeep && mustKeep.size) for (const i of mustKeep)kept.add(i);
357
+ return Array.from(kept).sort((a, b)=>a - b);
358
+ }
359
+ function sampleShape(path, samplesPerSeg = 10) {
360
+ const { v, i, o, c } = path;
361
+ const n = v.length;
362
+ if (0 === n) return [];
363
+ const pts = [];
364
+ const segs = c ? n : n - 1;
365
+ for(let k = 0; k < segs; k++){
366
+ const a = v[k];
367
+ const b = v[modWrap(k + 1, n)];
368
+ const cp1 = add(v[k], o[k] || [
369
+ 0,
370
+ 0
371
+ ]);
372
+ const cp2 = add(v[modWrap(k + 1, n)], i[modWrap(k + 1, n)] || [
373
+ 0,
374
+ 0
375
+ ]);
376
+ const steps = Math.max(2, samplesPerSeg);
377
+ for(let s = 0; s <= steps; s++){
378
+ const t = s / steps;
379
+ pts.push(cubicBezier(a, cp1, cp2, b, t));
380
+ }
381
+ }
382
+ return pts;
383
+ }
384
+ function maxHausdorffLikeError(a, b, samplesPerSeg) {
385
+ const sa = sampleShape(a, samplesPerSeg);
386
+ const sb = sampleShape(b, samplesPerSeg);
387
+ const m = Math.max(sa.length, sb.length);
388
+ let maxErr = 0;
389
+ for(let i = 0; i < m; i++){
390
+ const pa = sa[i % sa.length];
391
+ const pb = sb[i % sb.length];
392
+ const d = dist(pa, pb);
393
+ if (d > maxErr) maxErr = d;
394
+ }
395
+ return maxErr;
396
+ }
397
+ function computeCornerSet(v, closed, angleDegThreshold) {
398
+ const n = v.length;
399
+ const keep = new Set();
400
+ for(let k = 0; k < n; k++){
401
+ const a = v[modWrap(k - 1, n)];
402
+ const b = v[k];
403
+ const c = v[modWrap(k + 1, n)];
404
+ let ang = angleDeg(a, b, c);
405
+ if (!closed && (0 === k || k === n - 1)) {
406
+ keep.add(k);
407
+ continue;
408
+ }
409
+ if (ang < angleDegThreshold) keep.add(k);
410
+ }
411
+ return keep;
412
+ }
413
+ function simplifyStaticPath(inPath, opts) {
414
+ const { v, c } = inPath;
415
+ if (!v || v.length < opts.minPoints) return inPath;
416
+ const { diag } = bboxOfVertices(v);
417
+ const tolAbs = opts.relativeTolerance ? opts.tolerance * diag : opts.tolerance;
418
+ const cornerSet = opts.preserveCorners ? computeCornerSet(v, c, opts.cornerAngleDeg) : new Set();
419
+ const mustKeep = new Set(cornerSet);
420
+ if (!c) {
421
+ mustKeep.add(0);
422
+ mustKeep.add(v.length - 1);
423
+ }
424
+ const keptIdx = c ? rdpClosed(v, tolAbs, mustKeep) : Array.from(new Set([
425
+ ...rdpIndices(v, tolAbs),
426
+ ...mustKeep
427
+ ])).sort((a, b)=>a - b);
428
+ if (keptIdx.length < opts.minPoints) return inPath;
429
+ const v2 = keptIdx.map((i)=>inPath.v[i]);
430
+ const { i: i2, o: o2 } = catmullRomHandles(v2, c, opts.tension);
431
+ const out = {
432
+ v: v2,
433
+ i: i2,
434
+ o: o2,
435
+ c
436
+ };
437
+ const err = maxHausdorffLikeError(inPath, out, opts.samplesPerSegment);
438
+ if (err > tolAbs) return inPath;
439
+ return out;
440
+ }
441
+ function tryRemoveIndexAcrossFrames(frames, j, minPoints, tolAbsPerFrame, samplesPerSeg, tension) {
442
+ const n = frames[0].v.length;
443
+ if (n <= minPoints) return {
444
+ ok: false
445
+ };
446
+ const outFrames = [];
447
+ for (const f of frames){
448
+ const { v, c } = f;
449
+ const nNow = v.length;
450
+ if (nNow !== n) return {
451
+ ok: false
452
+ };
453
+ if (nNow - 1 < minPoints) return {
454
+ ok: false
455
+ };
456
+ const keptIdx = [];
457
+ for(let k = 0; k < nNow; k++)if (k !== j) keptIdx.push(k);
458
+ const v2 = keptIdx.map((i)=>f.v[i]);
459
+ const { i: i2, o: o2 } = catmullRomHandles(v2, c, tension);
460
+ const candidate = {
461
+ v: v2,
462
+ i: i2,
463
+ o: o2,
464
+ c
465
+ };
466
+ const err = maxHausdorffLikeError(f, candidate, samplesPerSeg);
467
+ if (err > tolAbsPerFrame) return {
468
+ ok: false
469
+ };
470
+ outFrames.push(candidate);
471
+ }
472
+ return {
473
+ ok: true,
474
+ framesOut: outFrames
475
+ };
476
+ }
477
+ function simplifyAnimatedConsistent(keyframes, opts) {
478
+ const frames = keyframes.map((kf)=>kf.s && kf.s[0] ? kf.s[0] : null).filter(Boolean);
479
+ if (0 === frames.length) return keyframes;
480
+ const n0 = frames[0].v.length;
481
+ const closed0 = frames[0].c;
482
+ for (const f of frames)if (f.v.length !== n0 || f.c !== closed0) return keyframes;
483
+ if (n0 < opts.minPoints) return keyframes;
484
+ const tolAbsPerFrame = frames.map((f)=>{
485
+ const { diag } = bboxOfVertices(f.v);
486
+ return opts.relativeTolerance ? opts.tolerance * diag : opts.tolerance;
487
+ });
488
+ const cornerUnion = new Set();
489
+ if (opts.preserveCorners) for (const f of frames){
490
+ const cs = computeCornerSet(f.v, f.c, opts.cornerAngleDeg);
491
+ cs.forEach((i)=>cornerUnion.add(i));
492
+ }
493
+ if (!closed0) {
494
+ cornerUnion.add(0);
495
+ cornerUnion.add(n0 - 1);
496
+ }
497
+ function importanceAt(j) {
498
+ let sum = 0;
499
+ for(let idx = 0; idx < frames.length; idx++){
500
+ const f = frames[idx];
501
+ const n = f.v.length;
502
+ const a = f.v[modWrap(j - 1, n)];
503
+ const b = f.v[modWrap(j, n)];
504
+ const c = f.v[modWrap(j + 1, n)];
505
+ sum += perpendicularDistance(b, a, c);
506
+ }
507
+ return sum / frames.length;
508
+ }
509
+ let removable = [];
510
+ for(let j = 0; j < n0; j++)if (!cornerUnion.has(j)) removable.push(j);
511
+ removable.sort((a, b)=>importanceAt(a) - importanceAt(b));
512
+ let currentFrames = frames.map((f)=>({
513
+ v: f.v.slice(),
514
+ i: f.i.slice(),
515
+ o: f.o.slice(),
516
+ c: f.c
517
+ }));
518
+ for (const j0 of removable){
519
+ const nNow = currentFrames[0].v.length;
520
+ if (nNow <= opts.minPoints) break;
521
+ const targetPos = frames[0].v[j0];
522
+ let jCurr = -1;
523
+ let best = 1 / 0;
524
+ for(let j = 0; j < currentFrames[0].v.length; j++){
525
+ const d = dist(currentFrames[0].v[j], targetPos);
526
+ if (d < best) {
527
+ best = d;
528
+ jCurr = j;
529
+ }
530
+ }
531
+ if (jCurr < 0 || 0 === jCurr || jCurr === currentFrames[0].v.length - 1 && !closed0) continue;
532
+ const trial = tryRemoveIndexAcrossFrames(currentFrames, jCurr, opts.minPoints, Math.max(...tolAbsPerFrame), opts.samplesPerSegment, opts.tension);
533
+ if (trial.ok && trial.framesOut) currentFrames = trial.framesOut;
534
+ }
535
+ let idx = 0;
536
+ for (const kf of keyframes)if (kf.s && kf.s[0]) {
537
+ const f = currentFrames[idx++];
538
+ kf.s = [
539
+ f
540
+ ];
541
+ if (kf.e && kf.e[0]) kf.e = [
542
+ f
543
+ ];
544
+ }
545
+ return keyframes;
546
+ }
547
+ function traverseShapes(items, fn, path = []) {
548
+ if (!items) return;
549
+ items.forEach((it, idx)=>{
550
+ const p = path.concat(idx);
551
+ if (it && "gr" === it.ty) traverseShapes(it.it, fn, p);
552
+ else if (it && "sh" === it.ty && it.ks) fn(it, p);
553
+ });
554
+ }
555
+ function simplifyPaths(input, options = {}) {
556
+ const opts = {
557
+ mode: options.mode ?? "static-only",
558
+ tolerance: options.tolerance ?? 1.0,
559
+ relativeTolerance: options.relativeTolerance ?? false,
560
+ minPoints: options.minPoints ?? 6,
561
+ preserveCorners: options.preserveCorners ?? true,
562
+ cornerAngleDeg: options.cornerAngleDeg ?? 150,
563
+ algorithm: "rdp",
564
+ samplesPerSegment: options.samplesPerSegment ?? 10,
565
+ tension: options.tension ?? 1.0,
566
+ onLog: options.onLog ?? (()=>{})
567
+ };
568
+ const out = {
569
+ ...input,
570
+ layers: input.layers ? JSON.parse(JSON.stringify(input.layers)) : []
571
+ };
572
+ let totalPaths = 0;
573
+ let simplifiedPaths = 0;
574
+ let staticSimplified = 0;
575
+ let animatedSimplified = 0;
576
+ let totalBefore = 0;
577
+ let totalAfter = 0;
578
+ const warnings = [];
579
+ for (const layer of out.layers || [])if (4 === layer.ty) traverseShapes(layer.shapes, (node)=>{
580
+ const ks = node.ks;
581
+ if (!ks || !ks.k) return;
582
+ if (("static-only" === opts.mode || "all" === opts.mode) && 0 === ks.a) {
583
+ const path = ks.k;
584
+ totalPaths++;
585
+ totalBefore += path.v.length;
586
+ const newPath = simplifyStaticPath(path, opts);
587
+ node.ks.k = newPath;
588
+ totalAfter += newPath.v.length;
589
+ if (newPath !== path) {
590
+ simplifiedPaths++;
591
+ staticSimplified++;
592
+ }
593
+ }
594
+ if (("animated-consistent" === opts.mode || "all" === opts.mode) && 1 === ks.a) {
595
+ const arr = ks.k;
596
+ if (!Array.isArray(arr) || arr.length < 2) return;
597
+ if (arr.some((kf)=>!kf.s || !kf.s[0])) return void warnings.push("animated path with missing s[0] skipped");
598
+ totalPaths++;
599
+ const beforeN = arr[0].s[0].v.length;
600
+ totalBefore += beforeN;
601
+ const newArr = simplifyAnimatedConsistent(arr, opts);
602
+ node.ks.k = newArr;
603
+ const afterN = newArr[0].s[0].v.length;
604
+ totalAfter += afterN;
605
+ if (afterN < beforeN) {
606
+ simplifiedPaths++;
607
+ animatedSimplified++;
608
+ }
609
+ }
610
+ });
611
+ const avgReduction = totalBefore > 0 ? (totalBefore - totalAfter) / totalBefore * 100 : 0;
612
+ if (warnings.length) warnings.forEach((w)=>opts.onLog(`[simplify-paths] ${w}`));
613
+ opts.onLog(`[simplify-paths] paths: ${totalPaths}, simplified: ${simplifiedPaths} (static ${staticSimplified}, animated ${animatedSimplified}), avg vertex reduction: ${avgReduction.toFixed(2)}%`);
614
+ return {
615
+ data: out,
616
+ stats: {
617
+ totalPaths,
618
+ simplifiedPaths,
619
+ staticSimplified,
620
+ animatedSimplified,
621
+ avgVertexReduction: Number(avgReduction.toFixed(2)),
622
+ warnings
623
+ }
624
+ };
625
+ }
626
+ const simplifyPathsPlugin = {
627
+ name: 'simplify-paths',
628
+ description: 'Simplify Lottie path data using RDP algorithm with Catmull-Rom handle regeneration',
629
+ version: '1.0.0',
630
+ defaultOptions: {
631
+ mode: 'static-only',
632
+ tolerance: 1.0,
633
+ relativeTolerance: false,
634
+ minPoints: 6,
635
+ preserveCorners: true,
636
+ cornerAngleDeg: 150,
637
+ algorithm: 'rdp',
638
+ samplesPerSegment: 10,
639
+ tension: 1.0
640
+ },
641
+ async apply (data, options, context) {
642
+ const sizeBefore = calculateSize(data);
643
+ let logMessages = [];
644
+ const onLog = (msg)=>{
645
+ if (context.logger) context.logger.debug(msg);
646
+ logMessages.push(msg);
647
+ };
648
+ const mergedOptions = {
649
+ ...options,
650
+ onLog
651
+ };
652
+ const result = simplifyPaths(data, mergedOptions);
653
+ const sizeAfter = calculateSize(result.data);
654
+ return {
655
+ data: result.data,
656
+ changed: result.stats.simplifiedPaths > 0,
657
+ stats: {
658
+ itemsProcessed: result.stats.totalPaths,
659
+ itemsRemoved: result.stats.totalPaths - result.stats.simplifiedPaths,
660
+ sizeBefore,
661
+ sizeAfter
662
+ }
663
+ };
664
+ }
665
+ };
666
+ let lastRequestTime = 0;
667
+ const MIN_REQUEST_INTERVAL = 2000;
668
+ let isServiceUnavailable = false;
669
+ function sleep(ms) {
670
+ return new Promise((resolve)=>setTimeout(resolve, ms));
671
+ }
672
+ function getRandomIP() {
673
+ return Array.from(Array(4)).map(()=>Math.floor(255 * Math.random()).toString()).join('.');
674
+ }
675
+ async function downloadImg(url) {
676
+ const response = await node_fetch(url);
677
+ if (!response.ok) throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
678
+ const buffer = await response.arrayBuffer();
679
+ return Buffer.from(buffer);
680
+ }
681
+ async function compress(file) {
682
+ if (isServiceUnavailable) throw new Error('TinyPNG service is temporarily unavailable (marked as failed)');
683
+ async function attemptRequest() {
684
+ const now = Date.now();
685
+ const timeSinceLastRequest = now - lastRequestTime;
686
+ if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) {
687
+ const waitTime = MIN_REQUEST_INTERVAL - timeSinceLastRequest;
688
+ await sleep(waitTime);
689
+ }
690
+ lastRequestTime = Date.now();
691
+ const response = await node_fetch('https://tinify.cn/backend/opt/shrink', {
692
+ method: 'POST',
693
+ headers: {
694
+ 'Cache-Control': 'no-cache',
695
+ 'Content-Type': 'application/x-www-form-urlencoded',
696
+ 'X-Forwarded-For': getRandomIP(),
697
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
698
+ Referer: 'https://tinify.cn',
699
+ Origin: 'https://tinify.cn'
700
+ },
701
+ body: file
702
+ });
703
+ if (429 === response.status) throw new Error(`Rate limited (429): ${response.statusText}`);
704
+ if (!response.ok) throw new Error(`TinyPNG compression failed: ${response.status} ${response.statusText}`);
705
+ const result = await response.json();
706
+ if (!result?.output?.url) throw new Error('TinyPNG response missing download URL');
707
+ const compressedBuffer = await downloadImg(result.output.url);
708
+ return [
709
+ compressedBuffer,
710
+ 'png'
711
+ ];
712
+ }
713
+ try {
714
+ return await attemptRequest();
715
+ } catch (error) {
716
+ const errorMessage = error.message;
717
+ if (errorMessage.includes('Rate limited (429)')) {
718
+ console.log('TinyPNG rate limited, waiting 5s before retry...');
719
+ await sleep(5000);
720
+ try {
721
+ return await attemptRequest();
722
+ } catch (retryError) {
723
+ isServiceUnavailable = true;
724
+ console.log('TinyPNG service marked as unavailable after retry failure');
725
+ throw new Error(`TinyPNG failed after retry: ${retryError.message}`);
726
+ }
727
+ }
728
+ throw error;
729
+ }
730
+ }
731
+ let NosClient = null;
732
+ async function loadNosClient() {
733
+ if (NosClient) return NosClient;
734
+ try {
735
+ const sdk = await import("@nos-sdk/nos-node-sdk");
736
+ NosClient = sdk.NosClient;
737
+ return NosClient;
738
+ } catch (error) {
739
+ throw new Error("NOS SDK \u672A\u5B89\u88C5\uFF0C\u8BF7\u5148\u5B89\u88C5 @nos-sdk/nos-node-sdk");
740
+ }
741
+ }
742
+ const NOS_CONF_PATH = node_path.resolve(node_os.homedir(), ".nos-conf", "config.json");
743
+ class NOSConf {
744
+ constructor(){
745
+ this.nconf = nconf;
746
+ this.nconf.env().file({
747
+ file: NOS_CONF_PATH
748
+ });
749
+ NOSConf.init();
750
+ }
751
+ static init() {
752
+ const dirPath = node_path.dirname(NOS_CONF_PATH);
753
+ node_fs.mkdirSync(dirPath, {
754
+ recursive: true
755
+ });
756
+ }
757
+ getConfig() {
758
+ return {
759
+ accessKey: this.nconf.get("accessKey"),
760
+ accessSecret: this.nconf.get("accessSecret"),
761
+ endpoint: this.nconf.get("endpoint"),
762
+ defaultBucket: this.nconf.get("defaultBucket"),
763
+ host: this.nconf.get("host"),
764
+ protocol: this.nconf.get("protocol")
765
+ };
766
+ }
767
+ setConfig(config) {
768
+ if (config.accessKey) this.nconf.set("accessKey", config.accessKey);
769
+ if (config.accessSecret) this.nconf.set("accessSecret", config.accessSecret);
770
+ if (config.endpoint) this.nconf.set("endpoint", config.endpoint);
771
+ if (config.defaultBucket) this.nconf.set("defaultBucket", config.defaultBucket);
772
+ if (config.host) this.nconf.set("host", config.host);
773
+ if (config.protocol) this.nconf.set("protocol", config.protocol);
774
+ this.nconf.save(void 0);
775
+ }
776
+ reset() {
777
+ this.nconf.reset();
778
+ this.nconf.save(void 0);
779
+ }
780
+ }
781
+ const nosConf = new NOSConf();
782
+ let client = null;
783
+ const upload = async (file)=>{
784
+ if (!client) {
785
+ const NosClientClass = await loadNosClient();
786
+ client = new NosClientClass(nosConf.getConfig());
787
+ }
788
+ await client.putObject({
789
+ objectKey: file.name,
790
+ body: file.data
791
+ });
792
+ const fileUrl = `${nosConf.getConfig().endpoint}/${file.name}`;
793
+ return fileUrl;
794
+ };
795
+ async function uploadToCDN(buffer, fileName, config, assetId) {
796
+ if (!config.enabled) throw new Error("CDN \u4E0A\u4F20\u672A\u542F\u7528");
797
+ const timestamp = new Date().getTime();
798
+ const finalFileName = config.generateFileName ? config.generateFileName(fileName, assetId, timestamp) : `${config.prefix || "lottie-assets"}/${timestamp}/${fileName}`;
799
+ return await upload({
800
+ data: buffer,
801
+ name: finalFileName
802
+ });
803
+ }
804
+ var external_sharp_ = __webpack_require__("sharp");
805
+ var external_sharp_default = /*#__PURE__*/ __webpack_require__.n(external_sharp_);
806
+ const external_ssim_js_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("ssim.js");
807
+ var external_ssim_js_default = /*#__PURE__*/ __webpack_require__.n(external_ssim_js_namespaceObject);
808
+ const DEFAULT_LEVEL = "standard";
809
+ const LEVEL_THRESHOLDS = {
810
+ mild: 0.995,
811
+ standard: 0.990,
812
+ aggressive: 0.975
813
+ };
814
+ const PRESETS = {
815
+ mild: {
816
+ webpQ: 90,
817
+ webpNearQ: 85,
818
+ alphaQ: 90,
819
+ pngQ: 100,
820
+ jpegQ: 86
821
+ },
822
+ standard: {
823
+ webpQ: 85,
824
+ webpNearQ: 72,
825
+ alphaQ: 85,
826
+ pngQ: 100,
827
+ jpegQ: 82
828
+ },
829
+ aggressive: {
830
+ webpQ: 70,
831
+ webpNearQ: 68,
832
+ alphaQ: 80,
833
+ pngQ: 100,
834
+ jpegQ: 78
835
+ }
836
+ };
837
+ async function compressImage(input, options = {}) {
838
+ const level = options.level ?? DEFAULT_LEVEL;
839
+ const threshold = options.ssimThreshold ?? LEVEL_THRESHOLDS[level];
840
+ const allow = new Set(options.allowFormats ?? [
841
+ "png",
842
+ "webp-lossy",
843
+ "webp-near",
844
+ "jpeg"
845
+ ]);
846
+ const forceEffort = options.forceEffort;
847
+ const base = external_sharp_default()(input, {
848
+ limitInputPixels: false
849
+ });
850
+ const meta = await base.metadata();
851
+ const hasAlpha = Boolean(meta.hasAlpha || meta.channels && meta.channels >= 4);
852
+ const candidates = await buildCandidates(base, hasAlpha, level, allow, forceEffort);
853
+ const tried = [];
854
+ for (const c of candidates){
855
+ const buf = await c.encode();
856
+ const s = await calcSSIM(input, buf);
857
+ tried.push({
858
+ format: c.outFormat,
859
+ variant: c.variant,
860
+ label: c.label,
861
+ bytes: buf.byteLength,
862
+ ssim: s,
863
+ buffer: buf
864
+ });
865
+ }
866
+ const pick = tried.sort((a, b)=>a.bytes - b.bytes)[0];
867
+ return {
868
+ ...pick,
869
+ level,
870
+ threshold,
871
+ hasAlpha,
872
+ tried
873
+ };
874
+ }
875
+ async function buildCandidates(base, hasAlpha, level, allow, forceEffort) {
876
+ const list = [];
877
+ const p = PRESETS[level];
878
+ const pngEffort = forceEffort ?? 10;
879
+ const webpEffort = forceEffort ?? 6;
880
+ if (allow.has("png")) list.push({
881
+ variant: "png8",
882
+ outFormat: "png",
883
+ label: `png8 q${p.pngQ} e${pngEffort}`,
884
+ encode: ()=>base.clone().png({
885
+ palette: true,
886
+ quality: p.pngQ,
887
+ compressionLevel: 9,
888
+ effort: pngEffort,
889
+ progressive: false
890
+ }).toBuffer()
891
+ });
892
+ if (allow.has("webp-lossy")) list.push({
893
+ variant: "webp-lossy",
894
+ outFormat: "webp",
895
+ label: `webp-lossy q${p.webpQ}${hasAlpha ? ` aq${p.alphaQ}` : ""} e${webpEffort}`,
896
+ encode: ()=>base.clone().webp({
897
+ quality: p.webpQ,
898
+ alphaQuality: hasAlpha ? p.alphaQ : void 0,
899
+ effort: webpEffort,
900
+ smartSubsample: true
901
+ }).toBuffer()
902
+ });
903
+ if (allow.has("webp-near")) list.push({
904
+ variant: "webp-near",
905
+ outFormat: "webp",
906
+ label: `webp-nearLossless q${p.webpNearQ} e${webpEffort}`,
907
+ encode: ()=>base.clone().webp({
908
+ nearLossless: true,
909
+ quality: p.webpNearQ,
910
+ effort: webpEffort
911
+ }).toBuffer()
912
+ });
913
+ if (!hasAlpha && allow.has("jpeg")) list.push({
914
+ variant: "jpeg",
915
+ outFormat: "jpeg",
916
+ label: `jpeg q${p.jpegQ} mozjpeg`,
917
+ encode: ()=>base.clone().jpeg({
918
+ quality: p.jpegQ,
919
+ mozjpeg: true,
920
+ progressive: true
921
+ }).toBuffer()
922
+ });
923
+ return list;
924
+ }
925
+ async function calcSSIM(orig, cand) {
926
+ const origSharp = external_sharp_default()(orig);
927
+ const candSharp = external_sharp_default()(cand);
928
+ const origMeta = await origSharp.metadata();
929
+ const { width, height } = origMeta;
930
+ if (!width || !height) throw new Error("Unable to get image dimensions");
931
+ const origRawData = await origSharp.resize(width, height).ensureAlpha().raw().toBuffer();
932
+ const candRawData = await candSharp.resize(width, height).ensureAlpha().raw().toBuffer();
933
+ const origImageData = {
934
+ data: new Uint8ClampedArray(origRawData),
935
+ width,
936
+ height
937
+ };
938
+ const candImageData = {
939
+ data: new Uint8ClampedArray(candRawData),
940
+ width,
941
+ height
942
+ };
943
+ const { mssim } = external_ssim_js_default()(origImageData, candImageData, {
944
+ ssim: "original",
945
+ downsample: "original",
946
+ windowSize: 11,
947
+ bitDepth: 8
948
+ });
949
+ return "number" == typeof mssim ? mssim : 0;
950
+ }
951
+ function isDataURL(s) {
952
+ return !!s && /^data:image\/[^;]+;base64,/.test(s);
953
+ }
954
+ function parseDataURL(s) {
955
+ const [h, b] = s.split(",", 2);
956
+ const mime = h.slice(5, h.indexOf(";"));
957
+ return {
958
+ mime,
959
+ buf: Buffer.from(b, "base64")
960
+ };
961
+ }
962
+ function mimeFromExt(ext) {
963
+ ext = ext.toLowerCase();
964
+ if ("png" === ext) return "image/png";
965
+ if ("jpg" === ext || "jpeg" === ext) return "image/jpeg";
966
+ if ("webp" === ext) return "image/webp";
967
+ if ("avif" === ext) return "image/avif";
968
+ if ("gif" === ext) return "image/gif";
969
+ if ("svg" === ext) return "image/svg+xml";
970
+ return "application/octet-stream";
971
+ }
972
+ function extFromMime(m) {
973
+ m = m.toLowerCase();
974
+ if (m.includes("png")) return "png";
975
+ if (m.includes("jpeg")) return "jpg";
976
+ if (m.includes("webp")) return "webp";
977
+ if (m.includes("avif")) return "avif";
978
+ if (m.includes("gif")) return "gif";
979
+ if (m.includes("svg")) return "svg";
980
+ return "bin";
981
+ }
982
+ function toDataURL(mime, buf) {
983
+ return `data:${mime};base64,` + buf.toString("base64");
984
+ }
985
+ function hash(buf) {
986
+ let h = 2166136261;
987
+ for(let i = 0; i < buf.length; i++){
988
+ h ^= buf[i];
989
+ h = Math.imul(h, 16777619);
990
+ }
991
+ return (h >>> 0).toString(16);
992
+ }
993
+ async function loadSharp() {
994
+ try {
995
+ return __webpack_require__("sharp");
996
+ } catch {
997
+ return null;
998
+ }
999
+ }
1000
+ function estimateMaxScale(json) {
1001
+ const m = new Map();
1002
+ for (const ly of json.layers || []){
1003
+ if (ly?.ty !== 2) continue;
1004
+ const id = ly.refId;
1005
+ if (!id) continue;
1006
+ let mx = 100;
1007
+ const s = ly.ks?.s;
1008
+ if (s?.a === 0 && Array.isArray(s.k)) mx = Math.max(mx, s.k[0] ?? 100, s.k[1] ?? 100);
1009
+ else if (s?.a === 1 && Array.isArray(s.k)) for (const kf of s.k){
1010
+ const arr = kf?.s ?? kf?.e;
1011
+ if (Array.isArray(arr)) mx = Math.max(mx, arr[0] ?? 100, arr[1] ?? 100);
1012
+ }
1013
+ m.set(id, Math.max(m.get(id) ?? 100, mx));
1014
+ }
1015
+ return m;
1016
+ }
1017
+ async function compressImages(input, opt = {}) {
1018
+ const log = opt.onLog ?? (()=>{});
1019
+ const safeOpt = {
1020
+ ...opt
1021
+ };
1022
+ delete safeOpt.onLog;
1023
+ log('Plugin configuration: ' + JSON.stringify(safeOpt, null, 2));
1024
+ const sharp = await loadSharp();
1025
+ log('Sharp library loaded: ' + !!sharp);
1026
+ if (!sharp) {
1027
+ const warn = "[compress-images] \u672A\u5B89\u88C5 sharp\uFF0C\u8DF3\u8FC7\u538B\u7F29";
1028
+ log(warn);
1029
+ return {
1030
+ data: input,
1031
+ stats: {
1032
+ total: 0,
1033
+ optimized: 0,
1034
+ before: 0,
1035
+ after: 0,
1036
+ saved: 0,
1037
+ savedPct: 0,
1038
+ perAsset: [],
1039
+ warnings: [
1040
+ warn
1041
+ ]
1042
+ }
1043
+ };
1044
+ }
1045
+ const cfg = {
1046
+ enableWebp: opt.enableWebp ?? true,
1047
+ enableAvif: opt.enableAvif ?? false,
1048
+ quality: opt.quality ?? "balanced",
1049
+ longEdge: opt.longEdge ?? 0,
1050
+ embed: opt.embed ?? true,
1051
+ baseDir: opt.baseDir ?? process.cwd(),
1052
+ outDir: opt.outDir ?? node_path.join(process.cwd(), "lottie-assets"),
1053
+ useTinyPNG: opt.useTinyPNG ?? false,
1054
+ ssimOptions: opt.ssimOptions ?? {
1055
+ level: "standard"
1056
+ }
1057
+ };
1058
+ if (!cfg.embed && !node_fs.existsSync(cfg.outDir)) node_fs.mkdirSync(cfg.outDir, {
1059
+ recursive: true
1060
+ });
1061
+ const out = JSON.parse(JSON.stringify(input));
1062
+ const stats = {
1063
+ total: 0,
1064
+ optimized: 0,
1065
+ before: 0,
1066
+ after: 0,
1067
+ saved: 0,
1068
+ savedPct: 0,
1069
+ perAsset: [],
1070
+ warnings: []
1071
+ };
1072
+ const maxScale = estimateMaxScale(input);
1073
+ if (!Array.isArray(out.assets)) return {
1074
+ data: out,
1075
+ stats
1076
+ };
1077
+ for (const a of out.assets){
1078
+ const src = a?.p;
1079
+ if (!src) continue;
1080
+ let buf, mime = "", ext = "";
1081
+ try {
1082
+ if (isDataURL(src)) {
1083
+ const p = parseDataURL(src);
1084
+ buf = p.buf;
1085
+ mime = p.mime;
1086
+ ext = extFromMime(mime);
1087
+ } else {
1088
+ const full = node_path.join(cfg.baseDir, a.u ?? "", src);
1089
+ if (!node_fs.existsSync(full)) continue;
1090
+ buf = node_fs.readFileSync(full);
1091
+ ext = node_path.extname(src).slice(1).toLowerCase();
1092
+ mime = mimeFromExt(ext);
1093
+ }
1094
+ } catch (e) {
1095
+ stats.warnings.push(`[compress-images] \u{8BFB}\u{53D6}\u{5931}\u{8D25} ${a.id ?? ""}: ${e.message}`);
1096
+ continue;
1097
+ }
1098
+ if (!buf || /svg|gif/.test(ext) || /svg|gif/.test(mime)) {
1099
+ log(`Skipping asset ${a.id}: unsupported format (${ext}/${mime})`);
1100
+ continue;
1101
+ }
1102
+ if (buf.byteLength < 4096) {
1103
+ log(`Skipping asset ${a.id}: file too small (${buf.byteLength} bytes < 4096 bytes threshold)`);
1104
+ continue;
1105
+ }
1106
+ stats.total++;
1107
+ stats.before += buf.byteLength;
1108
+ let originalBuf = buf;
1109
+ let originalExt = ext;
1110
+ let originalMime = mime;
1111
+ try {
1112
+ if (cfg.useTinyPNG) {
1113
+ const fileSizeKB = buf.byteLength / 1024;
1114
+ try {
1115
+ log(`Using TinyPNG for asset ${a.id} (${Math.round(fileSizeKB)}KB)`);
1116
+ const [tinyPngBuf, tinyPngExt] = await compress(buf);
1117
+ log(`TinyPNG compressed: ${Math.round(buf.byteLength / 1024)}KB -> ${Math.round(tinyPngBuf.byteLength / 1024)}KB`);
1118
+ buf = tinyPngBuf;
1119
+ ext = tinyPngExt;
1120
+ mime = mimeFromExt(ext);
1121
+ originalBuf = buf;
1122
+ originalExt = ext;
1123
+ originalMime = mime;
1124
+ } catch (tinyError) {
1125
+ log(`TinyPNG failed for asset ${a.id}: ${tinyError.message}, falling back to original`);
1126
+ stats.warnings.push(`[compress-images] TinyPNG \u{5931}\u{8D25} ${a.id ?? ""}: ${tinyError.message}, \u{4F7F}\u{7528}\u{539F}\u{59CB}\u{56FE}\u{7247}`);
1127
+ }
1128
+ }
1129
+ const meta = await sharp(buf).metadata();
1130
+ const hasAlpha = !!meta.hasAlpha;
1131
+ let W = meta.width || 0, H = meta.height || 0;
1132
+ if (cfg.longEdge && Math.max(W, H) > cfg.longEdge) {
1133
+ const s = cfg.longEdge / Math.max(W, H);
1134
+ W = Math.round(W * s);
1135
+ H = Math.round(H * s);
1136
+ }
1137
+ const ms = maxScale.get(a.id || "") ?? 100;
1138
+ if (ms > 100 && meta.width && meta.height) {
1139
+ const minW = Math.round(meta.width * ms / 100), minH = Math.round(meta.height * ms / 100);
1140
+ W = Math.max(W, Math.min(minW, meta.width));
1141
+ H = Math.max(H, Math.min(minH, meta.height));
1142
+ }
1143
+ log(`Using SSIM compression algorithm for asset ${a.id} (level: ${cfg.ssimOptions.level})`);
1144
+ const ssimOptions = {
1145
+ level: cfg.ssimOptions.level,
1146
+ ssimThreshold: cfg.ssimOptions.ssimThreshold,
1147
+ allowFormats: [],
1148
+ fallbackToBestSSIM: cfg.ssimOptions.fallbackToBestSSIM,
1149
+ forceEffort: cfg.ssimOptions.forceEffort
1150
+ };
1151
+ if (cfg.enableWebp) ssimOptions.allowFormats.push("webp-lossy", "webp-near");
1152
+ if (!hasAlpha) ssimOptions.allowFormats.push("jpeg");
1153
+ ssimOptions.allowFormats.push("png");
1154
+ let resizedBuf = buf;
1155
+ if (W && H && (W < (meta.width || 0) || H < (meta.height || 0))) {
1156
+ resizedBuf = await sharp(buf).rotate().withMetadata({
1157
+ exif: void 0,
1158
+ icc: void 0
1159
+ }).resize(W, H, {
1160
+ fit: "inside",
1161
+ withoutEnlargement: true
1162
+ }).png().toBuffer();
1163
+ log(`Resized image from ${meta.width}x${meta.height} to ${W}x${H}`);
1164
+ }
1165
+ const ssimResult = await compressImage(resizedBuf, ssimOptions);
1166
+ const useOriginal = ssimResult.bytes >= originalBuf.byteLength;
1167
+ let finalBuf, finalExt, finalMime, finalBytes;
1168
+ let finalSSIM;
1169
+ let resized = !!(W && H && (W < (meta.width || 0) || H < (meta.height || 0)));
1170
+ if (useOriginal) {
1171
+ log(`SSIM compressed result (${Math.round(ssimResult.bytes / 1024)}KB, SSIM: ${ssimResult.ssim.toFixed(3)}) is not better than original (${Math.round(originalBuf.byteLength / 1024)}KB), using original`);
1172
+ finalBuf = originalBuf;
1173
+ finalExt = originalExt;
1174
+ finalMime = originalMime;
1175
+ finalBytes = originalBuf.byteLength;
1176
+ finalSSIM = 1.0;
1177
+ resized = false;
1178
+ } else {
1179
+ log(`SSIM result: ${ssimResult.label} - ${Math.round(ssimResult.bytes / 1024)}KB (SSIM: ${ssimResult.ssim.toFixed(3)}, threshold: ${ssimResult.threshold})`);
1180
+ finalBuf = ssimResult.buffer;
1181
+ finalExt = "jpeg" === ssimResult.format ? "jpg" : ssimResult.format;
1182
+ finalMime = mimeFromExt(finalExt);
1183
+ finalBytes = ssimResult.bytes;
1184
+ finalSSIM = ssimResult.ssim;
1185
+ }
1186
+ if (cfg.embed) {
1187
+ a.u = "";
1188
+ a.p = toDataURL(finalMime, finalBuf);
1189
+ a.e = 1;
1190
+ if (useOriginal) {
1191
+ a.w = a.w;
1192
+ a.h = a.h;
1193
+ } else {
1194
+ a.w = W || meta.width || a.w;
1195
+ a.h = H || meta.height || a.h;
1196
+ }
1197
+ } else {
1198
+ const baseName = a.id ? `${a.id}.${finalExt}` : `${hash(finalBuf)}.${finalExt}`;
1199
+ if (opt.cdn?.enabled) try {
1200
+ log(`Uploading asset ${a.id} to CDN...`);
1201
+ const cdnUrl = await uploadToCDN(finalBuf, baseName, opt.cdn, a.id);
1202
+ log(`Asset ${a.id} uploaded to CDN: ${cdnUrl}`);
1203
+ a.u = "";
1204
+ a.p = cdnUrl;
1205
+ a.e = 0;
1206
+ } catch (cdnError) {
1207
+ log(`CDN upload failed for asset ${a.id}: ${cdnError.message}, falling back to local file`);
1208
+ stats.warnings.push(`[compress-images] CDN \u{4E0A}\u{4F20}\u{5931}\u{8D25} ${a.id ?? ""}: ${cdnError.message}, \u{4F7F}\u{7528}\u{672C}\u{5730}\u{6587}\u{4EF6}`);
1209
+ const outPath = node_path.join(cfg.outDir, baseName);
1210
+ node_fs.writeFileSync(outPath, finalBuf);
1211
+ a.u = node_path.relative(cfg.baseDir, cfg.outDir).replace(/\\/g, "/") + "/";
1212
+ a.p = baseName;
1213
+ a.e = 0;
1214
+ }
1215
+ else {
1216
+ const outPath = node_path.join(cfg.outDir, baseName);
1217
+ node_fs.writeFileSync(outPath, finalBuf);
1218
+ a.u = node_path.relative(cfg.baseDir, cfg.outDir).replace(/\\/g, "/") + "/";
1219
+ a.p = baseName;
1220
+ a.e = 0;
1221
+ }
1222
+ if (useOriginal) {
1223
+ a.w = a.w;
1224
+ a.h = a.h;
1225
+ } else {
1226
+ a.w = W || meta.width || a.w;
1227
+ a.h = H || meta.height || a.h;
1228
+ }
1229
+ }
1230
+ stats.optimized++;
1231
+ stats.after += finalBytes;
1232
+ stats.perAsset.push({
1233
+ id: a.id,
1234
+ from: useOriginal ? originalExt : extFromMime(mime),
1235
+ to: finalExt,
1236
+ orig: originalBuf.byteLength,
1237
+ out: finalBytes,
1238
+ ssim: finalSSIM,
1239
+ resized: useOriginal ? false : resized
1240
+ });
1241
+ } catch (e) {
1242
+ stats.after += buf.byteLength;
1243
+ stats.perAsset.push({
1244
+ id: a.id,
1245
+ from: extFromMime(mime),
1246
+ to: extFromMime(mime),
1247
+ orig: buf.byteLength,
1248
+ out: buf.byteLength
1249
+ });
1250
+ stats.warnings.push(`[compress-images] \u{5904}\u{7406}\u{5931}\u{8D25} ${a.id ?? ""}: ${e.message}`);
1251
+ }
1252
+ }
1253
+ stats.saved = Math.max(0, stats.before - stats.after);
1254
+ stats.savedPct = stats.before ? +(stats.saved / stats.before * 100).toFixed(2) : 0;
1255
+ return {
1256
+ data: out,
1257
+ stats
1258
+ };
1259
+ }
1260
+ const compressImagesPlugin = {
1261
+ name: 'compress-images',
1262
+ description: 'Optimize Lottie image assets with simplified options: WebP/AVIF toggle, quality presets, and smart resizing',
1263
+ version: '2.0.0',
1264
+ defaultOptions: {
1265
+ enableWebp: true,
1266
+ enableAvif: false,
1267
+ quality: 'balanced',
1268
+ longEdge: 0,
1269
+ embed: true,
1270
+ baseDir: process.cwd(),
1271
+ outDir: 'lottie-assets',
1272
+ useTinyPNG: false,
1273
+ cdn: {
1274
+ enabled: false,
1275
+ prefix: 'lottie-assets'
1276
+ },
1277
+ ssimOptions: {
1278
+ level: 'standard'
1279
+ }
1280
+ },
1281
+ async apply (data, options, context) {
1282
+ const sizeBefore = calculateSize(data);
1283
+ const pluginOptions = {
1284
+ ...options,
1285
+ onLog: (msg)=>{
1286
+ if (context.logger) context.logger.info(`[compress-images] ${msg}`);
1287
+ }
1288
+ };
1289
+ const result = await compressImages(data, pluginOptions);
1290
+ const sizeAfter = calculateSize(result.data);
1291
+ return {
1292
+ data: result.data,
1293
+ changed: result.stats.optimized > 0,
1294
+ stats: {
1295
+ itemsProcessed: result.stats.total,
1296
+ itemsRemoved: 0,
1297
+ sizeBefore,
1298
+ sizeAfter
1299
+ },
1300
+ warnings: result.stats.warnings
1301
+ };
1302
+ }
1303
+ };
1304
+ const DEFAULT_OPTIONS = {
1305
+ keepMatchName: true,
1306
+ keepIds: false,
1307
+ mergeNearbyNumbers: false,
1308
+ markTiny: false,
1309
+ precisionTargetKeys: [
1310
+ 't',
1311
+ 'sr',
1312
+ 'to',
1313
+ 'ti',
1314
+ 'color',
1315
+ 'ip',
1316
+ 'op',
1317
+ 'st',
1318
+ 's',
1319
+ 'e'
1320
+ ],
1321
+ frameKeyDigits: 0,
1322
+ normalKeyDigits: 3,
1323
+ mergeStep: 0.001,
1324
+ colorQuantizePrecision: 255,
1325
+ roundThreshold: 0.01,
1326
+ removeMetadataKeys: true,
1327
+ enableGlobalPrecision: true,
1328
+ globalMaxDecimals: 3,
1329
+ compressNames: true,
1330
+ removeDefaultValues: true,
1331
+ customMetadataKeys: [],
1332
+ customDefaultValues: {},
1333
+ onLog: ()=>{}
1334
+ };
1335
+ function deepClone(x) {
1336
+ return JSON.parse(JSON.stringify(x));
1337
+ }
1338
+ function toBase36(n) {
1339
+ const chars = '0123456789abcdefghijklmnopqrstuvwxyz';
1340
+ if (0 === n) return '0';
1341
+ let q = Math.floor(Math.abs(n));
1342
+ let out = '';
1343
+ while(q > 0){
1344
+ const m = q % 36;
1345
+ out = chars[m] + out;
1346
+ q = (q - m) / 36;
1347
+ }
1348
+ return out;
1349
+ }
1350
+ function walk(obj, visit) {
1351
+ const go = (node, key, parent)=>{
1352
+ visit(node, key, parent);
1353
+ if (!node || 'object' != typeof node) return;
1354
+ if (Array.isArray(node)) {
1355
+ for(let i = 0; i < node.length; i++)go(node[i], String(i), node);
1356
+ return;
1357
+ }
1358
+ for (const k of Object.keys(node))go(node[k], k, node);
1359
+ };
1360
+ go(obj, null, null);
1361
+ }
1362
+ function roundNearInteger(v, eps = 0.01) {
1363
+ const r = Math.round(v);
1364
+ return Math.abs(v - r) <= eps ? r : v;
1365
+ }
1366
+ function roundTo(num, digits) {
1367
+ const f = Math.pow(10, digits);
1368
+ return Math.round(num * f) / f;
1369
+ }
1370
+ function quantize255(x, precision = 255) {
1371
+ const snapped = Math.round(x * precision) / precision;
1372
+ return roundTo(snapped, 3);
1373
+ }
1374
+ function applyMarkAndRound(json, mark, roundThreshold) {
1375
+ let marked = false;
1376
+ let rounded = 0;
1377
+ if (void 0 !== mark) {
1378
+ json.tiny = 'number' == typeof mark ? mark : 1;
1379
+ marked = true;
1380
+ }
1381
+ if ('number' == typeof json.fr) {
1382
+ const oldFr = json.fr;
1383
+ json.fr = roundNearInteger(json.fr, roundThreshold);
1384
+ if (oldFr !== json.fr) rounded++;
1385
+ }
1386
+ if ('number' == typeof json.op) {
1387
+ const oldOp = json.op;
1388
+ json.op = roundNearInteger(json.op, roundThreshold);
1389
+ if (oldOp !== json.op) rounded++;
1390
+ }
1391
+ return {
1392
+ marked,
1393
+ rounded
1394
+ };
1395
+ }
1396
+ function shortenIdRef(json) {
1397
+ const ids = [];
1398
+ walk(json, (node, key)=>{
1399
+ if (!key || 'object' != typeof node) return;
1400
+ const v = node.id;
1401
+ if ('string' == typeof v) ids.push(v);
1402
+ });
1403
+ const uniq = Array.from(new Set(ids));
1404
+ const map = {};
1405
+ uniq.forEach((v, i)=>map[v] = toBase36(i));
1406
+ let shortened = 0;
1407
+ walk(json, (node, key)=>{
1408
+ if (!key || 'object' != typeof node) return;
1409
+ const o = node;
1410
+ if ('string' == typeof o.id && map[o.id]) {
1411
+ o.id = map[o.id];
1412
+ shortened++;
1413
+ }
1414
+ if ('string' == typeof o.refId && map[o.refId]) {
1415
+ o.refId = map[o.refId];
1416
+ shortened++;
1417
+ }
1418
+ });
1419
+ return shortened;
1420
+ }
1421
+ function applyNumericPrecision(json, targetKeys, frameKeyDigits, normalKeyDigits, colorPrecision) {
1422
+ const TARGET_KEYS = new Set(targetKeys);
1423
+ let processed = 0;
1424
+ const walkWithParentKey = (obj, parentKey)=>{
1425
+ if (!obj || 'object' != typeof obj) return;
1426
+ if (Array.isArray(obj)) {
1427
+ for(let i = 0; i < obj.length; i++)walkWithParentKey(obj[i], null);
1428
+ return;
1429
+ }
1430
+ for (const k of Object.keys(obj)){
1431
+ const val = obj[k];
1432
+ if ('k' === k && ('c' === parentKey || 'v' === parentKey) && Array.isArray(val)) {
1433
+ for(let i = 0; i < val.length; i++)if ('number' == typeof val[i]) {
1434
+ val[i] = quantize255(val[i], colorPrecision);
1435
+ processed++;
1436
+ }
1437
+ }
1438
+ if (TARGET_KEYS.has(k)) {
1439
+ if ('number' == typeof val) {
1440
+ const digits = 'ip' === k || 'op' === k ? frameKeyDigits : normalKeyDigits;
1441
+ obj[k] = roundTo(val, digits);
1442
+ processed++;
1443
+ } else if (Array.isArray(val)) {
1444
+ const digits = 'ip' === k || 'op' === k ? frameKeyDigits : normalKeyDigits;
1445
+ obj[k] = val.map((x)=>{
1446
+ if ('number' == typeof x) {
1447
+ processed++;
1448
+ return roundTo(x, digits);
1449
+ }
1450
+ return x;
1451
+ });
1452
+ }
1453
+ }
1454
+ walkWithParentKey(val, k);
1455
+ }
1456
+ };
1457
+ walkWithParentKey(json, null);
1458
+ return processed;
1459
+ }
1460
+ function applyGlobalPrecision(json, enabled, maxDecimals) {
1461
+ if (!enabled) return 0;
1462
+ let processed = 0;
1463
+ walk(json, (node, key, parent)=>{
1464
+ if (!key || !parent || 'object' != typeof parent) return;
1465
+ const val = parent[key];
1466
+ if ('number' == typeof val && !Number.isInteger(val)) {
1467
+ const str = val.toString();
1468
+ const decimalIndex = str.indexOf('.');
1469
+ if (-1 !== decimalIndex && str.length - decimalIndex - 1 > maxDecimals) {
1470
+ parent[key] = roundTo(val, maxDecimals);
1471
+ processed++;
1472
+ }
1473
+ }
1474
+ });
1475
+ return processed;
1476
+ }
1477
+ function removeMetadataKeys(json, shouldRemove, keepMatchName, customKeys = []) {
1478
+ const baseKeys = [
1479
+ 'n'
1480
+ ];
1481
+ if (!keepMatchName) baseKeys.push('mn');
1482
+ const metadataKeys = shouldRemove ? [
1483
+ 'ix',
1484
+ 'bm',
1485
+ 'ddd',
1486
+ 'np',
1487
+ 'cix'
1488
+ ] : [];
1489
+ const DELETE_KEYS = new Set([
1490
+ ...baseKeys,
1491
+ ...metadataKeys,
1492
+ ...customKeys
1493
+ ]);
1494
+ let deleted = 0;
1495
+ walk(json, (node, key, parent)=>{
1496
+ if (!key || !parent || 'object' != typeof parent) return;
1497
+ if (DELETE_KEYS.has(key)) {
1498
+ delete parent[key];
1499
+ deleted++;
1500
+ }
1501
+ });
1502
+ return deleted;
1503
+ }
1504
+ function removeDefaultValues(json, shouldRemove, customDefaults = {}) {
1505
+ if (!shouldRemove) return {
1506
+ defaultValuesRemoved: 0,
1507
+ hiddenFalseRemoved: 0
1508
+ };
1509
+ const defaultValueMap = {
1510
+ bm: 0,
1511
+ ddd: 0,
1512
+ ao: 0,
1513
+ sr: 1,
1514
+ ks: null,
1515
+ ...customDefaults
1516
+ };
1517
+ let defaultValuesRemoved = 0;
1518
+ let hiddenFalseRemoved = 0;
1519
+ walk(json, (node, key, parent)=>{
1520
+ if (!key || !parent || 'object' != typeof parent) return;
1521
+ const val = parent[key];
1522
+ if (key in defaultValueMap && val === defaultValueMap[key]) {
1523
+ delete parent[key];
1524
+ defaultValuesRemoved++;
1525
+ }
1526
+ if ('hd' === key && false === val) {
1527
+ delete parent[key];
1528
+ hiddenFalseRemoved++;
1529
+ }
1530
+ });
1531
+ return {
1532
+ defaultValuesRemoved,
1533
+ hiddenFalseRemoved
1534
+ };
1535
+ }
1536
+ function compressNames(json, shouldCompress) {
1537
+ if (!shouldCompress) return 0;
1538
+ const names = [];
1539
+ walk(json, (node, key)=>{
1540
+ if ('nm' === key && 'string' == typeof node && node.length > 1) names.push(node);
1541
+ });
1542
+ const uniqueNames = Array.from(new Set(names));
1543
+ const compressChars = "\u4FEE\u63CF\u586B\u53D8\u5F62\u72B6\u52A8\u753B\u56FE\u5C42\u6548\u679C\u906E\u7F69\u8DEF\u5F84\u6587\u5B57\u989C\u8272\u9634\u5F71\u53D1\u5149\u5916\u8FB9\u5185\u8FB9\u9AD8\u4EAE\u4E2D";
1544
+ const nameMap = {};
1545
+ uniqueNames.forEach((name, index)=>{
1546
+ if (index < compressChars.length) nameMap[name] = compressChars[index];
1547
+ else nameMap[name] = String(index - compressChars.length);
1548
+ });
1549
+ let compressed = 0;
1550
+ walk(json, (node, key, parent)=>{
1551
+ if (!key || !parent || 'object' != typeof parent) return;
1552
+ if ('nm' === key && 'string' == typeof node && nameMap[node]) {
1553
+ parent[key] = nameMap[node];
1554
+ compressed++;
1555
+ }
1556
+ });
1557
+ return compressed;
1558
+ }
1559
+ function mergeNearbyNumbers(json, step) {
1560
+ const nums = [];
1561
+ walk(json, (node)=>{
1562
+ if ('number' == typeof node) nums.push(node);
1563
+ });
1564
+ const uniq = Array.from(new Set(nums)).sort((a, b)=>a - b);
1565
+ const groups = [];
1566
+ let g = [];
1567
+ const isStep = (a, b)=>Number((b - a).toFixed(10)) === Number(step.toFixed(10));
1568
+ for(let i = 0; i < uniq.length; i++){
1569
+ const cur = uniq[i];
1570
+ const nxt = uniq[i + 1];
1571
+ if (void 0 !== nxt && isStep(cur, nxt)) {
1572
+ if (0 === g.length) g.push(cur);
1573
+ g.push(nxt);
1574
+ } else if (g.length > 0) {
1575
+ groups.push(g.slice());
1576
+ g = [];
1577
+ }
1578
+ }
1579
+ const replace = new Map();
1580
+ for (const arr of groups)for(let i = 1; i < arr.length; i += 2){
1581
+ const a = arr[i - 1], b = arr[i];
1582
+ const sa = String(a), sb = String(b);
1583
+ if (sa.length <= sb.length) replace.set(b, a);
1584
+ else replace.set(a, b);
1585
+ }
1586
+ if (0 === replace.size) return 0;
1587
+ let merged = 0;
1588
+ walk(json, (node, key, parent)=>{
1589
+ if (!key || !parent) return;
1590
+ const v = parent[key];
1591
+ if ('number' == typeof v && replace.has(v)) {
1592
+ parent[key] = replace.get(v);
1593
+ merged++;
1594
+ }
1595
+ });
1596
+ return merged;
1597
+ }
1598
+ function fixMissingInd(json) {
1599
+ let fixed = 0;
1600
+ const patch = (layers)=>{
1601
+ if (!Array.isArray(layers)) return;
1602
+ for(let i = 0; i < layers.length; i++){
1603
+ const lyr = layers[i];
1604
+ if (lyr && (void 0 === lyr.ind || null === lyr.ind)) {
1605
+ lyr.ind = i + 1;
1606
+ fixed++;
1607
+ }
1608
+ }
1609
+ };
1610
+ patch(json.layers);
1611
+ if (Array.isArray(json.assets)) for (const a of json.assets)patch(a.layers);
1612
+ return fixed;
1613
+ }
1614
+ function countLayersAndAssets(json) {
1615
+ let layers = 0;
1616
+ let assets = 0;
1617
+ if (Array.isArray(json.layers)) layers += json.layers.length;
1618
+ if (Array.isArray(json.assets)) {
1619
+ assets += json.assets.length;
1620
+ for (const asset of json.assets)if (Array.isArray(asset.layers)) layers += asset.layers.length;
1621
+ }
1622
+ return {
1623
+ layers,
1624
+ assets
1625
+ };
1626
+ }
1627
+ function minifyLottieJson(input, options = {}) {
1628
+ const opts = {
1629
+ ...DEFAULT_OPTIONS,
1630
+ ...options
1631
+ };
1632
+ const { onLog } = opts;
1633
+ const json = deepClone(input);
1634
+ const originalSize = JSON.stringify(json).length;
1635
+ const { layers: layersCount, assets: assetsCount } = countLayersAndAssets(json);
1636
+ let idsShortened = 0;
1637
+ let numbersPrecisionReduced = 0;
1638
+ let nearbyNumbersMerged = 0;
1639
+ let missingIndicesFixed = 0;
1640
+ let metadataKeysRemoved = 0;
1641
+ let globalPrecisionProcessed = 0;
1642
+ let namesCompressed = 0;
1643
+ let defaultValuesRemoved = 0;
1644
+ let hiddenFalseRemoved = 0;
1645
+ applyMarkAndRound(json, opts.markTiny, opts.roundThreshold);
1646
+ if (!opts.keepIds) idsShortened = shortenIdRef(json);
1647
+ numbersPrecisionReduced = applyNumericPrecision(json, opts.precisionTargetKeys, opts.frameKeyDigits, opts.normalKeyDigits, opts.colorQuantizePrecision);
1648
+ globalPrecisionProcessed = applyGlobalPrecision(json, opts.enableGlobalPrecision, opts.globalMaxDecimals);
1649
+ metadataKeysRemoved = removeMetadataKeys(json, opts.removeMetadataKeys, opts.keepMatchName, opts.customMetadataKeys);
1650
+ const defaultResult = removeDefaultValues(json, opts.removeDefaultValues, opts.customDefaultValues);
1651
+ defaultValuesRemoved = defaultResult.defaultValuesRemoved;
1652
+ hiddenFalseRemoved = defaultResult.hiddenFalseRemoved;
1653
+ if (opts.compressNames) namesCompressed = compressNames(json, opts.compressNames);
1654
+ if (opts.mergeNearbyNumbers) nearbyNumbersMerged = mergeNearbyNumbers(json, opts.mergeStep);
1655
+ missingIndicesFixed = fixMissingInd(json);
1656
+ const minifiedSize = JSON.stringify(json).length;
1657
+ const savedBytes = originalSize - minifiedSize;
1658
+ const compressionRatio = originalSize > 0 ? 1 - minifiedSize / originalSize : 0;
1659
+ const stats = {
1660
+ totalItems: layersCount + assetsCount,
1661
+ processedItems: layersCount + assetsCount,
1662
+ warnings: [],
1663
+ originalSize,
1664
+ minifiedSize,
1665
+ compressionRatio,
1666
+ savedBytes,
1667
+ layersProcessed: layersCount,
1668
+ assetsProcessed: assetsCount,
1669
+ idsShortened,
1670
+ numbersPrecisionReduced,
1671
+ attributesRemoved: metadataKeysRemoved,
1672
+ nearbyNumbersMerged,
1673
+ missingIndicesFixed,
1674
+ metadataKeysRemoved,
1675
+ globalPrecisionProcessed,
1676
+ namesCompressed,
1677
+ defaultValuesRemoved,
1678
+ hiddenFalseRemoved
1679
+ };
1680
+ return {
1681
+ data: json,
1682
+ stats
1683
+ };
1684
+ }
1685
+ const minifyJsonPlugin = {
1686
+ name: 'minify-json',
1687
+ description: 'Optimize Lottie JSON structure: round frame rates, quantize colors, reduce precision, shorten IDs, remove redundant fields',
1688
+ version: '1.0.0',
1689
+ defaultOptions: {
1690
+ keepMatchName: true,
1691
+ keepIds: false,
1692
+ mergeNearbyNumbers: false,
1693
+ markTiny: false,
1694
+ precisionTargetKeys: [
1695
+ 't',
1696
+ 'sr',
1697
+ 'to',
1698
+ 'ti',
1699
+ 'color',
1700
+ 'ip',
1701
+ 'op',
1702
+ 'st',
1703
+ 's',
1704
+ 'e'
1705
+ ],
1706
+ frameKeyDigits: 0,
1707
+ normalKeyDigits: 3,
1708
+ mergeStep: 0.001,
1709
+ colorQuantizePrecision: 255,
1710
+ roundThreshold: 0.01,
1711
+ removeMetadataKeys: true,
1712
+ enableGlobalPrecision: true,
1713
+ globalMaxDecimals: 3,
1714
+ compressNames: true,
1715
+ removeDefaultValues: true,
1716
+ customMetadataKeys: [],
1717
+ customDefaultValues: {}
1718
+ },
1719
+ async apply (data, options, context) {
1720
+ const sizeBefore = calculateSize(data);
1721
+ const pluginOptions = {
1722
+ ...options,
1723
+ onLog: (msg)=>{
1724
+ if (context.logger) context.logger.info(`[minify-json] ${msg}`);
1725
+ }
1726
+ };
1727
+ const result = minifyLottieJson(data, pluginOptions);
1728
+ const sizeAfter = calculateSize(result.data);
1729
+ const warnings = [];
1730
+ if (pluginOptions.mergeNearbyNumbers && result.stats.nearbyNumbersMerged > 0) warnings.push(`\u{6FC0}\u{8FDB}\u{4F18}\u{5316}\u{FF1A}\u{5408}\u{5E76}\u{4E86} ${result.stats.nearbyNumbersMerged} \u{4E2A}\u{76F8}\u{90BB}\u{6570}\u{503C}\u{FF0C}\u{53EF}\u{80FD}\u{5F15}\u{5165}\u{8F7B}\u{5FAE}\u{6570}\u{503C}\u{6270}\u{52A8}`);
1731
+ if (!pluginOptions.keepMatchName && result.stats.attributesRemoved > 0) warnings.push("\u5DF2\u5220\u9664 matchName \u5C5E\u6027\uFF0C\u67D0\u4E9B\u5DE5\u5177\u53EF\u80FD\u65E0\u6CD5\u6B63\u786E\u89E3\u6790");
1732
+ if (!pluginOptions.keepIds && result.stats.idsShortened > 0) warnings.push(`\u{5DF2}\u{77ED}\u{540D}\u{5316} ${result.stats.idsShortened} \u{4E2A} ID\u{FF0C}\u{786E}\u{4FDD}\u{4E0D}\u{4F9D}\u{8D56}\u{539F}\u{59CB} ID \u{503C}`);
1733
+ return {
1734
+ data: result.data,
1735
+ changed: result.stats.savedBytes > 0,
1736
+ stats: {
1737
+ itemsProcessed: result.stats.totalItems,
1738
+ itemsRemoved: result.stats.attributesRemoved,
1739
+ sizeBefore,
1740
+ sizeAfter
1741
+ },
1742
+ warnings
1743
+ };
1744
+ }
1745
+ };
1746
+ const plugins_plugins = [
1747
+ removeUnusedAssetsPlugin,
1748
+ simplifyPathsPlugin,
1749
+ compressImagesPlugin,
1750
+ minifyJsonPlugin
1751
+ ];
1752
+ function getAvailablePlugins() {
1753
+ return [
1754
+ ...plugins_plugins
1755
+ ];
1756
+ }
1757
+ function getPluginByName(name) {
1758
+ return plugins_plugins.find((plugin)=>plugin.name === name);
1759
+ }
1760
+ function getPluginsByCategory(_category) {
1761
+ console.warn('getPluginsByCategory is deprecated. Use plugin groups from presets/plugin-groups.ts instead');
1762
+ return [];
1763
+ }
1764
+ function validateQualityLevel(value, fieldName) {
1765
+ const errors = [];
1766
+ if (void 0 !== value) {
1767
+ const validLevels = [
1768
+ 'lossless',
1769
+ 'balanced',
1770
+ 'smallest'
1771
+ ];
1772
+ if (!validLevels.includes(value)) errors.push(`${fieldName} must be one of: ${validLevels.join(', ')}, got: ${value}`);
1773
+ }
1774
+ return errors;
1775
+ }
1776
+ function validateNumberRange(value, fieldName, min, max) {
1777
+ const errors = [];
1778
+ if (void 0 !== value) if ('number' != typeof value) errors.push(`${fieldName} must be a number, got: ${typeof value}`);
1779
+ else {
1780
+ if (void 0 !== min && value < min) errors.push(`${fieldName} must be >= ${min}, got: ${value}`);
1781
+ if (void 0 !== max && value > max) errors.push(`${fieldName} must be <= ${max}, got: ${value}`);
1782
+ }
1783
+ return errors;
1784
+ }
1785
+ function validateBoolean(value, fieldName) {
1786
+ const errors = [];
1787
+ if (void 0 !== value && 'boolean' != typeof value) errors.push(`${fieldName} must be a boolean, got: ${typeof value}`);
1788
+ return errors;
1789
+ }
1790
+ function validateCompressImagesOptions(options) {
1791
+ const errors = [];
1792
+ const warnings = [];
1793
+ const suggestions = [];
1794
+ errors.push(...validateBoolean(options.enableWebp, 'enableWebp'));
1795
+ errors.push(...validateBoolean(options.enableAvif, 'enableAvif'));
1796
+ errors.push(...validateBoolean(options.embed, 'embed'));
1797
+ errors.push(...validateQualityLevel(options.quality, 'quality'));
1798
+ errors.push(...validateNumberRange(options.longEdge, 'longEdge', 0));
1799
+ if (false === options.enableWebp && false === options.enableAvif && 'smallest' === options.quality) {
1800
+ warnings.push("\u8D28\u91CF\u8BBE\u7F6E\u4E3A smallest \u4F46\u7981\u7528\u4E86 WebP \u548C AVIF\uFF0C\u53EF\u80FD\u65E0\u6CD5\u8FBE\u5230\u6700\u4F73\u538B\u7F29\u6548\u679C");
1801
+ suggestions.push("\u5EFA\u8BAE\u542F\u7528 WebP \u6216 AVIF \u4EE5\u83B7\u5F97\u66F4\u597D\u7684\u538B\u7F29\u6548\u679C");
1802
+ }
1803
+ if ('lossless' === options.quality && (options.enableWebp || options.enableAvif)) suggestions.push("\u4F7F\u7528\u65E0\u635F\u8D28\u91CF\u65F6\uFF0C\u73B0\u4EE3\u683C\u5F0F (WebP/AVIF) \u7684\u4F18\u52BF\u8F83\u5C0F\uFF0C\u8003\u8651\u4F7F\u7528 balanced \u8D28\u91CF");
1804
+ if (options.longEdge && options.longEdge < 256) warnings.push(`longEdge \u{8BBE}\u{7F6E}\u{8FC7}\u{5C0F} (${options.longEdge}px)\u{FF0C}\u{53EF}\u{80FD}\u{5BFC}\u{81F4}\u{56FE}\u{50CF}\u{8D28}\u{91CF}\u{4E25}\u{91CD}\u{4E0B}\u{964D}`);
1805
+ return {
1806
+ valid: 0 === errors.length,
1807
+ errors,
1808
+ warnings,
1809
+ suggestions
1810
+ };
1811
+ }
1812
+ function validateSimplifyPathsOptions(options) {
1813
+ const errors = [];
1814
+ const warnings = [];
1815
+ const suggestions = [];
1816
+ errors.push(...validateNumberRange(options.tolerance, 'tolerance', 0));
1817
+ errors.push(...validateNumberRange(options.minPoints, 'minPoints', 3));
1818
+ errors.push(...validateNumberRange(options.cornerAngleDeg, 'cornerAngleDeg', 0, 180));
1819
+ errors.push(...validateNumberRange(options.samplesPerSegment, 'samplesPerSegment', 1));
1820
+ errors.push(...validateNumberRange(options.tension, 'tension', 0, 2));
1821
+ errors.push(...validateBoolean(options.relativeTolerance, 'relativeTolerance'));
1822
+ errors.push(...validateBoolean(options.preserveCorners, 'preserveCorners'));
1823
+ if (void 0 !== options.mode) {
1824
+ const validModes = [
1825
+ 'static-only',
1826
+ 'animated-consistent',
1827
+ 'all'
1828
+ ];
1829
+ if (!validModes.includes(options.mode)) errors.push(`mode must be one of: ${validModes.join(', ')}, got: ${options.mode}`);
1830
+ }
1831
+ if (void 0 !== options.algorithm && 'rdp' !== options.algorithm) errors.push(`algorithm must be 'rdp', got: ${options.algorithm}`);
1832
+ if (options.tolerance && options.tolerance > 10) warnings.push(`tolerance \u{503C}\u{8F83}\u{5927} (${options.tolerance})\u{FF0C}\u{53EF}\u{80FD}\u{5BFC}\u{81F4}\u{8DEF}\u{5F84}\u{8FC7}\u{5EA6}\u{7B80}\u{5316}`);
1833
+ if (options.minPoints && options.minPoints < 3) errors.push("minPoints \u5FC5\u987B\u81F3\u5C11\u4E3A 3 \u624D\u80FD\u5F62\u6210\u6709\u6548\u8DEF\u5F84");
1834
+ return {
1835
+ valid: 0 === errors.length,
1836
+ errors,
1837
+ warnings,
1838
+ suggestions
1839
+ };
1840
+ }
1841
+ function validateRemoveUnusedAssetsOptions(options) {
1842
+ const errors = [];
1843
+ const warnings = [];
1844
+ const suggestions = [];
1845
+ errors.push(...validateBoolean(options.keepAssetsWithoutId, 'keepAssetsWithoutId'));
1846
+ errors.push(...validateBoolean(options.keepUnreferencedPrecomps, 'keepUnreferencedPrecomps'));
1847
+ if (void 0 !== options.preserveAssetIds) if (Array.isArray(options.preserveAssetIds)) {
1848
+ for (const id of options.preserveAssetIds)if ('string' != typeof id) {
1849
+ errors.push('All items in preserveAssetIds must be strings');
1850
+ break;
1851
+ }
1852
+ } else errors.push('preserveAssetIds must be an array');
1853
+ return {
1854
+ valid: 0 === errors.length,
1855
+ errors,
1856
+ warnings,
1857
+ suggestions
1858
+ };
1859
+ }
1860
+ const pluginValidators = {
1861
+ 'compress-images': validateCompressImagesOptions,
1862
+ 'simplify-paths': validateSimplifyPathsOptions,
1863
+ 'remove-unused-assets': validateRemoveUnusedAssetsOptions
1864
+ };
1865
+ function validatePluginConfig(pluginName, options) {
1866
+ const validator = pluginValidators[pluginName];
1867
+ if (!validator) return {
1868
+ valid: true,
1869
+ errors: [],
1870
+ warnings: [
1871
+ `No validator found for plugin: ${pluginName}`
1872
+ ],
1873
+ suggestions: []
1874
+ };
1875
+ return validator(options);
1876
+ }
1877
+ function validateAllPluginConfigs(plugins, pluginOptions) {
1878
+ const results = {};
1879
+ for (const plugin of plugins){
1880
+ const options = {
1881
+ ...plugin.defaultOptions || {},
1882
+ ...pluginOptions[plugin.name] || {}
1883
+ };
1884
+ results[plugin.name] = validatePluginConfig(plugin.name, options);
1885
+ }
1886
+ return results;
1887
+ }
1888
+ function detectConfigConflicts(plugins, pluginOptions) {
1889
+ const conflicts = [];
1890
+ const compressImagesOptions = pluginOptions['compress-images'];
1891
+ const simplifyPathsOptions = pluginOptions['simplify-paths'];
1892
+ if (compressImagesOptions?.quality === 'lossless' && simplifyPathsOptions?.tolerance && simplifyPathsOptions.tolerance > 1) conflicts.push("配置冲突:compress-images 使用无损质量,但 simplify-paths 的容差较大,可能导致视觉质量不一致");
1893
+ if (compressImagesOptions?.enableAvif === true && compressImagesOptions?.longEdge === 0) conflicts.push("\u6027\u80FD\u8B66\u544A\uFF1A\u542F\u7528\u4E86 AVIF \u4E14\u672A\u9650\u5236\u56FE\u50CF\u5C3A\u5BF8\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5904\u7406\u65F6\u95F4\u8FC7\u957F");
1894
+ return conflicts;
1895
+ }
1896
+ class LottieOptimizer {
1897
+ constructor(logger){
1898
+ this.plugins = new Map();
1899
+ this.logger = logger || createLogger();
1900
+ this.loadPlugins();
1901
+ }
1902
+ loadPlugins() {
1903
+ const availablePlugins = getAvailablePlugins();
1904
+ for (const plugin of availablePlugins)this.plugins.set(plugin.name, plugin);
1905
+ }
1906
+ async optimize(data, options = {}, inputFileSize) {
1907
+ const startTime = Date.now();
1908
+ const originalSize = inputFileSize || calculateSize(data);
1909
+ this.logger.info(`Starting optimization of ${originalSize} bytes Lottie`);
1910
+ const effectiveOptions = options;
1911
+ const pluginsToApply = await this.resolvePlugins(effectiveOptions);
1912
+ if (effectiveOptions.debug?.showConfigSources) {
1913
+ const validationResults = validateAllPluginConfigs(pluginsToApply, effectiveOptions.pluginOptions || {});
1914
+ const conflicts = detectConfigConflicts(pluginsToApply, effectiveOptions.pluginOptions || {});
1915
+ for (const [pluginName, result] of Object.entries(validationResults)){
1916
+ if (!result.valid) this.logger.warn(`[${pluginName}] Configuration errors: ${result.errors.join(', ')}`);
1917
+ if (result.warnings.length > 0) this.logger.warn(`[${pluginName}] Warnings: ${result.warnings.join(', ')}`);
1918
+ if (result.suggestions.length > 0) this.logger.info(`[${pluginName}] Suggestions: ${result.suggestions.join(', ')}`);
1919
+ }
1920
+ if (conflicts.length > 0) {
1921
+ this.logger.warn('Configuration conflicts detected:');
1922
+ conflicts.forEach((conflict)=>this.logger.warn(` - ${conflict}`));
1923
+ }
1924
+ }
1925
+ const stats = {
1926
+ originalSize,
1927
+ optimizedSize: originalSize,
1928
+ compressionRatio: 0,
1929
+ pluginsApplied: [],
1930
+ processingTime: 0
1931
+ };
1932
+ let currentData = JSON.parse(JSON.stringify(data));
1933
+ const pluginReports = [];
1934
+ const warnings = [];
1935
+ const errors = [];
1936
+ for(let i = 0; i < pluginsToApply.length; i++){
1937
+ const plugin = pluginsToApply[i];
1938
+ try {
1939
+ this.logger.info(`Applying plugin: ${plugin.name}`);
1940
+ const pluginStartTime = Date.now();
1941
+ const sizeBefore = calculateSize(currentData);
1942
+ const context = {
1943
+ originalData: data,
1944
+ currentData,
1945
+ stats,
1946
+ logger: this.logger
1947
+ };
1948
+ const defaultOptions = plugin.defaultOptions || {};
1949
+ const presetOptions = effectiveOptions.pluginOptions?.[plugin.name] || {};
1950
+ const pluginOptions = {
1951
+ ...defaultOptions,
1952
+ ...presetOptions
1953
+ };
1954
+ if (effectiveOptions.debug?.showEffectiveConfig) {
1955
+ this.logger.info(`[${plugin.name}] Effective configuration:`);
1956
+ this.logger.info(JSON.stringify(pluginOptions, null, 2));
1957
+ }
1958
+ if (effectiveOptions.debug?.showConfigSources) {
1959
+ this.logger.info(`[${plugin.name}] Configuration sources:`);
1960
+ this.logger.info(` Default options: ${JSON.stringify(defaultOptions, null, 2)}`);
1961
+ this.logger.info(` Preset/User options: ${JSON.stringify(presetOptions, null, 2)}`);
1962
+ }
1963
+ const result = await plugin.apply(currentData, pluginOptions, context);
1964
+ if (result.changed) {
1965
+ currentData = result.data;
1966
+ stats.pluginsApplied.push(plugin.name);
1967
+ }
1968
+ if (plugin.validate) {
1969
+ const isValid = await plugin.validate(result);
1970
+ if (!isValid) {
1971
+ this.logger.warn(`Plugin ${plugin.name} validation failed`);
1972
+ warnings.push(`Plugin ${plugin.name} validation failed`);
1973
+ }
1974
+ }
1975
+ const sizeAfter = calculateSize(currentData);
1976
+ const processingTime = Date.now() - pluginStartTime;
1977
+ pluginReports.push({
1978
+ pluginName: plugin.name,
1979
+ sizeBefore: result.stats.sizeBefore || sizeBefore,
1980
+ sizeAfter: result.stats.sizeAfter || sizeAfter,
1981
+ itemsProcessed: result.stats.itemsProcessed,
1982
+ itemsRemoved: result.stats.itemsRemoved,
1983
+ processingTime
1984
+ });
1985
+ if (result.warnings) warnings.push(...result.warnings);
1986
+ if (result.errors) errors.push(...result.errors);
1987
+ const reportedSizeBefore = result.stats.sizeBefore || sizeBefore;
1988
+ const reportedSizeAfter = result.stats.sizeAfter || sizeAfter;
1989
+ this.logger.debug(`Plugin ${plugin.name} completed: ${reportedSizeBefore} -> ${reportedSizeAfter} bytes`);
1990
+ } catch (error) {
1991
+ const errorMsg = `Plugin ${plugin.name} failed: ${error}`;
1992
+ this.logger.error(errorMsg);
1993
+ errors.push(errorMsg);
1994
+ }
1995
+ }
1996
+ const finalSize = calculateSize(currentData);
1997
+ stats.optimizedSize = finalSize;
1998
+ stats.compressionRatio = (originalSize - finalSize) / originalSize * 100;
1999
+ stats.processingTime = Date.now() - startTime;
2000
+ const summary = this.generateSummary(stats);
2001
+ this.logger.info(`Optimization complete: ${originalSize} -> ${finalSize} bytes (${stats.compressionRatio.toFixed(1)}% reduction)`);
2002
+ return {
2003
+ data: currentData,
2004
+ stats,
2005
+ report: {
2006
+ summary,
2007
+ details: pluginReports,
2008
+ warnings,
2009
+ errors
2010
+ }
2011
+ };
2012
+ }
2013
+ async resolvePlugins(effectiveOptions) {
2014
+ let pluginNames = [];
2015
+ if (effectiveOptions.plugins) if ('string' != typeof effectiveOptions.plugins[0]) return effectiveOptions.plugins;
2016
+ else pluginNames = effectiveOptions.plugins;
2017
+ else pluginNames = Array.from(this.plugins.keys());
2018
+ if (effectiveOptions.disable) {
2019
+ const disabled = Array.isArray(effectiveOptions.disable) ? effectiveOptions.disable : [
2020
+ effectiveOptions.disable
2021
+ ];
2022
+ pluginNames = pluginNames.filter((name)=>!disabled.includes(name));
2023
+ }
2024
+ const resolvedPlugins = [];
2025
+ for (const name of pluginNames){
2026
+ const plugin = this.plugins.get(name);
2027
+ if (plugin) resolvedPlugins.push(plugin);
2028
+ else this.logger.warn(`Plugin "${name}" not found`);
2029
+ }
2030
+ return resolvedPlugins;
2031
+ }
2032
+ generateSummary(stats) {
2033
+ const reduction = stats.compressionRatio.toFixed(1);
2034
+ const time = (stats.processingTime / 1000).toFixed(2);
2035
+ return `Optimized ${stats.originalSize} -> ${stats.optimizedSize} bytes (${reduction}% reduction) in ${time}s using ${stats.pluginsApplied.length} plugins`;
2036
+ }
2037
+ getAvailablePlugins() {
2038
+ return Array.from(this.plugins.keys());
2039
+ }
2040
+ getPlugin(name) {
2041
+ return this.plugins.get(name);
2042
+ }
2043
+ }
2044
+ async function optimize(data, options = {}, inputFileSize) {
2045
+ const optimizer = new LottieOptimizer();
2046
+ return optimizer.optimize(data, options, inputFileSize);
2047
+ }
2048
+ export { LottieOptimizer, calculateCompressionRatio, calculateSize, createLogger, createSilentLogger, formatSize, getAvailablePlugins, getPluginByName, getPluginsByCategory, optimize };