@timur00kh/whisper.wasm 0.0.6 → 0.0.7-canary.5ece826
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/README.md +3 -3
- package/dist/__tests__/setup.d.ts +2 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +58 -54
- package/dist/index.umd.js +1 -1
- package/dist/whisper/WhisperWasmService.d.ts.map +1 -1
- package/dist/whisper/types.d.ts +5 -5
- package/dist/whisper/types.d.ts.map +1 -1
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A TypeScript wrapper for [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm install @timur00kh/whisper.wasm
|
|
19
|
+
npm install @timur00kh/whisper.wasm@canary
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Quick Start
|
|
@@ -24,7 +24,7 @@ npm install @timur00kh/whisper.wasm
|
|
|
24
24
|
### Basic Usage
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import { WhisperWasmService, ModelManager } from 'whisper.wasm';
|
|
27
|
+
import { WhisperWasmService, ModelManager } from '@timur00kh/whisper.wasm';
|
|
28
28
|
|
|
29
29
|
// Initialize the service
|
|
30
30
|
const whisper = new WhisperWasmService({ logLevel: 1 });
|
|
@@ -59,7 +59,7 @@ for await (const segment of stream) {
|
|
|
59
59
|
### Model Management
|
|
60
60
|
|
|
61
61
|
```typescript
|
|
62
|
-
import { ModelManager, getAllModels } from 'whisper.wasm';
|
|
62
|
+
import { ModelManager, getAllModels } from '@timur00kh/whisper.wasm';
|
|
63
63
|
|
|
64
64
|
const modelManager = new ModelManager();
|
|
65
65
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":""}
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=class p{constructor(e=p.levels.INFO,t=""){this.level=e,this.prefix=t}debug(...e){this.level<=p.levels.DEBUG&&console.debug(`[${this.prefix}] [DEBUG]`,...e)}info(...e){this.level<=p.levels.INFO&&console.info(`[${this.prefix}] [INFO]`,...e)}warn(...e){this.level<=p.levels.WARN&&console.warn(`[${this.prefix}] [WARN]`,...e)}error(...e){this.level<=p.levels.ERROR&&console.error(`[${this.prefix}] [ERROR]`,...e)}setLevel(e){this.level=e}getLevel(){return this.level}};p.levels={DEBUG:0,INFO:1,WARN:2,ERROR:3};let b=p;const B=async()=>WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11])),T={language:"auto",threads:4,translate:!1};function M(l){const e=String(l).trim().replace(",","."),t=e.split(":").map(Number);if(t.some(Number.isNaN))throw new Error(`Bad time: "${l}"`);let r=0,a=0,i=0;if(t.length===3)[r,a]=t,i=parseFloat(e.split(":").pop()||"0");else if(t.length===2)[a]=t,i=parseFloat(e.split(":").pop()||"0");else throw new Error(`Bad time format: "${l}"`);return Math.floor(((r*60+a)*60+i)*1e3)}function R(l){const t=/^\s*\[?\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*-->\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*\]?\s*(.*)\s*$/.exec(l);if(!t)throw new Error("Line does not match VTT-like pattern: "+l);const r=t[1],a=t[2],i=t[3]||"",n=M(r),s=M(a);if(s<n)throw new Error("End time is before start time");return{startMs:n,endMs:s,start:r,end:a,text:i}}function q(l){return new Promise(e=>setTimeout(e,l))}function W(l,e=16e3*100){const t=[];for(let r=0;r<l.length;r+=e)t.push(l.subarray(r,r+e));return t}class L{constructor(e,t){this.whisperService=e,this.logger=new b((t==null?void 0:t.logLevel)||b.levels.ERROR,"TranscriptionSession")}async*streamimg(e,t={}){const r=W(e);let a=0;for await(const i of r){const n=[];let s=null,o=!1,h,d=0;for(this.whisperService.transcribe(i,c=>{d=c.timeEnd,c.timeStart+=a,c.timeEnd+=a,s?(s(c),s=null):n.push(c)},t).then(()=>{o=!0,a+=d,s==null||s(void 0)}).catch(c=>{h=c});;){if(h)throw h;if(o)break;if(n.length)yield n.shift();else{const c=await new Promise(f=>s=f);c&&(yield c)}}t.sleepMsBetweenChunks&&await q(t.sleepMsBetweenChunks)}}}class x extends EventTarget{on(e,t){return this.addEventListener(e,t),()=>this.removeEventListener(e,t)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t}))}}class A{constructor(e){this.wasmModule=null,this.instance=null,this.modelFileName="whisper.bin",this.isTranscribing=!1,this.bus=new x,this.logger=new b((e==null?void 0:e.logLevel)??b.levels.ERROR,"WhisperWasmService"),e!=null&&e.init&&this.loadWasmScript()}async checkWasmSupport(){return await B()}async loadWasmScript(){this.wasmModule=await(await Promise.resolve().then(()=>require("./libmain-D50HCaHR.js"))).default({print:(e,...t)=>{this.logger.debug(t),e.startsWith("[")?(this.logger.info(e),this.bus.emit("transcribe",e)):(this.logger.debug(e),this.bus.emit("system_info",e))},printErr:(e,...t)=>{this.logger.debug(t),this.logger.warn(e),this.bus.emit("transcribeError",e)}})}async loadWasmModule(e){if(!await this.checkWasmSupport())throw new Error("WASM is not supported");return this.wasmModule&&(this.wasmModule.FS_unlink(this.modelFileName),this.wasmModule.free()),await this.loadWasmScript(),await q(100),this.storeFS(this.modelFileName,e),this.instance=this.wasmModule.init(this.modelFileName),Promise.resolve()}storeFS(e,t){if(!this.wasmModule)throw new Error("WASM module not loaded");try{this.wasmModule.FS_unlink(e)}catch{}this.wasmModule.FS_createDataFile("/",e,t,!0,!0,!0)}async transcribe(e,t,r={}){if(this.isTranscribing)throw new Error("Already transcribing");if(!this.wasmModule)throw new Error("WASM module not loaded");if(!this.instance)throw new Error("WASM instance not loaded");const a=120;e.length>16e3*a&&this.logger.warn("It's not recommended to transcribe audio data that is longer than 120 seconds"),this.isTranscribing=!0;const{language:i,threads:n,translate:s}={...T,...r},o=[],h=Date.now();return this.wasmModule.full_default(this.instance,e,i,n,s),await new Promise((d,c)=>{const f=this.bus.on("transcribe",g=>{const{startMs:w,endMs:y,text:_}=R(g.detail),v={timeStart:w,timeEnd:y,text:_,raw:g.detail};o.push(v),t==null||t(v)}),u=setTimeout(()=>{this.isTranscribing=!1,f(),m(),this.logger.error("Transcribe timeout"),c(new Error("Transcribe timeout"))},a*2*1e3),m=this.bus.on("transcribeError",g=>{this.isTranscribing=!1,f(),m(),clearTimeout(u),this.logger.debug("Transcribe error",g.detail),d({segments:o,transcribeDurationMs:Date.now()-h})})})}createSession(){return new L(this,{logLevel:this.logger.getLevel()})}}const S={"tiny.en":{id:"tiny.en",name:"Tiny English",size:75,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin"},tiny:{id:"tiny",name:"Tiny Multilingual",size:75,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin"},"base.en":{id:"base.en",name:"Base English",size:142,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"},base:{id:"base",name:"Base Multilingual",size:142,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin"},"small.en":{id:"small.en",name:"Small English",size:466,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin"},small:{id:"small",name:"Small Multilingual",size:466,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin"},"tiny.en-q5_1":{id:"tiny.en-q5_1",name:"Tiny English (Q5_1)",size:31,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q5_1.bin"},"tiny-q5_1":{id:"tiny-q5_1",name:"Tiny Multilingual (Q5_1)",size:31,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin"},"base.en-q5_1":{id:"base.en-q5_1",name:"Base English (Q5_1)",size:57,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q5_1.bin"},"base-q5_1":{id:"base-q5_1",name:"Base Multilingual (Q5_1)",size:57,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin"},"small.en-q5_1":{id:"small.en-q5_1",name:"Small English (Q5_1)",size:182,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en-q5_1.bin"},"small-q5_1":{id:"small-q5_1",name:"Small Multilingual (Q5_1)",size:182,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q5_1.bin"},"medium.en-q5_0":{id:"medium.en-q5_0",name:"Medium English (Q5_0)",size:515,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en-q5_0.bin"},"medium-q5_0":{id:"medium-q5_0",name:"Medium Multilingual (Q5_0)",size:515,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q5_0.bin"},"large-q5_0":{id:"large-q5_0",name:"Large Multilingual (Q5_0)",size:1030,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-q5_0.bin"}};function z(){return Object.values(S).map(({url:l,...e})=>e)}function E(l){return S[l]}class F{constructor(e={logLevel:b.levels.ERROR}){this.cacheEnabled=!0,this.models=z(),this.logger=new b(e.logLevel,"ModelManager")}async loadModel(e,t=!0,r){var m;const a=E(e);if(!a)throw new Error(`Model ${e} not found in config`);if(this.cacheEnabled&&t){const g=await this.getCachedModel(e);if(g)return this.logger.info(`Model ${e} loaded from cache`),r&&r(100),g}this.logger.info(`Loading model ${e} from ${a.url}`);const i=await fetch(a.url);if(!i.ok)throw new Error(`Failed to load model: ${i.statusText}`);const n=i.headers.get("content-length"),s=n?parseInt(n,10):0;let o=0;const h=(m=i.body)==null?void 0:m.getReader();if(!h)throw new Error("Response body is not readable");const d=[];try{let g=!1;for(;!g;){const w=await h.read();if(g=w.done,!g&&w.value&&(d.push(w.value),o+=w.value.length,r&&s>0)){const y=Math.round(o/s*100);r(y)}}}finally{h.releaseLock()}const c=d.reduce((g,w)=>g+w.length,0),f=new Uint8Array(c);let u=0;for(const g of d)f.set(g,u),u+=g.length;return this.cacheEnabled&&t&&await this.saveModelToCache(e,f),r&&r(100),f}async loadModelByUrl(e,t){var r;try{if(this.cacheEnabled){const u=await this.getCachedModelByUrl(e);if(u)return this.logger.info(`WASM module loaded from cache by URL: ${e}`),t&&t(100),u}this.logger.info(`Loading WASM module from URL: ${e}`);const a=await fetch(e);if(!a.ok)throw new Error(`Failed to load WASM module: ${a.statusText}`);const i=a.headers.get("content-length"),n=i?parseInt(i,10):0;let s=0;const o=(r=a.body)==null?void 0:r.getReader();if(!o)throw new Error("Response body is not readable");const h=[];try{let u=!1;for(;!u;){const m=await o.read();if(u=m.done,!u&&m.value&&(h.push(m.value),s+=m.value.length,t&&n>0)){const g=Math.round(s/n*100);t(g)}}}finally{o.releaseLock()}const d=h.reduce((u,m)=>u+m.length,0),c=new Uint8Array(d);let f=0;for(const u of h)c.set(u,f),f+=u.length;return this.cacheEnabled&&await this.saveModelToCacheByUrl(e,c),t&&t(100),c}catch(a){throw this.logger.error(a),new Error("Failed to load WASM module")}}async getCachedModelByUrl(e){try{const a=(await this.openIndexedDB()).transaction(["modelsByUrl"],"readonly").objectStore("modelsByUrl");return new Promise((i,n)=>{const s=a.get(e);s.onsuccess=()=>{const o=s.result;o&&o.data?i(o.data):i(null)},s.onerror=()=>n(s.error)})}catch(t){return this.logger.error("Error reading model from cache by URL:",t),null}}async saveModelToCacheByUrl(e,t){try{const i=(await this.openIndexedDB()).transaction(["modelsByUrl"],"readwrite").objectStore("modelsByUrl");await new Promise((n,s)=>{const o=i.put({url:e,data:t,timestamp:Date.now(),size:t.length});o.onsuccess=()=>n(),o.onerror=()=>s(o.error)}),this.logger.info(`Model saved to cache by URL: ${e}`)}catch(r){this.logger.error("Error saving model to cache by URL:",r)}}async getAvailableModels(){const e=[...this.models];if(!this.cacheEnabled)return e;try{const t=await this.getCachedModelNames();return e.map(r=>({...r,cached:t.includes(r.id)}))}catch(t){return this.logger.error("Error checking cache status:",t),e}}getAvailableModelsSync(){return[...this.models]}getModelConfig(e){return E(e)}async saveModelToCache(e,t){try{const i=(await this.openIndexedDB()).transaction(["models"],"readwrite").objectStore("models");await new Promise((n,s)=>{const o=i.put({name:e,data:t,timestamp:Date.now(),size:t.length});o.onsuccess=()=>n(),o.onerror=()=>s(o.error)}),this.logger.info(`Model ${e} saved to cache`)}catch(r){this.logger.error("Error saving model to cache:",r)}}async getCachedModel(e){try{const a=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((i,n)=>{const s=a.get(e);s.onsuccess=()=>{const o=s.result;o&&o.data?i(o.data):i(null)},s.onerror=()=>n(s.error)})}catch(t){return this.logger.error("Error getting cached model:",t),null}}async getCachedModelNames(){try{const r=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((a,i)=>{const n=r.getAllKeys();n.onsuccess=()=>{const s=n.result;a(s)},n.onerror=()=>i(n.error)})}catch(e){return this.logger.error("Error getting cached model names:",e),[]}}async openIndexedDB(){return new Promise((e,t)=>{const r=indexedDB.open("WhisperModels",2);r.onerror=()=>t(r.error),r.onsuccess=()=>e(r.result),r.onupgradeneeded=a=>{const i=a.target.result;if(!i.objectStoreNames.contains("models")){const n=i.createObjectStore("models",{keyPath:"name"});n.createIndex("timestamp","timestamp",{unique:!1}),n.createIndex("size","size",{unique:!1})}if(!i.objectStoreNames.contains("modelsByUrl")){const n=i.createObjectStore("modelsByUrl",{keyPath:"url"});n.createIndex("timestamp","timestamp",{unique:!1}),n.createIndex("size","size",{unique:!1})}}})}async clearCache(){try{const t=(await this.openIndexedDB()).transaction(["models","modelsByUrl"],"readwrite"),r=t.objectStore("models");await new Promise((i,n)=>{const s=r.clear();s.onsuccess=()=>i(),s.onerror=()=>n(s.error)});const a=t.objectStore("modelsByUrl");await new Promise((i,n)=>{const s=a.clear();s.onsuccess=()=>i(),s.onerror=()=>n(s.error)}),this.logger.info("Model cache cleared")}catch(e){this.logger.error("Error clearing cache:",e)}}async getCacheInfo(){try{const r=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((a,i)=>{const n=r.getAll();n.onsuccess=()=>{const s=n.result,o=s.reduce((h,d)=>h+(d.size||0),0);a({count:s.length,totalSize:o})},n.onerror=()=>i(n.error)})}catch(e){return this.logger.error("Error getting cache info:",e),{count:0,totalSize:0}}}}exports.ModelManager=F;exports.WhisperWasmService=A;exports.getAllModels=z;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=class p{constructor(e=p.levels.INFO,t=""){this.level=e,this.prefix=t}debug(...e){this.level<=p.levels.DEBUG&&console.debug(`[${this.prefix}] [DEBUG]`,...e)}info(...e){this.level<=p.levels.INFO&&console.info(`[${this.prefix}] [INFO]`,...e)}warn(...e){this.level<=p.levels.WARN&&console.warn(`[${this.prefix}] [WARN]`,...e)}error(...e){this.level<=p.levels.ERROR&&console.error(`[${this.prefix}] [ERROR]`,...e)}setLevel(e){this.level=e}getLevel(){return this.level}};p.levels={DEBUG:0,INFO:1,WARN:2,ERROR:3};let b=p;const B=async()=>WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11])),T={language:"auto",threads:4,translate:!1};function M(l){const e=String(l).trim().replace(",","."),t=e.split(":").map(Number);if(t.some(Number.isNaN))throw new Error(`Bad time: "${l}"`);let r=0,a=0,i=0;if(t.length===3)[r,a]=t,i=parseFloat(e.split(":").pop()||"0");else if(t.length===2)[a]=t,i=parseFloat(e.split(":").pop()||"0");else throw new Error(`Bad time format: "${l}"`);return Math.floor(((r*60+a)*60+i)*1e3)}function R(l){const t=/^\s*\[?\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*-->\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*\]?\s*(.*)\s*$/.exec(l);if(!t)throw new Error("Line does not match VTT-like pattern: "+l);const r=t[1],a=t[2],i=t[3]||"",n=M(r),s=M(a);if(s<n)throw new Error("End time is before start time");return{startMs:n,endMs:s,start:r,end:a,text:i}}function q(l){return new Promise(e=>setTimeout(e,l))}function W(l,e=16e3*100){const t=[];for(let r=0;r<l.length;r+=e)t.push(l.subarray(r,r+e));return t}class L{constructor(e,t){this.whisperService=e,this.logger=new b((t==null?void 0:t.logLevel)||b.levels.ERROR,"TranscriptionSession")}async*streamimg(e,t={}){const r=W(e);let a=0;for await(const i of r){const n=[];let s=null,o=!1,h,d=0;for(this.whisperService.transcribe(i,c=>{d=c.timeEnd,c.timeStart+=a,c.timeEnd+=a,s?(s(c),s=null):n.push(c)},t).then(()=>{o=!0,a+=d,s==null||s(void 0)}).catch(c=>{h=c});;){if(h)throw h;if(o)break;if(n.length)yield n.shift();else{const c=await new Promise(f=>s=f);c&&(yield c)}}t.sleepMsBetweenChunks&&await q(t.sleepMsBetweenChunks)}}}class x extends EventTarget{on(e,t){return this.addEventListener(e,t),()=>this.removeEventListener(e,t)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t}))}}class A{constructor(e){this.wasmModule=null,this.instance=null,this.modelFileName="whisper.bin",this.isTranscribing=!1,this.bus=new x,this.logger=new b((e==null?void 0:e.logLevel)??b.levels.ERROR,"WhisperWasmService"),e!=null&&e.init&&this.loadWasmScript()}async checkWasmSupport(){return await B()}async loadWasmScript(){this.wasmModule=await(await Promise.resolve().then(()=>require("./libmain-D50HCaHR.js"))).default({print:(e,...t)=>{this.logger.debug(t),e.startsWith("[")?(this.logger.info(e),this.bus.emit("transcribe",e)):(this.logger.debug(e),this.bus.emit("system_info",e))},printErr:(e,...t)=>{this.logger.debug(t),this.logger.warn(e),this.bus.emit("transcribeError",e)}})}async loadWasmModule(e){if(!await this.checkWasmSupport())throw new Error("WASM is not supported");return this.wasmModule&&(this.wasmModule.FS_unlink(this.modelFileName),this.wasmModule.free()),await this.loadWasmScript(),await q(100),this.storeFS(this.modelFileName,e),this.instance=this.wasmModule.init(this.modelFileName),Promise.resolve()}storeFS(e,t){if(!this.wasmModule)throw new Error("WASM module not loaded");try{this.wasmModule.FS_unlink(e)}catch{}this.wasmModule.FS_createDataFile("/",e,t,!0,!0,!0)}async transcribe(e,t,r={}){if(this.isTranscribing)throw new Error("Already transcribing");if(!this.wasmModule)throw new Error("WASM module not loaded");if(!this.instance)throw new Error("WASM instance not loaded");const a=120;e.length>16e3*a&&this.logger.warn("It's not recommended to transcribe audio data that is longer than 120 seconds"),this.isTranscribing=!0;const{language:i="auto",threads:n=4,translate:s=!1}={...T,...r},o=[],h=Date.now();return this.wasmModule.full_default(this.instance,e,i,n,s),await new Promise((d,c)=>{const f=this.bus.on("transcribe",g=>{const{startMs:w,endMs:y,text:_}=R(g.detail),v={timeStart:w,timeEnd:y,text:_,raw:g.detail};o.push(v),t==null||t(v)}),u=setTimeout(()=>{this.isTranscribing=!1,f(),m(),this.logger.error("Transcribe timeout"),c(new Error("Transcribe timeout"))},a*2*1e3),m=this.bus.on("transcribeError",g=>{this.isTranscribing=!1,f(),m(),clearTimeout(u),this.logger.debug("Transcribe error",g.detail),d({segments:o,transcribeDurationMs:Date.now()-h})})})}createSession(){return new L(this,{logLevel:this.logger.getLevel()})}}const S={"tiny.en":{id:"tiny.en",name:"Tiny English",size:75,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin"},tiny:{id:"tiny",name:"Tiny Multilingual",size:75,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin"},"base.en":{id:"base.en",name:"Base English",size:142,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"},base:{id:"base",name:"Base Multilingual",size:142,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin"},"small.en":{id:"small.en",name:"Small English",size:466,language:"en",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin"},small:{id:"small",name:"Small Multilingual",size:466,language:"multilingual",quantized:!1,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin"},"tiny.en-q5_1":{id:"tiny.en-q5_1",name:"Tiny English (Q5_1)",size:31,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q5_1.bin"},"tiny-q5_1":{id:"tiny-q5_1",name:"Tiny Multilingual (Q5_1)",size:31,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin"},"base.en-q5_1":{id:"base.en-q5_1",name:"Base English (Q5_1)",size:57,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q5_1.bin"},"base-q5_1":{id:"base-q5_1",name:"Base Multilingual (Q5_1)",size:57,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin"},"small.en-q5_1":{id:"small.en-q5_1",name:"Small English (Q5_1)",size:182,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en-q5_1.bin"},"small-q5_1":{id:"small-q5_1",name:"Small Multilingual (Q5_1)",size:182,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q5_1.bin"},"medium.en-q5_0":{id:"medium.en-q5_0",name:"Medium English (Q5_0)",size:515,language:"en",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en-q5_0.bin"},"medium-q5_0":{id:"medium-q5_0",name:"Medium Multilingual (Q5_0)",size:515,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q5_0.bin"},"large-q5_0":{id:"large-q5_0",name:"Large Multilingual (Q5_0)",size:1030,language:"multilingual",quantized:!0,url:"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-q5_0.bin"}};function z(){return Object.values(S).map(({url:l,...e})=>e)}function E(l){return S[l]}class F{constructor(e={logLevel:b.levels.ERROR}){this.cacheEnabled=!0,this.models=z(),this.logger=new b(e.logLevel,"ModelManager")}async loadModel(e,t=!0,r){var m;const a=E(e);if(!a)throw new Error(`Model ${e} not found in config`);if(this.cacheEnabled&&t){const g=await this.getCachedModel(e);if(g)return this.logger.info(`Model ${e} loaded from cache`),r&&r(100),g}this.logger.info(`Loading model ${e} from ${a.url}`);const i=await fetch(a.url);if(!i.ok)throw new Error(`Failed to load model: ${i.statusText}`);const n=i.headers.get("content-length"),s=n?parseInt(n,10):0;let o=0;const h=(m=i.body)==null?void 0:m.getReader();if(!h)throw new Error("Response body is not readable");const d=[];try{let g=!1;for(;!g;){const w=await h.read();if(g=w.done,!g&&w.value&&(d.push(w.value),o+=w.value.length,r&&s>0)){const y=Math.round(o/s*100);r(y)}}}finally{h.releaseLock()}const c=d.reduce((g,w)=>g+w.length,0),f=new Uint8Array(c);let u=0;for(const g of d)f.set(g,u),u+=g.length;return this.cacheEnabled&&t&&await this.saveModelToCache(e,f),r&&r(100),f}async loadModelByUrl(e,t){var r;try{if(this.cacheEnabled){const u=await this.getCachedModelByUrl(e);if(u)return this.logger.info(`WASM module loaded from cache by URL: ${e}`),t&&t(100),u}this.logger.info(`Loading WASM module from URL: ${e}`);const a=await fetch(e);if(!a.ok)throw new Error(`Failed to load WASM module: ${a.statusText}`);const i=a.headers.get("content-length"),n=i?parseInt(i,10):0;let s=0;const o=(r=a.body)==null?void 0:r.getReader();if(!o)throw new Error("Response body is not readable");const h=[];try{let u=!1;for(;!u;){const m=await o.read();if(u=m.done,!u&&m.value&&(h.push(m.value),s+=m.value.length,t&&n>0)){const g=Math.round(s/n*100);t(g)}}}finally{o.releaseLock()}const d=h.reduce((u,m)=>u+m.length,0),c=new Uint8Array(d);let f=0;for(const u of h)c.set(u,f),f+=u.length;return this.cacheEnabled&&await this.saveModelToCacheByUrl(e,c),t&&t(100),c}catch(a){throw this.logger.error(a),new Error("Failed to load WASM module")}}async getCachedModelByUrl(e){try{const a=(await this.openIndexedDB()).transaction(["modelsByUrl"],"readonly").objectStore("modelsByUrl");return new Promise((i,n)=>{const s=a.get(e);s.onsuccess=()=>{const o=s.result;o&&o.data?i(o.data):i(null)},s.onerror=()=>n(s.error)})}catch(t){return this.logger.error("Error reading model from cache by URL:",t),null}}async saveModelToCacheByUrl(e,t){try{const i=(await this.openIndexedDB()).transaction(["modelsByUrl"],"readwrite").objectStore("modelsByUrl");await new Promise((n,s)=>{const o=i.put({url:e,data:t,timestamp:Date.now(),size:t.length});o.onsuccess=()=>n(),o.onerror=()=>s(o.error)}),this.logger.info(`Model saved to cache by URL: ${e}`)}catch(r){this.logger.error("Error saving model to cache by URL:",r)}}async getAvailableModels(){const e=[...this.models];if(!this.cacheEnabled)return e;try{const t=await this.getCachedModelNames();return e.map(r=>({...r,cached:t.includes(r.id)}))}catch(t){return this.logger.error("Error checking cache status:",t),e}}getAvailableModelsSync(){return[...this.models]}getModelConfig(e){return E(e)}async saveModelToCache(e,t){try{const i=(await this.openIndexedDB()).transaction(["models"],"readwrite").objectStore("models");await new Promise((n,s)=>{const o=i.put({name:e,data:t,timestamp:Date.now(),size:t.length});o.onsuccess=()=>n(),o.onerror=()=>s(o.error)}),this.logger.info(`Model ${e} saved to cache`)}catch(r){this.logger.error("Error saving model to cache:",r)}}async getCachedModel(e){try{const a=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((i,n)=>{const s=a.get(e);s.onsuccess=()=>{const o=s.result;o&&o.data?i(o.data):i(null)},s.onerror=()=>n(s.error)})}catch(t){return this.logger.error("Error getting cached model:",t),null}}async getCachedModelNames(){try{const r=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((a,i)=>{const n=r.getAllKeys();n.onsuccess=()=>{const s=n.result;a(s)},n.onerror=()=>i(n.error)})}catch(e){return this.logger.error("Error getting cached model names:",e),[]}}async openIndexedDB(){return new Promise((e,t)=>{const r=indexedDB.open("WhisperModels",2);r.onerror=()=>t(r.error),r.onsuccess=()=>e(r.result),r.onupgradeneeded=a=>{const i=a.target.result;if(!i.objectStoreNames.contains("models")){const n=i.createObjectStore("models",{keyPath:"name"});n.createIndex("timestamp","timestamp",{unique:!1}),n.createIndex("size","size",{unique:!1})}if(!i.objectStoreNames.contains("modelsByUrl")){const n=i.createObjectStore("modelsByUrl",{keyPath:"url"});n.createIndex("timestamp","timestamp",{unique:!1}),n.createIndex("size","size",{unique:!1})}}})}async clearCache(){try{const t=(await this.openIndexedDB()).transaction(["models","modelsByUrl"],"readwrite"),r=t.objectStore("models");await new Promise((i,n)=>{const s=r.clear();s.onsuccess=()=>i(),s.onerror=()=>n(s.error)});const a=t.objectStore("modelsByUrl");await new Promise((i,n)=>{const s=a.clear();s.onsuccess=()=>i(),s.onerror=()=>n(s.error)}),this.logger.info("Model cache cleared")}catch(e){this.logger.error("Error clearing cache:",e)}}async getCacheInfo(){try{const r=(await this.openIndexedDB()).transaction(["models"],"readonly").objectStore("models");return new Promise((a,i)=>{const n=r.getAll();n.onsuccess=()=>{const s=n.result,o=s.reduce((h,d)=>h+(d.size||0),0);a({count:s.length,totalSize:o})},n.onerror=()=>i(n.error)})}catch(e){return this.logger.error("Error getting cache info:",e),{count:0,totalSize:0}}}}exports.ModelManager=F;exports.WhisperWasmService=A;exports.getAllModels=z;
|
package/dist/index.es.js
CHANGED
|
@@ -49,12 +49,12 @@ function T(l) {
|
|
|
49
49
|
const t = /^\s*\[?\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*-->\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*\]?\s*(.*)\s*$/.exec(l);
|
|
50
50
|
if (!t)
|
|
51
51
|
throw new Error("Line does not match VTT-like pattern: " + l);
|
|
52
|
-
const r = t[1], a = t[2], i = t[3] || "",
|
|
53
|
-
if (
|
|
52
|
+
const r = t[1], a = t[2], i = t[3] || "", n = M(r), s = M(a);
|
|
53
|
+
if (s < n)
|
|
54
54
|
throw new Error("End time is before start time");
|
|
55
55
|
return {
|
|
56
|
-
startMs:
|
|
57
|
-
endMs:
|
|
56
|
+
startMs: n,
|
|
57
|
+
endMs: s,
|
|
58
58
|
start: r,
|
|
59
59
|
end: a,
|
|
60
60
|
text: i
|
|
@@ -77,25 +77,25 @@ class x {
|
|
|
77
77
|
const r = R(e);
|
|
78
78
|
let a = 0;
|
|
79
79
|
for await (const i of r) {
|
|
80
|
-
const
|
|
81
|
-
let
|
|
80
|
+
const n = [];
|
|
81
|
+
let s = null, o = !1, h, d = 0;
|
|
82
82
|
for (this.whisperService.transcribe(
|
|
83
83
|
i,
|
|
84
84
|
(c) => {
|
|
85
|
-
d = c.timeEnd, c.timeStart += a, c.timeEnd += a,
|
|
85
|
+
d = c.timeEnd, c.timeStart += a, c.timeEnd += a, s ? (s(c), s = null) : n.push(c);
|
|
86
86
|
},
|
|
87
87
|
t
|
|
88
88
|
).then(() => {
|
|
89
|
-
o = !0, a += d,
|
|
89
|
+
o = !0, a += d, s == null || s(void 0);
|
|
90
90
|
}).catch((c) => {
|
|
91
91
|
h = c;
|
|
92
92
|
}); ; ) {
|
|
93
93
|
if (h) throw h;
|
|
94
94
|
if (o) break;
|
|
95
|
-
if (
|
|
96
|
-
yield
|
|
95
|
+
if (n.length)
|
|
96
|
+
yield n.shift();
|
|
97
97
|
else {
|
|
98
|
-
const c = await new Promise((f) =>
|
|
98
|
+
const c = await new Promise((f) => s = f);
|
|
99
99
|
c && (yield c);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -153,11 +153,15 @@ class A {
|
|
|
153
153
|
e.length > 16e3 * a && this.logger.warn(
|
|
154
154
|
"It's not recommended to transcribe audio data that is longer than 120 seconds"
|
|
155
155
|
), this.isTranscribing = !0;
|
|
156
|
-
const {
|
|
156
|
+
const {
|
|
157
|
+
language: i = "auto",
|
|
158
|
+
threads: n = 4,
|
|
159
|
+
translate: s = !1
|
|
160
|
+
} = {
|
|
157
161
|
...B,
|
|
158
162
|
...r
|
|
159
163
|
}, o = [], h = Date.now();
|
|
160
|
-
return this.wasmModule.full_default(this.instance, e, i,
|
|
164
|
+
return this.wasmModule.full_default(this.instance, e, i, n, s), await new Promise((d, c) => {
|
|
161
165
|
const f = this.bus.on("transcribe", (g) => {
|
|
162
166
|
const { startMs: w, endMs: y, text: z } = T(g.detail), v = {
|
|
163
167
|
timeStart: w,
|
|
@@ -329,7 +333,7 @@ class F {
|
|
|
329
333
|
const i = await fetch(a.url);
|
|
330
334
|
if (!i.ok)
|
|
331
335
|
throw new Error(`Failed to load model: ${i.statusText}`);
|
|
332
|
-
const
|
|
336
|
+
const n = i.headers.get("content-length"), s = n ? parseInt(n, 10) : 0;
|
|
333
337
|
let o = 0;
|
|
334
338
|
const h = (m = i.body) == null ? void 0 : m.getReader();
|
|
335
339
|
if (!h)
|
|
@@ -339,8 +343,8 @@ class F {
|
|
|
339
343
|
let g = !1;
|
|
340
344
|
for (; !g; ) {
|
|
341
345
|
const w = await h.read();
|
|
342
|
-
if (g = w.done, !g && w.value && (d.push(w.value), o += w.value.length, r &&
|
|
343
|
-
const y = Math.round(o /
|
|
346
|
+
if (g = w.done, !g && w.value && (d.push(w.value), o += w.value.length, r && s > 0)) {
|
|
347
|
+
const y = Math.round(o / s * 100);
|
|
344
348
|
r(y);
|
|
345
349
|
}
|
|
346
350
|
}
|
|
@@ -368,8 +372,8 @@ class F {
|
|
|
368
372
|
const a = await fetch(e);
|
|
369
373
|
if (!a.ok)
|
|
370
374
|
throw new Error(`Failed to load WASM module: ${a.statusText}`);
|
|
371
|
-
const i = a.headers.get("content-length"),
|
|
372
|
-
let
|
|
375
|
+
const i = a.headers.get("content-length"), n = i ? parseInt(i, 10) : 0;
|
|
376
|
+
let s = 0;
|
|
373
377
|
const o = (r = a.body) == null ? void 0 : r.getReader();
|
|
374
378
|
if (!o)
|
|
375
379
|
throw new Error("Response body is not readable");
|
|
@@ -378,8 +382,8 @@ class F {
|
|
|
378
382
|
let u = !1;
|
|
379
383
|
for (; !u; ) {
|
|
380
384
|
const m = await o.read();
|
|
381
|
-
if (u = m.done, !u && m.value && (h.push(m.value),
|
|
382
|
-
const g = Math.round(
|
|
385
|
+
if (u = m.done, !u && m.value && (h.push(m.value), s += m.value.length, t && n > 0)) {
|
|
386
|
+
const g = Math.round(s / n * 100);
|
|
383
387
|
t(g);
|
|
384
388
|
}
|
|
385
389
|
}
|
|
@@ -401,12 +405,12 @@ class F {
|
|
|
401
405
|
async getCachedModelByUrl(e) {
|
|
402
406
|
try {
|
|
403
407
|
const a = (await this.openIndexedDB()).transaction(["modelsByUrl"], "readonly").objectStore("modelsByUrl");
|
|
404
|
-
return new Promise((i,
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
const o =
|
|
408
|
+
return new Promise((i, n) => {
|
|
409
|
+
const s = a.get(e);
|
|
410
|
+
s.onsuccess = () => {
|
|
411
|
+
const o = s.result;
|
|
408
412
|
o && o.data ? i(o.data) : i(null);
|
|
409
|
-
},
|
|
413
|
+
}, s.onerror = () => n(s.error);
|
|
410
414
|
});
|
|
411
415
|
} catch (t) {
|
|
412
416
|
return this.logger.error("Error reading model from cache by URL:", t), null;
|
|
@@ -418,14 +422,14 @@ class F {
|
|
|
418
422
|
async saveModelToCacheByUrl(e, t) {
|
|
419
423
|
try {
|
|
420
424
|
const i = (await this.openIndexedDB()).transaction(["modelsByUrl"], "readwrite").objectStore("modelsByUrl");
|
|
421
|
-
await new Promise((
|
|
425
|
+
await new Promise((n, s) => {
|
|
422
426
|
const o = i.put({
|
|
423
427
|
url: e,
|
|
424
428
|
data: t,
|
|
425
429
|
timestamp: Date.now(),
|
|
426
430
|
size: t.length
|
|
427
431
|
});
|
|
428
|
-
o.onsuccess = () =>
|
|
432
|
+
o.onsuccess = () => n(), o.onerror = () => s(o.error);
|
|
429
433
|
}), this.logger.info(`Model saved to cache by URL: ${e}`);
|
|
430
434
|
} catch (r) {
|
|
431
435
|
this.logger.error("Error saving model to cache by URL:", r);
|
|
@@ -466,14 +470,14 @@ class F {
|
|
|
466
470
|
async saveModelToCache(e, t) {
|
|
467
471
|
try {
|
|
468
472
|
const i = (await this.openIndexedDB()).transaction(["models"], "readwrite").objectStore("models");
|
|
469
|
-
await new Promise((
|
|
473
|
+
await new Promise((n, s) => {
|
|
470
474
|
const o = i.put({
|
|
471
475
|
name: e,
|
|
472
476
|
data: t,
|
|
473
477
|
timestamp: Date.now(),
|
|
474
478
|
size: t.length
|
|
475
479
|
});
|
|
476
|
-
o.onsuccess = () =>
|
|
480
|
+
o.onsuccess = () => n(), o.onerror = () => s(o.error);
|
|
477
481
|
}), this.logger.info(`Model ${e} saved to cache`);
|
|
478
482
|
} catch (r) {
|
|
479
483
|
this.logger.error("Error saving model to cache:", r);
|
|
@@ -485,12 +489,12 @@ class F {
|
|
|
485
489
|
async getCachedModel(e) {
|
|
486
490
|
try {
|
|
487
491
|
const a = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
488
|
-
return new Promise((i,
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
const o =
|
|
492
|
+
return new Promise((i, n) => {
|
|
493
|
+
const s = a.get(e);
|
|
494
|
+
s.onsuccess = () => {
|
|
495
|
+
const o = s.result;
|
|
492
496
|
o && o.data ? i(o.data) : i(null);
|
|
493
|
-
},
|
|
497
|
+
}, s.onerror = () => n(s.error);
|
|
494
498
|
});
|
|
495
499
|
} catch (t) {
|
|
496
500
|
return this.logger.error("Error getting cached model:", t), null;
|
|
@@ -503,11 +507,11 @@ class F {
|
|
|
503
507
|
try {
|
|
504
508
|
const r = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
505
509
|
return new Promise((a, i) => {
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
a(
|
|
510
|
-
},
|
|
510
|
+
const n = r.getAllKeys();
|
|
511
|
+
n.onsuccess = () => {
|
|
512
|
+
const s = n.result;
|
|
513
|
+
a(s);
|
|
514
|
+
}, n.onerror = () => i(n.error);
|
|
511
515
|
});
|
|
512
516
|
} catch (e) {
|
|
513
517
|
return this.logger.error("Error getting cached model names:", e), [];
|
|
@@ -522,12 +526,12 @@ class F {
|
|
|
522
526
|
r.onerror = () => t(r.error), r.onsuccess = () => e(r.result), r.onupgradeneeded = (a) => {
|
|
523
527
|
const i = a.target.result;
|
|
524
528
|
if (!i.objectStoreNames.contains("models")) {
|
|
525
|
-
const
|
|
526
|
-
|
|
529
|
+
const n = i.createObjectStore("models", { keyPath: "name" });
|
|
530
|
+
n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("size", "size", { unique: !1 });
|
|
527
531
|
}
|
|
528
532
|
if (!i.objectStoreNames.contains("modelsByUrl")) {
|
|
529
|
-
const
|
|
530
|
-
|
|
533
|
+
const n = i.createObjectStore("modelsByUrl", { keyPath: "url" });
|
|
534
|
+
n.createIndex("timestamp", "timestamp", { unique: !1 }), n.createIndex("size", "size", { unique: !1 });
|
|
531
535
|
}
|
|
532
536
|
};
|
|
533
537
|
});
|
|
@@ -538,14 +542,14 @@ class F {
|
|
|
538
542
|
async clearCache() {
|
|
539
543
|
try {
|
|
540
544
|
const t = (await this.openIndexedDB()).transaction(["models", "modelsByUrl"], "readwrite"), r = t.objectStore("models");
|
|
541
|
-
await new Promise((i,
|
|
542
|
-
const
|
|
543
|
-
|
|
545
|
+
await new Promise((i, n) => {
|
|
546
|
+
const s = r.clear();
|
|
547
|
+
s.onsuccess = () => i(), s.onerror = () => n(s.error);
|
|
544
548
|
});
|
|
545
549
|
const a = t.objectStore("modelsByUrl");
|
|
546
|
-
await new Promise((i,
|
|
547
|
-
const
|
|
548
|
-
|
|
550
|
+
await new Promise((i, n) => {
|
|
551
|
+
const s = a.clear();
|
|
552
|
+
s.onsuccess = () => i(), s.onerror = () => n(s.error);
|
|
549
553
|
}), this.logger.info("Model cache cleared");
|
|
550
554
|
} catch (e) {
|
|
551
555
|
this.logger.error("Error clearing cache:", e);
|
|
@@ -558,11 +562,11 @@ class F {
|
|
|
558
562
|
try {
|
|
559
563
|
const r = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
560
564
|
return new Promise((a, i) => {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
a({ count:
|
|
565
|
-
},
|
|
565
|
+
const n = r.getAll();
|
|
566
|
+
n.onsuccess = () => {
|
|
567
|
+
const s = n.result, o = s.reduce((h, d) => h + (d.size || 0), 0);
|
|
568
|
+
a({ count: s.length, totalSize: o });
|
|
569
|
+
}, n.onerror = () => i(n.error);
|
|
566
570
|
});
|
|
567
571
|
} catch (e) {
|
|
568
572
|
return this.logger.error("Error getting cache info:", e), { count: 0, totalSize: 0 };
|