@wasmedia/wexiftool 0.0.0-stage → 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.
@@ -0,0 +1,761 @@
1
+ var H = Object.defineProperty;
2
+ var L = (n, t, e) => t in n ? H(n, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : n[t] = e;
3
+ var A = (n, t, e) => L(n, typeof t != "symbol" ? t + "" : t, e);
4
+ class b extends Error {
5
+ constructor(t, e) {
6
+ super(t, e), this.name = new.target.name;
7
+ }
8
+ }
9
+ class it extends b {
10
+ }
11
+ class at extends b {
12
+ constructor(e, r) {
13
+ super(`${e} is locked by another sync access handle (another tab or worker may be using it)`, { cause: r });
14
+ A(this, "path");
15
+ this.path = e;
16
+ }
17
+ }
18
+ class R extends b {
19
+ }
20
+ class ct extends b {
21
+ }
22
+ class lt extends b {
23
+ }
24
+ class ut extends b {
25
+ constructor(e, r) {
26
+ super(`commit() failed after ${e.length} file(s): ${(r == null ? void 0 : r.message) ?? String(r)}`, { cause: r });
27
+ A(this, "committed");
28
+ this.committed = e;
29
+ }
30
+ }
31
+ const g = "/wexiftool", j = "/opt/perlwasm/bin/exiftool";
32
+ function B(n) {
33
+ n.ENV && (n.ENV.LC_ALL = "C");
34
+ }
35
+ class C {
36
+ constructor(t, e) {
37
+ this.wasmUrl = t, this.onWasmProgress = e, this.module = null, this.modulePromise = null, this.tail = Promise.resolve(), this.createModule = null, this.stdoutSink = null, this.stderrSink = null;
38
+ }
39
+ /**
40
+ * Lazily initialize the WASM module on first use.
41
+ */
42
+ async init() {
43
+ if (!this.module) {
44
+ if (this.modulePromise) {
45
+ await this.modulePromise;
46
+ return;
47
+ }
48
+ this.modulePromise = this.loadModule(), this.module = await this.modulePromise;
49
+ try {
50
+ this.module.FS.mkdir(g);
51
+ } catch {
52
+ }
53
+ }
54
+ }
55
+ async loadModule() {
56
+ const t = {
57
+ noInitialRun: !0,
58
+ print: (i) => {
59
+ var s;
60
+ (s = this.stdoutSink) == null || s.call(this, i);
61
+ },
62
+ printErr: (i) => {
63
+ var s;
64
+ (s = this.stderrSink) == null || s.call(this, i);
65
+ },
66
+ preRun: [B]
67
+ }, e = this.wasmUrl ?? this.defaultWasmUrl(), r = await this.importModuleFactory(e);
68
+ this.createModule = r;
69
+ const o = e.substring(0, e.lastIndexOf("/") + 1);
70
+ if (this.onWasmProgress && typeof fetch < "u") {
71
+ const i = await this.fetchWithProgress(o + "perl.wasm");
72
+ return await r({
73
+ ...t,
74
+ locateFile: (s) => o + s,
75
+ wasmBinary: i
76
+ });
77
+ }
78
+ return await r({
79
+ ...t,
80
+ locateFile: (i) => o + i
81
+ });
82
+ }
83
+ async fetchWithProgress(t) {
84
+ const e = await fetch(t);
85
+ if (!e.ok)
86
+ throw new Error(`Failed to fetch ${t}: ${e.status} ${e.statusText}`);
87
+ const r = e.headers.get("content-length"), o = r ? parseInt(r, 10) : null, i = e.body.getReader(), s = [];
88
+ let c = 0;
89
+ for (; ; ) {
90
+ const { done: d, value: u } = await i.read();
91
+ if (d) break;
92
+ s.push(u), c += u.byteLength, this.onWasmProgress(c, o);
93
+ }
94
+ const a = new Uint8Array(c);
95
+ let l = 0;
96
+ for (const d of s)
97
+ a.set(d, l), l += d.byteLength;
98
+ return a;
99
+ }
100
+ async importModuleFactory(t) {
101
+ var i;
102
+ if (typeof process < "u" && ((i = process.versions) != null && i.node)) {
103
+ const s = await import(
104
+ /* @vite-ignore */
105
+ t
106
+ );
107
+ return s.default ?? s;
108
+ }
109
+ const e = await fetch(t);
110
+ if (!e.ok)
111
+ throw new Error(`Failed to fetch ${t}: ${e.status} ${e.statusText}`);
112
+ const r = await e.text();
113
+ return new Function(
114
+ "exports",
115
+ "module",
116
+ "define",
117
+ `${r}
118
+ return createPerlModule;`
119
+ )(void 0, void 0, void 0);
120
+ }
121
+ defaultWasmUrl() {
122
+ return typeof __dirname < "u" ? require("path").join(__dirname, "..", "wasm", "perl.js") : new URL(
123
+ /* @vite-ignore */
124
+ "../wasm/perl.js",
125
+ import.meta.url
126
+ ).href;
127
+ }
128
+ /**
129
+ * Run exiftool over in-memory files. Only one run happens at a time (Perl is
130
+ * single-threaded). `result.files` holds the files the run changed.
131
+ */
132
+ async exec(t, e) {
133
+ const r = [...e].map(([s, c]) => ({ name: s, source: c })), { committed: o, ...i } = await this.run(t, r);
134
+ return i;
135
+ }
136
+ /**
137
+ * Run exiftool over any mix of in-memory files and file references.
138
+ *
139
+ * If every input is bytes, they are staged in MEMFS. Otherwise each input
140
+ * gets its own wopfs mount, so referenced files are read in place rather
141
+ * than copied in. Both use the same layout, `/wexiftool/<i>/<name>`, and
142
+ * both make inputs read-only, so exiftool sees identical files and produces
143
+ * identical output either way.
144
+ */
145
+ async run(t, e, r = {}) {
146
+ return await this.init(), this.enqueue(
147
+ () => e.some((o) => !(o.source instanceof Uint8Array)) ? this.runMounted(t, e, r) : this.runStaged(t, e)
148
+ );
149
+ }
150
+ runStaged(t, e) {
151
+ const r = this.module.FS, o = e.map((s, c) => `${g}/${c}/${v(s)}`), i = /* @__PURE__ */ new Map();
152
+ try {
153
+ e.forEach((c, a) => {
154
+ r.mkdir(`${g}/${a}`), r.writeFile(o[a], c.source), r.chmod(o[a], 292), i.set(o[a], F(r, o[a]));
155
+ });
156
+ const s = this.callExiftool(t, e, o);
157
+ return e.forEach((c, a) => {
158
+ if (F(r, o[a]) !== i.get(o[a]))
159
+ try {
160
+ s.files.set(c.name, r.readFile(o[a]));
161
+ } catch {
162
+ }
163
+ }), { ...s, committed: /* @__PURE__ */ new Map() };
164
+ } finally {
165
+ e.forEach((s, c) => M(r, `${g}/${c}`));
166
+ }
167
+ }
168
+ async runMounted(t, e, r) {
169
+ var a;
170
+ const { mountFiles: o } = await import("./index-D8B3sucz.js").then((l) => l.a), i = this.module, s = e.map((l, d) => `${g}/${d}/${v(l)}`), c = [];
171
+ try {
172
+ for (const [u, h] of e.entries()) {
173
+ const w = ((a = h.commitTo) == null ? void 0 : a.dir) ?? (r.readOnly ? void 0 : r.privateDir);
174
+ if (w === void 0 && !r.readOnly)
175
+ throw new Error(`Input "${h.name}" is mounted, so the run needs a privateDir, a commitTo or readOnly`);
176
+ c.push(
177
+ await o(i, `${g}/${u}`, {
178
+ inputs: { [v(h)]: h.source },
179
+ outputDir: w,
180
+ // Read-only: no outputDir, so wopfs creates no scratch directory or
181
+ // pool, and anything exiftool tried to create would fail with
182
+ // EROFS. Otherwise exiftool creates one temp file per file it
183
+ // writes, and spill covers anything unexpected.
184
+ scratch: w === void 0 ? 0 : r.scratch ?? 1,
185
+ onExhausted: "spill",
186
+ backend: r.backend
187
+ })
188
+ );
189
+ }
190
+ const l = this.callExiftool(t, e, s), d = /* @__PURE__ */ new Map();
191
+ for (const [u, h] of e.entries()) {
192
+ const w = await c[u].commit(), m = w.find((f) => f.path === v(h));
193
+ if (h.commitTo)
194
+ m && d.set(h.name, m);
195
+ else
196
+ for (const f of w)
197
+ f === m && l.files.set(h.name, await N(f)), await q(f, r.privateDir);
198
+ }
199
+ return { ...l, committed: d };
200
+ } finally {
201
+ for (const l of c) await l.release();
202
+ }
203
+ }
204
+ /**
205
+ * Map names in `args` to their paths, run exiftool, and map paths in its
206
+ * output back to names. Synchronous: everything callMain touches must
207
+ * already be in place.
208
+ */
209
+ callExiftool(t, e, r) {
210
+ const o = this.module, i = new Map(e.map((u, h) => [u.name, r[h]])), s = [j];
211
+ for (const u of t)
212
+ s.push(
213
+ u.toLowerCase() === "-overwrite_original_in_place" ? "-overwrite_original" : i.get(u) ?? u
214
+ );
215
+ const c = new Set(t);
216
+ for (const u of e)
217
+ c.has(u.name) || s.push(i.get(u.name));
218
+ let a = "", l = "";
219
+ this.stdoutSink = (u) => {
220
+ a += u + `
221
+ `;
222
+ }, this.stderrSink = (u) => {
223
+ l += u + `
224
+ `;
225
+ };
226
+ let d;
227
+ try {
228
+ d = o.callMain(s);
229
+ } catch (u) {
230
+ if (u.status !== void 0)
231
+ d = u.status;
232
+ else
233
+ throw u;
234
+ } finally {
235
+ this.stdoutSink = null, this.stderrSink = null;
236
+ }
237
+ for (const [u, h] of i)
238
+ a = a.replaceAll(h, u), l = l.replaceAll(h, u);
239
+ return e.forEach((u, h) => {
240
+ const w = new RegExp(`${g}/${h}(?![0-9])`, "g"), m = u.name.lastIndexOf("/"), f = m > 0 ? u.name.slice(0, m) : m === 0 ? "/" : ".";
241
+ a = a.replace(w, () => f), l = l.replace(w, () => f);
242
+ }), { stdout: a.trimEnd(), stderr: l.trimEnd(), exitCode: d, files: /* @__PURE__ */ new Map() };
243
+ }
244
+ /**
245
+ * Serialize runs. A run spans awaits (mounting, committing), so this is a
246
+ * promise chain rather than a flag: the next run starts only when the
247
+ * previous one has fully settled.
248
+ */
249
+ enqueue(t) {
250
+ const e = this.tail.then(t, t);
251
+ return this.tail = e.catch(() => {
252
+ }), e;
253
+ }
254
+ /**
255
+ * Release WASM module resources.
256
+ */
257
+ destroy() {
258
+ this.module = null, this.modulePromise = null, this.createModule = null, this.tail = Promise.resolve();
259
+ }
260
+ }
261
+ function v(n) {
262
+ var e;
263
+ const t = (((e = n.commitTo) == null ? void 0 : e.as) ?? n.name).split("/").pop();
264
+ return t && t !== "." && t !== ".." ? t : "input.bin";
265
+ }
266
+ function F(n, t) {
267
+ try {
268
+ const e = n.stat(t);
269
+ return `${e.ino}:${e.size}:${+e.mtime}`;
270
+ } catch {
271
+ return "missing";
272
+ }
273
+ }
274
+ function M(n, t) {
275
+ let e;
276
+ try {
277
+ e = n.readdir(t);
278
+ } catch {
279
+ return;
280
+ }
281
+ for (const r of e) {
282
+ if (r === "." || r === "..") continue;
283
+ const o = `${t}/${r}`;
284
+ try {
285
+ n.unlink(o);
286
+ } catch {
287
+ M(n, o);
288
+ }
289
+ }
290
+ try {
291
+ n.rmdir(t);
292
+ } catch {
293
+ }
294
+ }
295
+ async function N(n) {
296
+ if (n.handle) return new Uint8Array(await (await n.handle.getFile()).arrayBuffer());
297
+ if (n.hostPath) {
298
+ const { readFile: t } = await import(
299
+ /* @vite-ignore */
300
+ "node:fs/promises"
301
+ );
302
+ return new Uint8Array(await t(n.hostPath));
303
+ }
304
+ return n.bytes ?? new Uint8Array();
305
+ }
306
+ async function q(n, t) {
307
+ if (n.hostPath) {
308
+ const { rm: e } = await import(
309
+ /* @vite-ignore */
310
+ "node:fs/promises"
311
+ );
312
+ await e(n.hostPath, { force: !0 });
313
+ return;
314
+ }
315
+ if (typeof t != "string") {
316
+ const e = n.path.split("/"), r = e.pop();
317
+ let o = t;
318
+ for (const i of e) o = await o.getDirectoryHandle(i);
319
+ await o.removeEntry(r).catch(() => {
320
+ });
321
+ }
322
+ }
323
+ function p(n) {
324
+ return typeof n == "object" && n !== null && n.kind === "file" && typeof n.getFile == "function";
325
+ }
326
+ async function k(n) {
327
+ if (n instanceof Uint8Array)
328
+ return { name: "input.bin", data: n };
329
+ if (n instanceof ArrayBuffer)
330
+ return { name: "input.bin", data: new Uint8Array(n) };
331
+ if (p(n)) {
332
+ const t = await n.getFile();
333
+ return { name: n.name || "input.bin", data: new Uint8Array(await t.arrayBuffer()) };
334
+ }
335
+ if (typeof File < "u" && n instanceof File) {
336
+ const t = await n.arrayBuffer();
337
+ return { name: n.name || "input.bin", data: new Uint8Array(t) };
338
+ }
339
+ if (typeof Blob < "u" && n instanceof Blob) {
340
+ const t = await n.arrayBuffer();
341
+ return { name: "input.bin", data: new Uint8Array(t) };
342
+ }
343
+ throw new Error("Unsupported input type");
344
+ }
345
+ async function z(n, t) {
346
+ const e = G(t);
347
+ if (typeof n.createSyncAccessHandle == "function") {
348
+ const r = await n.createSyncAccessHandle();
349
+ try {
350
+ r.truncate(0);
351
+ let o = 0;
352
+ for (; o < e.byteLength; ) {
353
+ const i = o === 0 ? e : e.slice(o), s = r.write(i, { at: o });
354
+ if (s <= 0)
355
+ throw new Error(`Short write to ${n.name} at byte ${o} of ${e.byteLength}`);
356
+ o += s;
357
+ }
358
+ r.flush();
359
+ } finally {
360
+ r.close();
361
+ }
362
+ return;
363
+ }
364
+ if (typeof n.createWritable == "function") {
365
+ const r = await n.createWritable();
366
+ try {
367
+ await r.write(e), await r.close();
368
+ } catch (o) {
369
+ throw await r.abort(o).catch(() => {
370
+ }), o;
371
+ }
372
+ return;
373
+ }
374
+ throw new Error(
375
+ `Cannot write to file handle "${n.name}": it has neither createSyncAccessHandle() (dedicated workers only) nor createWritable().`
376
+ );
377
+ }
378
+ function G(n) {
379
+ const { buffer: t, byteOffset: e, byteLength: r } = n;
380
+ return t instanceof ArrayBuffer && e === 0 && r === t.byteLength ? t : n.slice().buffer;
381
+ }
382
+ const V = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
383
+ __proto__: null,
384
+ isFileHandle: p,
385
+ resolveInput: k,
386
+ writeToHandle: z
387
+ }, Symbol.toStringTag, { value: "Module" })), T = ".wexiftool";
388
+ function O() {
389
+ var i, s;
390
+ const n = globalThis, t = !!((s = (i = n.process) == null ? void 0 : i.versions) != null && s.node) && typeof n.window > "u", e = n.DedicatedWorkerGlobalScope, r = n.FileSystemFileHandle, o = !t && typeof e == "function" && globalThis instanceof e && !!(r && "createSyncAccessHandle" in r.prototype);
391
+ return { node: t, opfs: o };
392
+ }
393
+ async function P(n, t, e, r) {
394
+ if (typeof t == "string") {
395
+ if (!e.node) throw new Error(`"${t}": file paths are only supported under Node`);
396
+ const { resolve: i, dirname: s, basename: c } = await import(
397
+ /* @vite-ignore */
398
+ "node:path"
399
+ ), a = i(t);
400
+ if (r === void 0) return { input: { name: n, source: a } };
401
+ if (typeof r != "string") return { input: { name: n, source: a }, writeAfter: r };
402
+ const l = i(r);
403
+ return { input: { name: n, source: a, commitTo: { dir: s(l), as: c(l) } } };
404
+ }
405
+ if (p(t) && e.opfs && await U(t)) {
406
+ const i = t;
407
+ if (r === void 0) return { input: { name: n, source: i } };
408
+ const s = p(r) ? await Y(r) : null;
409
+ return s ? { input: { name: n, source: i, commitTo: { dir: s, as: r.name } } } : { input: { name: n, source: i }, writeAfter: r };
410
+ }
411
+ const { data: o } = await k(t);
412
+ return { input: { name: n, source: o }, writeAfter: r };
413
+ }
414
+ const S = /* @__PURE__ */ new WeakMap();
415
+ async function J(n, t) {
416
+ try {
417
+ const e = await X(t);
418
+ e && await e.isSameEntry(n) && S.set(n, t);
419
+ } catch {
420
+ }
421
+ }
422
+ async function K(n) {
423
+ return (await navigator.storage.getDirectory()).resolve(n);
424
+ }
425
+ async function X(n) {
426
+ if (n.length === 0) return null;
427
+ let t = await navigator.storage.getDirectory();
428
+ for (const e of n.slice(0, -1)) t = await t.getDirectoryHandle(e);
429
+ return t.getFileHandle(n[n.length - 1]);
430
+ }
431
+ const Q = 5e3;
432
+ async function U(n) {
433
+ const t = S.get(n);
434
+ if (t) return t;
435
+ const r = [[await navigator.storage.getDirectory(), []]];
436
+ let o = 0;
437
+ for (; r.length > 0; ) {
438
+ const [i, s] = r.shift();
439
+ for await (const [c, a] of i.entries()) {
440
+ if (++o > Q) return null;
441
+ if (a.kind === "file") {
442
+ if (c === n.name && await a.isSameEntry(n)) {
443
+ const l = [...s, c];
444
+ return S.set(n, l), l;
445
+ }
446
+ } else s.length === 0 && c === T || r.push([a, [...s, c]]);
447
+ }
448
+ }
449
+ return null;
450
+ }
451
+ async function Y(n) {
452
+ const t = await U(n);
453
+ if (!t) return null;
454
+ let e = await navigator.storage.getDirectory();
455
+ for (const r of t.slice(0, -1)) e = await e.getDirectoryHandle(r);
456
+ return e;
457
+ }
458
+ async function _(n, t) {
459
+ const e = globalThis.crypto.randomUUID().slice(0, 8);
460
+ if (n.node) {
461
+ const { mkdtemp: s, rm: c } = await import(
462
+ /* @vite-ignore */
463
+ "node:fs/promises"
464
+ ), { tmpdir: a } = await import(
465
+ /* @vite-ignore */
466
+ "node:os"
467
+ ), { join: l } = await import(
468
+ /* @vite-ignore */
469
+ "node:path"
470
+ ), d = await s(l(a(), `wexiftool-${e}-`));
471
+ try {
472
+ return await t(d);
473
+ } finally {
474
+ await c(d, { recursive: !0, force: !0 });
475
+ }
476
+ }
477
+ const o = await (await navigator.storage.getDirectory()).getDirectoryHandle(T, { create: !0 }), i = await o.getDirectoryHandle(e, { create: !0 });
478
+ try {
479
+ return await t(i);
480
+ } finally {
481
+ await o.removeEntry(e, { recursive: !0 }).catch(() => {
482
+ });
483
+ }
484
+ }
485
+ async function W(n, t) {
486
+ if (typeof n == "string") {
487
+ const { writeFile: r } = await import(
488
+ /* @vite-ignore */
489
+ "node:fs/promises"
490
+ );
491
+ await r(n, t);
492
+ return;
493
+ }
494
+ const { writeToHandle: e } = await Promise.resolve().then(() => V);
495
+ await e(n, t);
496
+ }
497
+ const Z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
498
+ __proto__: null,
499
+ detectEnvironment: O,
500
+ planInput: P,
501
+ registerHint: J,
502
+ resolveOnCallerSide: K,
503
+ withPrivateDir: _,
504
+ writeRef: W
505
+ }, Symbol.toStringTag, { value: "Module" }));
506
+ function tt(n) {
507
+ const t = n, e = [];
508
+ return "input" in t && e.push(t.input), t.inputs && e.push(...t.inputs), t.files && e.push(...t.files.values()), t.options && "output" in t.options && e.push(t.options.output), e.filter(p);
509
+ }
510
+ class D {
511
+ constructor(t, e) {
512
+ this.port = t, this.onWasmProgress = e, this.nextId = 1, this.pending = /* @__PURE__ */ new Map(), this.destroyed = !1, t.addEventListener("message", (r) => this.receive(r.data)), t.addEventListener("error", (r) => this.failAll(rt(r, "Worker error"))), t.addEventListener(
513
+ "messageerror",
514
+ () => this.failAll("Worker sent a message that could not be deserialized")
515
+ );
516
+ }
517
+ /**
518
+ * Load the WASM module inside `worker` and resolve once it's ready.
519
+ */
520
+ static async create(t, e) {
521
+ const r = new D(t, e == null ? void 0 : e.onWasmProgress);
522
+ return await r.call({
523
+ op: "init",
524
+ options: { wasmUrl: e == null ? void 0 : e.wasmUrl, progress: !!(e != null && e.onWasmProgress) }
525
+ }), r;
526
+ }
527
+ read(t) {
528
+ return this.call({ op: "read", input: t });
529
+ }
530
+ readMany(t) {
531
+ return this.call({ op: "readMany", inputs: t });
532
+ }
533
+ readRaw(t, e) {
534
+ return this.call({ op: "readRaw", input: t, args: e });
535
+ }
536
+ write(t, e, r) {
537
+ return this.call({ op: "write", input: t, tags: e, options: r });
538
+ }
539
+ exec(t, e, r) {
540
+ return this.call({ op: "exec", args: t, files: e, options: r });
541
+ }
542
+ /**
543
+ * Free the module. Terminates the worker when given a real `Worker`, which
544
+ * releases its memory outright; otherwise asks it to drop the module.
545
+ */
546
+ async destroy() {
547
+ if (this.destroyed = !0, typeof this.port.terminate == "function") {
548
+ this.port.terminate(), this.failAll("WExifToolWorker was destroyed");
549
+ return;
550
+ }
551
+ await this.call({ op: "destroy" });
552
+ }
553
+ async call(t) {
554
+ const e = this.nextId++, r = await et(t);
555
+ if (this.destroyed && t.op !== "destroy") throw new Error("WExifToolWorker was destroyed");
556
+ return new Promise((o, i) => {
557
+ this.pending.set(e, { resolve: o, reject: i });
558
+ try {
559
+ this.port.postMessage({ ...t, id: e, hints: r });
560
+ } catch (s) {
561
+ this.pending.delete(e), i(s instanceof Error ? s : new Error(String(s)));
562
+ }
563
+ });
564
+ }
565
+ receive(t) {
566
+ var r;
567
+ if (t.kind === "progress") {
568
+ (r = this.onWasmProgress) == null || r.call(this, t.loaded, t.total);
569
+ return;
570
+ }
571
+ const e = this.pending.get(t.id);
572
+ e && (this.pending.delete(t.id), t.ok ? e.resolve(t.value) : e.reject(new Error(t.error)));
573
+ }
574
+ failAll(t) {
575
+ const e = new Error(t);
576
+ for (const r of this.pending.values()) r.reject(e);
577
+ this.pending.clear();
578
+ }
579
+ }
580
+ async function et(n) {
581
+ var o, i;
582
+ const t = globalThis;
583
+ if (typeof t.window > "u" || typeof ((i = (o = t.navigator) == null ? void 0 : o.storage) == null ? void 0 : i.getDirectory) != "function") return;
584
+ const e = tt(n);
585
+ if (e.length === 0) return;
586
+ const { resolveOnCallerSide: r } = await Promise.resolve().then(() => Z);
587
+ return Promise.all(e.map((s) => r(s).catch(() => null)));
588
+ }
589
+ function rt(n, t) {
590
+ const e = n == null ? void 0 : n.message;
591
+ return typeof e == "string" && e ? `${t}: ${e}` : t;
592
+ }
593
+ class I {
594
+ constructor(t) {
595
+ this.glue = t;
596
+ }
597
+ /**
598
+ * Create and initialize a WExifTool instance.
599
+ * The WASM module is lazy-loaded on first use.
600
+ */
601
+ static async create(t) {
602
+ const e = new C(t == null ? void 0 : t.wasmUrl, t == null ? void 0 : t.onWasmProgress);
603
+ return await e.init(), new I(e);
604
+ }
605
+ /**
606
+ * Read all metadata from a file.
607
+ * Returns a record of tag names to values.
608
+ *
609
+ * A host path (Node) or an OPFS file handle (in a dedicated worker) is read
610
+ * in place, without copying the file into memory.
611
+ */
612
+ async read(t) {
613
+ const e = x(t), r = await this.runFiles(["-json", "-G", e], [[e, t]], { readOnly: !0 });
614
+ if (r.exitCode !== 0 && r.stderr)
615
+ throw new Error(`ExifTool error: ${r.stderr}`);
616
+ try {
617
+ const o = JSON.parse(r.stdout);
618
+ return Array.isArray(o) ? o[0] : o;
619
+ } catch {
620
+ throw new Error(`Failed to parse ExifTool output: ${r.stdout}`);
621
+ }
622
+ }
623
+ /**
624
+ * Read metadata from several files in one exiftool run, which saves
625
+ * starting Perl once per file. Results are in input order.
626
+ *
627
+ * A file exiftool can't read gets an entry with an `Error` tag rather than
628
+ * failing the batch, as with native `exiftool -json`.
629
+ */
630
+ async readMany(t) {
631
+ if (t.length === 0) return [];
632
+ const e = nt(t.map(x)), r = await this.runFiles(
633
+ ["-json", "-G", ...e],
634
+ t.map((i, s) => [e[s], i]),
635
+ { readOnly: !0 }
636
+ );
637
+ let o;
638
+ try {
639
+ o = JSON.parse(r.stdout);
640
+ } catch {
641
+ throw new Error(`ExifTool error: ${r.stderr || `unparseable output: ${r.stdout}`}`);
642
+ }
643
+ if (!Array.isArray(o) || o.length !== t.length)
644
+ throw new Error(`ExifTool error: ${r.stderr || `expected ${t.length} results`}`);
645
+ return o;
646
+ }
647
+ async write(t, e, r) {
648
+ const o = x(t), i = Object.entries(e).map(([l, d]) => `-${l}=${d}`), s = typeof t == "string" || p(t), c = s ? (r == null ? void 0 : r.output) ?? t : void 0, a = await this.runFiles(
649
+ ["-overwrite_original", ...i, o],
650
+ [[o, t]],
651
+ { targets: c === void 0 ? void 0 : /* @__PURE__ */ new Map([[o, c]]) }
652
+ );
653
+ if (a.exitCode !== 0 && a.stderr)
654
+ throw new Error(`ExifTool error: ${a.stderr}`);
655
+ return s ? (!a.written.includes(o) && c !== t && await W(c, (await k(t)).data), c) : a.files.get(o) ?? (await k(t)).data;
656
+ }
657
+ /**
658
+ * Run exiftool with arbitrary flags and return raw stdout.
659
+ */
660
+ async readRaw(t, e) {
661
+ const r = x(t), o = await this.runFiles([...e, r], [[r, t]], { scratch: 0 });
662
+ if (o.exitCode !== 0 && o.stderr)
663
+ throw new Error(`ExifTool error: ${o.stderr}`);
664
+ return o.stdout;
665
+ }
666
+ /**
667
+ * Low-level: run exiftool with arbitrary args and a map of files.
668
+ * This is the most flexible API — CLI and other methods build on it.
669
+ *
670
+ * Values may be bytes, file handles or (under Node) host paths. Files the
671
+ * run changed come back as bytes in `result.files`.
672
+ *
673
+ * With `writeBack: true`, a changed file that came from a handle or host
674
+ * path is written back to it instead, and its name is listed in
675
+ * `result.written` rather than returned as bytes. Byte inputs always come
676
+ * back in `result.files`.
677
+ */
678
+ async exec(t, e, r) {
679
+ const o = [...e ?? []], i = r != null && r.writeBack ? new Map(
680
+ o.filter(([, s]) => typeof s == "string" || p(s))
681
+ ) : void 0;
682
+ return this.runFiles(t, o, { targets: i });
683
+ }
684
+ /**
685
+ * Plan every input (mount or copy), run, and write back results the
686
+ * mounts couldn't commit in place.
687
+ */
688
+ async runFiles(t, e, r) {
689
+ const o = O();
690
+ try {
691
+ return await this.runPlanned(t, e, r, o);
692
+ } catch (i) {
693
+ if (o.opfs && i instanceof R)
694
+ return this.runPlanned(t, e, r, { ...o, opfs: !1 });
695
+ throw i;
696
+ }
697
+ }
698
+ async runPlanned(t, e, { targets: r, scratch: o, readOnly: i }, s) {
699
+ var m;
700
+ const c = [], a = [];
701
+ for (const [f, y] of e) {
702
+ let E = await P(f, y, s, r == null ? void 0 : r.get(f));
703
+ const $ = E.input.source;
704
+ typeof $ == "object" && !($ instanceof Uint8Array) && (await ot($, a) ? E = await P(f, y, { ...s, opfs: !1 }, r == null ? void 0 : r.get(f)) : a.push($)), c.push(E);
705
+ }
706
+ const l = c.map((f) => f.input);
707
+ let d;
708
+ if (l.every((f) => f.source instanceof Uint8Array))
709
+ d = await this.glue.exec(t, new Map(l.map((f) => [f.name, f.source])));
710
+ else {
711
+ const f = s.node ? "node" : "opfs";
712
+ d = !i && l.some((y) => !y.commitTo) ? await _(s, (y) => this.glue.run(t, l, { privateDir: y, backend: f, scratch: o })) : await this.glue.run(t, l, { backend: f, scratch: o, readOnly: i });
713
+ }
714
+ const u = [...((m = d.committed) == null ? void 0 : m.keys()) ?? []];
715
+ for (const { input: f, writeAfter: y } of c) {
716
+ const E = d.files.get(f.name);
717
+ y && E && (await W(y, E), d.files.delete(f.name), u.push(f.name));
718
+ }
719
+ const { committed: h, ...w } = d;
720
+ return { ...w, written: u };
721
+ }
722
+ /**
723
+ * Free WASM module resources.
724
+ */
725
+ destroy() {
726
+ this.glue.destroy();
727
+ }
728
+ }
729
+ function x(n) {
730
+ return typeof n == "string" ? n : (p(n) || typeof File < "u" && n instanceof File) && n.name || "input.bin";
731
+ }
732
+ function nt(n) {
733
+ const t = new Set(n), e = /* @__PURE__ */ new Map();
734
+ return n.map((r) => {
735
+ const o = (e.get(r) ?? 0) + 1;
736
+ if (e.set(r, o), o === 1) return r;
737
+ const i = r.lastIndexOf("/"), s = r.lastIndexOf("."), c = s > i + 1 ? s : r.length;
738
+ let a = o, l = `${r.slice(0, c)} (${a})${r.slice(c)}`;
739
+ for (; t.has(l); ) l = `${r.slice(0, c)} (${++a})${r.slice(c)}`;
740
+ return t.add(l), l;
741
+ });
742
+ }
743
+ async function ot(n, t) {
744
+ for (const e of t)
745
+ if (e === n || await e.isSameEntry(n)) return !0;
746
+ return !1;
747
+ }
748
+ export {
749
+ I as W,
750
+ it as a,
751
+ lt as b,
752
+ ct as c,
753
+ ut as d,
754
+ R as e,
755
+ at as f,
756
+ b as g,
757
+ tt as h,
758
+ p as i,
759
+ D as j,
760
+ Z as m
761
+ };