@takazudo/zudo-image-tweaker 0.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +57 -0
  4. package/dist/blurhash/index.d.ts +35 -0
  5. package/dist/blurhash/index.js +40 -0
  6. package/dist/browser/exif-orientation.d.ts +12 -0
  7. package/dist/browser/index.d.ts +7 -0
  8. package/dist/browser/index.js +160 -0
  9. package/dist/browser/orientation-semantics.d.ts +37 -0
  10. package/dist/browser/orientation.d.ts +25 -0
  11. package/dist/browser/prepare-upload.d.ts +100 -0
  12. package/dist/budget/index.d.ts +73 -0
  13. package/dist/budget/index.js +157 -0
  14. package/dist/calibrate/index.d.ts +129 -0
  15. package/dist/calibrate/index.js +206 -0
  16. package/dist/composite/index.d.ts +50 -0
  17. package/dist/composite/index.js +56 -0
  18. package/dist/exif/index.d.ts +41 -0
  19. package/dist/exif/index.js +100 -0
  20. package/dist/heif/index.d.ts +139 -0
  21. package/dist/heif/index.js +275 -0
  22. package/dist/index.d.ts +14 -0
  23. package/dist/index.js +68 -0
  24. package/dist/ogp/index.d.ts +72 -0
  25. package/dist/ogp/index.js +117 -0
  26. package/dist/presets-PTxNoMdy.js +630 -0
  27. package/dist/product-photo/index.d.ts +50 -0
  28. package/dist/product-photo/index.js +189 -0
  29. package/dist/product-photo/shadow.d.ts +48 -0
  30. package/dist/run-BIjGA2Fu.js +22 -0
  31. package/dist/square/index.d.ts +98 -0
  32. package/dist/square/index.js +89 -0
  33. package/dist/variants/engine.d.ts +38 -0
  34. package/dist/variants/feature-detect.d.ts +8 -0
  35. package/dist/variants/hash-cache.d.ts +63 -0
  36. package/dist/variants/heic.d.ts +15 -0
  37. package/dist/variants/index.d.ts +5 -0
  38. package/dist/variants/index.js +19 -0
  39. package/dist/variants/presets.d.ts +18 -0
  40. package/dist/variants/repair.d.ts +28 -0
  41. package/dist/variants/run.d.ts +36 -0
  42. package/dist/variants/tags.d.ts +13 -0
  43. package/dist/variants/types.d.ts +208 -0
  44. package/package.json +126 -0
