wco_models 3.1.0.302 → 3.1.0.304

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.txt +13 -0
  3. data/app/assets/javascripts/vendor/dynamicbones.mjs +1053 -0
  4. data/app/assets/javascripts/vendor/talkinghead.mjs +4825 -0
  5. data/app/controllers/wco/api_controller.rb +5 -2
  6. data/app/controllers/wco/application_controller.rb +31 -5
  7. data/app/controllers/wco/newsproducer_controller.rb +22 -0
  8. data/app/controllers/wco/newsvideos_controller.rb +1 -33
  9. data/app/controllers/wco/reports_controller.rb +21 -0
  10. data/app/helpers/wco/application_helper.rb +1 -1
  11. data/app/models/wco/ai_writer.rb +2 -1
  12. data/app/models/wco/leadset.rb +1 -1
  13. data/app/models/wco/newspartial.rb +10 -6
  14. data/app/models/wco/newsvideo.rb +46 -9
  15. data/app/models/wco/setting.rb +24 -0
  16. data/app/models/wco_email/email_filter.rb +1 -1
  17. data/app/views/wco/_main_footer.haml +2 -0
  18. data/app/views/wco/application/settings.haml +19 -0
  19. data/app/views/wco/newspartials/_show_in_newsvideo.haml +10 -4
  20. data/app/views/wco/newsproducer/_studio_1.js +279 -0
  21. data/app/views/wco/newsproducer/canvas.html.erb +263 -0
  22. data/app/views/wco/newsproducer/canvas_minimal.html.erb +132 -0
  23. data/app/views/wco/newsproducer/studio_1.html.erb +26 -0
  24. data/app/views/wco/reports/show.haml +9 -4
  25. data/config/initializers/assets.rb +2 -0
  26. data/config/routes.rb +15 -4
  27. data/lib/shortcuts.rb +4 -3
  28. metadata +27 -5
  29. data/config/initializers/ahoy.rb-trash +0 -12
  30. /data/app/views/wco/reports/{_to_facebook.html → _to_facebook_js.html} +0 -0
  31. /data/lib/{20260402_tmp.rb → done/20260402_migrate_email_filters.rb} +0 -0
