@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.
- package/LICENSE +21 -0
- package/LICENSE-exiftool +19 -0
- package/README.md +105 -2
- package/dist/cli.cjs +19 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.es.js +81 -0
- package/dist/index-B49L_wsJ.cjs +1 -0
- package/dist/index-D8B3sucz.js +681 -0
- package/dist/node-CRF3Yhkk.cjs +1 -0
- package/dist/node-P4tq-yoT.js +102 -0
- package/dist/wexiftool-CL0NBE7L.js +761 -0
- package/dist/wexiftool-DICKi0_a.cjs +4 -0
- package/dist/wexiftool.cjs +1 -0
- package/dist/wexiftool.d.ts +255 -0
- package/dist/wexiftool.es.js +5 -0
- package/dist/worker.cjs +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.es.js +64 -0
- package/package.json +69 -4
- package/wasm/package.json +4 -0
- package/wasm/perl.js +2 -0
- package/wasm/perl.wasm +4 -0
|
@@ -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 { }
|
package/dist/worker.cjs
ADDED
|
@@ -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);
|
package/dist/worker.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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
|
+
}
|