gnilets commited on
Commit
65919e0
·
verified ·
1 Parent(s): bf1867d

Update main.ts

Browse files
Files changed (1) hide show
  1. main.ts +117 -166
main.ts CHANGED
@@ -1,127 +1,92 @@
1
- import { serve } from "https://deno.land/std/http/server.ts";
2
- import { EdgeSpeechTTS } from "https://esm.sh/@lobehub/tts@1";
3
-
4
-
5
- const VOICES_URL = "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4";
6
-
7
- async function fetchVoiceList() {
8
- const response = await fetch(VOICES_URL);
9
- const voices = await response.json();
10
- return voices.reduce((acc: Record<string, { model: string, name: string, friendlyName: string, locale: string }[]>, voice: any) => {
11
- const { ShortName: model, ShortName: name, FriendlyName: friendlyName, Locale: locale } = voice;
12
- if (!acc[locale]) acc[locale] = [];
13
- acc[locale].push({ model, name, friendlyName, locale });
14
- return acc;
15
- }, {});
16
- }
17
 
18
  async function synthesizeSpeech(model: string, voice: string, text: string) {
19
- let voiceName;
20
- let rate = 0;
21
- let pitch = 0;
22
-
23
- if (!model.includes("Neural")) {
24
- switch (model) {
25
- case "ava":
26
- voiceName = "en-US-AvaMultilingualNeural";
27
- break;
28
- case "andrew":
29
- voiceName = "en-US-AndrewMultilingualNeural";
30
- break;
31
- case "emma":
32
- voiceName = "en-US-EmmaMultilingualNeural";
33
- break;
34
- case "brian":
35
- voiceName = "en-US-BrianMultilingualNeural";
36
- break;
37
- case "vivienne":
38
- voiceName = "fr-FR-VivienneMultilingualNeural";
39
- break;
40
- case "remy":
41
- voiceName = "fr-FR-RemyMultilingualNeural";
42
- break;
43
- case "seraphina":
44
- voiceName = "de-DE-SeraphinaMultilingualNeural";
45
- break;
46
- case "florian":
47
- voiceName = "de-DE-FlorianMultilingualNeural";
48
- break;
49
- case "dmitry":
50
- voiceName = "ru-RU-DmitryNeural";
51
- break;
52
- case "svetlana":
53
- voiceName = "ru-RU-SvetlanaNeural";
54
- break;
55
- default:
56
- voiceName = "en-US-BrianMultilingualNeural";
57
- break;
 
 
 
 
 
 
58
  }
59
- } else {
60
- voiceName = model;
61
- const params = Object.fromEntries(
62
- voice.split("|").map((p) => p.split(":") as [string, string])
63
- );
64
- rate = Number(params["rate"] || 0);
65
- pitch = Number(params["pitch"] || 0);
66
- }
67
 
68
- const tts = new EdgeSpeechTTS();
69
-
70
- const payload = {
71
- input: text,
72
- options: {
73
- rate: rate,
74
- pitch: pitch,
75
- voice: voiceName
76
- },
77
- };
78
- const response = await tts.create(payload);
79
- const mp3Buffer = new Uint8Array(await response.arrayBuffer());
80
-
81
- console.log(`Successfully synthesized speech, returning audio/mpeg response`);
82
- return new Response(mp3Buffer, {
83
- headers: { "Content-Type": "audio/mpeg" },
84
- });
85
  }
86
 
87
  function validateContentType(req: Request, expected: string) {
88
- const contentType = req.headers.get("Content-Type");
89
- if (contentType !== expected) {
90
- console.log(`Invalid Content-Type ${contentType}, expected ${expected}`);
91
- return new Response("Bad Request", { status: 400 });
92
- }
93
  }
94
 
95
- async function handleDebugRequest(req: Request) {
96
- const url = new URL(req.url);
97
- const voice = url.searchParams.get("voice") || "";
98
- const model = url.searchParams.get("model") || "";
99
- const text = url.searchParams.get("text") || "";
100
-
101
- console.log(`Debug request with model=${model}, voice=${voice}, text=${text}`);
102
-
103
- if (!voice || !model || !text) {
104
- console.log("Missing required parameters");
105
- return new Response("Bad Request", { status: 400 });
106
- }
107
-
108
- return synthesizeSpeech(model, voice, text);
109
  }
