mistpe commited on
Commit
e708129
·
verified ·
1 Parent(s): 05ca874

Update main.ts

Browse files
Files changed (1) hide show
  1. main.ts +3 -321
main.ts CHANGED
@@ -155,195 +155,7 @@
155
  // });
156
  // }
157
  // });
158
- import { serve } from "https://deno.land/std/http/server.ts";
159
- import { EdgeSpeechTTS } from "https://esm.sh/@lobehub/tts@1";
160
-
161
- const AUTH_TOKEN = Deno.env.get("AUTH_TOKEN");
162
- const VOICES_URL = "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4";
163
-
164
- async function fetchVoiceList() {
165
- const response = await fetch(VOICES_URL);
166
- const voices = await response.json();
167
- return voices.reduce((acc: Record<string, { model: string, name: string, friendlyName: string, locale: string }[]>, voice: any) => {
168
- const { ShortName: model, ShortName: name, FriendlyName: friendlyName, Locale: locale } = voice;
169
- if (!acc[locale]) acc[locale] = [];
170
- acc[locale].push({ model, name, friendlyName, locale });
171
- return acc;
172
- }, {});
173
- }
174
-
175
- async function synthesizeSpeech(model: string, voice: string, text: string) {
176
- let voiceName;
177
- let rate = 0;
178
- let pitch = 0;
179
-
180
- if (model.includes("tts")) {
181
- rate = 0.1;
182
- pitch = 0.2;
183
-
184
- switch (voice) {
185
- case "alloy":
186
- voiceName = "zh-CN-YunjianNeural";
187
- break;
188
- case "echo":
189
- voiceName = "zh-CN-YunyangNeural";
190
- break;
191
- case "fable":
192
- voiceName = "zh-CN-XiaoxiaoNeural";
193
- break;
194
- case "onyx":
195
- voiceName = "zh-TW-HsiaoChenNeural";
196
- break;
197
- default:
198
- voiceName = "zh-CN-YunxiNeural";
199
- break;
200
- }
201
- } else {
202
- voiceName = model;
203
- const params = Object.fromEntries(
204
- voice.split("|").map((p) => p.split(":") as [string, string])
205
- );
206
- rate = Number(params["rate"] || 0);
207
- pitch = Number(params["pitch"] || 0);
208
- }
209
-
210
- const tts = new EdgeSpeechTTS();
211
-
212
- const payload = {
213
- input: text,
214
- options: {
215
- rate: rate,
216
- pitch: pitch,
217
- voice: voiceName
218
- },
219
- };
220
- const response = await tts.create(payload);
221
- const mp3Buffer = new Uint8Array(await response.arrayBuffer());
222
-
223
- console.log(`Successfully synthesized speech, returning audio/mpeg response`);
224
- return new Response(mp3Buffer, {
225
- headers: { "Content-Type": "audio/mpeg" },
226
- });
227
- }
228
-
229
- function unauthorized(req: Request) {
230
- const authHeader = req.headers.get("Authorization");
231
- return AUTH_TOKEN && authHeader !== `Bearer ${AUTH_TOKEN}`;
232
- }
233
-
234
- function validateContentType(req: Request, expected: string) {
235
- const contentType = req.headers.get("Content-Type");
236
- if (contentType !== expected) {
237
- console.log(`Invalid Content-Type ${contentType}, expected ${expected}`);
238
- return new Response("Bad Request", { status: 400 });
239
- }
240
- }
241
-
242
- async function handleDebugRequest(req: Request) {
243
- const url = new URL(req.url);
244
- const voice = url.searchParams.get("voice") || "";
245
- const model = url.searchParams.get("model") || "";
246
- const text = url.searchParams.get("text") || "";
247
-
248
- console.log(`Debug request with model=${model}, voice=${voice}, text=${text}`);
249
-
250
- if (!voice || !model || !text) {
251
- console.log("Missing required parameters");
252
- return new Response("Bad Request", { status: 400 });
253
- }
254
-
255
- return synthesizeSpeech(model, voice, text);
256
- }
257
-
258
- async function handleSynthesisRequest(req: Request) {
259
- if (unauthorized(req)) {
260
- console.log("Unauthorized request");
261
- return new Response("Unauthorized", { status: 401 });
262
- }
263
-
264
- if (req.method !== "POST") {
265
- console.log(`Invalid method ${req.method}, expected POST`);
266
- return new Response("Method Not Allowed", { status: 405 });
267
- }
268
-
269
- const invalidContentType = validateContentType(req, "application/json");
270
- if (invalidContentType) return invalidContentType;
271
-
272
- const { model, input, voice } = await req.json();
273
- console.log(`Synthesis request with model=${model}, input=${input}, voice=${voice}`);
274
-
275
- return synthesizeSpeech(model, voice, input);
276
- }
277
-
278
- async function handleDemoRequest(req: Request) {
279
- const groupedVoiceList = await fetchVoiceList();
280
-
281
- const html = `<!DOCTYPE html>
282
- <html lang="zh-CN">
283
- <head>
284
- <meta charset="UTF-8">
285
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
286
- <title>Edge TTS 语音合成演示</title>
287
- <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
288
- <style>
289
- :root {
290
- --primary-color: #1890ff;
291
- --primary-light: #40a9ff;
292
- --primary-dark: #096dd9;
293
- --secondary-color: #52c41a;
294
- --accent-color: #722ed1;
295
- --text-color: #262626;
296
- --text-secondary: #8c8c8c;
297
- --bg-color: #ffffff;
298
- --bg-secondary: #fafafa;
299
- --border-color: #d9d9d9;
300
- --border-radius: 8px;
301
- --shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
302
- --shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.15);
303
- }
304
-
305
- * {
306
- box-sizing: border-box;
307
- margin: 0;
308
- padding: 0;
309
- }
310
-
311
- body {
312
- font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
313
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
314
- color: var(--text-color);
315
- min-height: 100vh;
316
- line-height: 1.6;
317
- overflow-x: hidden;
318
- }
319
 
320
- .container {
321
- max-width: 1200px;
322
- margin: 0 auto;
323
- padding: 20px;
324
- min-height: 100vh;
325
- }
326
-
327
- .header {
328
- text-align: center;
329
- margin-bottom: 30px;
330
- color: white;
331
- }
332
-
333
- .header h1 {
334
- font-size: clamp(1.8rem, 4vw, 3rem);
335
- font-weight: 700;
336
- margin-bottom: 10px;
337
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
338
- }
339
-
340
- .header p {
341
- font-size: clamp(1rem, 2.5vw, 1.2rem);
342
- opacity: 0.9;
343
- font-weight: 300;
344
- }
345
-
346
- .main-content {
347
  display: grid;
348
  grid-template-columns: 1fr;
349
  gap: 20px;
@@ -421,144 +233,14 @@ async function handleDemoRequest(req: Request) {
421
  margin-bottom: 20px;
422
  }
423
 
424
- .slider-wrapper {
425
- position: relative;
426
- margin-bottom: 8px;
427
- }
428
-
429
- .slider {
430
- width: 100%;
431
- height: 6px;
432
- border-radius: 3px;
433
- background: var(--border-color);
434
- outline: none;
435
- -webkit-appearance: none;
436
- appearance: none;
437
- cursor: pointer;
438
- }
439
-
440
- .slider::-webkit-slider-thumb {
441
- -webkit-appearance: none;
442
- appearance: none;
443
- width: 20px;
444
- height: 20px;
445
- border-radius: 50%;
446
- background: var(--primary-color);
447
- cursor: pointer;
448
- border: 2px solid white;
449
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
450
- transition: all 0.3s ease;
451
- }
452
-
453
- .slider::-webkit-slider-thumb:hover {
454
- transform: scale(1.1);
455
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
456
  }
457
 
458
- .slider::-moz-range-thumb {
459
- width: 20px;
460
- height: 20px;
461
- border-radius: 50%;
462
- background: var(--primary-color);
463
- cursor: pointer;
464
- border: 2px solid white;
465
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
466
- }
467
-
468
- .slider-value {
469
- font-size: 0.85rem;
470
- color: var(--text-secondary);
471
- text-align: center;
472
- background: var(--bg-secondary);
473
- padding: 4px 8px;
474
- border-radius: 4px;
475
- min-width: 60px;
476
- }
477
-
478
- .voice-container {
479
- max-height: 60vh;
480
- overflow-y: auto;
481
- border: 1px solid var(--border-color);
482
- border-radius: var(--border-radius);
483
- background: var(--bg-secondary);
484
- }
485
-
486
- .voice-container::-webkit-scrollbar {
487
- width: 6px;
488
- }
489
-
490
- .voice-container::-webkit-scrollbar-track {
491
- background: var(--bg-secondary);
492
- }
493
-
494
- .voice-container::-webkit-scrollbar-thumb {
495
- background: var(--border-color);
496
- border-radius: 3px;
497
- }
498
-
499
- .voice-container::-webkit-scrollbar-thumb:hover {
500
- background: var(--text-secondary);
501
- }
502
-
503
- .voice-group {
504
- border-bottom: 1px solid var(--border-color);
505
- background: white;
506
- transition: all 0.3s ease;
507
- }
508
 
509
  .voice-group:last-child {
510
  border-bottom: none;
511
- }
512
-
513
- .voice-header {
514
- padding: 16px 20px;
515
- cursor: pointer;
516
- display: flex;
517
- justify-content: space-between;
518
- align-items: center;
519
- background: var(--bg-color);
520
- transition: all 0.3s ease;
521
- border-left: 4px solid transparent;
522
- }
523
-
524
- .voice-header:hover {
525
- background: var(--bg-secondary);
526
- border-left-color: var(--primary-color);
527
- }
528
-
529
- .voice-header.active {
530
- background: var(--primary-color);
531
- color: white;
532
- border-left-color: var(--primary-dark);
533
- }
534
-
535
- .voice-header-title {
536
- font-weight: 500;
537
- font-size: 0.95rem;
538
- }
539
-
540
- .voice-header-count {
541
- font-size: 0.8rem;
542
- opacity: 0.8;
543
- background: rgba(255, 255, 255, 0.2);
544
- padding: 2px 8px;
545
- border-radius: 12px;
546
- margin-left: 8px;
547
- }
548
-
549
- .chevron {
550
- transition: transform 0.3s ease;
551
- font-size: 0.8rem;
552
- }
553
-
554
- .voice-group.open .chevron {
555
- transform: rotate(180deg);
556
- }
557
-
558
- .voice-buttons {
559
- padding: 16px 20px;
560
- display: none;
561
- gap: 8px;
562
  flex-wrap: wrap;
563
  background: var(--bg-secondary);
564
  }
 
155
  // });
156
  // }
157
  // });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  display: grid;
160
  grid-template-columns: 1fr;
161
  gap: 20px;
 
233
  margin-bottom: 20px;
234
  }
235
 
236
+ .
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }
238
 
239
+ .slide
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  .voice-group:last-child {
242
  border-bottom: none;
243
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  flex-wrap: wrap;
245
  background: var(--bg-secondary);
246
  }