@streamoji/aitwin 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){"use strict";function it(t,e,n){return{width:t.crop?.source_width??e,height:t.crop?.source_height??n}}function ct(t,e){const n=t.crop;return{x:e?.source_x??n?.x??0,y:e?.source_y??n?.y??0,width:e?.source_w??n?.width??t.frame_width??0,height:e?.source_h??n?.height??t.frame_height??0}}function lt(t){return t?(t.w??0)>0&&(t.h??0)>0:!1}function M(t){return t.includes("/custom-faces/")}function K(t){const e=t.binBase.trim().replace(/\/$/,""),n=t.twinBase.trim().replace(/\/$/,"");if(!(t.encrypted||M(e)||M(n)))return{twinBase:n,binBase:e,encrypted:!1};const s=M(e)?e:M(n)?n:e;return{twinBase:s,binBase:s,encrypted:!0}}function m(t){return K(t).encrypted}function _t(t){const e=t.trim();if(e.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(e.length/2);for(let r=0;r<e.length;r+=2)n[r/2]=Number.parseInt(e.slice(r,r+2),16);return n}function B(t){return Uint8Array.from(t)}function ut(t,e){const n=new Uint8Array(t.length+e.length);return n.set(t,0),n.set(e,t.length),n}async function ft(t,e){if(!e)throw new Error("Asset key is required for encrypted assets");if(t.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(t),r=B(n.subarray(0,12)),s=B(n.subarray(12,28)),o=B(n.subarray(28)),a=B(ut(o,s)),c=await crypto.subtle.importKey("raw",_t(e),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},c,a)}async function $(t,e){const n=await fetch(t);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${t}`);const r=await n.arrayBuffer();return ft(r,e)}async function dt(t,e){if(!e)throw new Error("Missing asset key for encrypted JSON");const n=await $(t,e),r=new TextDecoder().decode(n);return JSON.parse(r)}async function wt(t){const e=new Blob([t],{type:"image/webp"});return createImageBitmap(e)}async function pt(t){const e=new Blob([t],{type:"image/png"});return createImageBitmap(e)}function C(t,e){return`${t}/${e}_minus_sil.png`}const Y=5;function J(t){const e=t?.composite_threshold;if(typeof e=="number"&&e>0)return e;const n=t?.crop?.content_threshold;return typeof n=="number"&&n>0?n:Y}const v=["t01_0.25","t02_0.50","t03_0.75"],ht=v.length,G=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 Q(t,e){return`${t}__${e}`}function X(t,e){return`pairs/${t}/${e}_minus_sil.png`}function q(t){return`${t}_minus_sil.png`}function yt(t,e){if(e.has(t))return t;const n=/^pairs\/([^/]+)\//.exec(t);if(!n)return null;const r=n[1].split("__")[1];if(!r||r==="sil")return null;const s=q(r);return e.has(s)?s:null}function mt(t,e){return t.filter(n=>e.has(n))}function gt(t,e){if(t===e)return[];const n=[],r=Q(t,e);if(G.has(r)){for(const o of v)n.push(X(r,o));return n}const s=Q(e,t);if(G.has(s)){for(let o=ht-1;o>=0;o--)n.push(X(s,v[o]));return n}return[]}function Et(t){return t<16?[1]:t<24?[1]:t<35?[0,1,2]:[0,1,2]}function It(t,e){return Et(e).map(r=>t[r]).filter(r=>r!=null)}function bt(t,e,n){if(!n)return`${t}/${e}`;const r=e.split("/").pop()??e,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${t}/${s}.bin`}function Dt(t,e,n,r,s=Y){const o=new OffscreenCanvas(n,r),a=o.getContext("2d",{willReadFrequently:!0});a.drawImage(t,0,0,n,r);const c=a.getImageData(0,0,n,r),i=a.createImageData(n,r);i.data.set(c.data);const f=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const d of e){f.clearRect(0,0,n,r),f.drawImage(d,0,0,n,r);const _=f.getImageData(0,0,n,r);for(let u=0;u<i.data.length;u+=4){const y=_.data[u],F=_.data[u+1],x=_.data[u+2];Math.max(y,F,x)>s&&(i.data[u]=y,i.data[u+1]=F,i.data[u+2]=x,i.data[u+3]=255)}}return a.putImageData(i,0,0),o}[.32,.24,.16,.08,0].map(t=>`eyes_${t.toFixed(2)}`);const P=2,St=-30,j=20,Ft={fear:-20,anger:-20,sad:-10,neutral:0,love:0,happy:0,disgust:10};function Z(t){return`brow_${t}`}function Mt(t){return Ft[t]}function V(t){return Math.max(St,Math.min(j,t))}function tt(t){const e=V(t);if(e%P===0)return[e];const n=Math.floor(e/P)*P,r=Math.min(j,n+P),s=V(n);return e-s<=r-e?s===r?[s]:[s,r]:s===r?[r]:[r,s]}const Bt={[-20]:"anger",[-10]:"sad",0:"neutral",10:"disgust"};function Pt(t,e){if(!t)return null;if(e(t))return t;const n=t.match(/^brow_(-?\d+)\/(eyes_[^/]+)_minus_sil\.png$/);if(n){const s=Number(n[1]),o=n[2];for(const c of tt(s)){if(c===s)continue;const i=C(Z(c),o);if(e(i))return i}const a=Bt[s];if(a){const c=C(a,o);if(e(c))return c}return null}const r=t.match(/^(fear|anger|sad|neutral|love|happy|disgust)\/(eyes_[^/]+)_minus_sil\.png$/);if(r){const s=r[1],o=r[2],a=Mt(s);for(const c of[a,...tt(a)]){const i=C(Z(c),o);if(e(i))return i}}return null}const et=-1,kt=1e5;function Ot(t){return t>=kt}function Rt(t){return t<0}let g=0,nt=Promise.resolve(),E=!1;function H(t){const e=nt.then(t);return nt=e.then(()=>{},()=>{}),e}function I(t,e){const n=e>0?-e:et,r={type:"renderDone",requestId:t,generation:n};self.postMessage(r)}async function At(t){const e=typeof t.liveSeq=="number"?t.liveSeq:0,n=Ot(e);if(E&&!n){I(t.requestId,e);return}if(e>0&&!n&&(g=e),!w)throw new Error("Worker not initialized");if(e>0&&!n&&e!==g){I(t.requestId,e);return}if(await S(h??void 0),e>0&&!n&&e!==g){I(t.requestId,e);return}if(await at(w,t.diffPath,t.expressionDiffPath??null,t.threshold),e>0&&!n&&e!==g){I(t.requestId,e);return}const r=e>0?-e:et;await L(r),I(t.requestId,e)}let w=null,b=null,h=null,U=null,k="sil";const O=new Map;function D(){if(!U)throw new Error("Worker assets not configured");return U}function rt(){const t=D();return m(t)?`${t.binBase}/sil.bin`:`${t.twinBase}/sil.png`}function Tt(){const t=D();return m(t)?`${t.binBase}/atlas.bin`:`${t.twinBase}/atlas.json`}function xt(){const t=D();return m(t)?`${t.binBase}/expression_atlas.bin`:`${t.twinBase}/expression_atlas.json`}function $t(){const t=D();return m(t)?t.binBase:t.twinBase}function R(){return m(D())}async function N(t){const e=await fetch(t);if(!e.ok)throw new Error(`Failed to load image: ${t}`);const n=await e.blob();return createImageBitmap(n)}async function Ct(t){return R()?pt(await $(rt(),t??"")):N(rt())}async function st(t,e,n){const r=R(),s=r?await dt(t,n):await fetch(t).then(l=>{if(!l.ok)throw new Error(`Failed to load ${t}`);return l.json()}),o=s.sheets?.[0];if(!o?.path)throw new Error(`${t} has no sheets[0].path`);const a=r?await wt(await $(bt(e,o.path,r),n??"")):await N(`${e}/${o.path}`),c=new Map;for(const l of s.cells??[])l.path&&c.set(l.path,l);const i=new Map;for(const l of s.sheets??[])i.set(l.index,l);return{atlas:a,atlasMeta:s,cellByPath:c,sheetByIndex:i,diffBase:e}}async function S(t){if(R()&&!t)throw new Error("Encrypted assets enabled but no key provided");if(b&&(!R()||h===(t??null)))return b;h=t??null,O.clear();const e=$t();return b=(async()=>{const n=await Ct(t),r=await st(Tt(),e,t);let s=null;try{s=await st(xt(),e,t)}catch{s=null}return{sil:n,viseme:r,expression:s}})(),b}function A(t){return it(t.viseme.atlasMeta,t.sil.width,t.sil.height)}function vt(t,e){if(!t.sheetByIndex.get(e.sheet??0))throw new Error(`Unknown sheet index: ${e.sheet}`);const r=new OffscreenCanvas(e.w,e.h);return r.getContext("2d",{willReadFrequently:!0}).drawImage(t.atlas,e.x,e.y,e.w,e.h,0,0,e.w,e.h),r}async function qt(t){return createImageBitmap(t)}async function ot(t,e,n){const r=`${e.diffBase}::${n}`,s=O.get(r);if(s)return s;const o=(async()=>{const a=e.cellByPath.get(n);if(!a)throw new Error(`No atlas cell for path: ${n}`);const c=ct(e.atlasMeta,a),i=A(t);if(lt(a)){const d=vt(e,a),_=new OffscreenCanvas(i.width,i.height),u=_.getContext("2d");return u.fillStyle="#000",u.fillRect(0,0,i.width,i.height),u.drawImage(d,c.x,c.y),qt(_)}const l=`${e.diffBase}/${n}`,f=await N(l);if(f.width===i.width&&f.height===i.height)return f;throw f.close(),new Error(`Diff PNG wrong size for ${n}`)})();return O.set(r,o),o}function T(t,e){const n={type:"status",label:t,generation:e};self.postMessage(n)}function Ht(t,e){const n={type:"error",requestId:t,message:e};self.postMessage(n)}async function L(t){if(!w)return;const e=Rt(t);if(!e&&t!==p)return;const n=await createImageBitmap(w);if(!e&&t!==p){n.close();return}const r={type:"frame",bitmap:n,generation:t};self.postMessage(r,{transfer:[n]})}function Ut(t,e,n){return new Promise((r,s)=>{setTimeout(()=>{e!==n?s(new DOMException("Aborted","AbortError")):r()},t)})}async function at(t,e,n,r){const s=await S(h??void 0),o=r??J(s.viseme.atlasMeta),{width:a,height:c}=A(s);(t.width!==a||t.height!==c)&&(t.width=a,t.height=c);const i=[];if(e){const d=new Set(s.viseme.cellByPath.keys()),_=yt(e,d)??(d.has(e)?e:null);_&&i.push(await ot(s,s.viseme,_))}if(n&&s.expression){const d=Pt(n,_=>s.expression.cellByPath.has(_));d&&i.push(await ot(s,s.expression,d))}const l=t.getContext("2d");if(l.clearRect(0,0,a,c),i.length===0){l.drawImage(s.sil,0,0,a,c);return}const f=Dt(s.sil,i,a,c,o);l.drawImage(f,0,0)}async function W(t,e,n,r,s){await at(t,e,n,s),await L(r)}async function z(t,e,n){k="sil";const r=await S(h??void 0);if(e!==n)throw new DOMException("Aborted","AbortError");const{width:s,height:o}=A(r);t.width=s,t.height=o;const a=t.getContext("2d");a.clearRect(0,0,s,o),a.drawImage(r.sil,0,0,s,o),await L(e)}async function Nt(t,e,n,r,s={}){const o=s.from??k,a=s.transitionDurationMs??400,c=s.gapMs??a,i=await S(h??void 0),l=s.threshold??J(i.viseme.atlasMeta);if(o===e){if(T(e,n),e==="sil"?await z(t,n,r):await W(t,q(e),null,n,l),n!==r)throw new DOMException("Aborted","AbortError");k=e;return}const f=gt(o,e),d=new Set(i.viseme.cellByPath.keys());let _=It(f,c);_=mt(_,d);const u=_.length>0?a/_.length:0;_.length===0&&T(`${o} → ${e} (no pair; final only)`,n);for(let y=0;y<_.length;y++){if(n!==r)throw new DOMException("Aborted","AbortError");const F=_[y],x=F.split("/").pop()?.replace("_minus_sil.png","")??`frame ${y}`;if(T(`${o} → ${e} (${x})`,n),await W(t,F,null,n,l),n!==r)throw new DOMException("Aborted","AbortError");u>0&&await Ut(u,n,r)}if(n!==r)throw new DOMException("Aborted","AbortError");if(T(e,n),e==="sil"?await z(t,n,r):await W(t,q(e),null,n,l),n!==r)throw new DOMException("Aborted","AbortError");k=e}let p=0;self.onmessage=t=>{const e=t.data;(async()=>{try{switch(e.type){case"init":{w=new OffscreenCanvas(1,1);const n={type:"ready",requestId:e.requestId};self.postMessage(n);break}case"loadAssets":{U=K(e.urls),b=null,h=null,O.clear();const n=await S(e.keyHex),{width:r,height:s}=A(n),o={type:"loadAssetsDone",requestId:e.requestId,width:r,height:s,visemePaths:[...n.viseme.cellByPath.keys()]};self.postMessage(o);break}case"cancelLiveDraws":{g+=1;const n={type:"renderDone",requestId:e.requestId,generation:p};self.postMessage(n);break}case"drawFrame":{await H(()=>At(e));break}case"renderSil":{await H(async()=>{E=!0;try{if(p=e.generation,!w)throw new Error("Worker not initialized");await z(w,e.generation,p)}finally{E=!1}});const n={type:"renderDone",requestId:e.requestId,generation:e.generation};self.postMessage(n);break}case"renderViseme":{await H(async()=>{E=!0;try{if(p=e.generation,!w)throw new Error("Worker not initialized");await Nt(w,e.to,e.generation,p,{from:e.from,transitionDurationMs:e.transitionDurationMs,gapMs:e.gapMs,threshold:e.threshold})}finally{E=!1}});const n={type:"renderDone",requestId:e.requestId,generation:e.generation};self.postMessage(n);break}default:break}}catch(n){if(n instanceof DOMException&&n.name==="AbortError"){const r={type:"renderAborted",requestId:e.requestId,generation:"generation"in e?e.generation:p};self.postMessage(r);return}Ht(e.requestId,n instanceof Error?n.message:String(n))}})()}})();
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,15 @@ export declare type AiTwinHandle = {
|
|
|
11
11
|
stop: () => void;
|
|
12
12
|
setTtsEngineId: (engineId: TtsEngineId) => void;
|
|
13
13
|
renderViseme: (toViseme: string, options?: AiTwinRenderVisemeOptions) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Hold a still-face expression preview (eyebrow atlas mood).
|
|
16
|
+
* Suspends idle video until the next speak/stop clears preview mode.
|
|
17
|
+
*/
|
|
18
|
+
setEmotion: (emotion: string) => void;
|
|
19
|
+
/** Active expression preview emotion, or null when not in preview mode. */
|
|
20
|
+
getEmotion: () => string | null;
|
|
21
|
+
/** Leave expression preview and return to normal idle. */
|
|
22
|
+
clearEmotionPreview: () => void;
|
|
14
23
|
isReady: () => boolean;
|
|
15
24
|
getStatus: () => AvatarTtsLipsyncStatus;
|
|
16
25
|
/** Connect mic + WebSocket voice session over `/ws/voice`. */
|
|
@@ -24,11 +33,6 @@ export declare type AiTwinHandle = {
|
|
|
24
33
|
* Null until a voice session (or TTS playback) has primed audio.
|
|
25
34
|
*/
|
|
26
35
|
getVoiceStream: () => MediaStream | null;
|
|
27
|
-
/** Mute speaker output (getVoiceStream stays hot for external mixing). */
|
|
28
|
-
setMuted: (muted: boolean) => void;
|
|
29
|
-
isMuted: () => boolean;
|
|
30
|
-
/** Persona / knowledge context id from getAiTwin, when loaded via cloud id. */
|
|
31
|
-
getPersonaId: () => string | null;
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
export declare class AiTwinNotFoundError extends Error {
|
|
@@ -144,9 +148,6 @@ export declare type AvatarTtsLipsyncController = {
|
|
|
144
148
|
setLipsyncWarmupHold: (enabled: boolean, shouldHold?: () => boolean) => void;
|
|
145
149
|
/** Live MediaStream of the synthesized voice for recording/mixing (null until audio is primed). */
|
|
146
150
|
getVoiceStream: () => MediaStream | null;
|
|
147
|
-
/** Mute speaker output (getVoiceStream stays hot for external mixing). */
|
|
148
|
-
setMuted: (muted: boolean) => void;
|
|
149
|
-
isMuted: () => boolean;
|
|
150
151
|
setPlaybackSpeed: (rate: number) => void;
|
|
151
152
|
getPlaybackSpeed: () => number;
|
|
152
153
|
subscribe: (listener: () => void) => () => void;
|
|
@@ -166,14 +167,38 @@ export declare type AvatarTtsSpeakOptions = {
|
|
|
166
167
|
ttsEngineId?: TtsEngineId;
|
|
167
168
|
};
|
|
168
169
|
|
|
170
|
+
/** Pair transitions for playback, including sil→first and last→sil bookends. */
|
|
171
|
+
export declare function buildPlaybackTransitions(queue: VisemeQueueItem[]): RawVisemeTransition[];
|
|
172
|
+
|
|
173
|
+
/** Collapsed + compacted viseme series used for lipsync playback. */
|
|
174
|
+
export declare function buildPlaybackVisemeSeries(queue: VisemeQueueItem[]): VisemeQueueItem[];
|
|
175
|
+
|
|
176
|
+
export declare function buildRawVisemeTransitions(queue: VisemeQueueItem[]): RawVisemeTransition[];
|
|
177
|
+
|
|
169
178
|
export declare function buildVoiceWebSocketUrl(options: RealtimeVoiceOptions): string;
|
|
170
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Drop brief middle visemes in {aa,E,I} and {DD,SS,kk,CH} when either adjacent
|
|
182
|
+
* gap is under VISEME_SKIP_GAP_MS. First and last entries in the series are kept.
|
|
183
|
+
*/
|
|
184
|
+
export declare function compactSkippableVisemes(series: VisemeQueueItem[]): VisemeQueueItem[];
|
|
185
|
+
|
|
186
|
+
export declare const CONSONANT_SKIP_GROUP: Set<string>;
|
|
187
|
+
|
|
171
188
|
export declare function createAvatarTtsLipsyncController(onStatus: (status: AvatarTtsLipsyncStatus) => void, apiBase?: string, options?: AvatarTtsLipsyncControllerOptions): AvatarTtsLipsyncController;
|
|
172
189
|
|
|
173
190
|
export declare function createRealtimeVoiceLipsyncController(onStatus: (status: AvatarTtsLipsyncStatus) => void, initialOptions?: RealtimeVoiceOptions): RealtimeVoiceLipsyncController;
|
|
174
191
|
|
|
175
192
|
export declare const DEFAULT_TTS_ENGINE_ID: TtsEngineId;
|
|
176
193
|
|
|
194
|
+
export declare const EMOTION_TO_EYEBROW: Record<ExpressionEmotion, number>;
|
|
195
|
+
|
|
196
|
+
export declare function emotionToEyebrow(emotion: ExpressionEmotion): number;
|
|
197
|
+
|
|
198
|
+
export declare const EXPRESSION_ATLAS_EMOTIONS: readonly ["fear", "anger", "sad", "neutral", "love", "happy", "disgust"];
|
|
199
|
+
|
|
200
|
+
export declare type ExpressionEmotion = (typeof EXPRESSION_ATLAS_EMOTIONS)[number];
|
|
201
|
+
|
|
177
202
|
export declare function faceAssetUrls(faceId: string, cdnBase?: string): TwinAssetUrls;
|
|
178
203
|
|
|
179
204
|
export declare function fetchAiTwin(id: string, baseUrl?: string): Promise<AiTwinRecord>;
|
|
@@ -241,16 +266,28 @@ export declare function getAiTwinSourceUrl(faceId: string, cdnBase?: string, ext
|
|
|
241
266
|
/** Public CDN thumbnail for custom-faces/{faceId}/ (same id as pipeline avatarId). */
|
|
242
267
|
export declare function getAiTwinThumbnailUrl(faceId: string, cdnBase?: string): string;
|
|
243
268
|
|
|
269
|
+
/** Visemes removed by compaction (for debug). */
|
|
270
|
+
export declare function listSkippedVisemes(queue: VisemeQueueItem[]): string[];
|
|
271
|
+
|
|
244
272
|
export declare const MIC_SAMPLE_RATE = 16000;
|
|
245
273
|
|
|
246
274
|
/** Google TTS streams Oculus viseme symbols; normalize casing and unknown ids. */
|
|
247
275
|
export declare function normalizeOculusViseme(symbol: string | null | undefined): string;
|
|
248
276
|
|
|
277
|
+
export declare function normalizeStreamEmotion(raw: string | undefined | null, fallback?: ExpressionEmotion): ExpressionEmotion;
|
|
278
|
+
|
|
249
279
|
/** Oculus viseme ids for Talking Lady stills and pair transitions (15 standard). */
|
|
250
280
|
export declare const OCULUS_VISEME_IDS: readonly ["aa", "CH", "DD", "E", "FF", "I", "O", "PP", "RR", "SS", "TH", "U", "kk", "nn", "sil"];
|
|
251
281
|
|
|
252
282
|
export declare const PLAYBACK_SAMPLE_RATE = 16000;
|
|
253
283
|
|
|
284
|
+
declare type RawVisemeTransition = {
|
|
285
|
+
from: string;
|
|
286
|
+
to: string;
|
|
287
|
+
fromVtime: number;
|
|
288
|
+
toVtime: number;
|
|
289
|
+
};
|
|
290
|
+
|
|
254
291
|
export declare type RealtimeLipsyncDebugSnapshot = {
|
|
255
292
|
event: "metadata" | "audio_chunk" | "lipsync" | "bot_started" | "bot_stopped" | "user_started" | "interrupt" | "reset";
|
|
256
293
|
spokenText: string;
|
|
@@ -301,6 +338,9 @@ export declare function resolveDirectFaceId(avatarId?: string, faceId?: string):
|
|
|
301
338
|
/** Pick the active viseme at playback offset (latest vtime wins on overlap). */
|
|
302
339
|
export declare function resolveVisemeAtTime(queue: VisemeQueueItem[], elapsedMs: number): string;
|
|
303
340
|
|
|
341
|
+
/** Latest non-sil viseme in the playback series at or before elapsedMs. */
|
|
342
|
+
export declare function resolveVisemeFromPlaybackSeries(series: VisemeQueueItem[], elapsedMs: number): string;
|
|
343
|
+
|
|
304
344
|
/** Word active at playback offset (ms). */
|
|
305
345
|
export declare function resolveWordAtTime(words: WordQueueItem[], timeMs: number): WordQueueItem | null;
|
|
306
346
|
|
|
@@ -339,6 +379,9 @@ export declare type TwinAssetUrls = {
|
|
|
339
379
|
|
|
340
380
|
export declare const VISEME_IDS: readonly ["aa", "E", "I", "O", "U", "PP", "FF", "DD", "SS", "TH", "CH", "RR", "kk", "nn"];
|
|
341
381
|
|
|
382
|
+
/** Minimum gap (ms) between consecutive vtimes to keep a skippable viseme. */
|
|
383
|
+
export declare const VISEME_SKIP_GAP_MS = 35;
|
|
384
|
+
|
|
342
385
|
/** Preset Cartesia voice (Katie) for testing without a custom clone. */
|
|
343
386
|
export declare const VISEME_TEST_CARTESIA_VOICE_ID = "f786b574-daa5-4673-aa0c-cbe3e8534c02";
|
|
344
387
|
|
|
@@ -370,6 +413,8 @@ export declare type VoiceConnectOptions = {
|
|
|
370
413
|
wsUrl?: string;
|
|
371
414
|
};
|
|
372
415
|
|
|
416
|
+
export declare const VOWEL_SKIP_GROUP: Set<string>;
|
|
417
|
+
|
|
373
418
|
export declare interface WordQueueItem {
|
|
374
419
|
word: string;
|
|
375
420
|
wtime: number;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(){"use strict";function et(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function nt(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 rt(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}function k(e){return e.includes("/custom-faces/")}function z(e){const t=e.binBase.trim().replace(/\/$/,""),n=e.twinBase.trim().replace(/\/$/,"");if(!(e.encrypted||k(t)||k(n)))return{twinBase:n,binBase:t,encrypted:!1};const s=k(t)?t:k(n)?n:t;return{twinBase:s,binBase:s,encrypted:!0}}function m(e){return z(e).encrypted}function st(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 P(e){return Uint8Array.from(e)}function at(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function ot(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=P(n.subarray(0,12)),s=P(n.subarray(12,28)),a=P(n.subarray(28)),o=P(at(a,s)),l=await crypto.subtle.importKey("raw",st(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},l,o)}async function O(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 ot(r,t)}async function it(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await O(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function ct(e){const t=new Blob([e],{type:"image/webp"});return createImageBitmap(t)}async function lt(e){const t=new Blob([e],{type:"image/png"});return createImageBitmap(t)}const W=5;function K(e){const t=e?.composite_threshold;if(typeof t=="number"&&t>0)return t;const n=e?.crop?.content_threshold;return typeof n=="number"&&n>0?n:W}const T=["t01_0.25","t02_0.50","t03_0.75"],_t=T.length,J=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 G(e,t){return`${e}__${t}`}function Q(e,t){return`pairs/${e}/${t}_minus_sil.png`}function $(e){return`${e}_minus_sil.png`}function ft(e,t){if(t.has(e))return e;const n=/^pairs\/([^/]+)\//.exec(e);if(!n)return null;const r=n[1].split("__")[1];if(!r||r==="sil")return null;const s=$(r);return t.has(s)?s:null}function ut(e,t){return e.filter(n=>t.has(n))}function dt(e,t){if(e===t)return[];const n=[],r=G(e,t);if(J.has(r)){for(const a of T)n.push(Q(r,a));return n}const s=G(t,e);if(J.has(s)){for(let a=_t-1;a>=0;a--)n.push(Q(s,T[a]));return n}return[]}function wt(e){return e<16?[1]:e<24?[1]:e<35?[0,1,2]:[0,1,2]}function ht(e,t){return wt(t).map(r=>e[r]).filter(r=>r!=null)}function pt(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 yt(e,t,n,r,s=W){const a=new OffscreenCanvas(n,r),o=a.getContext("2d",{willReadFrequently:!0});o.drawImage(e,0,0,n,r);const l=o.getImageData(0,0,n,r),i=o.createImageData(n,r);i.data.set(l.data);const u=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const w of t){u.clearRect(0,0,n,r),u.drawImage(w,0,0,n,r);const _=u.getImageData(0,0,n,r);for(let f=0;f<i.data.length;f+=4){const y=_.data[f],F=_.data[f+1],x=_.data[f+2];Math.max(y,F,x)>s&&(i.data[f]=y,i.data[f+1]=F,i.data[f+2]=x,i.data[f+3]=255)}}return o.putImageData(i,0,0),a}const j=-1,mt=1e5;function gt(e){return e>=mt}function Dt(e){return e<0}let g=0,X=Promise.resolve(),D=!1;function v(e){const t=X.then(e);return X=t.then(()=>{},()=>{}),t}function E(e,t){const n=t>0?-t:j,r={type:"renderDone",requestId:e,generation:n};self.postMessage(r)}async function Et(e){const t=typeof e.liveSeq=="number"?e.liveSeq:0,n=gt(t);if(D&&!n){E(e.requestId,t);return}if(t>0&&!n&&(g=t),!d)throw new Error("Worker not initialized");if(t>0&&!n&&t!==g){E(e.requestId,t);return}if(await b(p??void 0),t>0&&!n&&t!==g){E(e.requestId,t);return}if(await tt(d,e.diffPath,e.expressionDiffPath??null,e.threshold),t>0&&!n&&t!==g){E(e.requestId,t);return}const r=t>0?-t:j;await U(r),E(e.requestId,t)}let d=null,I=null,p=null,q=null,A="sil";const B=new Map;function S(){if(!q)throw new Error("Worker assets not configured");return q}function Y(){const e=S();return m(e)?`${e.binBase}/sil.bin`:`${e.twinBase}/sil.png`}function It(){const e=S();return m(e)?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function St(){const e=S();return m(e)?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function bt(){const e=S();return m(e)?e.binBase:e.twinBase}function M(){return m(S())}async function H(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 Ft(e){return M()?lt(await O(Y(),e??"")):H(Y())}async function Z(e,t,n){const r=M(),s=r?await it(e,n):await fetch(e).then(c=>{if(!c.ok)throw new Error(`Failed to load ${e}`);return c.json()}),a=s.sheets?.[0];if(!a?.path)throw new Error(`${e} has no sheets[0].path`);const o=r?await ct(await O(pt(t,a.path,r),n??"")):await H(`${t}/${a.path}`),l=new Map;for(const c of s.cells??[])c.path&&l.set(c.path,c);const i=new Map;for(const c of s.sheets??[])i.set(c.index,c);return{atlas:o,atlasMeta:s,cellByPath:l,sheetByIndex:i,diffBase:t}}async function b(e){if(M()&&!e)throw new Error("Encrypted assets enabled but no key provided");if(I&&(!M()||p===(e??null)))return I;p=e??null,B.clear();const t=bt();return I=(async()=>{const n=await Ft(e),r=await Z(It(),t,e);let s=null;try{s=await Z(St(),t,e)}catch{s=null}return{sil:n,viseme:r,expression:s}})(),I}function R(e){return et(e.viseme.atlasMeta,e.sil.width,e.sil.height)}function kt(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 Pt(e){return createImageBitmap(e)}async function V(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 l=nt(t.atlasMeta,o),i=R(e);if(rt(o)){const w=kt(t,o),_=new OffscreenCanvas(i.width,i.height),f=_.getContext("2d");return f.fillStyle="#000",f.fillRect(0,0,i.width,i.height),f.drawImage(w,l.x,l.y),Pt(_)}const c=`${t.diffBase}/${n}`,u=await H(c);if(u.width===i.width&&u.height===i.height)return u;throw u.close(),new Error(`Diff PNG wrong size for ${n}`)})();return B.set(r,a),a}function C(e,t){const n={type:"status",label:e,generation:t};self.postMessage(n)}function At(e,t){const n={type:"error",requestId:e,message:t};self.postMessage(n)}async function U(e){if(!d)return;const t=Dt(e);if(!t&&e!==h)return;const n=await createImageBitmap(d);if(!t&&e!==h){n.close();return}const r={type:"frame",bitmap:n,generation:e};self.postMessage(r,{transfer:[n]})}function Bt(e,t,n){return new Promise((r,s)=>{setTimeout(()=>{t!==n?s(new DOMException("Aborted","AbortError")):r()},e)})}async function tt(e,t,n,r){const s=await b(p??void 0),a=r??K(s.viseme.atlasMeta),{width:o,height:l}=R(s);(e.width!==o||e.height!==l)&&(e.width=o,e.height=l);const i=[];if(t){const w=new Set(s.viseme.cellByPath.keys()),_=ft(t,w)??(w.has(t)?t:null);_&&i.push(await V(s,s.viseme,_))}n&&s.expression&&s.expression.cellByPath.get(n)&&i.push(await V(s,s.expression,n));const c=e.getContext("2d");if(c.clearRect(0,0,o,l),i.length===0){c.drawImage(s.sil,0,0,o,l);return}const u=yt(s.sil,i,o,l,a);c.drawImage(u,0,0)}async function L(e,t,n,r,s){await tt(e,t,n,s),await U(r)}async function N(e,t,n){A="sil";const r=await b(p??void 0);if(t!==n)throw new DOMException("Aborted","AbortError");const{width:s,height:a}=R(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 U(t)}async function Mt(e,t,n,r,s={}){const a=s.from??A,o=s.transitionDurationMs??400,l=s.gapMs??o,i=await b(p??void 0),c=s.threshold??K(i.viseme.atlasMeta);if(a===t){if(C(t,n),t==="sil"?await N(e,n,r):await L(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t;return}const u=dt(a,t),w=new Set(i.viseme.cellByPath.keys());let _=ht(u,l);_=ut(_,w);const f=_.length>0?o/_.length:0;_.length===0&&C(`${a} → ${t} (no pair; final only)`,n);for(let y=0;y<_.length;y++){if(n!==r)throw new DOMException("Aborted","AbortError");const F=_[y],x=F.split("/").pop()?.replace("_minus_sil.png","")??`frame ${y}`;if(C(`${a} → ${t} (${x})`,n),await L(e,F,null,n,c),n!==r)throw new DOMException("Aborted","AbortError");f>0&&await Bt(f,n,r)}if(n!==r)throw new DOMException("Aborted","AbortError");if(C(t,n),t==="sil"?await N(e,n,r):await L(e,$(t),null,n,c),n!==r)throw new DOMException("Aborted","AbortError");A=t}let h=0;self.onmessage=e=>{const t=e.data;(async()=>{try{switch(t.type){case"init":{d=new OffscreenCanvas(1,1);const n={type:"ready",requestId:t.requestId};self.postMessage(n);break}case"loadAssets":{q=z(t.urls),I=null,p=null,B.clear();const n=await b(t.keyHex),{width:r,height:s}=R(n),a={type:"loadAssetsDone",requestId:t.requestId,width:r,height:s,visemePaths:[...n.viseme.cellByPath.keys()]};self.postMessage(a);break}case"cancelLiveDraws":{g+=1;const n={type:"renderDone",requestId:t.requestId,generation:h};self.postMessage(n);break}case"drawFrame":{await v(()=>Et(t));break}case"renderSil":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await N(d,t.generation,h)}finally{D=!1}});const n={type:"renderDone",requestId:t.requestId,generation:t.generation};self.postMessage(n);break}case"renderViseme":{await v(async()=>{D=!0;try{if(h=t.generation,!d)throw new Error("Worker not initialized");await Mt(d,t.to,t.generation,h,{from:t.from,transitionDurationMs:t.transitionDurationMs,gapMs:t.gapMs,threshold:t.threshold})}finally{D=!1}});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}At(t.requestId,n instanceof Error?n.message:String(n))}})()}})();
|