110
 
111
  async function handleSynthesisRequest(req: Request) {
112
-
113
- if (req.method !== "POST") {
114
- console.log(`Invalid method ${req.method}, expected POST`);
115
- return new Response("Method Not Allowed", { status: 405 });
116
- }
117
-
118
- const invalidContentType = validateContentType(req, "application/json");
119
- if (invalidContentType) return invalidContentType;
120
-
121
- const { model, input, voice } = await req.json();
122
- console.log(`Synthesis request with model=${model}, input=${input}, voice=${voice}`);
123
-
124
- return synthesizeSpeech(model, voice, input);
125
  }
126
 
127
 
@@ -225,12 +190,12 @@ async function handleDemoRequest(req: Request) {
225
  }
226
 
227
  #audioPlayerContainer {
228
- text-align: center; /* Центрируем содержимое контейнера */
229
  }
230
 
231
  audio {
232
  width: 100%;
233
- max-width: 600px; /* Ограничиваем максимальную ширину плеера */
234
  margin: 10px 0;
235
  }
236
 
@@ -283,18 +248,15 @@ async function handleDemoRequest(req: Request) {
283
  const audioUrl = URL.createObjectURL(blob);
284
  const audioPlayerContainer = document.getElementById('audioPlayerContainer');
285
 
286
- // Удаляем старый аудиоплеер, если он существует
287
  if (audio) {
288
  audio.pause();
289
  audioPlayerContainer.innerHTML = '';
290
  }
291
 
292
- // Создаем новый аудиоплеер
293
  audio = new Audio(audioUrl);
294
  audio.controls = true;
295
  audioPlayerContainer.appendChild(audio);
296
 
297
- // Создаем ссылку для скачивания
298
  const downloadLink = document.createElement('a');
299
  downloadLink.href = audioUrl;
300
  downloadLink.download = 'synthesized_voice.mp3';
@@ -302,10 +264,7 @@ async function handleDemoRequest(req: Request) {
302
  downloadLink.style.display = 'block';
303
  downloadLink.style.marginTop = '10px';
304
 
305
- // Добавляем ссылку для скачивания в контейнер
306
  audioPlayerContainer.appendChild(downloadLink);
307
-
308
- // Воспроизводим аудио
309
  audio.play();
310
  });
311
 
@@ -324,7 +283,7 @@ async function handleDemoRequest(req: Request) {
324
  voiceSelect.appendChild(option);
325
  });
326
  } catch (error) {
327
- console.error('Ошибка при получении списка моделей:', error);
328
  }
329
  }
330
 
