@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,4 @@
1
+ "use strict";var N=Object.create;var P=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var z=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,V=Object.prototype.hasOwnProperty;var J=(n,t,e)=>t in n?P(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var K=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of z(t))!V.call(n,o)&&o!==e&&P(n,o,{get:()=>t[o],enumerable:!(r=q(t,o))||r.enumerable});return n};var g=(n,t,e)=>(e=n!=null?N(G(n)):{},K(t||!n||!n.__esModule?P(e,"default",{value:n,enumerable:!0}):e,n));var A=(n,t,e)=>J(n,typeof t!="symbol"?t+"":t,e);var S=typeof document<"u"?document.currentScript:null;class b extends Error{constructor(t,e){super(t,e),this.name=new.target.name}}class X extends b{}class Q extends b{constructor(e,r){super(`${e} is locked by another sync access handle (another tab or worker may be using it)`,{cause:r});A(this,"path");this.path=e}}class O extends b{}class Y extends b{}class Z extends b{}class tt extends b{constructor(e,r){super(`commit() failed after ${e.length} file(s): ${(r==null?void 0:r.message)??String(r)}`,{cause:r});A(this,"committed");this.committed=e}}const E="/wexiftool",et="/opt/perlwasm/bin/exiftool";function rt(n){n.ENV&&(n.ENV.LC_ALL="C")}class nt{constructor(t,e){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}async init(){if(!this.module){if(this.modulePromise){await this.modulePromise;return}this.modulePromise=this.loadModule(),this.module=await this.modulePromise;try{this.module.FS.mkdir(E)}catch{}}}async loadModule(){const t={noInitialRun:!0,print:i=>{var s;(s=this.stdoutSink)==null||s.call(this,i)},printErr:i=>{var s;(s=this.stderrSink)==null||s.call(this,i)},preRun:[rt]},e=this.wasmUrl??this.defaultWasmUrl(),r=await this.importModuleFactory(e);this.createModule=r;const o=e.substring(0,e.lastIndexOf("/")+1);if(this.onWasmProgress&&typeof fetch<"u"){const i=await this.fetchWithProgress(o+"perl.wasm");return await r({...t,locateFile:s=>o+s,wasmBinary:i})}return await r({...t,locateFile:i=>o+i})}async fetchWithProgress(t){const e=await fetch(t);if(!e.ok)throw new Error(`Failed to fetch ${t}: ${e.status} ${e.statusText}`);const r=e.headers.get("content-length"),o=r?parseInt(r,10):null,i=e.body.getReader(),s=[];let c=0;for(;;){const{done:d,value:u}=await i.read();if(d)break;s.push(u),c+=u.byteLength,this.onWasmProgress(c,o)}const a=new Uint8Array(c);let l=0;for(const d of s)a.set(d,l),l+=d.byteLength;return a}async importModuleFactory(t){var i;if(typeof process<"u"&&((i=process.versions)!=null&&i.node)){const s=await import(t);return s.default??s}const e=await fetch(t);if(!e.ok)throw new Error(`Failed to fetch ${t}: ${e.status} ${e.statusText}`);const r=await e.text();return new Function("exports","module","define",`${r}
2
+ return createPerlModule;`)(void 0,void 0,void 0)}defaultWasmUrl(){return typeof __dirname<"u"?require("path").join(__dirname,"..","wasm","perl.js"):new URL("../wasm/perl.js",typeof document>"u"?require("url").pathToFileURL(__filename).href:S&&S.tagName.toUpperCase()==="SCRIPT"&&S.src||new URL("wexiftool-DICKi0_a.cjs",document.baseURI).href).href}async exec(t,e){const r=[...e].map(([s,c])=>({name:s,source:c})),{committed:o,...i}=await this.run(t,r);return i}async run(t,e,r={}){return await this.init(),this.enqueue(()=>e.some(o=>!(o.source instanceof Uint8Array))?this.runMounted(t,e,r):this.runStaged(t,e))}runStaged(t,e){const r=this.module.FS,o=e.map((s,c)=>`${E}/${c}/${x(s)}`),i=new Map;try{e.forEach((c,a)=>{r.mkdir(`${E}/${a}`),r.writeFile(o[a],c.source),r.chmod(o[a],292),i.set(o[a],I(r,o[a]))});const s=this.callExiftool(t,e,o);return e.forEach((c,a)=>{if(I(r,o[a])!==i.get(o[a]))try{s.files.set(c.name,r.readFile(o[a]))}catch{}}),{...s,committed:new Map}}finally{e.forEach((s,c)=>D(r,`${E}/${c}`))}}async runMounted(t,e,r){var a;const{mountFiles:o}=await Promise.resolve().then(()=>require("./index-B49L_wsJ.cjs")).then(l=>l.index),i=this.module,s=e.map((l,d)=>`${E}/${d}/${x(l)}`),c=[];try{for(const[u,h]of e.entries()){const w=((a=h.commitTo)==null?void 0:a.dir)??(r.readOnly?void 0:r.privateDir);if(w===void 0&&!r.readOnly)throw new Error(`Input "${h.name}" is mounted, so the run needs a privateDir, a commitTo or readOnly`);c.push(await o(i,`${E}/${u}`,{inputs:{[x(h)]:h.source},outputDir:w,scratch:w===void 0?0:r.scratch??1,onExhausted:"spill",backend:r.backend}))}const l=this.callExiftool(t,e,s),d=new Map;for(const[u,h]of e.entries()){const w=await c[u].commit(),m=w.find(f=>f.path===x(h));if(h.commitTo)m&&d.set(h.name,m);else for(const f of w)f===m&&l.files.set(h.name,await ot(f)),await st(f,r.privateDir)}return{...l,committed:d}}finally{for(const l of c)await l.release()}}callExiftool(t,e,r){const o=this.module,i=new Map(e.map((u,h)=>[u.name,r[h]])),s=[et];for(const u of t)s.push(u.toLowerCase()==="-overwrite_original_in_place"?"-overwrite_original":i.get(u)??u);const c=new Set(t);for(const u of e)c.has(u.name)||s.push(i.get(u.name));let a="",l="";this.stdoutSink=u=>{a+=u+`
3
+ `},this.stderrSink=u=>{l+=u+`
4
+ `};let d;try{d=o.callMain(s)}catch(u){if(u.status!==void 0)d=u.status;else throw u}finally{this.stdoutSink=null,this.stderrSink=null}for(const[u,h]of i)a=a.replaceAll(h,u),l=l.replaceAll(h,u);return e.forEach((u,h)=>{const w=new RegExp(`${E}/${h}(?![0-9])`,"g"),m=u.name.lastIndexOf("/"),f=m>0?u.name.slice(0,m):m===0?"/":".";a=a.replace(w,()=>f),l=l.replace(w,()=>f)}),{stdout:a.trimEnd(),stderr:l.trimEnd(),exitCode:d,files:new Map}}enqueue(t){const e=this.tail.then(t,t);return this.tail=e.catch(()=>{}),e}destroy(){this.module=null,this.modulePromise=null,this.createModule=null,this.tail=Promise.resolve()}}function x(n){var e;const t=(((e=n.commitTo)==null?void 0:e.as)??n.name).split("/").pop();return t&&t!=="."&&t!==".."?t:"input.bin"}function I(n,t){try{const e=n.stat(t);return`${e.ino}:${e.size}:${+e.mtime}`}catch{return"missing"}}function D(n,t){let e;try{e=n.readdir(t)}catch{return}for(const r of e){if(r==="."||r==="..")continue;const o=`${t}/${r}`;try{n.unlink(o)}catch{D(n,o)}}try{n.rmdir(t)}catch{}}async function ot(n){if(n.handle)return new Uint8Array(await(await n.handle.getFile()).arrayBuffer());if(n.hostPath){const{readFile:t}=await import("node:fs/promises");return new Uint8Array(await t(n.hostPath))}return n.bytes??new Uint8Array}async function st(n,t){if(n.hostPath){const{rm:e}=await import("node:fs/promises");await e(n.hostPath,{force:!0});return}if(typeof t!="string"){const e=n.path.split("/"),r=e.pop();let o=t;for(const i of e)o=await o.getDirectoryHandle(i);await o.removeEntry(r).catch(()=>{})}}function p(n){return typeof n=="object"&&n!==null&&n.kind==="file"&&typeof n.getFile=="function"}async function W(n){if(n instanceof Uint8Array)return{name:"input.bin",data:n};if(n instanceof ArrayBuffer)return{name:"input.bin",data:new Uint8Array(n)};if(p(n)){const t=await n.getFile();return{name:n.name||"input.bin",data:new Uint8Array(await t.arrayBuffer())}}if(typeof File<"u"&&n instanceof File){const t=await n.arrayBuffer();return{name:n.name||"input.bin",data:new Uint8Array(t)}}if(typeof Blob<"u"&&n instanceof Blob){const t=await n.arrayBuffer();return{name:"input.bin",data:new Uint8Array(t)}}throw new Error("Unsupported input type")}async function it(n,t){const e=at(t);if(typeof n.createSyncAccessHandle=="function"){const r=await n.createSyncAccessHandle();try{r.truncate(0);let o=0;for(;o<e.byteLength;){const i=o===0?e:e.slice(o),s=r.write(i,{at:o});if(s<=0)throw new Error(`Short write to ${n.name} at byte ${o} of ${e.byteLength}`);o+=s}r.flush()}finally{r.close()}return}if(typeof n.createWritable=="function"){const r=await n.createWritable();try{await r.write(e),await r.close()}catch(o){throw await r.abort(o).catch(()=>{}),o}return}throw new Error(`Cannot write to file handle "${n.name}": it has neither createSyncAccessHandle() (dedicated workers only) nor createWritable().`)}function at(n){const{buffer:t,byteOffset:e,byteLength:r}=n;return t instanceof ArrayBuffer&&e===0&&r===t.byteLength?t:n.slice().buffer}const ct=Object.freeze(Object.defineProperty({__proto__:null,isFileHandle:p,resolveInput:W,writeToHandle:it},Symbol.toStringTag,{value:"Module"})),H=".wexiftool";function L(){var i,s;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);return{node:t,opfs:o}}async function F(n,t,e,r){if(typeof t=="string"){if(!e.node)throw new Error(`"${t}": file paths are only supported under Node`);const{resolve:i,dirname:s,basename:c}=await import("node:path"),a=i(t);if(r===void 0)return{input:{name:n,source:a}};if(typeof r!="string")return{input:{name:n,source:a},writeAfter:r};const l=i(r);return{input:{name:n,source:a,commitTo:{dir:s(l),as:c(l)}}}}if(p(t)&&e.opfs&&await R(t)){const i=t;if(r===void 0)return{input:{name:n,source:i}};const s=p(r)?await ht(r):null;return s?{input:{name:n,source:i,commitTo:{dir:s,as:r.name}}}:{input:{name:n,source:i},writeAfter:r}}const{data:o}=await W(t);return{input:{name:n,source:o},writeAfter:r}}const T=new WeakMap;async function lt(n,t){try{const e=await ft(t);e&&await e.isSameEntry(n)&&T.set(n,t)}catch{}}async function ut(n){return(await navigator.storage.getDirectory()).resolve(n)}async function ft(n){if(n.length===0)return null;let t=await navigator.storage.getDirectory();for(const e of n.slice(0,-1))t=await t.getDirectoryHandle(e);return t.getFileHandle(n[n.length-1])}const dt=5e3;async function R(n){const t=T.get(n);if(t)return t;const r=[[await navigator.storage.getDirectory(),[]]];let o=0;for(;r.length>0;){const[i,s]=r.shift();for await(const[c,a]of i.entries()){if(++o>dt)return null;if(a.kind==="file"){if(c===n.name&&await a.isSameEntry(n)){const l=[...s,c];return T.set(n,l),l}}else s.length===0&&c===H||r.push([a,[...s,c]])}}return null}async function ht(n){const t=await R(n);if(!t)return null;let e=await navigator.storage.getDirectory();for(const r of t.slice(0,-1))e=await e.getDirectoryHandle(r);return e}async function C(n,t){const e=globalThis.crypto.randomUUID().slice(0,8);if(n.node){const{mkdtemp:s,rm:c}=await import("node:fs/promises"),{tmpdir:a}=await import("node:os"),{join:l}=await import("node:path"),d=await s(l(a(),`wexiftool-${e}-`));try{return await t(d)}finally{await c(d,{recursive:!0,force:!0})}}const o=await(await navigator.storage.getDirectory()).getDirectoryHandle(H,{create:!0}),i=await o.getDirectoryHandle(e,{create:!0});try{return await t(i)}finally{await o.removeEntry(e,{recursive:!0}).catch(()=>{})}}async function M(n,t){if(typeof n=="string"){const{writeFile:r}=await import("node:fs/promises");await r(n,t);return}const{writeToHandle:e}=await Promise.resolve().then(()=>ct);await e(n,t)}const j=Object.freeze(Object.defineProperty({__proto__:null,detectEnvironment:L,planInput:F,registerHint:lt,resolveOnCallerSide:ut,withPrivateDir:C,writeRef:M},Symbol.toStringTag,{value:"Module"}));function B(n){const t=n,e=[];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)}class U{constructor(t,e){this.port=t,this.onWasmProgress=e,this.nextId=1,this.pending=new Map,this.destroyed=!1,t.addEventListener("message",r=>this.receive(r.data)),t.addEventListener("error",r=>this.failAll(mt(r,"Worker error"))),t.addEventListener("messageerror",()=>this.failAll("Worker sent a message that could not be deserialized"))}static async create(t,e){const r=new U(t,e==null?void 0:e.onWasmProgress);return await r.call({op:"init",options:{wasmUrl:e==null?void 0:e.wasmUrl,progress:!!(e!=null&&e.onWasmProgress)}}),r}read(t){return this.call({op:"read",input:t})}readMany(t){return this.call({op:"readMany",inputs:t})}readRaw(t,e){return this.call({op:"readRaw",input:t,args:e})}write(t,e,r){return this.call({op:"write",input:t,tags:e,options:r})}exec(t,e,r){return this.call({op:"exec",args:t,files:e,options:r})}async destroy(){if(this.destroyed=!0,typeof this.port.terminate=="function"){this.port.terminate(),this.failAll("WExifToolWorker was destroyed");return}await this.call({op:"destroy"})}async call(t){const e=this.nextId++,r=await wt(t);if(this.destroyed&&t.op!=="destroy")throw new Error("WExifToolWorker was destroyed");return new Promise((o,i)=>{this.pending.set(e,{resolve:o,reject:i});try{this.port.postMessage({...t,id:e,hints:r})}catch(s){this.pending.delete(e),i(s instanceof Error?s:new Error(String(s)))}})}receive(t){var r;if(t.kind==="progress"){(r=this.onWasmProgress)==null||r.call(this,t.loaded,t.total);return}const e=this.pending.get(t.id);e&&(this.pending.delete(t.id),t.ok?e.resolve(t.value):e.reject(new Error(t.error)))}failAll(t){const e=new Error(t);for(const r of this.pending.values())r.reject(e);this.pending.clear()}}async function wt(n){var o,i;const t=globalThis;if(typeof t.window>"u"||typeof((i=(o=t.navigator)==null?void 0:o.storage)==null?void 0:i.getDirectory)!="function")return;const e=B(n);if(e.length===0)return;const{resolveOnCallerSide:r}=await Promise.resolve().then(()=>j);return Promise.all(e.map(s=>r(s).catch(()=>null)))}function mt(n,t){const e=n==null?void 0:n.message;return typeof e=="string"&&e?`${t}: ${e}`:t}class _{constructor(t){this.glue=t}static async create(t){const e=new nt(t==null?void 0:t.wasmUrl,t==null?void 0:t.onWasmProgress);return await e.init(),new _(e)}async read(t){const e=k(t),r=await this.runFiles(["-json","-G",e],[[e,t]],{readOnly:!0});if(r.exitCode!==0&&r.stderr)throw new Error(`ExifTool error: ${r.stderr}`);try{const o=JSON.parse(r.stdout);return Array.isArray(o)?o[0]:o}catch{throw new Error(`Failed to parse ExifTool output: ${r.stdout}`)}}async readMany(t){if(t.length===0)return[];const e=yt(t.map(k)),r=await this.runFiles(["-json","-G",...e],t.map((i,s)=>[e[s],i]),{readOnly:!0});let o;try{o=JSON.parse(r.stdout)}catch{throw new Error(`ExifTool error: ${r.stderr||`unparseable output: ${r.stdout}`}`)}if(!Array.isArray(o)||o.length!==t.length)throw new Error(`ExifTool error: ${r.stderr||`expected ${t.length} results`}`);return o}async write(t,e,r){const o=k(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(["-overwrite_original",...i,o],[[o,t]],{targets:c===void 0?void 0:new Map([[o,c]])});if(a.exitCode!==0&&a.stderr)throw new Error(`ExifTool error: ${a.stderr}`);return s?(!a.written.includes(o)&&c!==t&&await M(c,(await W(t)).data),c):a.files.get(o)??(await W(t)).data}async readRaw(t,e){const r=k(t),o=await this.runFiles([...e,r],[[r,t]],{scratch:0});if(o.exitCode!==0&&o.stderr)throw new Error(`ExifTool error: ${o.stderr}`);return o.stdout}async exec(t,e,r){const o=[...e??[]],i=r!=null&&r.writeBack?new Map(o.filter(([,s])=>typeof s=="string"||p(s))):void 0;return this.runFiles(t,o,{targets:i})}async runFiles(t,e,r){const o=L();try{return await this.runPlanned(t,e,r,o)}catch(i){if(o.opfs&&i instanceof O)return this.runPlanned(t,e,r,{...o,opfs:!1});throw i}}async runPlanned(t,e,{targets:r,scratch:o,readOnly:i},s){var m;const c=[],a=[];for(const[f,y]of e){let $=await F(f,y,s,r==null?void 0:r.get(f));const v=$.input.source;typeof v=="object"&&!(v instanceof Uint8Array)&&(await pt(v,a)?$=await F(f,y,{...s,opfs:!1},r==null?void 0:r.get(f)):a.push(v)),c.push($)}const l=c.map(f=>f.input);let d;if(l.every(f=>f.source instanceof Uint8Array))d=await this.glue.exec(t,new Map(l.map(f=>[f.name,f.source])));else{const f=s.node?"node":"opfs";d=!i&&l.some(y=>!y.commitTo)?await C(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})}const u=[...((m=d.committed)==null?void 0:m.keys())??[]];for(const{input:f,writeAfter:y}of c){const $=d.files.get(f.name);y&&$&&(await M(y,$),d.files.delete(f.name),u.push(f.name))}const{committed:h,...w}=d;return{...w,written:u}}destroy(){this.glue.destroy()}}function k(n){return typeof n=="string"?n:(p(n)||typeof File<"u"&&n instanceof File)&&n.name||"input.bin"}function yt(n){const t=new Set(n),e=new Map;return n.map(r=>{const o=(e.get(r)??0)+1;if(e.set(r,o),o===1)return r;const i=r.lastIndexOf("/"),s=r.lastIndexOf("."),c=s>i+1?s:r.length;let a=o,l=`${r.slice(0,c)} (${a})${r.slice(c)}`;for(;t.has(l);)l=`${r.slice(0,c)} (${++a})${r.slice(c)}`;return t.add(l),l})}async function pt(n,t){for(const e of t)if(e===n||await e.isSameEntry(n))return!0;return!1}exports.WExifTool=_;exports.WExifToolWorker=U;exports.WopfsCommitError=tt;exports.WopfsError=b;exports.WopfsInputError=X;exports.WopfsLockedError=Q;exports.WopfsRuntimeError=Y;exports.WopfsStateError=Z;exports.WopfsUnsupportedError=O;exports.handlesIn=B;exports.isFileHandle=p;exports.mounts=j;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./wexiftool-DICKi0_a.cjs");exports.WExifTool=o.WExifTool;exports.WExifToolWorker=o.WExifToolWorker;
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Anything `read()`, `readRaw()` and `write()` accept as a file.
3
+ */
4
+ export declare type ByteInput = File | Blob | ArrayBuffer | Uint8Array;
5
+
6
+ export declare interface ExecOptions {
7
+ /**
8
+ * Write changed files back to the handles and host paths they came from,
9
+ * instead of returning their bytes. Byte inputs are always returned.
10
+ */
11
+ writeBack?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Result of a raw exec() call.
16
+ */
17
+ export declare interface ExecResult {
18
+ stdout: string;
19
+ stderr: string;
20
+ exitCode: number;
21
+ /** Files the run changed, as bytes, keyed by name. */
22
+ files: Map<string, Uint8Array>;
23
+ /** With `writeBack`, the names of the files written back in place. */
24
+ written?: string[];
25
+ }
26
+
27
+ /**
28
+ * A file handle wexiftool can read from and write to. Declared structurally
29
+ * so that a real `FileSystemFileHandle` (OPFS, or one from
30
+ * `showOpenFilePicker()`) satisfies it without the public types depending on
31
+ * the DOM lib — which Node-only consumers don't load.
32
+ *
33
+ * Reading needs only `getFile()`. Writing needs either
34
+ * `createSyncAccessHandle()` (dedicated workers) or `createWritable()`
35
+ * (main thread, and workers in browsers that have it).
36
+ */
37
+ export declare interface FileHandleLike {
38
+ readonly kind: 'file';
39
+ readonly name: string;
40
+ getFile(): Promise<Blob & {
41
+ readonly name?: string;
42
+ }>;
43
+ createWritable?(): Promise<WritableLike>;
44
+ createSyncAccessHandle?(): Promise<SyncAccessHandleLike>;
45
+ }
46
+
47
+ /** A file wexiftool can write back to: a handle, or a host path under Node. */
48
+ export declare type FileRef = FileHandleLike | string;
49
+
50
+ /** A `string` is a host file path, and is only accepted under Node. */
51
+ export declare type Input = ByteInput | FileHandleLike | string;
52
+
53
+ /**
54
+ * The shape both a `Worker` and a worker's own global scope share, and that
55
+ * Node's `MessagePort` also satisfies, which is what lets the protocol be
56
+ * tested without a browser.
57
+ */
58
+ declare interface PortLike {
59
+ postMessage(message: unknown, transfer?: unknown[]): void;
60
+ addEventListener(type: 'message', listener: (event: {
61
+ data: unknown;
62
+ }) => void): void;
63
+ addEventListener(type: 'error' | 'messageerror', listener: (event: unknown) => void): void;
64
+ }
65
+
66
+ /**
67
+ * The subset of `FileSystemSyncAccessHandle` wexiftool uses. Sync access
68
+ * handles exist only in dedicated workers.
69
+ *
70
+ * `write` takes a plain `ArrayBuffer` on purpose, here and in
71
+ * `WritableLike`. Since TS 5.7 `Uint8Array` is generic over
72
+ * `ArrayBufferLike`, and the DOM's write signatures accept only
73
+ * `ArrayBuffer`-backed views, so a `Uint8Array` parameter makes real handles
74
+ * fail to match. `Uint8Array<ArrayBuffer>` would fix that but doesn't parse
75
+ * on TS < 5.7, which would break this package's published types for those
76
+ * consumers. `ArrayBuffer` satisfies every version.
77
+ */
78
+ export declare interface SyncAccessHandleLike {
79
+ write(buffer: ArrayBuffer, options?: {
80
+ at?: number;
81
+ }): number;
82
+ truncate(newSize: number): void;
83
+ flush(): void;
84
+ close(): void;
85
+ }
86
+
87
+ /**
88
+ * WExifTool — run ExifTool in the browser or Node.js via WebAssembly.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const wexiftool = await WExifTool.create();
93
+ * const metadata = await wexiftool.read(file);
94
+ * const modified = await wexiftool.write(file, { Artist: 'Jane Doe' });
95
+ * wexiftool.destroy();
96
+ * ```
97
+ */
98
+ export declare class WExifTool {
99
+ private glue;
100
+ private constructor();
101
+ /**
102
+ * Create and initialize a WExifTool instance.
103
+ * The WASM module is lazy-loaded on first use.
104
+ */
105
+ static create(options?: WExifToolOptions): Promise<WExifTool>;
106
+ /**
107
+ * Read all metadata from a file.
108
+ * Returns a record of tag names to values.
109
+ *
110
+ * A host path (Node) or an OPFS file handle (in a dedicated worker) is read
111
+ * in place, without copying the file into memory.
112
+ */
113
+ read(input: Input): Promise<Record<string, unknown>>;
114
+ /**
115
+ * Read metadata from several files in one exiftool run, which saves
116
+ * starting Perl once per file. Results are in input order.
117
+ *
118
+ * A file exiftool can't read gets an entry with an `Error` tag rather than
119
+ * failing the batch, as with native `exiftool -json`.
120
+ */
121
+ readMany(inputs: Input[]): Promise<Array<Record<string, unknown>>>;
122
+ /**
123
+ * Write metadata tags to a file.
124
+ *
125
+ * Given bytes (or a `File`/`Blob`), returns the modified file as a
126
+ * `Uint8Array` and leaves the input alone.
127
+ *
128
+ * Given a file reference, writes the result to the file and resolves to
129
+ * the reference written. A reference is a file handle, or a host path
130
+ * under Node. By default the input is replaced in place, as with
131
+ * ExifTool's `-overwrite_original`; pass `options.output` to write
132
+ * somewhere else and keep the input. Nothing is written if ExifTool fails.
133
+ *
134
+ * A host path, or an OPFS handle in a dedicated worker, is written through
135
+ * a wopfs mount, so the file is never held in memory whole.
136
+ */
137
+ write<H extends FileHandleLike>(input: H, tags: Record<string, string | number>, options?: WriteHandleOptions<H>): Promise<H>;
138
+ write(input: string, tags: Record<string, string | number>, options?: WriteHandleOptions<string>): Promise<string>;
139
+ write(input: ByteInput, tags: Record<string, string | number>): Promise<Uint8Array>;
140
+ /**
141
+ * Run exiftool with arbitrary flags and return raw stdout.
142
+ */
143
+ readRaw(input: Input, args: string[]): Promise<string>;
144
+ /**
145
+ * Low-level: run exiftool with arbitrary args and a map of files.
146
+ * This is the most flexible API — CLI and other methods build on it.
147
+ *
148
+ * Values may be bytes, file handles or (under Node) host paths. Files the
149
+ * run changed come back as bytes in `result.files`.
150
+ *
151
+ * With `writeBack: true`, a changed file that came from a handle or host
152
+ * path is written back to it instead, and its name is listed in
153
+ * `result.written` rather than returned as bytes. Byte inputs always come
154
+ * back in `result.files`.
155
+ */
156
+ exec(args: string[], files?: Map<string, Uint8Array | FileRef>, options?: ExecOptions): Promise<ExecResult>;
157
+ /**
158
+ * Plan every input (mount or copy), run, and write back results the
159
+ * mounts couldn't commit in place.
160
+ */
161
+ private runFiles;
162
+ private runPlanned;
163
+ /**
164
+ * Free WASM module resources.
165
+ */
166
+ destroy(): void;
167
+ }
168
+
169
+ /**
170
+ * Options for creating a WExifTool instance.
171
+ */
172
+ export declare interface WExifToolOptions {
173
+ /** Custom URL or path to perl.js (the Emscripten module loader). */
174
+ wasmUrl?: string;
175
+ /**
176
+ * Called during the perl.wasm download with bytes loaded so far and
177
+ * total (null when the server doesn't send Content-Length). Setting
178
+ * this opts into a manual fetch with a streaming reader instead of
179
+ * letting Emscripten handle the download — adds a small overhead but
180
+ * lets you render real progress UI for the ~53 MB payload. Browser
181
+ * only; no-op in Node.
182
+ */
183
+ onWasmProgress?: (loaded: number, total: number | null) => void;
184
+ }
185
+
186
+ /**
187
+ * Same API as `WExifTool`, with exiftool running in a dedicated worker.
188
+ *
189
+ * The caller constructs the `Worker` so that bundling stays the caller's
190
+ * business — each bundler has its own way to emit a worker chunk.
191
+ *
192
+ * Inputs are copied to the worker by structured clone, never transferred, so
193
+ * a `Uint8Array` you pass stays usable. Results that come back as bytes are
194
+ * transferred from the worker, so they cost no extra copy.
195
+ */
196
+ export declare class WExifToolWorker {
197
+ private port;
198
+ private onWasmProgress?;
199
+ private nextId;
200
+ private pending;
201
+ private destroyed;
202
+ private constructor();
203
+ /**
204
+ * Load the WASM module inside `worker` and resolve once it's ready.
205
+ */
206
+ static create(worker: PortLike & {
207
+ terminate?(): void;
208
+ }, options?: WExifToolWorkerOptions): Promise<WExifToolWorker>;
209
+ read(input: Input): Promise<Record<string, unknown>>;
210
+ readMany(inputs: Input[]): Promise<Array<Record<string, unknown>>>;
211
+ readRaw(input: Input, args: string[]): Promise<string>;
212
+ /**
213
+ * See `WExifTool.write()`. With a handle, the write happens inside the
214
+ * worker; what comes back is a structured clone of the handle written, which
215
+ * refers to the same file.
216
+ */
217
+ write<H extends FileHandleLike>(input: H, tags: Record<string, string | number>, options?: WriteHandleOptions<H>): Promise<H>;
218
+ write(input: ByteInput, tags: Record<string, string | number>): Promise<Uint8Array>;
219
+ exec(args: string[], files?: Map<string, Uint8Array | FileHandleLike | string>, options?: ExecOptions): Promise<ExecResult>;
220
+ /**
221
+ * Free the module. Terminates the worker when given a real `Worker`, which
222
+ * releases its memory outright; otherwise asks it to drop the module.
223
+ */
224
+ destroy(): Promise<void>;
225
+ private call;
226
+ private receive;
227
+ private failAll;
228
+ }
229
+
230
+ export declare type WExifToolWorkerOptions = WExifToolOptions;
231
+
232
+ /**
233
+ * The subset of `FileSystemWritableFileStream` wexiftool uses.
234
+ */
235
+ export declare interface WritableLike {
236
+ write(data: ArrayBuffer): Promise<void>;
237
+ close(): Promise<void>;
238
+ abort(reason?: unknown): Promise<void>;
239
+ }
240
+
241
+ /**
242
+ * Options for `write()` when the input is a file handle.
243
+ */
244
+ export declare interface WriteHandleOptions<H extends FileHandleLike | string = FileHandleLike> {
245
+ /**
246
+ * Write the result here instead of back into the input. Without it the
247
+ * input is replaced in place, matching ExifTool's `-overwrite_original`.
248
+ * A bare file handle has no reference to its parent directory, so a
249
+ * sibling such as `photo.jpg_original` can't be created implicitly; to
250
+ * keep the original, create the destination yourself and pass it here.
251
+ */
252
+ output?: H;
253
+ }
254
+
255
+ export { }
@@ -0,0 +1,5 @@
1
+ import { W, j as f } from "./wexiftool-CL0NBE7L.js";
2
+ export {
3
+ W as WExifTool,
4
+ f as WExifToolWorker
5
+ };
@@ -0,0 +1 @@
1
+ "use strict";const c=require("./wexiftool-DICKi0_a.cjs");function p(l,r=c.WExifTool.create){let i=null;const f=(a,n=[])=>l.postMessage(a,n),o=()=>(i||(i=r()),i);l.addEventListener("message",a=>{const n=a.data,e=t=>f({kind:"result",id:n.id,ok:!1,error:t instanceof Error?t.message:String(t)});d(n).then(({value:t,transfer:s})=>{try{f({kind:"result",id:n.id,ok:!0,value:t},s)}catch(w){e(w)}},e)});async function d(a){if(a.hints){const{registerHint:n}=await Promise.resolve().then(()=>require("./wexiftool-DICKi0_a.cjs")).then(t=>t.mounts),e=c.handlesIn(a);for(const[t,s]of a.hints.entries())s&&e[t]&&await n(e[t],s)}switch(a.op){case"init":{const{wasmUrl:n,progress:e}=a.options;return i=r({wasmUrl:n,onWasmProgress:e?(t,s)=>f({kind:"progress",loaded:t,total:s}):void 0}),await i,{value:null}}case"read":return{value:await(await o()).read(a.input)};case"readMany":return{value:await(await o()).readMany(a.inputs)};case"readRaw":return{value:await(await o()).readRaw(a.input,a.args)};case"write":{const n=await o();if(c.isFileHandle(a.input))return{value:await n.write(a.input,a.tags,a.options)};if(typeof a.input=="string")return{value:await n.write(a.input,a.tags)};const e=await n.write(a.input,a.tags);return{value:e,transfer:u([e])}}case"exec":{const n=await(await o()).exec(a.args,a.files,a.options);return{value:n,transfer:u(n.files.values())}}case"destroy":return i&&(await i).destroy(),i=null,{value:null}}}}function u(l){const r=new Set;for(const i of l)i.buffer instanceof ArrayBuffer&&r.add(i.buffer);return[...r]}p(self);
@@ -0,0 +1 @@
1
+ export { }
@@ -0,0 +1,64 @@
1
+ import { W as u, h as p, i as v } from "./wexiftool-CL0NBE7L.js";
2
+ function g(l, r = u.create) {
3
+ let i = null;
4
+ const f = (a, n = []) => l.postMessage(a, n), o = () => (i || (i = r()), i);
5
+ l.addEventListener("message", (a) => {
6
+ const n = a.data, s = (t) => f({
7
+ kind: "result",
8
+ id: n.id,
9
+ ok: !1,
10
+ error: t instanceof Error ? t.message : String(t)
11
+ });
12
+ d(n).then(({ value: t, transfer: e }) => {
13
+ try {
14
+ f({ kind: "result", id: n.id, ok: !0, value: t }, e);
15
+ } catch (w) {
16
+ s(w);
17
+ }
18
+ }, s);
19
+ });
20
+ async function d(a) {
21
+ if (a.hints) {
22
+ const { registerHint: n } = await import("./wexiftool-CL0NBE7L.js").then((t) => t.m), s = p(a);
23
+ for (const [t, e] of a.hints.entries())
24
+ e && s[t] && await n(s[t], e);
25
+ }
26
+ switch (a.op) {
27
+ case "init": {
28
+ const { wasmUrl: n, progress: s } = a.options;
29
+ return i = r({
30
+ wasmUrl: n,
31
+ onWasmProgress: s ? (t, e) => f({ kind: "progress", loaded: t, total: e }) : void 0
32
+ }), await i, { value: null };
33
+ }
34
+ case "read":
35
+ return { value: await (await o()).read(a.input) };
36
+ case "readMany":
37
+ return { value: await (await o()).readMany(a.inputs) };
38
+ case "readRaw":
39
+ return { value: await (await o()).readRaw(a.input, a.args) };
40
+ case "write": {
41
+ const n = await o();
42
+ if (v(a.input))
43
+ return { value: await n.write(a.input, a.tags, a.options) };
44
+ if (typeof a.input == "string")
45
+ return { value: await n.write(a.input, a.tags) };
46
+ const s = await n.write(a.input, a.tags);
47
+ return { value: s, transfer: c([s]) };
48
+ }
49
+ case "exec": {
50
+ const n = await (await o()).exec(a.args, a.files, a.options);
51
+ return { value: n, transfer: c(n.files.values()) };
52
+ }
53
+ case "destroy":
54
+ return i && (await i).destroy(), i = null, { value: null };
55
+ }
56
+ }
57
+ }
58
+ function c(l) {
59
+ const r = /* @__PURE__ */ new Set();
60
+ for (const i of l)
61
+ i.buffer instanceof ArrayBuffer && r.add(i.buffer);
62
+ return [...r];
63
+ }
64
+ g(self);
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@wasmedia/wexiftool",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "packageManager": "pnpm@10.33.0",
5
+ "description": "Run ExifTool in the browser and Node.js via WebAssembly",
6
+ "type": "module",
7
+ "main": "dist/wexiftool.cjs",
8
+ "module": "dist/wexiftool.es.js",
9
+ "types": "dist/wexiftool.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/wexiftool.d.ts",
13
+ "import": "./dist/wexiftool.es.js",
14
+ "require": "./dist/wexiftool.cjs"
15
+ },
16
+ "./worker": "./dist/worker.es.js"
17
+ },
18
+ "bin": {
19
+ "wexiftool": "dist/cli.es.js"
20
+ },
21
+ "files": [
22
+ "dist/",
23
+ "wasm/",
24
+ "LICENSE",
25
+ "LICENSE-exiftool",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "build:wasm": "docker build -f build/Dockerfile -o wasm .",
30
+ "build": "vite build",
31
+ "build:standalone": "vite build --config vite.standalone.config.ts",
32
+ "dev": "vite --config vite.demo.config.ts",
33
+ "test": "vitest run --exclude 'test/smoke.test.ts'",
34
+ "test:watch": "vitest",
35
+ "test:smoke": "vitest run test/smoke.test.ts",
36
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.demo.json",
37
+ "test:browser": "pnpm run build && playwright test",
38
+ "check:package": "node scripts/check-package.mjs"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://gitlab.com/wasmedia/wexiftool.git"
43
+ },
44
+ "homepage": "https://exiftool.wasmedia.org/",
45
+ "bugs": {
46
+ "url": "https://gitlab.com/wasmedia/wexiftool/-/issues"
47
+ },
48
+ "keywords": [
49
+ "exiftool",
50
+ "exif",
51
+ "metadata",
52
+ "wasm",
53
+ "webassembly",
54
+ "image"
55
+ ],
56
+ "license": "MIT",
57
+ "devDependencies": {
58
+ "@playwright/test": "1.63.0",
59
+ "@types/node": "^25.5.2",
60
+ "@wasmedia/wopfs": "0.2.0",
61
+ "typescript": "^5.4.0",
62
+ "vite": "^5.4.0",
63
+ "vite-plugin-dts": "^4.5.4",
64
+ "vitest": "^1.6.0"
65
+ },
66
+ "pnpm": {
67
+ "onlyBuiltDependencies": [
68
+ "esbuild"
69
+ ]
70
+ }
71
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "type": "commonjs",
3
+ "//": "perl.js is a UMD bundle (module.exports / AMD define). This pins it to CJS so Node's dynamic import() doesn't treat it as ESM and lose access to module.exports."
4
+ }