@goodganglabs/lipsync-wasm-v1 0.4.5
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 +661 -0
- package/lipsync-wasm-wrapper.d.ts +59 -0
- package/lipsync-wasm-wrapper.js +1 -0
- package/lipsync_wasm_v1.js +1 -0
- package/lipsync_wasm_v1_bg.wasm +0 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
# @goodganglabs/lipsync-wasm-v1
|
|
2
|
+
|
|
3
|
+
WebAssembly-based real-time audio-to-blendshape lip sync engine.
|
|
4
|
+
Converts 16kHz PCM audio into **111-dimensional ARKit-compatible blendshape** frames at 30fps using a phoneme classification model.
|
|
5
|
+
|
|
6
|
+
**[Website](https://animasync.quasar.ggls.dev)** · **[Start Building](https://animasync.quasar.ggls.dev/examples/guide)** · **[GitHub](https://github.com/goodganglabs/AnimaSync)**
|
|
7
|
+
|
|
8
|
+
## Which Version?
|
|
9
|
+
|
|
10
|
+
| | V1 (this package) | V2 |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| **Dimensions** | 111-dim ARKit | 52-dim ARKit |
|
|
13
|
+
| **Model** | Phoneme classification | Student distillation |
|
|
14
|
+
| **Idle expression** | Built-in `IdleExpressionGenerator` | Built-in `IdleExpressionGenerator` |
|
|
15
|
+
| **VAD** | Built-in `VoiceActivityDetector` | Not included |
|
|
16
|
+
| **ONNX fallback** | Heuristic fallback | None (ONNX required) |
|
|
17
|
+
| **Post-processing** | Manual | Built-in (crisp mouth, fade, blinks) |
|
|
18
|
+
| **Recommendation** | Full expression control needed | Most use cases |
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- 111-dim ARKit blendshape output (phoneme-based model)
|
|
23
|
+
- Batch and real-time streaming processing
|
|
24
|
+
- Built-in expression preset blending
|
|
25
|
+
- Embedded VRMA bone animation data (idle + speaking)
|
|
26
|
+
- Built-in idle expression generator (eye blinks + micro expressions)
|
|
27
|
+
- VRM 18-dim blendshape output (automatic ARKit→VRM conversion)
|
|
28
|
+
- Built-in voice activity detection (VAD) with auto-calibration
|
|
29
|
+
- ONNX Runtime inference with automatic heuristic fallback
|
|
30
|
+
- 30-day free trial (no license key required)
|
|
31
|
+
- Runs entirely in the browser via WebAssembly
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- **onnxruntime-web** `>=1.17.0` (loaded via `<script>` tag before your module code)
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/ort.min.js"></script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @goodganglabs/lipsync-wasm-v1
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### Minimal Example (Batch Processing)
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { LipSyncWasmWrapper } from '@goodganglabs/lipsync-wasm-v1';
|
|
53
|
+
|
|
54
|
+
const lipsync = new LipSyncWasmWrapper();
|
|
55
|
+
await lipsync.init();
|
|
56
|
+
|
|
57
|
+
// Process an audio file
|
|
58
|
+
const result = await lipsync.processFile(audioFile);
|
|
59
|
+
|
|
60
|
+
// Each frame is a number[111] array of ARKit blendshape weights
|
|
61
|
+
for (let i = 0; i < result.frame_count; i++) {
|
|
62
|
+
const frame = lipsync.getFrame(result, i);
|
|
63
|
+
applyToAvatar(frame); // your rendering code
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
lipsync.dispose();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Complete Working Example (Three.js + VRM)
|
|
70
|
+
|
|
71
|
+
Copy-paste ready. This example handles **everything**: VRM loading, VRMA bone animations (idle/speaking crossfade), blendshape application, idle expressions, 30fps frame consumption, and audio-synced playback.
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!DOCTYPE html>
|
|
75
|
+
<html>
|
|
76
|
+
<head>
|
|
77
|
+
<meta charset="UTF-8">
|
|
78
|
+
<script type="importmap">
|
|
79
|
+
{ "imports": {
|
|
80
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.179.1/build/three.module.js",
|
|
81
|
+
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.179.1/examples/jsm/",
|
|
82
|
+
"@pixiv/three-vrm": "https://cdn.jsdelivr.net/npm/@pixiv/three-vrm@3.4.5/lib/three-vrm.module.min.js",
|
|
83
|
+
"@pixiv/three-vrm-animation": "https://cdn.jsdelivr.net/npm/@pixiv/three-vrm-animation@3.4.5/lib/three-vrm-animation.module.min.js"
|
|
84
|
+
}}
|
|
85
|
+
</script>
|
|
86
|
+
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/ort.min.js"></script>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
|
|
90
|
+
<canvas id="avatar-canvas" style="width:100%; height:500px;"></canvas>
|
|
91
|
+
<input type="file" id="audio-file" accept="audio/*">
|
|
92
|
+
|
|
93
|
+
<script type="module">
|
|
94
|
+
import * as THREE from 'three';
|
|
95
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
96
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
97
|
+
import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
|
|
98
|
+
import { VRMAnimationLoaderPlugin, createVRMAnimationClip } from '@pixiv/three-vrm-animation';
|
|
99
|
+
import { LipSyncWasmWrapper } from '@goodganglabs/lipsync-wasm-v1';
|
|
100
|
+
|
|
101
|
+
// ============================================================
|
|
102
|
+
// Step 1: ARKit Blendshape Mapping (first 52 of 111-dim)
|
|
103
|
+
// ============================================================
|
|
104
|
+
const ARKIT_NAMES = {
|
|
105
|
+
0:'browDownLeft',1:'browDownRight',2:'browInnerUp',3:'browOuterUpLeft',4:'browOuterUpRight',
|
|
106
|
+
5:'cheekPuff',6:'cheekSquintLeft',7:'cheekSquintRight',8:'eyeBlinkLeft',9:'eyeBlinkRight',
|
|
107
|
+
10:'eyeLookDownLeft',11:'eyeLookDownRight',12:'eyeLookInLeft',13:'eyeLookInRight',
|
|
108
|
+
14:'eyeLookOutLeft',15:'eyeLookOutRight',16:'eyeLookUpLeft',17:'eyeLookUpRight',
|
|
109
|
+
18:'eyeSquintLeft',19:'eyeSquintRight',20:'eyeWideLeft',21:'eyeWideRight',
|
|
110
|
+
22:'jawForward',23:'jawLeft',24:'jawOpen',25:'jawRight',
|
|
111
|
+
26:'mouthClose',27:'mouthDimpleLeft',28:'mouthDimpleRight',
|
|
112
|
+
29:'mouthFrownLeft',30:'mouthFrownRight',31:'mouthFunnel',
|
|
113
|
+
32:'mouthLeft',33:'mouthLowerDownLeft',34:'mouthLowerDownRight',
|
|
114
|
+
35:'mouthPressLeft',36:'mouthPressRight',37:'mouthPucker',
|
|
115
|
+
38:'mouthRight',39:'mouthRollLower',40:'mouthRollUpper',
|
|
116
|
+
41:'mouthShrugLower',42:'mouthShrugUpper',43:'mouthSmileLeft',44:'mouthSmileRight',
|
|
117
|
+
45:'mouthStretchLeft',46:'mouthStretchRight',47:'mouthUpperUpLeft',48:'mouthUpperUpRight',
|
|
118
|
+
49:'noseSneerLeft',50:'noseSneerRight',51:'tongueOut'
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function applyBlendshapes(vrm, frame) {
|
|
122
|
+
if (!vrm?.expressionManager) return;
|
|
123
|
+
for (const [idx, name] of Object.entries(ARKIT_NAMES)) {
|
|
124
|
+
vrm.expressionManager.setValue(name, frame[idx] || 0);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================
|
|
129
|
+
// Step 2: Three.js Scene
|
|
130
|
+
// ============================================================
|
|
131
|
+
const canvas = document.getElementById('avatar-canvas');
|
|
132
|
+
const scene = new THREE.Scene();
|
|
133
|
+
scene.background = new THREE.Color(0x1a1a2e);
|
|
134
|
+
|
|
135
|
+
const camera = new THREE.PerspectiveCamera(30, canvas.clientWidth / canvas.clientHeight, 0.1, 100);
|
|
136
|
+
camera.position.set(0, 1.25, 0.5);
|
|
137
|
+
|
|
138
|
+
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
|
139
|
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
|
140
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
141
|
+
|
|
142
|
+
const controls = new OrbitControls(camera, canvas);
|
|
143
|
+
controls.target.set(0, 1.25, 0);
|
|
144
|
+
controls.enableDamping = true;
|
|
145
|
+
|
|
146
|
+
scene.add(new THREE.AmbientLight(0xffffff, 2.0));
|
|
147
|
+
const dirLight = new THREE.DirectionalLight(0xffffff, 1.1);
|
|
148
|
+
dirLight.position.set(1, 3, 2);
|
|
149
|
+
scene.add(dirLight);
|
|
150
|
+
|
|
151
|
+
// ============================================================
|
|
152
|
+
// Step 3: Load VRM Avatar
|
|
153
|
+
// ============================================================
|
|
154
|
+
const loader = new GLTFLoader();
|
|
155
|
+
loader.register(p => new VRMLoaderPlugin(p));
|
|
156
|
+
|
|
157
|
+
const gltf = await new Promise((res, rej) => loader.load('your-avatar.vrm', res, undefined, rej));
|
|
158
|
+
const vrm = gltf.userData.vrm;
|
|
159
|
+
VRMUtils.removeUnnecessaryVertices(gltf.scene);
|
|
160
|
+
VRMUtils.removeUnnecessaryJoints(gltf.scene);
|
|
161
|
+
scene.add(vrm.scene);
|
|
162
|
+
|
|
163
|
+
const mixer = new THREE.AnimationMixer(vrm.scene);
|
|
164
|
+
|
|
165
|
+
// ============================================================
|
|
166
|
+
// Step 3.5: Detect VRM Mode (ARKit 52-dim vs VRM 18-dim)
|
|
167
|
+
// ============================================================
|
|
168
|
+
// VRoid Hub models use VRM expressions (aa, ih, ou, ee, oh, blink, etc.)
|
|
169
|
+
// instead of ARKit names (jawOpen, eyeBlinkLeft, etc.).
|
|
170
|
+
// Detect which format the model supports to apply the correct blendshapes.
|
|
171
|
+
|
|
172
|
+
const VRM_NAMES = [
|
|
173
|
+
'aa','ih','ou','ee','oh', // lip-sync (5)
|
|
174
|
+
'happy','angry','sad','relaxed','surprised', // emotions (5)
|
|
175
|
+
'blink','blinkLeft','blinkRight', // blink (3)
|
|
176
|
+
'lookUp','lookDown','lookLeft','lookRight', // gaze (4)
|
|
177
|
+
'neutral' // base (1)
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
let useVrmMode = false;
|
|
181
|
+
|
|
182
|
+
function detectVrmMode() {
|
|
183
|
+
if (!vrm?.expressionManager) return false;
|
|
184
|
+
const exprMap = vrm.expressionManager.expressionMap || vrm.expressionManager._expressionMap || {};
|
|
185
|
+
const names = Object.keys(exprMap);
|
|
186
|
+
const arkitProbes = ['jawOpen','mouthFunnel','mouthPucker','eyeBlinkLeft','eyeBlinkRight'];
|
|
187
|
+
const vrmProbes = ['aa','ih','ou','ee','oh'];
|
|
188
|
+
const hasArkit = arkitProbes.filter(n => names.includes(n)).length >= 3;
|
|
189
|
+
const hasVrm = vrmProbes.filter(n => names.includes(n)).length >= 3;
|
|
190
|
+
return !hasArkit && hasVrm;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
useVrmMode = detectVrmMode();
|
|
194
|
+
console.log('VRM mode:', useVrmMode);
|
|
195
|
+
|
|
196
|
+
function applyVrmBlendshapes(vrm, vrmFrame) {
|
|
197
|
+
if (!vrm?.expressionManager) return;
|
|
198
|
+
for (let i = 0; i < VRM_NAMES.length; i++) {
|
|
199
|
+
vrm.expressionManager.setValue(VRM_NAMES[i], vrmFrame[i] || 0);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================
|
|
204
|
+
// Step 4: Init LipSync
|
|
205
|
+
// ============================================================
|
|
206
|
+
const lipsync = new LipSyncWasmWrapper();
|
|
207
|
+
// For production, pass your license key:
|
|
208
|
+
// await lipsync.init({ licenseKey: 'ggl_your_key_here' });
|
|
209
|
+
await lipsync.init({
|
|
210
|
+
onProgress: (stage, pct) => console.log(`Init: ${stage} ${pct}%`)
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ============================================================
|
|
214
|
+
// Step 5: Load VRMA Bone Animations (idle + speaking)
|
|
215
|
+
// ============================================================
|
|
216
|
+
// The package embeds two VRMA animations: idle pose and speaking pose.
|
|
217
|
+
// Use AnimationMixer to crossfade between them when audio plays.
|
|
218
|
+
|
|
219
|
+
const vrmaData = lipsync.getVrmaBytes();
|
|
220
|
+
|
|
221
|
+
async function loadVRMA(bytes) {
|
|
222
|
+
const blob = new Blob([bytes], { type: 'application/octet-stream' });
|
|
223
|
+
const url = URL.createObjectURL(blob);
|
|
224
|
+
const vrmaLoader = new GLTFLoader();
|
|
225
|
+
vrmaLoader.register(p => new VRMAnimationLoaderPlugin(p));
|
|
226
|
+
const g = await new Promise((res, rej) => vrmaLoader.load(url, res, undefined, rej));
|
|
227
|
+
URL.revokeObjectURL(url);
|
|
228
|
+
return g.userData.vrmAnimations[0];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const idleAnim = await loadVRMA(vrmaData.idle);
|
|
232
|
+
const speakingAnim = await loadVRMA(vrmaData.speaking);
|
|
233
|
+
|
|
234
|
+
const idleClip = createVRMAnimationClip(idleAnim, vrm);
|
|
235
|
+
const speakingClip = createVRMAnimationClip(speakingAnim, vrm);
|
|
236
|
+
|
|
237
|
+
const idleAction = mixer.clipAction(idleClip);
|
|
238
|
+
const speakingAction = mixer.clipAction(speakingClip);
|
|
239
|
+
|
|
240
|
+
// LoopPingPong prevents visible seam when idle animation loops
|
|
241
|
+
idleAction.setLoop(THREE.LoopPingPong);
|
|
242
|
+
speakingAction.setLoop(THREE.LoopRepeat);
|
|
243
|
+
idleAction.setEffectiveWeight(1);
|
|
244
|
+
idleAction.play();
|
|
245
|
+
speakingAction.setEffectiveWeight(0);
|
|
246
|
+
speakingAction.play();
|
|
247
|
+
|
|
248
|
+
// Crossfade state
|
|
249
|
+
let isSpeaking = false;
|
|
250
|
+
let crossFadeProgress = 0; // 0 = idle, 1 = speaking
|
|
251
|
+
|
|
252
|
+
function transitionToSpeaking(instant) {
|
|
253
|
+
isSpeaking = true;
|
|
254
|
+
if (instant) crossFadeProgress = 1;
|
|
255
|
+
}
|
|
256
|
+
function transitionToIdle() {
|
|
257
|
+
isSpeaking = false;
|
|
258
|
+
}
|
|
259
|
+
function updateBoneWeights(delta) {
|
|
260
|
+
const target = isSpeaking ? 1 : 0;
|
|
261
|
+
if (Math.abs(crossFadeProgress - target) > 0.001) {
|
|
262
|
+
// Asymmetric crossfade: 0.8s into speaking, 1.0s back to idle
|
|
263
|
+
const duration = isSpeaking ? 0.8 : 1.0;
|
|
264
|
+
const step = delta / duration;
|
|
265
|
+
crossFadeProgress = target > crossFadeProgress
|
|
266
|
+
? Math.min(crossFadeProgress + step, 1)
|
|
267
|
+
: Math.max(crossFadeProgress - step, 0);
|
|
268
|
+
}
|
|
269
|
+
const t = crossFadeProgress;
|
|
270
|
+
const w = t * t * (3 - 2 * t); // smoothstep
|
|
271
|
+
speakingAction.setEffectiveWeight(w);
|
|
272
|
+
idleAction.setEffectiveWeight(1 - w);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ============================================================
|
|
276
|
+
// Step 6: Idle Expression Generator
|
|
277
|
+
// ============================================================
|
|
278
|
+
// Procedural eye blinks + micro expressions when no audio is playing.
|
|
279
|
+
const idle = new lipsync.wasmModule.IdleExpressionGenerator();
|
|
280
|
+
let elapsedSeconds = 0;
|
|
281
|
+
let prevFrame = null;
|
|
282
|
+
|
|
283
|
+
// ============================================================
|
|
284
|
+
// Step 7: Frame Queue + Render Loop
|
|
285
|
+
// ============================================================
|
|
286
|
+
// Frames are consumed at 30fps regardless of monitor refresh rate.
|
|
287
|
+
const frameQueue = [];
|
|
288
|
+
let streamTimeAccum = 0;
|
|
289
|
+
const FRAME_INTERVAL = 1 / 30;
|
|
290
|
+
|
|
291
|
+
const clock = new THREE.Clock();
|
|
292
|
+
|
|
293
|
+
function animate() {
|
|
294
|
+
requestAnimationFrame(animate);
|
|
295
|
+
const delta = clock.getDelta();
|
|
296
|
+
elapsedSeconds += delta;
|
|
297
|
+
controls.update();
|
|
298
|
+
|
|
299
|
+
// Bone animation crossfade
|
|
300
|
+
updateBoneWeights(delta);
|
|
301
|
+
mixer.update(delta);
|
|
302
|
+
|
|
303
|
+
// Consume blendshape frames at 30fps
|
|
304
|
+
streamTimeAccum += delta;
|
|
305
|
+
while (streamTimeAccum >= FRAME_INTERVAL) {
|
|
306
|
+
streamTimeAccum -= FRAME_INTERVAL;
|
|
307
|
+
if (frameQueue.length > 0) {
|
|
308
|
+
prevFrame = frameQueue.shift();
|
|
309
|
+
if (useVrmMode) {
|
|
310
|
+
applyVrmBlendshapes(vrm, prevFrame);
|
|
311
|
+
} else {
|
|
312
|
+
applyBlendshapes(vrm, prevFrame);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Idle expressions when queue is empty
|
|
318
|
+
if (frameQueue.length === 0 && !isSpeaking) {
|
|
319
|
+
const idleFrame = idle.get_frame(elapsedSeconds);
|
|
320
|
+
let frame = idleFrame;
|
|
321
|
+
if (prevFrame) {
|
|
322
|
+
frame = prevFrame.map((v, i) => v + 0.15 * ((idleFrame[i] || 0) - v));
|
|
323
|
+
prevFrame = frame;
|
|
324
|
+
}
|
|
325
|
+
if (useVrmMode) {
|
|
326
|
+
const vrmFrame = lipsync.wasmModule.convert_arkit_to_vrm(frame);
|
|
327
|
+
applyVrmBlendshapes(vrm, Array.from(vrmFrame));
|
|
328
|
+
} else {
|
|
329
|
+
applyBlendshapes(vrm, frame);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
vrm.update(delta);
|
|
334
|
+
renderer.render(scene, camera);
|
|
335
|
+
}
|
|
336
|
+
animate();
|
|
337
|
+
|
|
338
|
+
// ============================================================
|
|
339
|
+
// Step 8: Audio File Playback (batch processing)
|
|
340
|
+
// ============================================================
|
|
341
|
+
document.getElementById('audio-file').addEventListener('change', async (e) => {
|
|
342
|
+
const file = e.target.files[0];
|
|
343
|
+
if (!file) return;
|
|
344
|
+
|
|
345
|
+
// Process blendshapes
|
|
346
|
+
const result = await lipsync.processFile(file);
|
|
347
|
+
|
|
348
|
+
// Fill frame queue
|
|
349
|
+
frameQueue.length = 0;
|
|
350
|
+
for (let i = 0; i < result.frame_count; i++) {
|
|
351
|
+
if (useVrmMode) {
|
|
352
|
+
frameQueue.push(lipsync.getVrmFrame(result, i));
|
|
353
|
+
} else {
|
|
354
|
+
frameQueue.push(lipsync.getFrame(result, i));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Switch to speaking pose immediately
|
|
359
|
+
transitionToSpeaking(true);
|
|
360
|
+
|
|
361
|
+
// Play audio in sync
|
|
362
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
363
|
+
const audioCtx = new AudioContext();
|
|
364
|
+
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
|
|
365
|
+
const source = audioCtx.createBufferSource();
|
|
366
|
+
source.buffer = audioBuffer;
|
|
367
|
+
source.connect(audioCtx.destination);
|
|
368
|
+
source.start();
|
|
369
|
+
source.onended = () => transitionToIdle();
|
|
370
|
+
});
|
|
371
|
+
</script>
|
|
372
|
+
</body>
|
|
373
|
+
</html>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Licensing
|
|
377
|
+
|
|
378
|
+
The first call to `init()` automatically starts a **30-day free trial** (no signup, no API key). For production use, pass your license key:
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
await lipsync.init({ licenseKey: 'ggl_your_key_here' });
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
| | Free Trial | Licensed |
|
|
385
|
+
|---|---|---|
|
|
386
|
+
| **Duration** | 30 days from first use | Unlimited |
|
|
387
|
+
| **Setup** | None (automatic) | Pass `licenseKey` to `init()` |
|
|
388
|
+
| **Domain restriction** | None | Configurable per key |
|
|
389
|
+
| **Features** | Full access | Full access |
|
|
390
|
+
|
|
391
|
+
Contact [GoodGang Labs](https://goodganglabs.com) for license keys.
|
|
392
|
+
|
|
393
|
+
## API Reference
|
|
394
|
+
|
|
395
|
+
### Constructor
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
new LipSyncWasmWrapper(options?: { wasmPath?: string })
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
| Parameter | Type | Default | Description |
|
|
402
|
+
|-----------|------|---------|-------------|
|
|
403
|
+
| `wasmPath` | `string` | `'./lipsync_wasm_v1.js'` | Path to the WASM glue module |
|
|
404
|
+
|
|
405
|
+
> **Important:** `wasmPath` is resolved relative to the **HTML page**, not the wrapper JS file.
|
|
406
|
+
> - **With bundlers** (Vite, Webpack): the default `'./lipsync_wasm_v1.js'` works automatically.
|
|
407
|
+
> - **Without a bundler** (plain `<script type="module">`): use an absolute path:
|
|
408
|
+
> ```js
|
|
409
|
+
> new LipSyncWasmWrapper({
|
|
410
|
+
> wasmPath: '/node_modules/@goodganglabs/lipsync-wasm-v1/lipsync_wasm_v1.js'
|
|
411
|
+
> })
|
|
412
|
+
> ```
|
|
413
|
+
|
|
414
|
+
### `init(options?): Promise<InitResult>`
|
|
415
|
+
|
|
416
|
+
Initializes the WASM runtime, loads the ONNX model, and applies the expression preset.
|
|
417
|
+
|
|
418
|
+
| Option | Type | Default | Description |
|
|
419
|
+
|--------|------|---------|-------------|
|
|
420
|
+
| `licenseKey` | `string` | — | GoodGang Labs license key. Omit for 30-day free trial. |
|
|
421
|
+
| `onProgress` | `(stage, percent) => void` | — | Progress callback. Stages: `'wasm'`, `'license'`, `'decrypt'`, `'onnx'` |
|
|
422
|
+
| `preset` | `boolean \| string` | `true` | `true` = built-in preset, URL string = custom preset JSON, `false` = disabled |
|
|
423
|
+
|
|
424
|
+
Returns `{ mode: 'onnx' }` or `{ mode: 'heuristic' }` if ONNX is unavailable.
|
|
425
|
+
|
|
426
|
+
### Properties
|
|
427
|
+
|
|
428
|
+
| Property | Type | Description |
|
|
429
|
+
|----------|------|-------------|
|
|
430
|
+
| `ready` | `boolean` | `true` after `init()` completes |
|
|
431
|
+
| `modelVersion` | `'v1'` | Always `'v1'` |
|
|
432
|
+
| `blendshapeDim` | `111` | Output dimension per frame |
|
|
433
|
+
| `mode` | `'onnx' \| 'heuristic' \| null` | Inference mode after init |
|
|
434
|
+
| `wasmModule` | `object` | Direct WASM exports (for `IdleExpressionGenerator`, `VoiceActivityDetector`) |
|
|
435
|
+
|
|
436
|
+
### Processing Methods
|
|
437
|
+
|
|
438
|
+
| Method | Input | Output | Use Case |
|
|
439
|
+
|--------|-------|--------|----------|
|
|
440
|
+
| `processFile(file)` | `File` | `Promise<ProcessResult>` | Audio file upload |
|
|
441
|
+
| `processAudio(audio)` | `Float32Array` (16kHz) | `Promise<ProcessResult>` | Raw PCM buffer |
|
|
442
|
+
| `processAudioBuffer(buf)` | `AudioBuffer` | `Promise<ProcessResult>` | Web Audio API buffer |
|
|
443
|
+
| `processAudioChunk(chunk, isLast?)` | `Float32Array` | `Promise<ProcessResult \| null>` | Real-time streaming |
|
|
444
|
+
|
|
445
|
+
### `getFrame(result, frameIndex): number[]`
|
|
446
|
+
|
|
447
|
+
Extracts a single frame from `ProcessResult`. Returns `number[111]`.
|
|
448
|
+
|
|
449
|
+
### `getVrmFrame(result, frameIndex): number[]`
|
|
450
|
+
|
|
451
|
+
Extracts a single VRM 18-dim frame from `ProcessResult`. Returns `number[18]` with VRM expression weights. The WASM engine automatically converts ARKit 111-dim → VRM 18-dim with natural triangle blinks.
|
|
452
|
+
|
|
453
|
+
Available when `result.vrm_blendshapes` exists (always present in batch/streaming results).
|
|
454
|
+
|
|
455
|
+
### `getVrmaBytes(): { idle: Uint8Array, speaking: Uint8Array }`
|
|
456
|
+
|
|
457
|
+
Returns embedded VRMA bone animation data. Load with `GLTFLoader` + `VRMAnimationLoaderPlugin` (see Complete Example above).
|
|
458
|
+
|
|
459
|
+
### `getVrmExpressionNames(): string[]`
|
|
460
|
+
|
|
461
|
+
Returns the 18 VRM expression names in order: `['aa', 'ih', 'ou', 'ee', 'oh', 'happy', 'angry', 'sad', 'relaxed', 'surprised', 'blink', 'blinkLeft', 'blinkRight', 'lookUp', 'lookDown', 'lookLeft', 'lookRight', 'neutral']`.
|
|
462
|
+
|
|
463
|
+
### `reset(): void`
|
|
464
|
+
|
|
465
|
+
Resets internal state and ends any active streaming session.
|
|
466
|
+
|
|
467
|
+
### `dispose(): void`
|
|
468
|
+
|
|
469
|
+
Releases all WASM and ONNX resources.
|
|
470
|
+
|
|
471
|
+
### ProcessResult
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
{
|
|
475
|
+
blendshapes: number[]; // Flat array: frame_count * 111 values
|
|
476
|
+
vrm_blendshapes?: number[]; // Flat array: frame_count * 18 VRM values (use getVrmFrame() to extract)
|
|
477
|
+
frame_count: number; // Number of 30fps frames
|
|
478
|
+
fps: number; // Always 30
|
|
479
|
+
mode: string; // 'onnx' | 'heuristic' | 'streaming-onnx'
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Advanced Features
|
|
484
|
+
|
|
485
|
+
### Bone Animation Tips
|
|
486
|
+
|
|
487
|
+
The package embeds idle and speaking VRMA bone animations. Two key recommendations for smooth results:
|
|
488
|
+
|
|
489
|
+
**1. Use `LoopPingPong` for idle animation** — The idle clip's first and last keyframes don't perfectly match, so `LoopRepeat` causes a visible jump at the loop boundary. `LoopPingPong` (forward→backward→forward) eliminates this seam.
|
|
490
|
+
|
|
491
|
+
**2. Use asymmetric crossfade durations** — A slower transition into speaking (0.8s) feels more natural than an instant snap. The return to idle can be slightly slower (1.0s) for a relaxed feel. Apply `smoothstep` to the linear progress for ease-in/ease-out.
|
|
492
|
+
|
|
493
|
+
```js
|
|
494
|
+
// Idle: PingPong to avoid loop seam
|
|
495
|
+
idleAction.setLoop(THREE.LoopPingPong);
|
|
496
|
+
speakingAction.setLoop(THREE.LoopRepeat);
|
|
497
|
+
|
|
498
|
+
// Asymmetric crossfade: 0.8s into speaking, 1.0s back to idle
|
|
499
|
+
function updateBoneWeights(delta) {
|
|
500
|
+
const target = isSpeaking ? 1 : 0;
|
|
501
|
+
const duration = isSpeaking ? 0.8 : 1.0;
|
|
502
|
+
const step = delta / duration;
|
|
503
|
+
crossFadeProgress = target > crossFadeProgress
|
|
504
|
+
? Math.min(crossFadeProgress + step, 1)
|
|
505
|
+
: Math.max(crossFadeProgress - step, 0);
|
|
506
|
+
const w = crossFadeProgress * crossFadeProgress * (3 - 2 * crossFadeProgress); // smoothstep
|
|
507
|
+
speakingAction.setEffectiveWeight(w);
|
|
508
|
+
idleAction.setEffectiveWeight(1 - w);
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### IdleExpressionGenerator (V1 only)
|
|
513
|
+
|
|
514
|
+
Procedural idle animation: eye blinks (2.5–4.5s random interval, 15% double-blink), micro expressions (sinusoidal). See Step 6 in the Complete Example.
|
|
515
|
+
|
|
516
|
+
```js
|
|
517
|
+
const idle = new lipsync.wasmModule.IdleExpressionGenerator();
|
|
518
|
+
|
|
519
|
+
// In render loop (when no audio is playing):
|
|
520
|
+
const frame = idle.get_frame(elapsedSeconds); // number[52] (ARKit)
|
|
521
|
+
|
|
522
|
+
// For VRM mode, convert to 18-dim:
|
|
523
|
+
if (useVrmMode) {
|
|
524
|
+
const vrmFrame = lipsync.wasmModule.convert_arkit_to_vrm(frame);
|
|
525
|
+
applyVrmBlendshapes(vrm, Array.from(vrmFrame));
|
|
526
|
+
} else {
|
|
527
|
+
applyBlendshapes(vrm, frame);
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### VoiceActivityDetector (V1 only)
|
|
532
|
+
|
|
533
|
+
Built-in VAD with auto-calibration from ambient noise. Use it to trigger speaking/idle bone animation transitions during microphone streaming.
|
|
534
|
+
|
|
535
|
+
```js
|
|
536
|
+
// 1. Collect 1 second of ambient noise RMS values
|
|
537
|
+
const samples = []; // push RMS of each mic chunk
|
|
538
|
+
// After 1 second:
|
|
539
|
+
const mean = samples.reduce((a, b) => a + b) / samples.length;
|
|
540
|
+
const std = Math.sqrt(samples.reduce((a, b) => a + (b - mean) ** 2, 0) / samples.length);
|
|
541
|
+
const threshold = Math.max(mean + 2 * std, 0.005);
|
|
542
|
+
|
|
543
|
+
// 2. Create VAD
|
|
544
|
+
const vad = new lipsync.wasmModule.VoiceActivityDetector(threshold, 0.5);
|
|
545
|
+
|
|
546
|
+
// 3. Feed audio chunks
|
|
547
|
+
const speaking = vad.feed_audio(chunk); // boolean
|
|
548
|
+
if (speaking) transitionToSpeaking();
|
|
549
|
+
else transitionToIdle();
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Real-time Microphone Streaming
|
|
553
|
+
|
|
554
|
+
Use AudioWorklet to batch 1600 samples (100ms @ 16kHz), feed to `processAudioChunk()`, push frames to the queue. The render loop (Step 7 in the Complete Example) consumes them at 30fps automatically.
|
|
555
|
+
|
|
556
|
+
```js
|
|
557
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
558
|
+
audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true }
|
|
559
|
+
});
|
|
560
|
+
const audioCtx = new AudioContext({ sampleRate: 16000 });
|
|
561
|
+
const source = audioCtx.createMediaStreamSource(stream);
|
|
562
|
+
|
|
563
|
+
// AudioWorklet batches 128-sample inputs into 1600-sample chunks
|
|
564
|
+
const workletCode = `
|
|
565
|
+
class MicProcessor extends AudioWorkletProcessor {
|
|
566
|
+
constructor() { super(); this.buf = []; this.len = 0; }
|
|
567
|
+
process(inputs) {
|
|
568
|
+
const d = inputs[0][0];
|
|
569
|
+
if (d) { this.buf.push(new Float32Array(d)); this.len += d.length; }
|
|
570
|
+
if (this.len >= 1600) {
|
|
571
|
+
const out = new Float32Array(this.len);
|
|
572
|
+
let off = 0;
|
|
573
|
+
for (const b of this.buf) { out.set(b, off); off += b.length; }
|
|
574
|
+
this.port.postMessage(out);
|
|
575
|
+
this.buf = []; this.len = 0;
|
|
576
|
+
}
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
registerProcessor('mic-processor', MicProcessor);
|
|
581
|
+
`;
|
|
582
|
+
const blob = new Blob([workletCode], { type: 'application/javascript' });
|
|
583
|
+
await audioCtx.audioWorklet.addModule(URL.createObjectURL(blob));
|
|
584
|
+
const worklet = new AudioWorkletNode(audioCtx, 'mic-processor');
|
|
585
|
+
source.connect(worklet);
|
|
586
|
+
|
|
587
|
+
transitionToSpeaking(false);
|
|
588
|
+
|
|
589
|
+
worklet.port.onmessage = async (e) => {
|
|
590
|
+
const result = await lipsync.processAudioChunk(e.data);
|
|
591
|
+
if (result) {
|
|
592
|
+
for (let i = 0; i < result.frame_count; i++) {
|
|
593
|
+
if (useVrmMode) {
|
|
594
|
+
frameQueue.push(lipsync.getVrmFrame(result, i));
|
|
595
|
+
} else {
|
|
596
|
+
frameQueue.push(lipsync.getFrame(result, i));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// To stop: stream.getTracks().forEach(t => t.stop());
|
|
603
|
+
// audioCtx.close(); lipsync.reset(); transitionToIdle();
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
## Bundler Setup
|
|
607
|
+
|
|
608
|
+
### Vite
|
|
609
|
+
|
|
610
|
+
Works out of the box.
|
|
611
|
+
|
|
612
|
+
### Webpack
|
|
613
|
+
|
|
614
|
+
```js
|
|
615
|
+
// webpack.config.js
|
|
616
|
+
module.exports = {
|
|
617
|
+
experiments: { asyncWebAssembly: true },
|
|
618
|
+
};
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Plain HTML (no bundler)
|
|
622
|
+
|
|
623
|
+
```html
|
|
624
|
+
<script type="module">
|
|
625
|
+
import { LipSyncWasmWrapper }
|
|
626
|
+
from './node_modules/@goodganglabs/lipsync-wasm-v1/lipsync-wasm-wrapper.js';
|
|
627
|
+
|
|
628
|
+
// IMPORTANT: wasmPath must be absolute (resolved from HTML page, not JS file)
|
|
629
|
+
const lipsync = new LipSyncWasmWrapper({
|
|
630
|
+
wasmPath: '/node_modules/@goodganglabs/lipsync-wasm-v1/lipsync_wasm_v1.js'
|
|
631
|
+
});
|
|
632
|
+
await lipsync.init();
|
|
633
|
+
</script>
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### CDN
|
|
637
|
+
|
|
638
|
+
```html
|
|
639
|
+
<script type="importmap">
|
|
640
|
+
{ "imports": {
|
|
641
|
+
"@goodganglabs/lipsync-wasm-v1": "https://your-cdn.com/lipsync-wasm-v1/lipsync-wasm-wrapper.js"
|
|
642
|
+
}}
|
|
643
|
+
</script>
|
|
644
|
+
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.17.0/dist/ort.min.js"></script>
|
|
645
|
+
<script type="module">
|
|
646
|
+
import { LipSyncWasmWrapper } from '@goodganglabs/lipsync-wasm-v1';
|
|
647
|
+
const lipsync = new LipSyncWasmWrapper({
|
|
648
|
+
wasmPath: 'https://your-cdn.com/lipsync-wasm-v1/lipsync_wasm_v1.js'
|
|
649
|
+
});
|
|
650
|
+
</script>
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
## Deployment Notes
|
|
654
|
+
|
|
655
|
+
- `.wasm` files must be served with `Content-Type: application/wasm`
|
|
656
|
+
- CORS headers required for cross-origin WASM loading
|
|
657
|
+
- ONNX Runtime Web must be loaded before `init()` is called
|
|
658
|
+
|
|
659
|
+
## License
|
|
660
|
+
|
|
661
|
+
Proprietary — GoodGang Labs
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @goodganglabs/lipsync-wasm-v1
|
|
3
|
+
* Audio-to-blendshape lip sync engine (111-dim ARKit, phoneme model)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ProcessResult {
|
|
7
|
+
blendshapes: number[];
|
|
8
|
+
frame_count: number;
|
|
9
|
+
fps: number;
|
|
10
|
+
vrm_blendshapes?: number[];
|
|
11
|
+
mode?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface InitResult {
|
|
15
|
+
mode: 'onnx' | 'heuristic';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface VrmaBytes {
|
|
19
|
+
idle: Uint8Array;
|
|
20
|
+
speaking: Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InitOptions {
|
|
24
|
+
/** GoodGangLabs license key (e.g. "ggl_xxx"). Omit for 30-day free trial. */
|
|
25
|
+
licenseKey?: string;
|
|
26
|
+
onProgress?: (stage: string, percent: number) => void;
|
|
27
|
+
preset?: boolean | string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ConstructorOptions {
|
|
31
|
+
wasmPath?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class LipSyncWasmWrapper {
|
|
35
|
+
constructor(options?: ConstructorOptions);
|
|
36
|
+
|
|
37
|
+
readonly ready: boolean;
|
|
38
|
+
readonly modelVersion: 'v1';
|
|
39
|
+
readonly blendshapeDim: 111;
|
|
40
|
+
readonly mode: 'onnx' | 'heuristic' | null;
|
|
41
|
+
readonly vrmDim: 18;
|
|
42
|
+
readonly wasmModule: any;
|
|
43
|
+
|
|
44
|
+
init(options?: InitOptions): Promise<InitResult>;
|
|
45
|
+
|
|
46
|
+
processAudio(audio: Float32Array): Promise<ProcessResult>;
|
|
47
|
+
processAudioBuffer(audioBuffer: AudioBuffer): Promise<ProcessResult>;
|
|
48
|
+
processFile(file: File): Promise<ProcessResult>;
|
|
49
|
+
|
|
50
|
+
processAudioChunk(audioChunk: Float32Array, isLast?: boolean): Promise<ProcessResult | null>;
|
|
51
|
+
|
|
52
|
+
getFrame(result: ProcessResult, frameIndex: number): number[];
|
|
53
|
+
getVrmFrame(result: ProcessResult, frameIndex: number): number[];
|
|
54
|
+
getVrmExpressionNames(): string[];
|
|
55
|
+
|
|
56
|
+
getVrmaBytes(): VrmaBytes;
|
|
57
|
+
reset(): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=111,t="input1",n="https://lipsync-wasm.quasar.ggls.dev/license",s="__ggl_lipsync_trial",i="__ggl_lipsync_token_v1";export class LipSyncWasmWrapper{#e;#t=null;#n=null;#s=null;#i=!1;#r=!1;#a=null;constructor(e={}){this.#e=e.wasmPath||"./lipsync_wasm_v1.js"}get ready(){return this.#i}get modelVersion(){return"v1"}get blendshapeDim(){return e}get mode(){return this.#a}get vrmDim(){return 18}get wasmModule(){return this.#t}async init(e={}){const{licenseKey:t,onProgress:n,preset:s=!0}=e;n?.("wasm",0),this.#t=await import(this.#e),await this.#t.default(),this.#n=new this.#t.LipSyncWasm,n?.("wasm",100),n?.("license",0);const i=await this.#o(t);this.#l(i),n?.("license",100),n?.("onnx",0);try{if("undefined"==typeof ort)throw new Error("onnxruntime-web not available");ort.env.wasm.numThreads=1,n?.("decrypt",0);const e=Math.floor(Date.now()/1e3),t=this.#t.get_onnx_model_licensed(i,e);n?.("decrypt",100),this.#s=await ort.InferenceSession.create(t.buffer,{executionProviders:["wasm"],graphOptimizationLevel:"all"}),n?.("onnx",100)}catch(e){console.warn("[lipsync-v1] ONNX unavailable, using heuristic fallback:",e.message),this.#s=null,n?.("onnx-fallback",100)}return this.#n.set_license_validated(),await this.#c(s),this.#i=!0,this.#a=this.#s?"onnx":"heuristic",{mode:this.#a}}async processAudio(e){if(this.#h(),this.#s){const t=this.#n.prepare_onnx_input(e),n=await this.#d(t.mfcc_data,[1,t.num_frames,t.mfcc_dim]);return{...this.#n.assemble_and_postprocess(n),mode:"onnx"}}return{...this.#n.process_audio_with_animation(e),mode:"heuristic"}}async processAudioBuffer(e){return this.processAudio(function(e){const t=e.getChannelData(0);if(16e3===e.sampleRate)return t;const n=16e3/e.sampleRate,s=Math.round(t.length*n),i=new Float32Array(s);for(let e=0;e<s;e++){const s=e/n,r=Math.floor(s),a=Math.min(r+1,t.length-1);i[e]=t[r]+(s-r)*(t[a]-t[r])}return i}(e))}async processFile(e){const t=await e.arrayBuffer(),n=new OfflineAudioContext(1,1,16e3),s=await n.decodeAudioData(t);return this.processAudioBuffer(s)}async processAudioChunk(e,t=!1){if(this.#h(),!this.#s)return{...this.#n.process_audio_with_animation(e),mode:"heuristic"};this.#r||(this.#n.start_streaming(),this.#r=!0);const n=this.#n.feed_audio_chunk(e),s=t?this.#n.flush_audio_buffer():null,i=n.ready?n:s?.ready?s:null;if(!i)return t&&this.#p(),null;const r=await this.#d(i.mfcc_data,[1,i.num_frames,i.mfcc_dim]),a=this.#n.feed_onnx_result(r,i.num_classes);return t&&this.#p(),{...a,mode:"streaming-onnx"}}getFrame(t,n){if(!t?.blendshapes)return new Array(e).fill(0);if(Array.isArray(t.blendshapes)&&"number"==typeof t.blendshapes[0]){const s=n*e;return t.blendshapes.slice(s,s+e)}return t.blendshapes[n]||new Array(e).fill(0)}getVrmFrame(e,t){if(!e?.vrm_blendshapes)return new Array(18).fill(0);if(Array.isArray(e.vrm_blendshapes)&&"number"==typeof e.vrm_blendshapes[0]){const n=18*t;return e.vrm_blendshapes.slice(n,n+18)}return e.vrm_blendshapes[t]||new Array(18).fill(0)}getVrmExpressionNames(){return this.#n.get_vrm_expression_names()}getVrmaBytes(){return this.#h(),{idle:this.#n.get_idle_vrma_bytes(),speaking:this.#n.get_speaking_vrma_bytes()}}reset(){this.#r&&this.#p(),this.#n?.reset()}dispose(){this.reset(),this.#n&&(this.#n.free(),this.#n=null),this.#s=null,this.#t=null,this.#i=!1}#l(e){try{const t=e.split(".");if(3!==t.length)return;const n=JSON.parse(atob(t[1].replace(/-/g,"+").replace(/_/g,"/"))).dom;if(!n||"*"===n)return;const s="undefined"!=typeof window&&window.location?.hostname||"";if(!s)return;if(!n.split(",").map(e=>e.trim()).some(e=>e.startsWith("*.")?s===e.slice(2)||s.endsWith("."+e.slice(2)):s===e))throw new Error(`Domain not authorized: ${s} (allowed: ${n})`)}catch(e){if(e.message.includes("Domain not authorized"))throw e}}#h(){if(!this.#i||!this.#n)throw new Error("[lipsync-v1] Not initialized. Call init() first.")}#p(){this.#n.end_streaming(),this.#r=!1}async#d(e,n){const s=new ort.Tensor("float32",new Float32Array(e),n),i=await this.#s.run({[t]:s});return new Float32Array(i.dense_8.data)}async#c(e){if(!0===e)this.#n.load_default_preset();else if("string"==typeof e){const t=await fetch(e);if(!t.ok)throw new Error(`[lipsync-v1] Preset fetch failed: ${t.status}`);const{blendshape:n}=await t.json();if(!n?.length)throw new Error("[lipsync-v1] Invalid preset format");const s=n.length,i=n[0].length,r=new Float32Array(s*i);for(let e=0;e<s;e++)r.set(n[e],e*i);this.#n.load_preset(r,s,i)}}async#o(e){const t=this.#u();if(t)return t;if(e){const t=await this.#g(e);return this.#m(t),t}return this.#w()}async#g(e){const t=await this.#y(),s=await fetch(`${n}/validate`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({pkg:"lipsync-wasm-v1",fingerprint:t})});if(!s.ok){const e=await s.text().catch(()=>"");throw new Error(`[lipsync-v1] License validation failed (${s.status}): ${e}`)}const{token:i}=await s.json();if(!i)throw new Error("[lipsync-v1] License server returned no token");return i}async#w(){const e=await this.#y();let t,i=null;try{const e=localStorage.getItem(s);e&&(i=JSON.parse(e))}catch{}if(i&&Date.now()-(i.start||0)>2592e6)throw new Error('[lipsync-v1] Trial period expired (30 days). Pass a licenseKey to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })');try{if(i){const s=await fetch(`${n}/trial/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg:"lipsync-wasm-v1",fingerprint:e})});if(403===s.status)throw new Error('[lipsync-v1] Trial period expired (30 days). Purchase a license at https://lipsync-wasm.quasar.ggls.dev — then pass it to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })');if(404===s.status){try{localStorage.removeItem("lipsync_trial")}catch{}const s=await fetch(`${n}/trial/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg:"lipsync-wasm-v1",fingerprint:e})});if(429===s.status)throw new Error('[lipsync-v1] Trial limit reached for this network. Purchase a license at https://lipsync-wasm.quasar.ggls.dev — then pass it to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })');if(!s.ok)throw new Error(`HTTP ${s.status}`);t=(await s.json()).token,this.#f(e)}else{if(!s.ok)throw new Error(`HTTP ${s.status}`);t=(await s.json()).token}}else{const s=await fetch(`${n}/trial/start`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pkg:"lipsync-wasm-v1",fingerprint:e})});if(403===s.status)throw new Error('[lipsync-v1] Trial period expired (30 days). Purchase a license at https://lipsync-wasm.quasar.ggls.dev — then pass it to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })');if(429===s.status)throw new Error('[lipsync-v1] Trial limit reached for this network. Purchase a license at https://lipsync-wasm.quasar.ggls.dev — then pass it to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })');if(!s.ok)throw new Error(`HTTP ${s.status}`);t=(await s.json()).token}}catch(e){if(e.message.includes("[lipsync-v1]"))throw e;throw new Error('[lipsync-v1] License server unreachable. An internet connection is required. If you have a license key, pass it to init(): new LipSyncWasmWrapper().init({ licenseKey: "ggl_..." })')}if(!t)throw new Error("[lipsync-v1] License server returned no token");return this.#f(e),this.#m(t),t}#f(e){if(!localStorage.getItem(s))try{localStorage.setItem(s,JSON.stringify({start:Date.now(),fingerprint:e}))}catch{}}async#y(){const e=[navigator.userAgent||"",navigator.language||"",screen?.width||0,screen?.height||0,Intl?.DateTimeFormat()?.resolvedOptions()?.timeZone||""].join("|");if("function"==typeof crypto?.subtle?.digest){const t=(new TextEncoder).encode(e),n=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map(e=>e.toString(16).padStart(2,"0")).join("")}let t=0;for(let n=0;n<e.length;n++)t=(t<<5)-t+e.charCodeAt(n)|0;return"f"+Math.abs(t).toString(16)}#u(){try{const e=sessionStorage.getItem(i);if(!e)return null;const{token:t,cachedAt:n}=JSON.parse(e);return Date.now()-n>828e5?(sessionStorage.removeItem(i),null):t}catch{return null}}#m(e){try{sessionStorage.setItem(i,JSON.stringify({token:e,cachedAt:Date.now()}))}catch{}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class IdleExpressionGenerator{__destroy_into_raw(){const e=this.__wbg_ptr;return this.__wbg_ptr=0,t.unregister(this),e}free(){const e=this.__destroy_into_raw();k.__wbg_idleexpressiongenerator_free(e,0)}get_frame(e){const t=k.idleexpressiongenerator_get_frame(this.__wbg_ptr,e);var n=s(t[0],t[1]).slice();return k.__wbindgen_free(t[0],4*t[1],4),n}constructor(){const e=k.idleexpressiongenerator_new();return this.__wbg_ptr=e>>>0,t.register(this,this.__wbg_ptr,this),this}reset(){k.idleexpressiongenerator_reset(this.__wbg_ptr)}}Symbol.dispose&&(IdleExpressionGenerator.prototype[Symbol.dispose]=IdleExpressionGenerator.prototype.free);export class LipSyncWasm{__destroy_into_raw(){const e=this.__wbg_ptr;return this.__wbg_ptr=0,n.unregister(this),e}free(){const e=this.__destroy_into_raw();k.__wbg_lipsyncwasm_free(e,0)}assemble_and_postprocess(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_assemble_and_postprocess(this.__wbg_ptr,t,I);if(n[2])throw y(n[1]);return y(n[0])}clear_preset(){k.lipsyncwasm_clear_preset(this.__wbg_ptr)}end_streaming(){k.lipsyncwasm_end_streaming(this.__wbg_ptr)}extract_mfcc(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_extract_mfcc(this.__wbg_ptr,t,I);var _=s(n[0],n[1]).slice();return k.__wbindgen_free(n[0],4*n[1],4),_}extract_mfcc_chunk(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_extract_mfcc_chunk(this.__wbg_ptr,t,I);var _=s(n[0],n[1]).slice();return k.__wbindgen_free(n[0],4*n[1],4),_}feed_audio_chunk(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_feed_audio_chunk(this.__wbg_ptr,t,I);if(n[2])throw y(n[1]);return y(n[0])}feed_onnx_result(e,t){const n=p(e,k.__wbindgen_malloc),_=k.lipsyncwasm_feed_onnx_result(this.__wbg_ptr,n,I,t);if(_[2])throw y(_[1]);return y(_[0])}flush_audio_buffer(){const e=k.lipsyncwasm_flush_audio_buffer(this.__wbg_ptr);if(e[2])throw y(e[1]);return y(e[0])}get_corrections_enabled(){return 0!==k.lipsyncwasm_get_corrections_enabled(this.__wbg_ptr)}get_frame(e,t){const n=k.lipsyncwasm_get_frame(this.__wbg_ptr,e,t);if(n[3])throw y(n[2]);var _=s(n[0],n[1]).slice();return k.__wbindgen_free(n[0],4*n[1],4),_}get_frame_count(){return k.lipsyncwasm_get_frame_count(this.__wbg_ptr)>>>0}get_idle_vrma_bytes(){const e=k.lipsyncwasm_get_idle_vrma_bytes(this.__wbg_ptr);var t=o(e[0],e[1]).slice();return k.__wbindgen_free(e[0],1*e[1],1),t}get_speaking_vrma_bytes(){const e=k.lipsyncwasm_get_speaking_vrma_bytes(this.__wbg_ptr);var t=o(e[0],e[1]).slice();return k.__wbindgen_free(e[0],1*e[1],1),t}get_vrm_dim(){return k.lipsyncwasm_get_vrm_dim(this.__wbg_ptr)>>>0}get_vrm_expression_names(){const e=k.lipsyncwasm_get_vrm_expression_names(this.__wbg_ptr);var t=function(e,t){e>>>=0;const n=a(),_=[];for(let r=e;r<e+4*t;r+=4)_.push(k.__wbindgen_externrefs.get(n.getUint32(r,!0)));return k.__externref_drop_slice(e,t),_}(e[0],e[1]).slice();return k.__wbindgen_free(e[0],4*e[1],4),t}get_vrm_frame(e,t){const n=k.lipsyncwasm_get_vrm_frame(this.__wbg_ptr,e,t);if(n[3])throw y(n[2]);var _=s(n[0],n[1]).slice();return k.__wbindgen_free(n[0],4*n[1],4),_}has_preset(){return 0!==k.lipsyncwasm_has_preset(this.__wbg_ptr)}is_license_validated(){return 0!==k.lipsyncwasm_is_license_validated(this.__wbg_ptr)}is_streaming(){return 0!==k.lipsyncwasm_is_streaming(this.__wbg_ptr)}load_default_preset(){const e=k.lipsyncwasm_load_default_preset(this.__wbg_ptr);if(e[1])throw y(e[0])}load_preset(e,t,n){const _=p(e,k.__wbindgen_malloc);k.lipsyncwasm_load_preset(this.__wbg_ptr,_,I,t,n)}constructor(){const e=k.lipsyncwasm_new();if(e[2])throw y(e[1]);return this.__wbg_ptr=e[0]>>>0,n.register(this,this.__wbg_ptr,this),this}prepare_onnx_input(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_prepare_onnx_input(this.__wbg_ptr,t,I);if(n[2])throw y(n[1]);return y(n[0])}process_audio(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_process_audio(this.__wbg_ptr,t,I);if(n[2])throw y(n[1]);return y(n[0])}process_audio_with_animation(e){const t=p(e,k.__wbindgen_malloc),n=k.lipsyncwasm_process_audio_with_animation(this.__wbg_ptr,t,I);if(n[2])throw y(n[1]);return y(n[0])}process_phonemes(e,t){const n=p(e,k.__wbindgen_malloc),_=k.lipsyncwasm_process_phonemes(this.__wbg_ptr,n,I,t);if(_[2])throw y(_[1]);return y(_[0])}process_phonemes_chunk(e,t){const n=p(e,k.__wbindgen_malloc),_=k.lipsyncwasm_process_phonemes_chunk(this.__wbg_ptr,n,I,t);if(_[2])throw y(_[1]);return y(_[0])}process_phonemes_chunk_with_animation(e,t){const n=p(e,k.__wbindgen_malloc),_=k.lipsyncwasm_process_phonemes_chunk_with_animation(this.__wbg_ptr,n,I,t);if(_[2])throw y(_[1]);return y(_[0])}process_phonemes_with_animation(e,t){const n=p(e,k.__wbindgen_malloc),_=k.lipsyncwasm_process_phonemes_with_animation(this.__wbg_ptr,n,I,t);if(_[2])throw y(_[1]);return y(_[0])}reset(){k.lipsyncwasm_reset(this.__wbg_ptr)}set_corrections_enabled(e){k.lipsyncwasm_set_corrections_enabled(this.__wbg_ptr,e)}set_license_validated(){k.lipsyncwasm_set_license_validated(this.__wbg_ptr)}start_streaming(){k.lipsyncwasm_start_streaming(this.__wbg_ptr)}}Symbol.dispose&&(LipSyncWasm.prototype[Symbol.dispose]=LipSyncWasm.prototype.free);export class VoiceActivityDetector{__destroy_into_raw(){const e=this.__wbg_ptr;return this.__wbg_ptr=0,_.unregister(this),e}free(){const e=this.__destroy_into_raw();k.__wbg_voiceactivitydetector_free(e,0)}feed_audio(e){const t=p(e,k.__wbindgen_malloc);return 0!==k.voiceactivitydetector_feed_audio(this.__wbg_ptr,t,I)}is_speaking(){return 0!==k.voiceactivitydetector_is_speaking(this.__wbg_ptr)}constructor(e,t){const n=k.voiceactivitydetector_new(e,t);return this.__wbg_ptr=n>>>0,_.register(this,this.__wbg_ptr,this),this}reset(){k.voiceactivitydetector_reset(this.__wbg_ptr)}set_hold_time(e){k.voiceactivitydetector_set_hold_time(this.__wbg_ptr,e)}set_threshold(e){k.voiceactivitydetector_set_threshold(this.__wbg_ptr,e)}}Symbol.dispose&&(VoiceActivityDetector.prototype[Symbol.dispose]=VoiceActivityDetector.prototype.free);export function convert_arkit_to_vrm(e){const t=p(e,k.__wbindgen_malloc),n=k.convert_arkit_to_vrm(t,I);var _=s(n[0],n[1]).slice();return k.__wbindgen_free(n[0],4*n[1],4),_}export function get_onnx_model(){const e=k.get_onnx_model();if(e[3])throw y(e[2]);var t=o(e[0],e[1]).slice();return k.__wbindgen_free(e[0],1*e[1],1),t}export function get_onnx_model_licensed(e,t){const n=m(e,k.__wbindgen_malloc,k.__wbindgen_realloc),_=k.get_onnx_model_licensed(n,I,t);if(_[3])throw y(_[2]);var r=o(_[0],_[1]).slice();return k.__wbindgen_free(_[0],1*_[1],1),r}export function start(){k.start()}function e(){const e={__proto__:null,__wbg_Error_dbcd8782dbb273a2:function(e,t){return Error(u(e,t))},__wbg_Number_012552ac4683228d:function(e){return Number(e)},__wbg_String_8564e559799eccda:function(e,t){const n=m(String(t),k.__wbindgen_malloc,k.__wbindgen_realloc),_=I;a().setInt32(e+4,_,!0),a().setInt32(e+0,n,!0)},__wbg___wbindgen_boolean_get_7f1c4dd217655ab6:function(e){const t="boolean"==typeof e?e:void 0;return d(t)?16777215:t?1:0},__wbg___wbindgen_debug_string_6cf0badf0b90f6ef:function(e,t){const n=m(i(t),k.__wbindgen_malloc,k.__wbindgen_realloc),_=I;a().setInt32(e+4,_,!0),a().setInt32(e+0,n,!0)},__wbg___wbindgen_in_e32cbbbf71fdc915:function(e,t){return e in t},__wbg___wbindgen_is_function_4500d4795b15e70b:function(e){return"function"==typeof e},__wbg___wbindgen_is_object_f8b6723c60349a13:function(e){return"object"==typeof e&&null!==e},__wbg___wbindgen_is_undefined_1296fcc83c2da07a:function(e){return void 0===e},__wbg___wbindgen_jsval_loose_eq_3173dea557396a92:function(e,t){return e==t},__wbg___wbindgen_number_get_3330675b4e5c3680:function(e,t){const n="number"==typeof t?t:void 0;a().setFloat64(e+8,d(n)?0:n,!0),a().setInt32(e+0,!d(n),!0)},__wbg___wbindgen_string_get_7b8bc463f6cbeefe:function(e,t){const n="string"==typeof t?t:void 0;var _=d(n)?0:m(n,k.__wbindgen_malloc,k.__wbindgen_realloc),r=I;a().setInt32(e+4,r,!0),a().setInt32(e+0,_,!0)},__wbg___wbindgen_throw_89ca9e2c67795ec1:function(e,t){throw new Error(u(e,t))},__wbg_call_eb691bc2f5533064:function(){return g(function(e,t){return e.call(t)},arguments)},__wbg_debug_512ce669a0dda245:function(e){console.debug(e)},__wbg_done_82b14aeb31e98db6:function(e){return e.done},__wbg_error_a6fa202b58aa1cd3:function(e,t){let n,_;try{n=e,_=t,console.error(u(e,t))}finally{k.__wbindgen_free(n,_,1)}},__wbg_error_d0b9fd88b7a19297:function(e){console.error(e)},__wbg_get_ed44f5f876f22351:function(){return g(function(e,t){return Reflect.get(e,t)},arguments)},__wbg_get_unchecked_ae4d1600970be7c3:function(e,t){return e[t>>>0]},__wbg_get_with_ref_key_6412cf3094599694:function(e,t){return e[t]},__wbg_info_3fad8ca00db5aa77:function(e){console.info(e)},__wbg_instanceof_ArrayBuffer_4f2b9b5ed416155d:function(e){let t;try{t=e instanceof ArrayBuffer}catch(e){t=!1}return t},__wbg_instanceof_Uint8Array_6482c66fce35827d:function(e){let t;try{t=e instanceof Uint8Array}catch(e){t=!1}return t},__wbg_instanceof_Window_27a653e1b516dd65:function(e){let t;try{t=e instanceof Window}catch(e){t=!1}return t},__wbg_isArray_fe5201bfdab7e39d:function(e){return Array.isArray(e)},__wbg_isSafeInteger_d6215c7562dbc4db:function(e){return Number.isSafeInteger(e)},__wbg_iterator_63c3a1857203cf2f:function(){return Symbol.iterator},__wbg_length_f875d3a041bab91a:function(e){return e.length},__wbg_length_feaf2a40e5f9755a:function(e){return e.length},__wbg_log_240aa86e7eb48d31:function(e){console.log(e)},__wbg_new_227d7c05414eb861:function(){return new Error},__wbg_new_6e7681a5f6f98ceb:function(e){return new Uint8Array(e)},__wbg_new_6feff3e11e4d0799:function(){return new Object},__wbg_new_ff7f9cc4c9a4a0cf:function(){return new Array},__wbg_next_ae5b710aea83f41e:function(){return g(function(e){return e.next()},arguments)},__wbg_next_f577b3e02c9be709:function(e){return e.next},__wbg_now_7132c007bbc70074:function(e){return e.now()},__wbg_performance_c9d5082c67ac3b5d:function(e){const t=e.performance;return d(t)?0:r(t)},__wbg_prototypesetcall_37f00e1be5c4015a:function(e,t,n){Uint8Array.prototype.set.call(o(e,t),n)},__wbg_set_601f3e1d081df3ac:function(e,t,n){e[t>>>0]=n},__wbg_set_6be42768c690e380:function(e,t,n){e[t]=n},__wbg_stack_3b0d974bbf31e44f:function(e,t){const n=m(t.stack,k.__wbindgen_malloc,k.__wbindgen_realloc),_=I;a().setInt32(e+4,_,!0),a().setInt32(e+0,n,!0)},__wbg_static_accessor_GLOBAL_280fe6a619bbfbf6:function(){const e="undefined"==typeof global?null:global;return d(e)?0:r(e)},__wbg_static_accessor_GLOBAL_THIS_12c1f4811ec605d1:function(){const e="undefined"==typeof globalThis?null:globalThis;return d(e)?0:r(e)},__wbg_static_accessor_SELF_3a156961626f54d9:function(){const e="undefined"==typeof self?null:self;return d(e)?0:r(e)},__wbg_static_accessor_WINDOW_210015b3eb6018a4:function(){const e="undefined"==typeof window?null:window;return d(e)?0:r(e)},__wbg_value_3e1fdb73e1353fb3:function(e){return e.value},__wbg_warn_998077100f0e7387:function(e){console.warn(e)},__wbindgen_cast_0000000000000001:function(e){return e},__wbindgen_cast_0000000000000002:function(e,t){return u(e,t)},__wbindgen_init_externref_table:function(){const e=k.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}};return{__proto__:null,"./lipsync_wasm_v1_bg.js":e}}const t="undefined"==typeof FinalizationRegistry?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>k.__wbg_idleexpressiongenerator_free(e>>>0,1)),n="undefined"==typeof FinalizationRegistry?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>k.__wbg_lipsyncwasm_free(e>>>0,1)),_="undefined"==typeof FinalizationRegistry?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>k.__wbg_voiceactivitydetector_free(e>>>0,1));function r(e){const t=k.__externref_table_alloc();return k.__wbindgen_externrefs.set(t,e),t}function i(e){const t=typeof e;if("number"==t||"boolean"==t||null==e)return`${e}`;if("string"==t)return`"${e}"`;if("symbol"==t){const t=e.description;return null==t?"Symbol":`Symbol(${t})`}if("function"==t){const t=e.name;return"string"==typeof t&&t.length>0?`Function(${t})`:"Function"}if(Array.isArray(e)){const t=e.length;let n="[";t>0&&(n+=i(e[0]));for(let _=1;_<t;_++)n+=", "+i(e[_]);return n+="]",n}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(!(n&&n.length>1))return toString.call(e);if(_=n[1],"Object"==_)try{return"Object("+JSON.stringify(e)+")"}catch(e){return"Object"}return e instanceof Error?`${e.name}: ${e.message}\n${e.stack}`:_}function s(e,t){return e>>>=0,f().subarray(e/4,e/4+t)}function o(e,t){return e>>>=0,w().subarray(e/1,e/1+t)}let c=null;function a(){return(null===c||!0===c.buffer.detached||void 0===c.buffer.detached&&c.buffer!==k.memory.buffer)&&(c=new DataView(k.memory.buffer)),c}let l=null;function f(){return null!==l&&0!==l.byteLength||(l=new Float32Array(k.memory.buffer)),l}function u(e,t){return function(e,t){return x+=t,x>=v&&(h=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),h.decode(),x=t),h.decode(w().subarray(e,e+t))}(e>>>=0,t)}let b=null;function w(){return null!==b&&0!==b.byteLength||(b=new Uint8Array(k.memory.buffer)),b}function g(e,t){try{return e.apply(this,t)}catch(e){const t=r(e);k.__wbindgen_exn_store(t)}}function d(e){return null==e}function p(e,t){const n=t(4*e.length,4)>>>0;return f().set(e,n/4),I=e.length,n}function m(e,t,n){if(void 0===n){const n=A.encode(e),_=t(n.length,1)>>>0;return w().subarray(_,_+n.length).set(n),I=n.length,_}let _=e.length,r=t(_,1)>>>0;const i=w();let s=0;for(;s<_;s++){const t=e.charCodeAt(s);if(t>127)break;i[r+s]=t}if(s!==_){0!==s&&(e=e.slice(s)),r=n(r,_,_=s+3*e.length,1)>>>0;const t=w().subarray(r+s,r+_);s+=A.encodeInto(e,t).written,r=n(r,_,s,1)>>>0}return I=s,r}function y(e){const t=k.__wbindgen_externrefs.get(e);return k.__externref_table_dealloc(e),t}let h=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});h.decode();const v=2146435072;let x=0;const A=new TextEncoder;"encodeInto"in A||(A.encodeInto=function(e,t){const n=A.encode(e);return t.set(n),{read:e.length,written:n.length}});let S,k,I=0;function W(e,t){return k=e.exports,S=t,c=null,l=null,b=null,k.__wbindgen_start(),k}function O(t){if(void 0!==k)return k;void 0!==t&&(Object.getPrototypeOf(t)===Object.prototype?({module:t}=t):console.warn("using deprecated parameters for `initSync()`; pass a single object instead"));const n=e();return t instanceof WebAssembly.Module||(t=new WebAssembly.Module(t)),W(new WebAssembly.Instance(t,n),t)}async function j(t){if(void 0!==k)return k;void 0!==t&&(Object.getPrototypeOf(t)===Object.prototype?({module_or_path:t}=t):console.warn("using deprecated parameters for the initialization function; pass a single object instead")),void 0===t&&(t=new URL("lipsync_wasm_v1_bg.wasm",import.meta.url));const n=e();("string"==typeof t||"function"==typeof Request&&t instanceof Request||"function"==typeof URL&&t instanceof URL)&&(t=fetch(t));const{instance:_,module:r}=await async function(e,t){if("function"==typeof Response&&e instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(e,t)}catch(t){if(!e.ok||!function(e){switch(e){case"basic":case"cors":case"default":return!0}return!1}(e.type)||"application/wasm"===e.headers.get("Content-Type"))throw t;console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",t)}const n=await e.arrayBuffer();return await WebAssembly.instantiate(n,t)}{const n=await WebAssembly.instantiate(e,t);return n instanceof WebAssembly.Instance?{instance:n,module:e}:n}}(await t,n);return W(_,r)}export{O as initSync,j as default};
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@goodganglabs/lipsync-wasm-v1",
|
|
3
|
+
"version": "0.4.5",
|
|
4
|
+
"description": "WASM LipSync V1 - Phoneme-based 111-dim ARKit blendshape engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lipsync-wasm-wrapper.js",
|
|
7
|
+
"types": "lipsync-wasm-wrapper.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"README.md",
|
|
10
|
+
"lipsync-wasm-wrapper.js",
|
|
11
|
+
"lipsync-wasm-wrapper.d.ts",
|
|
12
|
+
"lipsync_wasm_v1.js",
|
|
13
|
+
"lipsync_wasm_v1_bg.wasm"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/goodganglabs/AnimaSync"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://animasync.quasar.ggls.dev/",
|
|
20
|
+
"keywords": ["wasm", "lipsync", "lip-sync", "blendshape", "arkit", "webassembly", "audio", "avatar", "vrm", "facial-animation", "speech-to-animation", "onnx", "rust", "real-time", "browser", "3d-avatar", "face-tracking", "viseme", "text-to-speech", "tts"],
|
|
21
|
+
"author": "GoodGangLabs",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"onnxruntime-web": ">=1.17.0"
|
|
25
|
+
}
|
|
26
|
+
}
|