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.
- checksums.yaml +4 -4
- data/README.txt +13 -0
- data/app/assets/javascripts/vendor/dynamicbones.mjs +1053 -0
- data/app/assets/javascripts/vendor/talkinghead.mjs +4825 -0
- data/app/controllers/wco/api_controller.rb +5 -2
- data/app/controllers/wco/application_controller.rb +31 -5
- data/app/controllers/wco/newsproducer_controller.rb +22 -0
- data/app/controllers/wco/newsvideos_controller.rb +1 -33
- data/app/controllers/wco/reports_controller.rb +21 -0
- data/app/helpers/wco/application_helper.rb +1 -1
- data/app/models/wco/ai_writer.rb +2 -1
- data/app/models/wco/leadset.rb +1 -1
- data/app/models/wco/newspartial.rb +10 -6
- data/app/models/wco/newsvideo.rb +46 -9
- data/app/models/wco/setting.rb +24 -0
- data/app/models/wco_email/email_filter.rb +1 -1
- data/app/views/wco/_main_footer.haml +2 -0
- data/app/views/wco/application/settings.haml +19 -0
- data/app/views/wco/newspartials/_show_in_newsvideo.haml +10 -4
- data/app/views/wco/newsproducer/_studio_1.js +279 -0
- data/app/views/wco/newsproducer/canvas.html.erb +263 -0
- data/app/views/wco/newsproducer/canvas_minimal.html.erb +132 -0
- data/app/views/wco/newsproducer/studio_1.html.erb +26 -0
- data/app/views/wco/reports/show.haml +9 -4
- data/config/initializers/assets.rb +2 -0
- data/config/routes.rb +15 -4
- data/lib/shortcuts.rb +4 -3
- metadata +27 -5
- data/config/initializers/ahoy.rb-trash +0 -12
- /data/app/views/wco/reports/{_to_facebook.html → _to_facebook_js.html} +0 -0
- /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>
|