@@ -334,61 +293,53 @@ async function handleDemoRequest(req: Request) {
334
  </body></html>`;
335
 
336
  return new Response(html, {
337
- headers: { "Content-Type": "text/html" },
338
  });
339
  }
340
 
341
  async function handleVoiceList() {
342
- let voices = [
343
- {model: 'ava', gender: 'female'},
344
- {model: 'andrew', gender: 'male'},
345
- {model: 'emma', gender: 'female'},
346
- {model: 'brian', gender: 'male'},
347
- {model: 'vivienne', gender: 'female'},
348
- {model: 'remy', gender: 'male'},
349
- {model: 'seraphina', gender: 'female'},
350
- {model: 'florian', gender: 'male'},
351
- {model: 'dmitry', gender: 'male'},
352
- {model: 'svetlana', gender: 'female'}
353
- ];
354
-
355
- const sortedVoiceList = voices.sort((a, b) => {
356
- if (a.gender === 'male' && b.gender === 'female') return -1;
357
- if (a.gender === 'female' && b.gender === 'male') return 1;
358
- return 0;
359
- });
360
-
361
- return new Response(JSON.stringify(sortedVoiceList), {
362
- headers: { "Content-Type": "application/json" },
363
- });
364
 
365
  }
366
 
367
 
368
  serve(async (req) => {
369
- try {
370
- const url = new URL(req.url);
371
 
372
- if (url.pathname === "/") {
373
- return handleDemoRequest(req);
374
- }
375
- if (url.pathname === "/v1/audio/models") {
376
- return handleVoiceList();
377
- }
378
- if (url.pathname === "/tts") {
379
- return handleDebugRequest(req);
380
- }
381
 
382
- if (url.pathname !== "/v1/audio/speech") {
383
- console.log(`Unhandled path ${url.pathname}`);
384
- return new Response("Not Found", { status: 404 });
385
- }
386
 
387
- return handleSynthesisRequest(req);
388
- } catch (err) {
389
- console.error(`Error processing request: ${err.message}`);
390
- return new Response(`Internal Server Error\n${err.message}`, {
391
- status: 500,
392
- });
393
- }
394
  });
 
1
+ import {serve} from "https://deno.land/std/http/server.ts";
2
+ import {EdgeSpeechTTS} from "https://esm.sh/@lobehub/tts@1";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  async function synthesizeSpeech(model: string, voice: string, text: string) {
5
+ let voiceName;
6
+ let rate = 0;
7
+ let pitch = 0;
8
+
9
+ if (!model.includes("Neural")) {
10
+ switch (model) {
11
+ case "ava":
12
+ voiceName = "en-US-AvaMultilingualNeural";
13
+ break;
14
+ case "andrew":
15
+ voiceName = "en-US-AndrewMultilingualNeural";
16
+ break;
17
+ case "emma":
18
+ voiceName = "en-US-EmmaMultilingualNeural";
19
+ break;
20
+ case "brian":
21
+ voiceName = "en-US-BrianMultilingualNeural";
22
+ break;
23
+ case "vivienne":
24
+ voiceName = "fr-FR-VivienneMultilingualNeural";
25
+ break;
26
+ case "remy":
27
+ voiceName = "fr-FR-RemyMultilingualNeural";
28
+ break;
29
+ case "seraphina":
30
+ voiceName = "de-DE-SeraphinaMultilingualNeural";
31
+ break;
32
+ case "florian":
33
+ voiceName = "de-DE-FlorianMultilingualNeural";
34
+ break;
35
+ case "dmitry":
36
+ voiceName = "ru-RU-DmitryNeural";
37
+ break;
38
+ case "svetlana":
39
+ voiceName = "ru-RU-SvetlanaNeural";
40
+ break;
41
+ default:
42
+ voiceName = "en-US-BrianMultilingualNeural";
43
+ break;
44
+ }
45
+ } else {
46
+ voiceName = model;
47
+ const params = Object.fromEntries(voice.split("|").map((p) => p.split(":") as [string, string]));
48
+ rate = Number(params["rate"] || 0);
49
+ pitch = Number(params["pitch"] || 0);
50
  }
 
 
 
 
 
 
 
 
51
 
52
+ const tts = new EdgeSpeechTTS();
53
+
54
+ const payload = {
55
+ input: text, options: {
56
+ rate: rate, pitch: pitch, voice: voiceName
57
+ },
58
+ };
59
+ const response = await tts.create(payload);
60
+ const mp3Buffer = new Uint8Array(await response.arrayBuffer());
61
+ return new Response(mp3Buffer, {
62
+ headers: {"Content-Type": "audio/mpeg"},
63
+ });
 
 
 
 
 
64
  }
65
 
66
  function validateContentType(req: Request, expected: string) {
67
+ const contentType = req.headers.get("Content-Type");
68
+ if (contentType !== expected) {
69
+ console.log(`Invalid Content-Type ${contentType}, expected ${expected}`);
70
+ return new Response("Bad Request", {status: 400});
71
+ }
72
  }
73
 
74
+ async function handleDebugRequest() {
75
+ const voice = "rate:0.0|pitch:0.0";
76
+ const model = "en-US-BrianMultilingualNeural";
77
+ const text = "Приветик! Надеюсь ты меня хорошо слышишь? Алё?!";
78
+ console.log(`model=${model}, voice=${voice}, text=${text}`);
79
+ return synthesizeSpeech(model, voice, text);
 
 
 
 
 
 
 
 
80
  }
81
 
82
  async function handleSynthesisRequest(req: Request) {
83
+ if (req.method !== "POST") {
84
+ return new Response("Method Not Allowed", {status: 405});
85
+ }
86
+ const invalidContentType = validateContentType(req, "application/json");
87
+ if (invalidContentType) return invalidContentType;
88
+ const {model, input, voice} = await req.json();
89
+ return synthesizeSpeech(model, voice, input);
 
 
 
 
 
 
90
  }
91
 
92
 
 
190
  }
191
 
192
  #audioPlayerContainer {
193
+ text-align: center;
194
  }
195
 
196
  audio {
197
  width: 100%;
198
+ max-width: 600px;
199
  margin: 10px 0;
200
  }
201
 
 
248
  const audioUrl = URL.createObjectURL(blob);
249
  const audioPlayerContainer = document.getElementById('audioPlayerContainer');
250
 
 
251
  if (audio) {
252
  audio.pause();
253
  audioPlayerContainer.innerHTML = '';
254
  }
255
 
 
256
  audio = new Audio(audioUrl);
257
  audio.controls = true;
258
  audioPlayerContainer.appendChild(audio);
259
 
 
260
  const downloadLink = document.createElement('a');
261
  downloadLink.href = audioUrl;
262
  downloadLink.download = 'synthesized_voice.mp3';
 
264
  downloadLink.style.display = 'block';
265
  downloadLink.style.marginTop = '10px';
266
 
 
267
  audioPlayerContainer.appendChild(downloadLink);
 
 
268
  audio.play();
269
  });
270
 
 
283
  voiceSelect.appendChild(option);
284
  });
285
  } catch (error) {
286
+ console.error('ошибка при получении списка моделей:', error);
287
  }
288
  }
289
 
 
293
  </body></html>`;
294
 
295
  return new Response(html, {
296
+ headers: {"Content-Type": "text/html"},
297
  });
298
  }