@@ -0,0 +1,630 @@
1
+ import m from "node:fs/promises";
2
+ import u from "node:path";
3
+ import j from "sharp";
4
+ import { encodeImageToBlurhash as it } from "./blurhash/index.js";
5
+ import { pickVariantWidths as st } from "./exif/index.js";
6
+ import { convertHeifToJpeg as ct } from "./heif/index.js";
7
+ import { generateSmartOgp as ut } from "./ogp/index.js";
8
+ import { randomUUID as Y } from "node:crypto";
9
+ import pt from "xxhash-wasm";
10
+ import { r as R, i as J, a as Q } from "./run-BIjGA2Fu.js";
11
+ import { constants as lt } from "node:fs";
12
+ import ft from "node:os";
13
+ let v = null;
14
+ function ht() {
15
+ return v || (v = pt(), v.catch(() => {
16
+ v = null;
17
+ })), v;
18
+ }
19
+ async function mt(t) {
20
+ const [a, e] = await Promise.all([ht(), m.readFile(t)]);
21
+ return a.h64Raw(e).toString(16);
22
+ }
23
+ async function L(t, a) {
24
+ const e = u.join(
25
+ u.dirname(t),
26
+ `.${u.basename(t)}.${Y()}.tmp`
27
+ );
28
+ try {
29
+ const r = await a(e);
30
+ return await m.rename(e, t), r;
31
+ } catch (r) {
32
+ throw await m.rm(e, { force: !0 }), r;
33
+ }
34
+ }
35
+ async function wt(t, a) {
36
+ await L(t, (e) => m.writeFile(e, a));
37
+ }
38
+ async function dt(t) {
39
+ let a;
40
+ try {
41
+ a = await m.readFile(t, "utf-8");
42
+ } catch {
43
+ return null;
44
+ }
45
+ try {
46
+ const e = JSON.parse(a);
47
+ return typeof e?.hash != "string" ? null : e;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function z(t, a) {
53
+ return wt(t, JSON.stringify(a, null, 2));
54
+ }
55
+ const B = /* @__PURE__ */ new Map();
56
+ async function gt(t, a) {
57
+ try {
58
+ return await R(t, a), !0;
59
+ } catch (e) {
60
+ return !J(e);
61
+ }
62
+ }
63
+ async function U(t, a) {
64
+ const e = B.get(t);
65
+ if (e !== void 0) return e;
66
+ const r = await gt(t, a);
67
+ return B.set(t, r), r;
68
+ }
69
+ function yt() {
70
+ return U("file", ["--version"]);
71
+ }
72
+ function H() {
73
+ return U("magick", ["-version"]);
74
+ }
75
+ function V() {
76
+ return U("ffmpeg", ["-version"]);
77
+ }
78
+ const Et = /* @__PURE__ */ new Set([".heic", ".heif"]), bt = /* @__PURE__ */ new Set([".jpg", ".jpeg"]), St = /* @__PURE__ */ new Set(["image/heic", "image/heic-sequence", "image/heif", "image/heif-sequence"]), Ft = /* @__PURE__ */ new Set([
79
+ "application/json",
80
+ "application/ld+json",
81
+ "application/manifest+json",
82
+ "application/xml",
83
+ "application/xhtml+xml",
84
+ "application/rss+xml",
85
+ "application/atom+xml",
86
+ "application/javascript",
87
+ "application/ecmascript",
88
+ "application/yaml",
89
+ "application/x-yaml",
90
+ "application/x-sh",
91
+ "application/x-httpd-php"
92
+ ]);
93
+ function Nt(t) {
94
+ return t.startsWith("text/") || Ft.has(t) ? !0 : t.startsWith("application/") && (t.endsWith("+xml") || t.endsWith("+json"));
95
+ }
96
+ async function K(t) {
97
+ if (!await yt()) return null;
98
+ try {
99
+ const { stdout: a } = await R("file", ["-b", "--mime-type", Q(t)]);
100
+ return a.trim();
101
+ } catch (a) {
102
+ return J(a), null;
103
+ }
104
+ }
105
+ async function Ot(t) {
106
+ const a = u.extname(t).toLowerCase();
107
+ if (Et.has(a)) return !0;
108
+ if (!bt.has(a)) return !1;
109
+ const e = await K(t);
110
+ return e !== null && St.has(e);
111
+ }
112
+ async function vt(t) {
113
+ const a = await K(t);
114
+ return a === null ? !1 : Nt(a);
115
+ }
116
+ const It = [
117
+ "premature end",
118
+ "bad huffman",
119
+ "corrupt jpeg",
120
+ "libspng read error"
121
+ ];
122
+ function Z(t) {
123
+ const a = (t?.message ?? "").toLowerCase();
124
+ return It.some((e) => a.includes(e));
125
+ }
126
+ async function Pt(t) {
127
+ try {
128
+ return await j(t).metadata(), !0;
129
+ } catch {
130
+ return !1;
131
+ }
132
+ }
133
+ async function $(t, a, e, r = "jpg") {
134
+ const n = u.join(ft.tmpdir(), `zit-repair-${process.pid}-${Y()}.${r}`);
135
+ try {
136
+ await R(t, a(Q(e), n));
137
+ const o = await m.readFile(n);
138
+ return o.length > 0 && await Pt(o) ? o : null;
139
+ } catch {
140
+ return null;
141
+ } finally {
142
+ await m.rm(n, { force: !0 });
143
+ }
144
+ }
145
+ function _t(t) {
146
+ return u.extname(t).toLowerCase() === ".png" ? ["png", "jpg"] : ["jpg", "png"];
147
+ }
148
+ async function Tt(t) {
149
+ const a = `${t}.corrupted.bak`;
150
+ try {
151
+ await m.copyFile(t, a, lt.COPYFILE_EXCL);
152
+ } catch (e) {
153
+ if (e.code === "EEXIST") return;
154
+ console.warn(
155
+ `zit/variants: could not back up corrupt source "${u.basename(t)}": ${e.message}`
156
+ );
157
+ }
158
+ }
159
+ async function tt(t, a = {}) {
160
+ if (a.backupCorrupted && await Tt(t), await H()) {
161
+ const [e, r] = _t(t), n = await $(
162
+ "magick",
163
+ (s, l) => [s, "-strip", "-set", "colorspace", "sRGB", l],
164
+ t,
165
+ e
166
+ );
167
+ if (n) return n;
168
+ const o = await $(
169
+ "magick",
170
+ (s, l) => [s, l],
171
+ t,
172
+ r
173
+ );
174
+ if (o) return o;
175
+ }
176
+ if (await V()) {
177
+ const e = await $(
178
+ "ffmpeg",
179
+ (r, n) => [
180
+ "-v",
181
+ "error",
182
+ "-err_detect",
183
+ "ignore_err",
184
+ "-i",
185
+ r,
186
+ "-vf",
187
+ "format=yuv420p",
188
+ "-y",
189
+ n
190
+ ],
191
+ t
192
+ );
193
+ if (e) return e;
194
+ }
195
+ return !await H() && !await V() && console.warn(
196
+ `zit/variants: cannot repair "${u.basename(t)}" — neither magick nor ffmpeg is available; skipping repair.`
197
+ ), null;
198
+ }
199
+ const W = "__ogonly", G = "__og";
200
+ function et(t) {
201
+ return u.basename(t, u.extname(t));
202
+ }
203
+ const jt = (t) => {
204
+ const a = et(t);
205
+ return a.endsWith(W) ? { mode: "ogonly", slug: a.slice(0, -W.length) } : a.endsWith(G) ? { mode: "og", slug: a.slice(0, -G.length) } : { mode: "full", slug: a };
206
+ }, xt = (t) => ({
207
+ mode: "full",
208
+ slug: et(t)
209
+ }), At = [600, 900, 1200, 1600, 2e3], Ct = 85, kt = ["webp"], Mt = 4, $t = "ogp.jpg", Dt = ".cache.json", Rt = ["jpg", "jpeg", "png", "webp", "heic", "heif", "gif", "avif"], Lt = new RegExp(`\\.(${Rt.join("|")})$`, "i"), Ut = (t, a) => `${t}w.${a}`;
210
+ class f extends Error {
211
+ stage;
212
+ constructor(a, e) {
213
+ super(e instanceof Error ? e.message : String(e), { cause: e }), this.name = "VariantProcessingError", this.stage = a;
214
+ }
215
+ }
216
+ function at(t) {
217
+ return {
218
+ outputDir: t.outputDir,
219
+ widths: t.widths ?? At,
220
+ formats: t.formats ?? kt,
221
+ quality: t.quality ?? Ct,
222
+ outputName: t.outputName ?? Ut,
223
+ ogpFileName: t.ogpFileName ?? $t,
224
+ ogpOptions: t.ogpOptions ?? {},
225
+ cacheFileName: t.cacheFileName ?? Dt,
226
+ fallbackBlurhash: t.fallbackBlurhash,
227
+ tagParser: t.tagParser ?? jt,
228
+ autoRepair: t.autoRepair ?? !0,
229
+ backupCorrupted: t.backupCorrupted ?? !1,
230
+ bakeExifOrientation: t.bakeExifOrientation ?? !1,
231
+ stripMetadata: t.stripMetadata ?? !1,
232
+ onMetadata: t.onMetadata,
233
+ onError: t.onError
234
+ };
235
+ }
236
+ function rt(t, a) {
237
+ const e = st(t, a);
238
+ return e.length > 0 ? e : [t];
239
+ }
240
+ function zt(t, a) {
241
+ return Number(t.toFixed(a));
242
+ }
243
+ function I(t) {
244
+ return `original${u.extname(t).toLowerCase()}`;
245
+ }
246
+ const Bt = /* @__PURE__ */ new Set(["gif", "webp", "png"]);
247
+ function Ht(t) {
248
+ return Bt.has(t.format ?? "") && (t.pages ?? 1) > 1;
249
+ }
250
+ function Vt(t) {
251
+ return t.length > 0 && t !== "." && t !== ".." && !t.includes("/") && !t.includes("\\") && !t.includes("\0");
252
+ }
253
+ const Wt = 4;
254
+ function Gt(t) {
255
+ return JSON.stringify({
256
+ pipelineVersion: Wt,
257
+ quality: t.quality,
258
+ widths: t.widths,
259
+ formats: t.formats,
260
+ ogpFileName: t.ogpFileName,
261
+ ogpOptions: t.ogpOptions,
262
+ fallbackBlurhash: t.fallbackBlurhash ?? null,
263
+ // bakeExifOrientation is deliberately NOT fingerprinted: it is a no-op
264
+ // (orientation is always baked at encode time, issue #29), so toggling
265
+ // it must keep the cache hit rather than regenerate identical outputs.
266
+ stripMetadata: t.stripMetadata
267
+ });
268
+ }
269
+ function Xt(t, a) {
270
+ if (t.length !== a.length) return !1;
271
+ const e = [...t].sort(), r = [...a].sort();
272
+ return e.every((n, o) => n === r[o]);
273
+ }
274
+ const X = /* @__PURE__ */ new Set(["webp", "jpg", "jpeg", "png", "avif", "tiff", "gif"]);
275
+ function qt(t, a, e) {
276
+ if (!X.has(a))
277
+ throw new Error(
278
+ `unsupported output format "${a}" — expected one of ${[...X].join(", ")}`
279
+ );
280
+ switch (a) {
281
+ case "webp":
282
+ return t.webp({ quality: e });
283
+ case "jpg":
284
+ case "jpeg":
285
+ return t.jpeg({ quality: e, progressive: !0, mozjpeg: !0 });
286
+ case "png":
287
+ return t.png();
288
+ case "avif":
289
+ return t.avif({ quality: e });
290
+ default:
291
+ return t.toFormat(a, { quality: e });
292
+ }
293
+ }
294
+ async function Yt(t, a, e) {
295
+ try {
296
+ return { metadata: await j(t).metadata(), input: t };
297
+ } catch (r) {
298
+ if (a && typeof t == "string" && Z(r)) {
299
+ const n = await tt(t, { backupCorrupted: e });
300
+ if (n)
301
+ return { metadata: await j(n).metadata(), input: n };
302
+ }
303
+ throw r;
304
+ }
305
+ }
306
+ async function q(t, a, e) {
307
+ try {
308
+ return await it(t);
309
+ } catch (r) {
310
+ return console.warn(`zit/variants: blurhash encoding failed for "${a}": ${r.message}`), e ?? null;
311
+ }
312
+ }
313
+ async function Jt(t, a, e, r) {
314
+ const n = [];
315
+ for (const o of rt(e, r.widths))
316
+ for (const s of r.formats) {
317
+ const l = r.outputName(o, s), y = u.join(a, l), w = j(t).rotate().resize({ width: o, withoutEnlargement: !0 });
318
+ r.stripMetadata || w.keepIccProfile();
319
+ const { size: d } = await L(
320
+ y,
321
+ (h) => qt(w, s, r.quality).toFile(h)
322
+ );
323
+ n.push({ width: o, format: s, filename: l, path: y, size: d });
324
+ }
325
+ return n;
326
+ }
327
+ async function D(t, a, e) {
328
+ const r = u.join(a, e.ogpFileName), n = await ut(t, {
329
+ quality: e.quality,
330
+ ...e.ogpOptions,
331
+ outPath: r
332
+ });
333
+ return {
334
+ filename: e.ogpFileName,
335
+ path: r,
336
+ size: n.buffer.length,
337
+ method: n.method
338
+ };
339
+ }
340
+ function Qt(t, a, e, r) {
341
+ if (t === "ogonly") return [r.ogpFileName];
342
+ if (a.animated)
343
+ return t === "og" ? [I(e), r.ogpFileName] : [I(e)];
344
+ const n = a.metadata?.width;
345
+ if (!n) return null;
346
+ const o = [];
347
+ for (const s of rt(n, r.widths))
348
+ for (const l of r.formats)
349
+ o.push(r.outputName(s, l));
350
+ return t === "og" && o.push(r.ogpFileName), o;
351
+ }
352
+ async function Kt(t, a, e) {
353
+ if (!e) return !1;
354
+ for (const r of a) {
355
+ const n = e[r];
356
+ if (typeof n != "number") return !1;
357
+ let o;
358
+ try {
359
+ o = await m.stat(u.join(t, r));
360
+ } catch {
361
+ return !1;
362
+ }
363
+ if (o.size !== n) return !1;
364
+ }
365
+ return !0;
366
+ }
367
+ async function Zt(t, a) {
368
+ const e = at(a), { inputPath: r } = t, { mode: n, slug: o } = t.tag ?? e.tagParser(u.basename(r));
369
+ if (!Vt(o))
370
+ throw new f(
371
+ "slug",
372
+ new Error(`unsafe output slug "${o}" derived from "${u.basename(r)}"`)
373
+ );
374
+ const s = u.join(e.outputDir, o), l = { slug: o, inputPath: r, outputDir: s, mode: n }, y = u.join(s, e.cacheFileName), w = Gt(e);
375
+ let d;
376
+ try {
377
+ d = await mt(r);
378
+ } catch (i) {
379
+ throw new f("io", i);
380
+ }
381
+ const h = await dt(y);
382
+ if (h && h.hash === d && h.configHash === w && h.mode === n) {
383
+ const i = Qt(n, h, r, e);
384
+ if (i && Array.isArray(h.outputs) && Xt(i, h.outputs) && await Kt(s, i, h.outputSizes))
385
+ return h.metadata && await e.onMetadata?.(h.metadata), {
386
+ ...l,
387
+ status: "skipped",
388
+ reason: "cache-hit",
389
+ animated: h.animated,
390
+ variants: [],
391
+ ogp: null,
392
+ metadata: h.metadata
393
+ };
394
+ }
395
+ if (await vt(r))
396
+ return { ...l, status: "skipped", reason: "not-an-image", animated: !1, variants: [], ogp: null, metadata: null };
397
+ try {
398
+ await m.mkdir(s, { recursive: !0 });
399
+ } catch (i) {
400
+ throw new f("io", i);
401
+ }
402
+ let c = r;
403
+ if (await Ot(r))
404
+ try {
405
+ c = (await ct(r, { quality: 90 })).buffer;
406
+ } catch (i) {
407
+ throw new f("heic", i);
408
+ }
409
+ let p;
410
+ try {
411
+ const i = await Yt(c, e.autoRepair, e.backupCorrupted);
412
+ p = i.metadata, c = i.input;
413
+ } catch (i) {
414
+ throw new f("probe", i);
415
+ }
416
+ const F = p.autoOrient?.width || p.width || 0, x = p.autoOrient?.height || p.height || 0;
417
+ if (!F || !x)
418
+ throw new f("probe", new Error(`could not determine dimensions for "${o}"`));
419
+ const E = Ht(p);
420
+ if (n === "ogonly") {
421
+ let i;
422
+ try {
423
+ i = await D(c, s, e);
424
+ } catch (S) {
425
+ throw new f("ogp", S);
426
+ }
427
+ return await z(y, {
428
+ hash: d,
429
+ configHash: w,
430
+ mode: n,
431
+ animated: E,
432
+ outputs: [i.filename],
433
+ outputSizes: { [i.filename]: i.size },
434
+ metadata: null
435
+ }), { ...l, status: "processed", animated: E, variants: [], ogp: i, metadata: null };
436
+ }
437
+ let A, O = [], b = null, C = null;
438
+ if (E) {
439
+ A = await q(c, o, e.fallbackBlurhash);
440
+ try {
441
+ const i = u.join(s, I(r));
442
+ await L(i, (S) => m.copyFile(r, S)), C = (await m.stat(i)).size;
443
+ } catch (i) {
444
+ throw new f("passthrough", i);
445
+ }
446
+ if (n === "og")
447
+ try {
448
+ b = await D(c, s, e);
449
+ } catch (i) {
450
+ throw new f("ogp", i);
451
+ }
452
+ } else {
453
+ const i = async (g) => {
454
+ const M = await q(g, o, e.fallbackBlurhash), T = await Jt(g, s, F, e);
455
+ let N = null;
456
+ if (n === "og")
457
+ try {
458
+ N = await D(g, s, e);
459
+ } catch (ot) {
460
+ throw new f("ogp", ot);
461
+ }
462
+ return { blurhash: M, variants: T, ogp: N };
463
+ };
464
+ let S;
465
+ try {
466
+ S = await i(c);
467
+ } catch (g) {
468
+ const M = g instanceof f ? g.cause : g, T = e.autoRepair && typeof c == "string" && Z(M) ? await tt(c, { backupCorrupted: e.backupCorrupted }) : null;
469
+ if (!T)
470
+ throw g instanceof f ? g : new f("variants", g);
471
+ try {
472
+ S = await i(T);
473
+ } catch (N) {
474
+ throw N instanceof f ? N : new f("variants", N);
475
+ }
476
+ }
477
+ ({ blurhash: A, variants: O, ogp: b } = S);
478
+ }
479
+ const k = u.extname(r).slice(1).toLowerCase(), P = {
480
+ slug: o,
481
+ blurhash: A,
482
+ width: F,
483
+ height: x,
484
+ aspectRatio: zt(x / F * 100, 2),
485
+ hasVariants: !E && O.length > 0,
486
+ hash: d,
487
+ processedAt: (/* @__PURE__ */ new Date()).toISOString()
488
+ };
489
+ k && k !== "webp" && (P.originalFormat = k);
490
+ const nt = [
491
+ ...O.map((i) => i.filename),
492
+ ...b ? [b.filename] : [],
493
+ ...E ? [I(r)] : []
494
+ ], _ = {};
495
+ for (const i of O) _[i.filename] = i.size;
496
+ b && (_[b.filename] = b.size), E && C !== null && (_[I(r)] = C);
497
+ try {
498
+ await z(y, { hash: d, configHash: w, mode: n, animated: E, outputs: nt, outputSizes: _, metadata: P });
499
+ } catch (i) {
500
+ throw new f("cache", i);
501
+ }
502
+ return await e.onMetadata?.(P), { ...l, status: "processed", animated: E, variants: O, ogp: b, metadata: P };
503
+ }
504
+ async function te(t) {
505
+ const a = /* @__PURE__ */ new Set(), e = [], r = (n) => {
506
+ const o = u.resolve(n);
507
+ a.has(o) || (a.add(o), e.push(n));
508
+ };
509
+ if (t.inputDir) {
510
+ const n = t.inputDir;
511
+ let o;
512
+ try {
513
+ o = await m.readdir(n);
514
+ } catch (s) {
515
+ throw new Error(`variants: could not read inputDir "${n}": ${s.message}`, {
516
+ cause: s
517
+ });
518
+ }
519
+ for (const s of o.sort())
520
+ Lt.test(s) && r(u.join(n, s));
521
+ }
522
+ if (t.files) for (const n of t.files) r(n);
523
+ return e;
524
+ }
525
+ async function ee(t, a, e) {
526
+ const r = Math.max(1, Math.min(a, t.length));
527
+ let n = 0;
528
+ const o = Array.from({ length: r }, async () => {
529
+ for (; n < t.length; ) {
530
+ const s = t[n++];
531
+ await e(s);
532
+ }
533
+ });
534
+ await Promise.all(o);
535
+ }
536
+ async function ae(t, a) {
537
+ const e = [];
538
+ let r;
539
+ try {
540
+ r = await m.readdir(t, { withFileTypes: !0 });
541
+ } catch {
542
+ return e;
543
+ }
544
+ const n = u.resolve(t);
545
+ for (const o of r) {
546
+ if (!o.isDirectory() || a.has(o.name)) continue;
547
+ const s = u.resolve(t, o.name);
548
+ u.dirname(s) === n && (await m.rm(s, { recursive: !0, force: !0 }), e.push(s));
549
+ }
550
+ return e;
551
+ }
552
+ async function de(t) {
553
+ const a = at(t), e = t.concurrency ?? Mt;
554
+ if (!Number.isFinite(e) || e < 1)
555
+ throw new Error(
556
+ `processImages: concurrency must be a finite number >= 1, received ${e}`
557
+ );
558
+ const r = await te(t), n = [], o = [], s = async (c) => {
559
+ o.push(c), await a.onError?.(c);
560
+ }, l = [];
561
+ let y = !1;
562
+ for (const c of r)
563
+ try {
564
+ l.push({ inputPath: c, tag: a.tagParser(u.basename(c)) });
565
+ } catch (p) {
566
+ y = !0, await s({
567
+ inputPath: c,
568
+ slug: u.basename(c),
569
+ stage: "slug",
570
+ error: p instanceof Error ? p.message : String(p)
571
+ });
572
+ }
573
+ const w = /* @__PURE__ */ new Map();
574
+ for (const c of l) {
575
+ const p = w.get(c.tag.slug);
576
+ p ? p.push(c) : w.set(c.tag.slug, [c]);
577
+ }
578
+ const d = [];
579
+ for (const [c, p] of w) {
580
+ if (p.length === 1) {
581
+ d.push(p[0]);
582
+ continue;
583
+ }
584
+ for (const F of p)
585
+ await s({
586
+ inputPath: F.inputPath,
587
+ slug: c,
588
+ stage: "slug",
589
+ error: `duplicate output slug "${c}" — ${p.length} inputs map to the same output directory`
590
+ });
591
+ }
592
+ await ee(d, e, async (c) => {
593
+ try {
594
+ n.push(await Zt(c, t));
595
+ } catch (p) {
596
+ await s({
597
+ inputPath: c.inputPath,
598
+ slug: c.tag?.slug ?? u.basename(c.inputPath),
599
+ stage: p instanceof f ? p.stage : "unknown",
600
+ error: p instanceof Error ? p.message : String(p)
601
+ });
602
+ }
603
+ });
604
+ const h = t.cleanupOrphans && !y ? await ae(a.outputDir, new Set(l.map((c) => c.tag.slug))) : [];
605
+ return { results: n, failed: o, removed: h };
606
+ }
607
+ const re = [400, 800, 1600], ge = {
608
+ widths: re,
609
+ bakeExifOrientation: !0,
610
+ stripMetadata: !0,
611
+ tagParser: xt
612
+ };
613
+ export {
614
+ Dt as D,
615
+ Lt as I,
616
+ re as P,
617
+ Rt as S,
618
+ f as V,
619
+ Mt as a,
620
+ kt as b,
621
+ $t as c,
622
+ Ct as d,
623
+ At as e,
624
+ jt as f,
625
+ de as g,
626
+ Zt as h,
627
+ xt as n,
628
+ ge as p,
629
+ rt as s
630
+ };
@@ -0,0 +1,50 @@
1
+ import type { Color as SharpColor } from 'sharp';
2
+ import { generateShadowLayers } from './shadow.js';
3
+ import type { GenerateShadowLayersOptions } from './shadow.js';
4
+ export { generateShadowLayers };
5
+ export type { ShadowMode, ShadowBlend, ShadowLayer, ShadowTuning, BottomShadowTuning, ProjectedShadowTuning, VignetteTuning, GenerateShadowLayersOptions, } from './shadow.js';
6
+ export type ImageInput = string | Buffer;
7
+ export interface OptionalPeerMissingError extends Error {
8
+ code: 'ERR_OPTIONAL_PEER_MISSING';
9
+ }
10
+ export interface AlphaTrimOptions {
11
+ threshold?: number;
12
+ }
13
+ export interface FlatColorBackground {
14
+ color: SharpColor;
15
+ }
16
+ export type ProductPhotoBackground = ImageInput | FlatColorBackground;
17
+ export interface ComposeProductPhotoOptions {
18
+ background: ProductPhotoBackground;
19
+ size?: number;
20
+ fit?: number;
21
+ quality?: number;
22
+ shadow?: boolean | GenerateShadowLayersOptions;
23
+ }
24
+ export interface ComposeProductPhotoResult {
25
+ buffer: Buffer;
26
+ width: number;
27
+ height: number;
28
+ format: 'jpeg';
29
+ }
30
+ /**
31
+ * Remove the background from a product image, returning an RGBA PNG buffer.
32
+ *
33
+ * Dynamic-imports the optional peer dependency `@imgly/background-removal-node`
34
+ * (an ONNX-based ML model, ~80MB download on first use) so consumers who
35
+ * never call this function never pay that cost. Accepts a file path,
36
+ * a Buffer, or a URL. Throws an error with `code: 'ERR_OPTIONAL_PEER_MISSING'`
37
+ * when the optional peer isn't installed.
38
+ */
39
+ export declare function removeBackground(input: ImageInput | URL): Promise<Buffer>;
40
+ /**
41
+ * Trim fully-transparent padding from an image with an alpha channel,
42
+ * tightly cropping to the opaque/semi-opaque content.
43
+ */
44
+ export declare function alphaTrim(input: ImageInput, options?: AlphaTrimOptions): Promise<Buffer>;
45
+ /**
46
+ * Compose a subject (typically background-removed and trimmed via
47
+ * `removeBackground`/`alphaTrim`) onto a background canvas, fitting it
48
+ * within a centered container and optionally adding synthesized shadows.
49
+ */
50
+ export declare function composeProductPhoto(subject: ImageInput, options: ComposeProductPhotoOptions): Promise<ComposeProductPhotoResult>;