fernandopruebas commited on
Commit
529b413
·
verified ·
1 Parent(s): 52149a5

Upload 2 files

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. index.html +188 -19
  3. persona.glb +3 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ persona.glb filter=lfs diff=lfs merge=lfs -text
index.html CHANGED
@@ -1,19 +1,188 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Avatar 3D con Voz + Lipsync (Hugging Face Space)</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <style>
8
+ html, body { margin:0; height:100%; background:#0b0d12; color:#e8e8e8; font-family:system-ui, Arial; }
9
+ #ui {
10
+ position: fixed; top: 12px; left: 12px; right: 12px; z-index: 10;
11
+ display: grid; gap: 8px; grid-template-columns: 1fr auto auto;
12
+ background: rgba(20,22,30,0.8); backdrop-filter: blur(8px);
13
+ border: 1px solid #2a2f3a; border-radius: 12px; padding: 12px;
14
+ }
15
+ #text { grid-column: 1 / -1; padding: 10px; border-radius: 8px; border: 1px solid #2a2f3a; background:#111522; color:#eee; }
16
+ .btn {
17
+ padding: 10px 14px; border: 1px solid #2a2f3a; background:#131827; color:#fff; border-radius: 10px;
18
+ cursor: pointer; font-weight: 600;
19
+ }
20
+ #canvas-wrap { position: absolute; inset:0; }
21
+ #help { position: fixed; bottom: 12px; left:12px; right:12px; color:#aeb3c2; font-size:12px; opacity:.9 }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <div id="ui">
26
+ <input id="text" placeholder="Escribe algo..." value="Hola, soy tu avatar 3D con lipsync desde Hugging Face." />
27
+ <button id="speak" class="btn"▶️ Hablar</button>
28
+ <button id="stop" class="btn">⏹️ Detener</button>
29
+ </div>
30
+ <div id="canvas-wrap"></div>
31
+ <div id="help">
32
+ Asegúrate de subir también <code>avatar.glb</code> al Space.
33
+ </div>
34
+
35
+ <!-- Three.js -->
36
+ <script src="https://unpkg.com/three@0.159.0/build/three.min.js"></script>
37
+ <script src="https://unpkg.com/three@0.159.0/examples/js/controls/OrbitControls.js"></script>
38
+ <script src="https://unpkg.com/three@0.159.0/examples/js/loaders/GLTFLoader.js"></script>
39
+
40
+ <script>
41
+ // ======= Escena =======
42
+ let scene, camera, renderer, controls;
43
+ let avatar, jawBone = null, mouthTargets = [];
44
+ let audioCtx, analyser, dataArray, srcNode, audioEl, rafLip;
45
+
46
+ init3D();
47
+ loadAvatar('avatar.glb');
48
+
49
+ function init3D() {
50
+ scene = new THREE.Scene();
51
+ scene.background = new THREE.Color(0x0b0d12);
52
+
53
+ camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 200);
54
+ camera.position.set(0, 1.6, 2.8);
55
+
56
+ renderer = new THREE.WebGLRenderer({ antialias: true });
57
+ renderer.setPixelRatio(devicePixelRatio);
58
+ renderer.setSize(innerWidth, innerHeight);
59
+ document.getElementById('canvas-wrap').appendChild(renderer.domElement);
60
+
61
+ controls = new THREE.OrbitControls(camera, renderer.domElement);
62
+ controls.target.set(0, 1.5, 0);
63
+ controls.update();
64
+
65
+ const hemi = new THREE.HemisphereLight(0xffffff, 0x222233, 1.0);
66
+ scene.add(hemi);
67
+ const dir = new THREE.DirectionalLight(0xffffff, 1.0);
68
+ dir.position.set(3,5,3);
69
+ scene.add(dir);
70
+
71
+ animate();
72
+ addEventListener('resize', onResize);
73
+ }
74
+
75
+ function onResize() {
76
+ camera.aspect = innerWidth/innerHeight;
77
+ camera.updateProjectionMatrix();
78
+ renderer.setSize(innerWidth, innerHeight);
79
+ }
80
+
81
+ function animate() {
82
+ requestAnimationFrame(animate);
83
+ renderer.render(scene, camera);
84
+ }
85
+
86
+ function loadAvatar(url) {
87
+ new THREE.GLTFLoader().load(url, (gltf) => {
88
+ avatar = gltf.scene;
89
+ scene.add(avatar);
90
+
91
+ // busca hueso mandíbula
92
+ avatar.traverse(o => {
93
+ if (o.isBone) {
94
+ const n = o.name.toLowerCase();
95
+ if (n.includes('jaw') || n.includes('mandible')) jawBone = o;
96
+ }
97
+ });
98
+
99
+ // busca morph targets de boca
100
+ avatar.traverse(o => {
101
+ if (o.isMesh && o.morphTargetDictionary) {
102
+ for (const [name, idx] of Object.entries(o.morphTargetDictionary)) {
103
+ if (name.toLowerCase().includes('mouth') || name.toLowerCase().includes('viseme'))
104
+ mouthTargets.push({ mesh:o, index:idx });
105
+ }
106
+ }
107
+ });
108
+ });
109
+ }
110
+
111
+ // ======= TTS directo con HF Inference API =======
112
+ const HF_TTS_URL = "https://api-inference.huggingface.co/models/facebook/mms-tts-spa";
113
+
114
+ async function tts(text) {
115
+ const r = await fetch(HF_TTS_URL, {
116
+ method: 'POST',
117
+ headers: { 'Content-Type':'application/json' },
118
+ body: JSON.stringify({ inputs: text })
119
+ });
120
+ if (!r.ok) throw new Error(await r.text());
121
+ return await r.blob();
122
+ }
123
+
124
+ // ======= Lipsync =======
125
+ function setupAnalyser(streamEl) {
126
+ audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
127
+ analyser = audioCtx.createAnalyser();
128
+ analyser.fftSize = 2048;
129
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
130
+
131
+ if (srcNode) srcNode.disconnect();
132
+ srcNode = audioCtx.createMediaElementSource(streamEl);
133
+ srcNode.connect(analyser);
134
+ analyser.connect(audioCtx.destination);
135
+ }
136
+
137
+ function startLipsync() {
138
+ cancelAnimationFrame(rafLip);
139
+ const loop = () => {
140
+ rafLip = requestAnimationFrame(loop);
141
+ analyser.getByteTimeDomainData(dataArray);
142
+ let sum = 0;
143
+ for (let i=0;i<dataArray.length;i++) {
144
+ const v = (dataArray[i]-128)/128;
145
+ sum += v*v;
146
+ }
147
+ const rms = Math.sqrt(sum/dataArray.length);
148
+ applyMouth(rms);
149
+ };
150
+ loop();
151
+ }
152
+
153
+ function stopLipsync() {
154
+ cancelAnimationFrame(rafLip);
155
+ applyMouth(0);
156
+ }
157
+
158
+ function applyMouth(power) {
159
+ const open = Math.min(1.0, power*6);
160
+ if (mouthTargets.length) {
161
+ mouthTargets.forEach(mt => mt.mesh.morphTargetInfluences[mt.index] = open);
162
+ } else if (jawBone) {
163
+ jawBone.rotation.x = -open*0.4;
164
+ }
165
+ }
166
+
167
+ function stopAllAudio() {
168
+ if (audioEl) { audioEl.pause(); audioEl.src = ''; audioEl = null; }
169
+ if (srcNode) { try{srcNode.disconnect();}catch(e){}; srcNode = null; }
170
+ stopLipsync();
171
+ }
172
+
173
+ // ======= UI =======
174
+ const elText = document.getElementById('text');
175
+ document.getElementById('speak').onclick = async () => {
176
+ try {
177
+ stopAllAudio();
178
+ const blob = await tts(elText.value.trim() || "Hola, probando voz y lipsync.");
179
+ audioEl = new Audio(URL.createObjectURL(blob));
180
+ audioEl.onplay = () => { setupAnalyser(audioEl); startLipsync(); };
181
+ audioEl.onended = stopAllAudio;
182
+ await audioEl.play();
183
+ } catch (e) { alert("Error TTS: " + e.message); }
184
+ };
185
+ document.getElementById('stop').onclick = stopAllAudio;
186
+ </script>
187
+ </body>
188
+ </html>
persona.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9b707d6cd9dfe6ea094abb30a75af22b34c8d41146c958baee28106126276626
3
+ size 2057600