299
 
300
  async function handleVoiceList() {
301
+ let voices = [{model: 'ava', gender: 'female'}, {model: 'andrew', gender: 'male'}, {model: 'emma', gender: 'female'}, {model: 'brian', gender: 'male'}, {model: 'vivienne', gender: 'female'}, {model: 'remy', gender: 'male'}, {
302
+ model: 'seraphina',
303
+ gender: 'female'
304
+ }, {model: 'florian', gender: 'male'}, {model: 'dmitry', gender: 'male'}, {model: 'svetlana', gender: 'female'}];
305
+
306
+ const sortedVoiceList = voices.sort((a, b) => {
307
+ if (a.gender === 'male' && b.gender === 'female') return -1;
308
+ if (a.gender === 'female' && b.gender === 'male') return 1;
309
+ return 0;
310
+ });
311
+
312
+ return new Response(JSON.stringify(sortedVoiceList), {
313
+ headers: {"Content-Type": "application/json"},
314
+ });
 
 
 
 
 
 
 
 
315
 
316
  }
317
 
318
 
319
  serve(async (req) => {
320
+ try {
321
+ const url = new URL(req.url);
322
 
323
+ if (url.pathname === "/") {
324
+ return handleDemoRequest(req);
325
+ }
326
+ if (url.pathname === "/v1/audio/models") {
327
+ return handleVoiceList();
328
+ }
329
+ if (url.pathname === "/tts") {
330
+ return handleDebugRequest();
331
+ }
332
 
333
+ if (url.pathname !== "/v1/audio/speech") {
334
+ console.log(`Unhandled path ${url.pathname}`);
335
+ return new Response("Not Found", {status: 404});
336
+ }
337
 
338
+ return handleSynthesisRequest(req);
339
+ } catch (err) {
340
+ console.error(`Error processing request: ${err.message}`);
341
+ return new Response(`Internal Server Error\n${err.message}`, {
342
+ status: 500,
343
+ });
344
+ }
345
  });