@streamoji/aitwin 0.1.1 → 0.1.4

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 @@
1
+ (function(){"use strict";function K(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function J(e,t){const n=e.crop;return{x:t?.source_x??n?.x??0,y:t?.source_y??n?.y??0,width:t?.source_w??n?.width??e.frame_width??0,height:t?.source_h??n?.height??e.frame_height??0}}function L(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}function G(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)n[r/2]=Number.parseInt(t.slice(r,r+2),16);return n}function I(e){return Uint8Array.from(e)}function j(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function Q(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(e),r=I(n.subarray(0,12)),s=I(n.subarray(12,28)),a=I(n.subarray(28)),o=I(j(a,s)),_=await crypto.subtle.importKey("raw",G(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},_,o)}async function T(e,t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const r=await n.arrayBuffer();return Q(r,t)}async function X(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await T(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function Y(e){const t=new Blob([e],{type:"image/webp"});return createImageBitmap(t)}const p=15,P=["t01_0.17","t02_0.33","t03_0.50","t04_0.67","t05_0.83"],Z=P.length,B=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function H(e,t){return`${e}__${t}`}function U(e,t){return`pairs/${e}/${t}_minus_sil.png`}function q(e){return`${e}_minus_sil.png`}function V(e,t){if(e===t)return[];const n=[],r=H(e,t);if(B.has(r)){for(const a of P)n.push(U(r,a));return n}const s=H(t,e);if(B.has(s)){for(let a=Z-1;a>=0;a--)n.push(U(s,P[a]));return n}return[]}function tt(e){return e<16?[]:e<35?[2]:e<51?[0,4]:e<67?[0,2,4]:e<83?[0,1,2,3]:[0,1,2,3,4]}function et(e,t){return tt(t).map(r=>e[r]).filter(r=>r!=null)}function nt(e,t,n){if(!n)return`${e}/${t}`;const r=t.split("/").pop()??t,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${s}.bin`}function rt(e,t,n,r,s=p){const a=new OffscreenCanvas(n,r),o=a.getContext("2d",{willReadFrequently:!0});o.drawImage(e,0,0,n,r);const _=o.getImageData(0,0,n,r),i=o.createImageData(n,r);i.data.set(_.data);const f=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const E of t){f.clearRect(0,0,n,r),f.drawImage(E,0,0,n,r);const d=f.getImageData(0,0,n,r);for(let c=0;c<i.data.length;c+=4){const D=d.data[c],R=d.data[c+1],z=d.data[c+2];Math.max(D,R,z)>s&&(i.data[c]=D,i.data[c+1]=R,i.data[c+2]=z,i.data[c+3]=255)}}return o.putImageData(i,0,0),a}const S=-1;let u=null,y=null,w=null,O=null,F="sil";const b=new Map;function g(){if(!O)throw new Error("Worker assets not configured");return O}function st(){return`${g().twinBase}/sil.png`}function at(){const e=g();return e.encrypted?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function ot(){const e=g();return e.encrypted?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function it(){const e=g();return e.encrypted?e.binBase:e.twinBase}function M(){return g().encrypted}async function x(e){const t=await fetch(e);if(!t.ok)throw new Error(`Failed to load image: ${e}`);const n=await t.blob();return createImageBitmap(n)}async function v(e,t,n){const r=M(),s=r?await X(e,n):await fetch(e).then(l=>{if(!l.ok)throw new Error(`Failed to load ${e}`);return l.json()}),a=s.sheets?.[0];if(!a?.path)throw new Error(`${e} has no sheets[0].path`);const o=r?await Y(await T(nt(t,a.path,r),n??"")):await x(`${t}/${a.path}`),_=new Map;for(const l of s.cells??[])l.path&&_.set(l.path,l);const i=new Map;for(const l of s.sheets??[])i.set(l.index,l);return{atlas:o,atlasMeta:s,cellByPath:_,sheetByIndex:i,diffBase:t}}async function m(e){if(M()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(y&&(!M()||w===(e??null)))return y;w=e??null,b.clear();const t=it();return y=(async()=>{const n=await x(st()),r=await v(at(),t,e);let s=null;try{s=await v(ot(),t,e)}catch{s=null}return{sil:n,viseme:r,expression:s}})(),y}function k(e){return K(e.viseme.atlasMeta,e.sil.width,e.sil.height)}function ct(e,t){if(!e.sheetByIndex.get(t.sheet??0))throw new Error(`Unknown sheet index: ${t.sheet}`);const r=new OffscreenCanvas(t.w,t.h);return r.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),r}async function _t(e){return createImageBitmap(e)}async function N(e,t,n){const r=`${t.diffBase}::${n}`,s=b.get(r);if(s)return s;const a=(async()=>{const o=t.cellByPath.get(n);if(!o)throw new Error(`No atlas cell for path: ${n}`);const _=J(t.atlasMeta,o),i=k(e);if(L(o)){const E=ct(t,o),d=new OffscreenCanvas(i.width,i.height),c=d.getContext("2d");return c.fillStyle="#000",c.fillRect(0,0,i.width,i.height),c.drawImage(E,_.x,_.y),_t(d)}const l=`${t.diffBase}/${n}`,f=await x(l);if(f.width===i.width&&f.height===i.height)return f;throw f.close(),new Error(`Diff PNG wrong size for ${n}`)})();return b.set(r,a),a}function A(e,t){const n={type:"status",label:e,generation:t};self.postMessage(n)}function lt(e,t){const n={type:"error",requestId:e,message:t};self.postMessage(n)}async function C(e){if(!u||e!==S&&e!==h)return;const t=await createImageBitmap(u);if(e!==S&&e!==h){t.close();return}const n={type:"frame",bitmap:t,generation:e};self.postMessage(n,{transfer:[t]})}function ft(e,t,n){return new Promise((r,s)=>{setTimeout(()=>{t!==n?s(new DOMException("Aborted","AbortError")):r()},e)})}async function W(e,t,n,r=p){const s=await m(w??void 0),{width:a,height:o}=k(s);(e.width!==a||e.height!==o)&&(e.width=a,e.height=o);const _=[];if(t){if(!s.viseme.cellByPath.get(t))throw new Error(`No viseme atlas cell for path: ${t}`);_.push(await N(s,s.viseme,t))}if(n&&s.expression){if(!s.expression.cellByPath.get(n))throw new Error(`No expression atlas cell for path: ${n}`);_.push(await N(s,s.expression,n))}const i=e.getContext("2d");if(i.clearRect(0,0,a,o),_.length===0){i.drawImage(s.sil,0,0,a,o);return}const l=rt(s.sil,_,a,o,r);i.drawImage(l,0,0)}async function $(e,t,n,r,s=p){await W(e,t,n,s),await C(r)}async function ut(e,t,n){F="sil";const r=await m(w??void 0);if(t!==n)throw new DOMException("Aborted","AbortError");const{width:s,height:a}=k(r);e.width=s,e.height=a;const o=e.getContext("2d");o.clearRect(0,0,s,a),o.drawImage(r.sil,0,0,s,a),await C(t)}async function dt(e,t,n,r,s={}){const a=s.from??F,o=s.transitionDurationMs??400,_=s.gapMs??o,i=s.threshold??p;if(await m(w??void 0),a===t){if(A(t,n),await $(e,q(t),null,n,i),n!==r)throw new DOMException("Aborted","AbortError");F=t;return}const l=V(a,t),f=et(l,_),E=q(t),d=f.length>0?o/f.length:0;f.length===0&&A(`${a} → ${t} (no pair; final only)`,n);for(let c=0;c<f.length;c++){if(n!==r)throw new DOMException("Aborted","AbortError");const D=f[c],R=D.split("/").pop()?.replace("_minus_sil.png","")??`frame ${c}`;if(A(`${a} → ${t} (${R})`,n),await $(e,D,null,n,i),n!==r)throw new DOMException("Aborted","AbortError");d>0&&await ft(d,n,r)}if(n!==r)throw new DOMException("Aborted","AbortError");if(A(t,n),await $(e,E,null,n,i),n!==r)throw new DOMException("Aborted","AbortError");F=t}let h=0;self.onmessage=e=>{const t=e.data;(async()=>{try{switch(t.type){case"init":{u=new OffscreenCanvas(1,1);const n={type:"ready",requestId:t.requestId};self.postMessage(n);break}case"loadAssets":{O=t.urls,y=null,w=null,b.clear();const n=await m(t.keyHex),{width:r,height:s}=k(n),a={type:"loadAssetsDone",requestId:t.requestId,width:r,height:s};self.postMessage(a);break}case"drawFrame":{if(!u)throw new Error("Worker not initialized");await m(w??void 0),await W(u,t.diffPath,t.expressionDiffPath??null,t.threshold??p),await C(S);const n={type:"renderDone",requestId:t.requestId,generation:S};self.postMessage(n);break}case"renderSil":{if(h=t.generation,!u)throw new Error("Worker not initialized");await ut(u,t.generation,h);const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}case"renderViseme":{if(h=t.generation,!u)throw new Error("Worker not initialized");await dt(u,t.to,t.generation,h,{from:t.from,transitionDurationMs:t.transitionDurationMs,gapMs:t.gapMs,threshold:t.threshold});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}default:break}}catch(n){if(n instanceof DOMException&&n.name==="AbortError"){const r={type:"renderAborted",requestId:t.requestId,generation:"generation"in t?t.generation:h};self.postMessage(r);return}lt(t.requestId,n instanceof Error?n.message:String(n))}})()}})();
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const AiTwin: ForwardRefExoticComponent<AiTwinProps & RefAttribut
7
7
  export declare type AiTwinHandle = {
8
8
  speakText: (text: string, options?: AvatarTtsSpeakOptions) => Promise<void>;
9
9
  stop: () => void;
10
- setTtsProvider: (provider: TtsProvider) => void;
10
+ setTtsEngineId: (engineId: TtsEngineId) => void;
11
11
  renderViseme: (toViseme: string, options?: AiTwinRenderVisemeOptions) => Promise<void>;
12
12
  isReady: () => boolean;
13
13
  getStatus: () => AvatarTtsLipsyncStatus;
@@ -30,13 +30,9 @@ export declare type AiTwinProps = {
30
30
  * When omitted, a dev token is fetched via getAuthToken.
31
31
  */
32
32
  authToken?: string;
33
- /** Streamoji API base (default: https://ai.streamoji.com). */
34
- apiBase?: string;
35
- /** R2 CDN base for custom face bundles (default: pub R2 custom-faces). */
36
- facesCdnBase?: string;
37
- /** TTS provider when using `assets` (default: `google`). */
38
- tts?: TtsProvider;
39
- /** TTS voice id override (provider-specific). */
33
+ /** TTS engine when using `assets` (default: Google engine id). */
34
+ ttsEngineId?: TtsEngineId;
35
+ /** TTS voice id override (engine-specific). */
40
36
  voiceId?: string;
41
37
  /** API speakingRate (default: 0.85). */
42
38
  speakingRate?: number;
@@ -57,7 +53,7 @@ export declare type AiTwinProps = {
57
53
  export declare type AiTwinRecord = {
58
54
  id: string;
59
55
  faceId: string;
60
- tts: TtsProvider_2;
56
+ ttsEngineId: TtsEngineId;
61
57
  voiceId?: string;
62
58
  gender?: string;
63
59
  createdAt?: string;
@@ -72,8 +68,8 @@ export declare type AiTwinRenderVisemeOptions = {
72
68
 
73
69
  export declare type AvatarTtsLipsyncController = {
74
70
  setDeveloperToken: (token: string) => void;
75
- setTtsProvider: (provider: TtsProvider) => void;
76
- getTtsProvider: () => TtsProvider;
71
+ setTtsEngineId: (engineId: TtsEngineId) => void;
72
+ getTtsEngineId: () => TtsEngineId;
77
73
  speak: (userQuery: string, options?: AvatarTtsSpeakOptions) => Promise<void>;
78
74
  stop: () => void;
79
75
  getVisemeQueue: () => VisemeQueueItem[];
@@ -94,14 +90,12 @@ export declare type AvatarTtsSpeakOptions = {
94
90
  /** Passed to API as speakingRate (optional; server defaults to 1.0). */
95
91
  speakingRate?: number;
96
92
  /** Overrides controller default for this request. */
97
- tts?: TtsProvider;
93
+ ttsEngineId?: TtsEngineId;
98
94
  };
99
95
 
100
96
  export declare function createAvatarTtsLipsyncController(onStatus: (status: AvatarTtsLipsyncStatus) => void, apiBase?: string): AvatarTtsLipsyncController;
101
97
 
102
- export declare const DEFAULT_API_BASE = "https://ai.streamoji.com";
103
-
104
- export declare const DEFAULT_FACES_CDN_BASE = "https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev/custom-faces";
98
+ export declare const DEFAULT_TTS_ENGINE_ID: TtsEngineId;
105
99
 
106
100
  export declare function fetchAiTwin(id: string, baseUrl?: string): Promise<AiTwinRecord>;
107
101
 
@@ -133,9 +127,14 @@ export declare const SPEAKING_RATE_MAX = 1.5;
133
127
 
134
128
  export declare const SPEAKING_RATE_MIN = 0.5;
135
129
 
136
- export declare type TtsProvider = "google" | "inworld" | "cartesia";
130
+ export declare const TTS_ENGINE_CARTESIA = "eng_c9b1e6d4";
131
+
132
+ /** Opaque TTS engine ids — must match backend TTS_ENGINE_MAP in main.py. */
133
+ export declare const TTS_ENGINE_GOOGLE = "eng_a7f2b9c1";
134
+
135
+ export declare const TTS_ENGINE_INWORLD = "eng_d4e8f3a2";
137
136
 
138
- declare type TtsProvider_2 = "google" | "inworld" | "cartesia";
137
+ export declare type TtsEngineId = typeof TTS_ENGINE_GOOGLE | typeof TTS_ENGINE_INWORLD | typeof TTS_ENGINE_CARTESIA;
139
138
 
140
139
  /** Runtime face bundle URLs (R2 custom-faces or local /twins/…). */
141
140
  export declare type TwinAssetUrls = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamoji/aitwin",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Embeddable React AI twin face with TTS lipsync",
5
5
  "type": "module",
6
6
  "main": "./dist/aitwin.cjs",
@@ -23,7 +23,10 @@
23
23
  "scripts": {
24
24
  "build": "vite build",
25
25
  "typecheck": "tsc -p tsconfig.json --noEmit",
26
- "prepublishOnly": "npm run build"
26
+ "test": "vitest run",
27
+ "upload:worker": "node scripts/upload-worker-to-r2.mjs",
28
+ "configure:cors": "node scripts/configure-r2-cors.mjs",
29
+ "prepublishOnly": "npm run build && npm run upload:worker"
27
30
  },
28
31
  "publishConfig": {
29
32
  "access": "public"
@@ -33,6 +36,7 @@
33
36
  "react-dom": "^18.0.0 || ^19.0.0"
34
37
  },
35
38
  "devDependencies": {
39
+ "@aws-sdk/client-s3": "^3.750.0",
36
40
  "@types/react": "^19.2.5",
37
41
  "@types/react-dom": "^19.2.3",
38
42
  "@vitejs/plugin-react": "^5.1.1",
@@ -40,6 +44,7 @@
40
44
  "react-dom": "^19.2.0",
41
45
  "typescript": "^5.9.3",
42
46
  "vite": "^7.2.4",
43
- "vite-plugin-dts": "^4.5.4"
47
+ "vite-plugin-dts": "^4.5.4",
48
+ "vitest": "^4.1.8"
44
49
  }
45
50
  }
@@ -1 +0,0 @@
1
- (function(){"use strict";function K(e,t,r){return{width:e.crop?.source_width??t,height:e.crop?.source_height??r}}function J(e,t){const r=e.crop;return{x:t?.source_x??r?.x??0,y:t?.source_y??r?.y??0,width:t?.source_w??r?.width??e.frame_width??0,height:t?.source_h??r?.height??e.frame_height??0}}function L(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}function G(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const r=new Uint8Array(t.length/2);for(let n=0;n<t.length;n+=2)r[n/2]=Number.parseInt(t.slice(n,n+2),16);return r}function I(e){return Uint8Array.from(e)}function j(e,t){const r=new Uint8Array(e.length+t.length);return r.set(e,0),r.set(t,e.length),r}async function Q(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const r=new Uint8Array(e),n=I(r.subarray(0,12)),s=I(r.subarray(12,28)),o=I(r.subarray(28)),i=I(j(o,s)),_=await crypto.subtle.importKey("raw",G(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:n,tagLength:128},_,i)}async function $(e,t){const r=await fetch(e);if(!r.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const n=await r.arrayBuffer();return Q(n,t)}async function X(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const r=await $(e,t),n=new TextDecoder().decode(r);return JSON.parse(n)}async function Y(e){const t=new Blob([e],{type:"image/webp"});return createImageBitmap(t)}function Z(){throw new Error("Twin assets not configured; call setActiveTwinAssets first")}function V(){return Z().encrypted}const p=15,P=["t01_0.17","t02_0.33","t03_0.50","t04_0.67","t05_0.83"],tt=P.length,B=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function H(e,t){return`${e}__${t}`}function U(e,t){return`pairs/${e}/${t}_minus_sil.png`}function q(e){return`${e}_minus_sil.png`}function et(e,t){if(e===t)return[];const r=[],n=H(e,t);if(B.has(n)){for(const o of P)r.push(U(n,o));return r}const s=H(t,e);if(B.has(s)){for(let o=tt-1;o>=0;o--)r.push(U(s,P[o]));return r}return[]}function rt(e){return e<16?[]:e<35?[2]:e<51?[0,4]:e<67?[0,2,4]:e<83?[0,1,2,3]:[0,1,2,3,4]}function nt(e,t){return rt(t).map(n=>e[n]).filter(n=>n!=null)}function st(e,t){if(!V())return`${e}/${t}`;const r=t.split("/").pop()??t,n=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${n}.bin`}function at(e,t,r,n,s=p){const o=new OffscreenCanvas(r,n),i=o.getContext("2d",{willReadFrequently:!0});i.drawImage(e,0,0,r,n);const _=i.getImageData(0,0,r,n),a=i.createImageData(r,n);a.data.set(_.data);const l=new OffscreenCanvas(r,n).getContext("2d",{willReadFrequently:!0});for(const E of t){l.clearRect(0,0,r,n),l.drawImage(E,0,0,r,n);const u=l.getImageData(0,0,r,n);for(let c=0;c<a.data.length;c+=4){const D=u.data[c],R=u.data[c+1],z=u.data[c+2];Math.max(D,R,z)>s&&(a.data[c]=D,a.data[c+1]=R,a.data[c+2]=z,a.data[c+3]=255)}}return i.putImageData(a,0,0),o}const S=-1;let f=null,y=null,d=null,O=null,F="sil";const A=new Map;function g(){if(!O)throw new Error("Worker assets not configured");return O}function ot(){return`${g().twinBase}/sil.png`}function it(){const e=g();return e.encrypted?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function ct(){const e=g();return e.encrypted?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function _t(){const e=g();return e.encrypted?e.binBase:e.twinBase}function b(){return g().encrypted}async function M(e){const t=await fetch(e);if(!t.ok)throw new Error(`Failed to load image: ${e}`);const r=await t.blob();return createImageBitmap(r)}async function v(e,t,r){const n=b()?await X(e,r):await fetch(e).then(a=>{if(!a.ok)throw new Error(`Failed to load ${e}`);return a.json()}),s=n.sheets?.[0];if(!s?.path)throw new Error(`${e} has no sheets[0].path`);const o=b()?await Y(await $(st(t,s.path),r??"")):await M(`${t}/${s.path}`),i=new Map;for(const a of n.cells??[])a.path&&i.set(a.path,a);const _=new Map;for(const a of n.sheets??[])_.set(a.index,a);return{atlas:o,atlasMeta:n,cellByPath:i,sheetByIndex:_,diffBase:t}}async function m(e){if(b()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(y&&(!b()||d===(e??null)))return y;d=e??null,A.clear();const t=_t();return y=(async()=>{const r=await M(ot()),n=await v(it(),t,e);let s=null;try{s=await v(ct(),t,e)}catch{s=null}return{sil:r,viseme:n,expression:s}})(),y}function x(e){return K(e.viseme.atlasMeta,e.sil.width,e.sil.height)}function lt(e,t){if(!e.sheetByIndex.get(t.sheet??0))throw new Error(`Unknown sheet index: ${t.sheet}`);const n=new OffscreenCanvas(t.w,t.h);return n.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),n}async function ft(e){return createImageBitmap(e)}async function N(e,t,r){const n=`${t.diffBase}::${r}`,s=A.get(n);if(s)return s;const o=(async()=>{const i=t.cellByPath.get(r);if(!i)throw new Error(`No atlas cell for path: ${r}`);const _=J(t.atlasMeta,i),a=x(e);if(L(i)){const E=lt(t,i),u=new OffscreenCanvas(a.width,a.height),c=u.getContext("2d");return c.fillStyle="#000",c.fillRect(0,0,a.width,a.height),c.drawImage(E,_.x,_.y),ft(u)}const h=`${t.diffBase}/${r}`,l=await M(h);if(l.width===a.width&&l.height===a.height)return l;throw l.close(),new Error(`Diff PNG wrong size for ${r}`)})();return A.set(n,o),o}function k(e,t){const r={type:"status",label:e,generation:t};self.postMessage(r)}function ut(e,t){const r={type:"error",requestId:e,message:t};self.postMessage(r)}async function C(e){if(!f||e!==S&&e!==w)return;const t=await createImageBitmap(f);if(e!==S&&e!==w){t.close();return}const r={type:"frame",bitmap:t,generation:e};self.postMessage(r,{transfer:[t]})}function dt(e,t,r){return new Promise((n,s)=>{setTimeout(()=>{t!==r?s(new DOMException("Aborted","AbortError")):n()},e)})}async function W(e,t,r,n=p){const s=await m(d??void 0),{width:o,height:i}=x(s);(e.width!==o||e.height!==i)&&(e.width=o,e.height=i);const _=[];if(t){if(!s.viseme.cellByPath.get(t))throw new Error(`No viseme atlas cell for path: ${t}`);_.push(await N(s,s.viseme,t))}if(r&&s.expression){if(!s.expression.cellByPath.get(r))throw new Error(`No expression atlas cell for path: ${r}`);_.push(await N(s,s.expression,r))}const a=e.getContext("2d");if(a.clearRect(0,0,o,i),_.length===0){a.drawImage(s.sil,0,0,o,i);return}const h=at(s.sil,_,o,i,n);a.drawImage(h,0,0)}async function T(e,t,r,n,s=p){await W(e,t,r,s),await C(n)}async function wt(e,t,r){F="sil";const n=await m(d??void 0);if(t!==r)throw new DOMException("Aborted","AbortError");const{width:s,height:o}=x(n);e.width=s,e.height=o;const i=e.getContext("2d");i.clearRect(0,0,s,o),i.drawImage(n.sil,0,0,s,o),await C(t)}async function ht(e,t,r,n,s={}){const o=s.from??F,i=s.transitionDurationMs??400,_=s.gapMs??i,a=s.threshold??p;if(await m(d??void 0),o===t){if(k(t,r),await T(e,q(t),null,r,a),r!==n)throw new DOMException("Aborted","AbortError");F=t;return}const h=et(o,t),l=nt(h,_),E=q(t),u=l.length>0?i/l.length:0;l.length===0&&k(`${o} → ${t} (no pair; final only)`,r);for(let c=0;c<l.length;c++){if(r!==n)throw new DOMException("Aborted","AbortError");const D=l[c],R=D.split("/").pop()?.replace("_minus_sil.png","")??`frame ${c}`;if(k(`${o} → ${t} (${R})`,r),await T(e,D,null,r,a),r!==n)throw new DOMException("Aborted","AbortError");u>0&&await dt(u,r,n)}if(r!==n)throw new DOMException("Aborted","AbortError");if(k(t,r),await T(e,E,null,r,a),r!==n)throw new DOMException("Aborted","AbortError");F=t}let w=0;self.onmessage=e=>{const t=e.data;(async()=>{try{switch(t.type){case"init":{f=new OffscreenCanvas(1,1);const r={type:"ready",requestId:t.requestId};self.postMessage(r);break}case"loadAssets":{O=t.urls,y=null,d=null,A.clear(),await m(t.keyHex);const r={type:"loadAssetsDone",requestId:t.requestId};self.postMessage(r);break}case"drawFrame":{if(!f)throw new Error("Worker not initialized");await m(d??void 0),await W(f,t.diffPath,t.expressionDiffPath??null,t.threshold??p),await C(S);const r={type:"renderDone",requestId:t.requestId,generation:S};self.postMessage(r);break}case"renderSil":{if(w=t.generation,!f)throw new Error("Worker not initialized");await wt(f,t.generation,w);const r={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(r);break}case"renderViseme":{if(w=t.generation,!f)throw new Error("Worker not initialized");await ht(f,t.to,t.generation,w,{from:t.from,transitionDurationMs:t.transitionDurationMs,gapMs:t.gapMs,threshold:t.threshold});const r={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(r);break}default:break}}catch(r){if(r instanceof DOMException&&r.name==="AbortError"){const n={type:"renderAborted",requestId:t.requestId,generation:"generation"in t?t.generation:w};self.postMessage(n);return}ut(t.requestId,r instanceof Error?r.message:String(r))}})()}})();