@@ -0,0 +1,279 @@
1
+
2
+ /*
3
+ **/
4
+ const logg = (a, b="", c=null) => {
5
+ if ('undefined' === typeof window) { return }
6
+ c = "string" === typeof c ? c : b.replace(/\W/g, "");
7
+ if (c.length > 0) {
8
+ window[c] = a;
9
+ }
10
+ console.log(`+++ ${b}:`, a); // eslint-disable-line no-console
11
+ };
12
+
13
+ let avatar_url = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.0.3/public/vendor/models/avatars/brunette.glb'
14
+
15
+ import * as THREE from 'three';
16
+
17
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
18
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
19
+
20
+ import { TalkingHead } from "talkinghead";
21
+
22
+ const params = new URLSearchParams(window.location.search);
23
+
24
+ let config = {
25
+ sampleRate: 8000,
26
+ mood: 'neutral',
27
+ gain: 0.5,
28
+ lipsyncType: "visemes",
29
+ lipsyncLang: "en",
30
+ waitForAudioChunks: false,
31
+ enableMetrics: false,
32
+ };
33
+ let width = 640;
34
+ let height = 480;
35
+ const FPS = 24;
36
+
37
+ let frame = 0;
38
+ const api_key = params.get('api_key')
39
+ const api_secret = params.get('api_secret')
40
+ const wco_origin = params.get('wco_origin')
41
+ const newspartial_id = params.get('newspartial_id')
42
+ let totalFrames
43
+
44
+ let camera, controls, head, renderer, scene;
45
+ let faceTarget = new THREE.Vector3();
46
+ let chunkedInput = null;
47
+ var capturer = new CCapture( { format: 'webm', framerate: FPS } );
48
+
49
+ async function init() {
50
+ if (newspartial_id) {
51
+ try {
52
+ const url = wco_origin + '/wco/api/newspartials/' + newspartial_id + '/config.json?api_key=' + api_key + '&api_secret=' + api_secret
53
+ chunkedInput = await fetch(url).then(r => r.json())
54
+ // API may return a JSON string payload
55
+ if (typeof chunkedInput === 'string') {
56
+ chunkedInput = JSON.parse(chunkedInput)
57
+ }
58
+ logg(chunkedInput, 'chunkedInput')
59
+ const last = chunkedInput.wtimes.length-1
60
+ const duration_ms = chunkedInput.wtimes[last] + chunkedInput.wdurations[last]
61
+ logg(duration_ms, 'duration_ms')
62
+ totalFrames = duration_ms/1000*FPS;
63
+ } catch (error) {
64
+ console.log(error);
65
+ chunkedInput = null;
66
+ totalFrames = undefined;
67
+ }
68
+ }
69
+
70
+ scene = new THREE.Scene();
71
+ scene.background = new THREE.Color(0xd3d3d3);
72
+
73
+ /* fov — Camera frustum vertical field of view.
74
+ * aspect — Camera frustum aspect ratio.
75
+ * near — Camera frustum near plane.
76
+ * far — Camera frustum far plane.
77
+ **/
78
+ camera = new THREE.PerspectiveCamera( 10, width/height, 0.1, 1000 )
79
+ camera.position.set( 0, 2, 8 )
80
+ camera.lookAt( 0, 0, 0 )
81
+
82
+ const ambientLight = new THREE.AmbientLight( 0xffffff );
83
+ scene.add( ambientLight );
84
+
85
+ const grid = new THREE.GridHelper(10, 10, 0x888888, 0xbbbbbb);
86
+ grid.position.y = 0;
87
+ scene.add(grid);
88
+
89
+ const nodeAvatar = document.getElementById('avatar');
90
+ head = new TalkingHead( nodeAvatar, {
91
+ avatarOnly: true,
92
+ avatarOnlyScene: scene,
93
+ avatarOnlyCamera: camera,
94
+
95
+ lipsyncModules: ["en"],
96
+ });
97
+ logg(head, 'head')
98
+
99
+
100
+
101
+ const nodeLoading = document.getElementById('loading');
102
+ try {
103
+ nodeLoading.textContent = "Loading...";
104
+ await head.showAvatar( {
105
+ url: avatar_url,
106
+ body: 'F',
107
+ avatarMood: 'neutral',
108
+ lipsyncLang: 'en'
109
+ }, (ev) => {
110
+ if ( ev.lengthComputable ) {
111
+ let val = Math.min(100,Math.round(ev.loaded/ev.total * 100 ));
112
+ nodeLoading.textContent = "Loading " + val + "%";
113
+ }
114
+ });
115
+ nodeLoading.style.display = 'none';
116
+
117
+ head.armature.position.set(0, 0, 0);
118
+ head.armature.rotation.set(0, 0, 0);
119
+ scene.add(head.armature);
120
+
121
+ // Put feet at the origin
122
+ head.armature.updateMatrixWorld(true);
123
+ const leftToe = new THREE.Vector3();
124
+ const rightToe = new THREE.Vector3();
125
+ head.objectLeftToeBase.getWorldPosition(leftToe);
126
+ head.objectRightToeBase.getWorldPosition(rightToe);
127
+ head.armature.position.set(
128
+ -(leftToe.x + rightToe.x) / 2,
129
+ -Math.min(leftToe.y, rightToe.y),
130
+ -(leftToe.z + rightToe.z) / 2
131
+ );
132
+ head.armature.updateMatrixWorld(true);
133
+
134
+ // Camera at head level, pointed at the face
135
+ const leftEye = new THREE.Vector3();
136
+ const rightEye = new THREE.Vector3();
137
+ head.objectLeftEye.getWorldPosition(leftEye);
138
+ head.objectRightEye.getWorldPosition(rightEye);
139
+ faceTarget.copy(leftEye).add(rightEye).multiplyScalar(0.5);
140
+
141
+ const camPos = new THREE.Vector3(0, 0, 2);
142
+ camPos.applyQuaternion(head.armature.quaternion);
143
+ camPos.add(faceTarget);
144
+ camera.position.copy(camPos);
145
+ camera.lookAt(faceTarget);
146
+
147
+ await head.streamStart({
148
+ sampleRate: config.sampleRate,
149
+ mood: config.mood,
150
+ gain: config.gain,
151
+ lipsyncType: config.lipsyncType,
152
+ lipsyncLang: config.lipsyncLang,
153
+ waitForAudioChunks: config.waitForAudioChunks,
154
+ metrics: config.enableMetrics ? { enabled: true, intervalHz: config.metricsInterval } : { enabled: false }
155
+ })
156
+
157
+ } catch (error) {
158
+ console.log(error);
159
+ nodeLoading.textContent = error.toString();
160
+ }
161
+
162
+
163
+ //
164
+
165
+ renderer = new THREE.WebGLRenderer( {
166
+ alpha: false,
167
+ antialias: true,
168
+ } );
169
+ renderer.setClearColor(0xd3d3d3, 1);
170
+ renderer.setPixelRatio( 1 ); // window.devicePixelRatio );
171
+ renderer.setSize( width, height );
172
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
173
+ renderer.toneMappingExposure = 1;
174
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
175
+ document.getElementById('rotatingC').appendChild( renderer.domElement );
176
+
177
+ //
178
+
179
+ controls = new OrbitControls( camera, renderer.domElement );
180
+ controls.enableDamping = true;
181
+ controls.enableRotate = true;
182
+ controls.enablePan = true;
183
+ controls.enableZoom = true;
184
+ controls.minDistance = 0.5;
185
+ controls.maxDistance = 100;
186
+ controls.target.copy(faceTarget);
187
+ controls.autoRotate = false;
188
+ controls.update();
189
+
190
+ //
191
+
192
+ window.addEventListener( 'resize', onWindowResize );
193
+
194
+
195
+ // Keep rendering so OrbitControls drag/damping stay live
196
+ renderer.setAnimationLoop(animate);
197
+
198
+ // Start capture/audio only after the renderer exists
199
+ if (newspartial_id && chunkedInput) {
200
+ capturer.start();
201
+ logg('capturer.start')
202
+ head.streamAudio(chunkedInput);
203
+ }
204
+ }
205
+
206
+ let semafore = false
207
+ document.addEventListener('DOMContentLoaded', async function(e) {
208
+ if (!semafore) {
209
+ semafore = true
210
+ await init()
211
+ }
212
+ })
213
+
214
+ function onWindowResize() {
215
+ camera.aspect = width / height;
216
+ camera.updateProjectionMatrix();
217
+ renderer.setSize( width, height );
218
+ render();
219
+ }
220
+
221
+ function render() {
222
+ renderer.render( scene, camera );
223
+ }
224
+
225
+ function animate() {
226
+ const t = frame / FPS;
227
+ if (head) head.animate(1000/FPS);
228
+ controls.update();
229
+ renderer.render( scene, camera );
230
+
231
+ if (totalFrames) {
232
+ capturer.capture( renderer.domElement );
233
+ frame++;
234
+ if (frame >= totalFrames) {
235
+ totalFrames = false
236
+ renderer.setAnimationLoop(null);
237
+ capturer.stop();
238
+ logg('capturer.stop')
239
+
240
+ if (newspartial_id) {
241
+ capturer.save((blob) => {
242
+ logg(blob, 'blob')
243
+ renderer.domElement.toBlob((thumb) => {
244
+ logg(thumb, 'thumb')
245
+ const form = new FormData();
246
+ form.append('video', blob, 'lips.webm')
247
+ form.append('name', '<ccapturejs>')
248
+ form.append('thumb', thumb)
249
+ form.append('newspartial_id', newspartial_id)
250
+
251
+ fetch(wco_origin + '/wco/api/videos/?api_key=' + api_key + '&api_secret=' + api_secret, {
252
+ method: 'POST',
253
+ headers: {
254
+ // 'Content-Type': 'video/webm',
255
+ },
256
+ body: form,
257
+ })
258
+ })
259
+ });
260
+ }
261
+ // Resume interactive orbit after capture
262
+ renderer.setAnimationLoop(animate);
263
+ }
264
+ }
265
+ }
266
+
267
+
268
+ // Speak when clicked
269
+ const nodeSpeak = document.getElementById('speak');
270
+ nodeSpeak.addEventListener('click', function () {
271
+ try {
272
+
273
+ } catch (error) {
274
+ console.log(error);
275
+ }
276
+ });
277
+
278
+
279
+ console.log('+++ loaded wco_models :: newsproducer :: studio_1.js')
@@ -0,0 +1,263 @@
1
+
2
+ <style>
3
+ body {
4
+ background: #efefef;
5
+ }
6
+ </style>
7
+
8
+ <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
9
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
10
+ <script src="/vendor/javascript/CCapture.all.min.js"></script>
11
+
12
+ <script type="importmap">
13
+ { "imports":
14
+ {
15
+ "three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js/+esm",
16
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/",
17
+ "talkinghead": "https://cdn.jsdelivr.net/gh/met4citizen/TalkingHead@1.7/modules/talkinghead.mjs"
18
+ }
19
+ }
20
+ </script>
21
+
22
+
23
+
24
+ <h1 class='center text-center'>{{ label }}</h1>
25
+ <button id='speak'>Speak</button>
26
+ <div id='avatar'></div>
27
+ <div id='loading'>Loading...</div>
28
+ <div id="rotatingC" class="r2" style="width: 640px;
29
+ height: 480px;
30
+ border: 1px solid red;
31
+ text-align: center; " >
32
+ </div>
33
+
34
+ <script type="module">
35
+ const logg = (a, b="", c=null) => {
36
+ if ('undefined' === typeof window) { return }
37
+ c = "string" === typeof c ? c : b.replace(/\W/g, "");
38
+ if (c.length > 0) {
39
+ window[c] = a;
40
+ }
41
+ console.log(`+++ ${b}:`, a); // eslint-disable-line no-console
42
+ };
43
+
44
+ let avatar_url = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.0.3/public/vendor/models/avatars/brunette.glb'
45
+
46
+ import * as THREE from 'three';
47
+
48
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
49
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
50
+
51
+ import { TalkingHead } from "talkinghead";
52
+
53
+ const params = new URLSearchParams(window.location.search);
54
+
55
+ let config = {
56
+ sampleRate: 8000,
57
+ mood: 'neutral',
58
+ gain: 0.5,
59
+ lipsyncType: "visemes",
60
+ lipsyncLang: "en",
61
+ waitForAudioChunks: false,
62
+ enableMetrics: false,
63
+ };
64
+ let width = 640;
65
+ let height = 480;
66
+ const FPS = 24;
67
+
68
+ let frame = 0;
69
+ const api_key = params.get('api_key')
70
+ const api_secret = params.get('api_secret')
71
+ const wco_origin = params.get('wco_origin')
72
+ const newspartial_id = params.get('newspartial_id')
73
+ let totalFrames
74
+
75
+ let camera, controls, head, renderer, scene;
76
+ var capturer = new CCapture( { format: 'webm', framerate: FPS } );
77
+
78
+ async function init() {
79
+ if (newspartial_id) {
80
+ const url = wco_origin + '/wco/api/newspartials/' + newspartial_id + '/config.json?api_key=' + api_key + '&api_secret=' + api_secret
81
+ let chunkedInput = await fetch(url).then(r => r.json())
82
+ logg(chunkedInput, 'chunkedInput')
83
+ const last = chunkedInput.wtimes.length-1
84
+ const duration_ms = chunkedInput.wtimes[last] + chunkedInput.wdurations[last]
85
+ logg(duration_ms, 'duration_ms')
86
+ totalFrames = duration_ms/1000*FPS;
87
+ }
88
+
89
+ scene = new THREE.Scene();
90
+
91
+ /* fov — Camera frustum vertical field of view.
92
+ * aspect — Camera frustum aspect ratio.
93
+ * near — Camera frustum near plane.
94
+ * far — Camera frustum far plane.
95
+ **/ //
96
+ camera = new THREE.PerspectiveCamera( 10, width/height, 0.1, 10 )
97
+ camera.position.set( 0, 0, 0 )
98
+ camera.rotation.set( 0, 0, 0 )
99
+
100
+ const ambientLight = new THREE.AmbientLight( 0xffffff );
101
+ scene.add( ambientLight );
102
+
103
+ const nodeAvatar = document.getElementById('avatar');
104
+ head = new TalkingHead( nodeAvatar, {
105
+ avatarOnly: true,
106
+ avatarOnlyCamera: camera,
107
+
108
+ lipsyncModules: ["en"],
109
+ // cameraView: "upper",
110
+ // update: function () {
111
+ // capturer.capture( renderer.domElement );
112
+ // }
113
+ });
114
+ logg(head, 'head')
115
+
116
+
117
+
118
+ const nodeLoading = document.getElementById('loading');
119
+ try {
120
+ nodeLoading.textContent = "Loading...";
121
+ await head.showAvatar( {
122
+ url: avatar_url,
123
+ body: 'F',
124
+ avatarMood: 'neutral',
125
+ lipsyncLang: 'en'
126
+ }, (ev) => {
127
+ if ( ev.lengthComputable ) {
128
+ let val = Math.min(100,Math.round(ev.loaded/ev.total * 100 ));
129
+ nodeLoading.textContent = "Loading " + val + "%";
130
+ }
131
+ });
132
+ nodeLoading.style.display = 'none';
133
+
134
+ head.armature.position.set(0,-3,0);
135
+ head.armature.rotation.set(0,1,0);
136
+ scene.add(head.armature);
137
+
138
+ await head.streamStart({
139
+ sampleRate: config.sampleRate,
140
+ mood: config.mood,
141
+ gain: config.gain,
142
+ lipsyncType: config.lipsyncType,
143
+ lipsyncLang: config.lipsyncLang,
144
+ waitForAudioChunks: config.waitForAudioChunks,
145
+ // Configure metrics: enabled/disabled and reporting rate
146
+ metrics: config.enableMetrics ? { enabled: true, intervalHz: config.metricsInterval } : { enabled: false }
147
+ })
148
+
149
+ if (newspartial_id) {
150
+ capturer.start();
151
+ logg('capturer.start')
152
+ head.streamAudio(chunkedInput);
153
+ }
154
+
155
+ } catch (error) {
156
+ console.log(error);
157
+ nodeLoading.textContent = error.toString();
158
+ }
159
+
160
+
161
+ //
162
+
163
+ renderer = new THREE.WebGLRenderer( {
164
+ alpha: false,
165
+ antialias: true,
166
+ } );
167
+ renderer.setPixelRatio( window.devicePixelRatio );
168
+ renderer.setSize( width, height );
169
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
170
+ renderer.toneMappingExposure = 1;
171
+ renderer.outputEncoding = THREE.sRGBEncoding;
172
+ document.getElementById('rotatingC').appendChild( renderer.domElement );
173
+
174
+ //
175
+
176
+ controls = new OrbitControls( camera, renderer.domElement );
177
+ controls.enableDamping = true;
178
+ controls.minDistance = 0.5;
179
+ controls.maxDistance = 100;
180
+ controls.autoRotate = false;
181
+ controls.update();
182
+
183
+ //
184
+
185
+ window.addEventListener( 'resize', onWindowResize );
186
+
187
+
188
+ // renderer.setAnimationLoop(animate);
189
+ animate()
190
+ }
191
+
192
+ let semafore = false
193
+ document.addEventListener('DOMContentLoaded', async function(e) {
194
+ if (!semafore) {
195
+ semafore = true
196
+ await init()
197
+ }
198
+ })
199
+
200
+ function onWindowResize() {
201
+ camera.aspect = w / h;
202
+ camera.updateProjectionMatrix();
203
+ renderer.setSize( w, h );
204
+ render();
205
+ }
206
+
207
+ function render() {
208
+ renderer.render( scene, camera );
209
+ }
210
+
211
+ function animate() {
212
+ const t = frame / FPS;
213
+ head.animate(1000/FPS);
214
+ controls.update();
215
+ renderer.render( scene, camera );
216
+ capturer.capture( renderer.domElement );
217
+
218
+ frame++;
219
+ if (frame < totalFrames) {
220
+ requestAnimationFrame(animate)
221
+ } else {
222
+ capturer.stop();
223
+ logg('capturer.stop')
224
+
225
+ if (wco_origin) {
226
+ capturer.save((blob) => {
227
+ logg(blob, 'blob')
228
+ renderer.domElement.toBlob((thumb) => {
229
+ logg(thumb, 'thumb')
230
+ const form = new FormData();
231
+ form.append('video', blob, 'lips.webm')
232
+ form.append('name', '<ccapturejs>')
233
+ form.append('thumb', thumb)
234
+ form.append('newspartial_id', newspartial_id)
235
+
236
+ fetch(wco_origin + '/wco/api/videos/?api_key=' + api_key + '&api_secret=' + api_secret, {
237
+ method: 'POST',
238
+ headers: {
239
+ // 'Content-Type': 'video/webm',
240
+ },
241
+ body: form,
242
+ })
243
+ })
244
+ });
245
+ }
246
+
247
+
248
+ }
249
+ }
250
+
251
+
252
+ // Speak when clicked
253
+ const nodeSpeak = document.getElementById('speak');
254
+ nodeSpeak.addEventListener('click', function () {
255
+ try {
256
+
257
+ } catch (error) {
258
+ console.log(error);
259
+ }
260
+ });
261
+
262
+
263
+ </script>
@@ -0,0 +1,132 @@
1
+ <script src="/vendor/javascript/CCapture.all.min.js"></script>
2
+ <style>
3
+ body, html { width:100%; height:100%; max-width: 800px; margin: auto; position: relative; background-color: #202020; color: white; }
4
+ #avatar { display: block; width:100%; height:100%; border: 1px solid yellow; }
5
+
6
+ #controls { display: block; position: absolute; top: 10px; left: 10px; right: 10px; height: 50px; }
7
+ #text { position: absolute; width: Calc( 100% - 110px ); height: 100%; top: 0; left: 0; bottom: 0; right: 110px; font-family: Arial; font-size: 20px; }
8
+ #speak { display: block; position: absolute; top: 0; bottom: 0; right: 0; height: 100%; width: 100px; font-family: Arial; font-size: 20px; }
9
+ #loading { display: block; position: absolute; bottom: 10px; left: 10px; right: 10px; height: 50px; font-family: Arial; font-size: 20px; }
10
+ </style>
11
+
12
+ <script type="importmap">
13
+ { "imports":
14
+ {
15
+ "three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js/+esm",
16
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/",
17
+ "talkinghead": "https://cdn.jsdelivr.net/gh/met4citizen/TalkingHead@1.7/modules/talkinghead.mjs"
18
+ }
19
+ }
20
+ </script>
21
+
22
+ <script type="module">
23
+ const logg = (a, b="", c=null) => {
24
+ if ('undefined' === typeof window) { return }
25
+ c = "string" === typeof c ? c : b.replace(/\W/g, "");
26
+ if (c.length > 0) {
27
+ window[c] = a;
28
+ }
29
+ console.log(`+++ ${b}:`, a); // eslint-disable-line no-console
30
+ };
31
+ let avatar_url = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.0.3/public/vendor/models/avatars/brunette.glb'
32
+
33
+
34
+ import { TalkingHead } from "talkinghead";
35
+
36
+ let head;
37
+ let camera, controls, renderer, scene;
38
+
39
+ document.addEventListener('DOMContentLoaded', async function(e) {
40
+
41
+ // Instantiate the class
42
+ // NOTE: Never put your API key in a client-side code unless you know
43
+ // that you are the only one to have access to that code!
44
+ const nodeAvatar = document.getElementById('avatar');
45
+ head = new TalkingHead( nodeAvatar, {
46
+ ttsEndpoint: "https://eu-texttospeech.googleapis.com/v1beta1/text:synthesize",
47
+ ttsApikey: "put-your-own-Google-TTS-API-key-here", // <- Change this
48
+ lipsyncModules: ["en", "fi"],
49
+ cameraView: "upper",
50
+ });
51
+
52
+ // Load and show the avatar
53
+ const nodeLoading = document.getElementById('loading');
54
+ try {
55
+ nodeLoading.textContent = "Loading...";
56
+ await head.showAvatar( {
57
+ url: avatar_url,
58
+ body: 'F',
59
+ avatarMood: 'neutral',
60
+ ttsLang: "en-GB",
61
+ ttsVoice: "en-GB-Standard-A",
62
+ lipsyncLang: 'en'
63
+ }, (ev) => {
64
+ if ( ev.lengthComputable ) {
65
+ let val = Math.min(100,Math.round(ev.loaded/ev.total * 100 ));
66
+ nodeLoading.textContent = "Loading " + val + "%";
67
+ }
68
+ });
69
+ nodeLoading.style.display = 'none';
70
+ } catch (error) {
71
+ console.log(error);
72
+ nodeLoading.textContent = error.toString();
73
+ }
74
+
75
+ // Speak when clicked
76
+ const nodeSpeak = document.getElementById('speak');
77
+ nodeSpeak.addEventListener('click', function () {
78
+ try {
79
+ const text = document.getElementById('text').value;
80
+ if ( text ) {
81
+ head.speakText( text );
82
+ }
83
+ } catch (error) {
84
+ console.log(error);
85
+ }
86
+ });
87
+
88
+ // Pause animation when document is not visible
89
+ document.addEventListener("visibilitychange", async function (ev) {
90
+ if (document.visibilityState === "visible") {
91
+ head.start();
92
+ } else {
93
+ head.stop();
94
+ }
95
+ });
96
+
97
+ // herehere
98
+ const params = new URLSearchParams(window.location.search);
99
+
100
+ const w = 1024;
101
+ const h = 576;
102
+ let config = {
103
+ sampleRate: 8000,
104
+ mood: 'neutral',
105
+ gain: 0.5,
106
+ lipsyncType: "visemes",
107
+ lipsyncLang: "en",
108
+ waitForAudioChunks: false,
109
+ enableMetrics: false,
110
+ };
111
+ const FPS = 24;
112
+ let frame = 0;
113
+ const api_key = params.get('api_key')
114
+ const api_secret = params.get('api_secret')
115
+ const wco_origin = params.get('wco_origin')
116
+ const newspartial_id = params.get('newspartial_id')
117
+ let totalFrames
118
+
119
+
120
+ var capturer = new CCapture( { format: 'webm', framerate: FPS } );
121
+
122
+ });
123
+
124
+ </script>
125
+
126
+ <div id="avatar"></div>
127
+ <div id="controls">
128
+ <input id="text" type="text" value="Hi there. How are you? I'm fine.">
129
+ <input id="speak" type="button" value="Speak">
130
+ </div>
131
+ <div id="loading"></div>
132
+
@@ -0,0 +1,26 @@
1
+
2
+ <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
3
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
4
+ <script src="/vendor/javascript/CCapture.all.min.js"></script>
5
+
6
+ <script type="importmap">
7
+ { "imports":
8
+ {
9
+ "three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js/+esm",
10
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/",
11
+ "talkinghead": "https://cdn.jsdelivr.net/gh/met4citizen/TalkingHead@1.7/modules/talkinghead.mjs"
12
+ }
13
+ }
14
+ </script>
15
+
16
+ <h1 class='center text-center'>{{ label }}</h1>
17
+ <button id='speak'>Speak</button>
18
+ <div id='avatar'></div>
19
+ <div id='loading'>Loading...</div>
20
+ <div id="rotatingC" class="r2" style="width: 640px;
21
+ height: 480px;
22
+ border: 1px solid red;
23
+ text-align: center; " >
24
+ </div>
25
+
26
+ <script type="module"><%= render 'studio_1.js' %></script>