@supertone/supertone 0.1.2 → 0.1.3
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.
- package/README.md +4 -4
- package/custom_test/realtime_tts_player.ts +120 -16
- package/custom_test/test_pronunciation_dictionary.ts +227 -0
- package/custom_test/test_real_api.ts +580 -0
- package/custom_test/test_text_utils_chunk_text_punctuation.ts +55 -0
- package/dist/commonjs/lib/config.d.ts +1 -1
- package/dist/commonjs/lib/config.d.ts.map +1 -1
- package/dist/commonjs/lib/config.js +1 -1
- package/dist/commonjs/lib/config.js.map +1 -1
- package/dist/commonjs/lib/custom_utils/index.d.ts +1 -0
- package/dist/commonjs/lib/custom_utils/index.d.ts.map +1 -1
- package/dist/commonjs/lib/custom_utils/index.js +5 -1
- package/dist/commonjs/lib/custom_utils/index.js.map +1 -1
- package/dist/commonjs/lib/custom_utils/pronunciation_utils.d.ts +24 -0
- package/dist/commonjs/lib/custom_utils/pronunciation_utils.d.ts.map +1 -0
- package/dist/commonjs/lib/custom_utils/pronunciation_utils.js +145 -0
- package/dist/commonjs/lib/custom_utils/pronunciation_utils.js.map +1 -0
- package/dist/commonjs/lib/custom_utils/text_utils.d.ts +1 -1
- package/dist/commonjs/lib/custom_utils/text_utils.d.ts.map +1 -1
- package/dist/commonjs/lib/custom_utils/text_utils.js +21 -4
- package/dist/commonjs/lib/custom_utils/text_utils.js.map +1 -1
- package/dist/commonjs/sdk/texttospeech.d.ts +17 -6
- package/dist/commonjs/sdk/texttospeech.d.ts.map +1 -1
- package/dist/commonjs/sdk/texttospeech.js +48 -25
- package/dist/commonjs/sdk/texttospeech.js.map +1 -1
- package/dist/esm/lib/config.d.ts +1 -1
- package/dist/esm/lib/config.d.ts.map +1 -1
- package/dist/esm/lib/config.js +1 -1
- package/dist/esm/lib/config.js.map +1 -1
- package/dist/esm/lib/custom_utils/index.d.ts +1 -0
- package/dist/esm/lib/custom_utils/index.d.ts.map +1 -1
- package/dist/esm/lib/custom_utils/index.js +2 -0
- package/dist/esm/lib/custom_utils/index.js.map +1 -1
- package/dist/esm/lib/custom_utils/pronunciation_utils.d.ts +24 -0
- package/dist/esm/lib/custom_utils/pronunciation_utils.d.ts.map +1 -0
- package/dist/esm/lib/custom_utils/pronunciation_utils.js +140 -0
- package/dist/esm/lib/custom_utils/pronunciation_utils.js.map +1 -0
- package/dist/esm/lib/custom_utils/text_utils.d.ts +1 -1
- package/dist/esm/lib/custom_utils/text_utils.d.ts.map +1 -1
- package/dist/esm/lib/custom_utils/text_utils.js +21 -4
- package/dist/esm/lib/custom_utils/text_utils.js.map +1 -1
- package/dist/esm/sdk/texttospeech.d.ts +17 -6
- package/dist/esm/sdk/texttospeech.d.ts.map +1 -1
- package/dist/esm/sdk/texttospeech.js +49 -26
- package/dist/esm/sdk/texttospeech.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/lib/config.ts +41 -41
- package/src/lib/custom_utils/index.ts +7 -0
- package/src/lib/custom_utils/pronunciation_utils.ts +193 -0
- package/src/lib/custom_utils/text_utils.ts +25 -4
- package/src/sdk/texttospeech.ts +99 -68
package/README.md
CHANGED
|
@@ -16,25 +16,25 @@ The SDK can be installed with either [npm](https://www.npmjs.com/), [pnpm](https
|
|
|
16
16
|
### NPM
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm add
|
|
19
|
+
npm add @supertone/supertone
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
### PNPM
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
pnpm add
|
|
25
|
+
pnpm add @supertone/supertone
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
### Bun
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
bun add
|
|
31
|
+
bun add @supertone/supertone
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
### Yarn
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
yarn add
|
|
37
|
+
yarn add @supertone/supertone zod
|
|
38
38
|
|
|
39
39
|
# Note that Yarn does not install peer dependencies automatically. You will need
|
|
40
40
|
# to install zod as shown above.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { spawn, ChildProcess } from "child_process";
|
|
7
7
|
import { Supertone } from "../src/index.js";
|
|
8
8
|
import * as models from "../src/models/index.js";
|
|
9
|
+
import type { PronunciationDictionaryEntry } from "../src/lib/custom_utils/index.js";
|
|
9
10
|
import * as dotenv from "dotenv";
|
|
10
11
|
import * as path from "path";
|
|
11
12
|
import { fileURLToPath } from "url";
|
|
@@ -277,11 +278,15 @@ async function simpleStreamingTts(
|
|
|
277
278
|
voiceId: string,
|
|
278
279
|
text: string,
|
|
279
280
|
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage = models
|
|
280
|
-
.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ko
|
|
281
|
+
.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ko,
|
|
282
|
+
pronunciationDictionary?: PronunciationDictionaryEntry[]
|
|
281
283
|
): Promise<boolean> {
|
|
282
284
|
console.log(`📝 "${text.slice(0, 50)}${text.length > 50 ? "..." : ""}"`);
|
|
283
285
|
console.log(`📏 Text length: ${text.length} characters`);
|
|
284
286
|
console.log(`🌐 Language: ${language}`);
|
|
287
|
+
if (pronunciationDictionary && pronunciationDictionary.length > 0) {
|
|
288
|
+
console.log(`📖 Pronunciation dictionary: ${pronunciationDictionary.length} entries`);
|
|
289
|
+
}
|
|
285
290
|
|
|
286
291
|
const player = new SimpleMpvPlayer();
|
|
287
292
|
|
|
@@ -295,17 +300,20 @@ async function simpleStreamingTts(
|
|
|
295
300
|
player.markApiCallStart();
|
|
296
301
|
console.log(" ⏱️ API call started...");
|
|
297
302
|
|
|
298
|
-
const response = await client.textToSpeech.streamSpeech(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
303
|
+
const response = await client.textToSpeech.streamSpeech(
|
|
304
|
+
{
|
|
305
|
+
voiceId: voiceId,
|
|
306
|
+
apiConvertTextToSpeechUsingCharacterRequest: {
|
|
307
|
+
text: text,
|
|
308
|
+
language: language,
|
|
309
|
+
outputFormat:
|
|
310
|
+
models.APIConvertTextToSpeechUsingCharacterRequestOutputFormat.Wav,
|
|
311
|
+
style: "neutral",
|
|
312
|
+
model: "sona_speech_1",
|
|
313
|
+
},
|
|
307
314
|
},
|
|
308
|
-
|
|
315
|
+
pronunciationDictionary ? { pronunciationDictionary } : undefined
|
|
316
|
+
);
|
|
309
317
|
|
|
310
318
|
// Mark API call end (response received)
|
|
311
319
|
player.markApiCallEnd();
|
|
@@ -387,8 +395,7 @@ async function simpleStreamingTts(
|
|
|
387
395
|
*/
|
|
388
396
|
async function simpleDemo(): Promise<void> {
|
|
389
397
|
const voiceId = "91992bbd4758bdcf9c9b01";
|
|
390
|
-
const scenarios: string[] = [
|
|
391
|
-
/*[
|
|
398
|
+
const scenarios: string[] = [
|
|
392
399
|
"안녕하세요! 심플한 테스트입니다.",
|
|
393
400
|
|
|
394
401
|
"실시간 텍스트 음성 변환 기술은 정말 놀랍습니다. 이 기술을 통해 긴 텍스트도 즉시 음성으로 들을 수 있게 되었습니다.",
|
|
@@ -403,10 +410,16 @@ async function simpleDemo(): Promise<void> {
|
|
|
403
410
|
|
|
404
411
|
// Scenario 800+ characters (~850 chars)
|
|
405
412
|
"옛날 한 작은 마을에 천재적인 재능을 가진 젊은 개발자가 살고 있었습니다. 그의 이름은 민준이였고, 어릴 때부터 컴퓨터와 프로그래밍에 남다른 관심을 보였습니다. 대학에서 컴퓨터 과학을 전공한 민준은 졸업 후 스타트업에 입사했습니다. 그곳에서 그는 인공지능과 음성 기술에 대한 깊은 지식을 쌓게 되었습니다. 어느 날, 민준은 시각 장애가 있는 친구 서연을 만났습니다. 서연은 인터넷의 수많은 정보를 텍스트로만 접할 수 있어 많은 불편함을 겪고 있었습니다. 당시의 음성 합성 기술은 로봇 같은 목소리를 내며, 긴 텍스트를 읽어주려면 모든 처리가 끝날 때까지 기다려야 했습니다. 이를 본 민준은 더 자연스럽고 빠른 음성 합성 기술을 만들기로 결심했습니다. 밤낮없이 연구에 매진한 민준은 혁신적인 아이디어를 떠올렸습니다. 긴 텍스트를 작은 단위로 나누어 실시간으로 처리하고, 첫 번째 부분이 완성되는 즉시 재생을 시작하는 스트리밍 방식이었습니다. 이 기술을 구현하기 위해 그는 최신 딥러닝 모델과 신경망 아키텍처를 연구했습니다. 수많은 시행착오를 거쳐 마침내 자연스러운 음성을 실시간으로 생성할 수 있는 시스템을 완성했습니다. 그의 기술은 문장의 문맥과 감정까지 이해하여 적절한 억양과 속도로 읽어주었습니다.",
|
|
406
|
-
]
|
|
413
|
+
];
|
|
407
414
|
|
|
408
415
|
// Additional test scenarios for word-based and character-based chunking
|
|
409
|
-
const additionalScenarios
|
|
416
|
+
const additionalScenarios: Array<{
|
|
417
|
+
text: string;
|
|
418
|
+
label: string;
|
|
419
|
+
category: string;
|
|
420
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage;
|
|
421
|
+
pronunciationDictionary?: PronunciationDictionaryEntry[];
|
|
422
|
+
}> = [
|
|
410
423
|
{
|
|
411
424
|
// Korean text WITHOUT punctuation to test word-based chunking
|
|
412
425
|
// Text length: ~450 characters (exceeds 300 char limit)
|
|
@@ -425,6 +438,87 @@ async function simpleDemo(): Promise<void> {
|
|
|
425
438
|
category: "Character-based Chunking Test",
|
|
426
439
|
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ja,
|
|
427
440
|
},
|
|
441
|
+
{
|
|
442
|
+
// English text with ellipsis punctuation (… ‥) - tests fix/text_utils multilingual punctuation
|
|
443
|
+
// Text length: ~380 characters (exceeds 300 char limit)
|
|
444
|
+
text: "Sometimes we need to pause and think… The ellipsis character is used to indicate a trailing thought or a pause in speech… This test verifies that the text chunking system correctly handles Unicode ellipsis characters‥ There are multiple types of ellipsis in Unicode… The horizontal ellipsis and the two dot leader are both supported‥ When processing long texts the SDK should split at these punctuation marks… This ensures natural pauses in the generated speech output‥ Let us verify everything works correctly…",
|
|
445
|
+
label: "Ellipsis punctuation test (… ‥) - 380+ chars",
|
|
446
|
+
category: "Multilingual Punctuation Test",
|
|
447
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.En,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
// Korean text with ellipsis (…) - tests Korean with Unicode ellipsis
|
|
451
|
+
// Text length: ~350 characters (exceeds 300 char limit)
|
|
452
|
+
text: "한국어 텍스트에서 말줄임표는 생각의 흐름을 나타냅니다… 이 테스트는 유니코드 말줄임표 문자가 올바르게 처리되는지 확인합니다… 인공지능 기술이 발전하면서 음성 합성의 품질도 크게 향상되었습니다… 특히 딥러닝을 활용한 최신 시스템은 매우 자연스러운 음성을 생성할 수 있습니다… 긴 텍스트를 처리할 때 SDK는 이러한 구두점에서 적절히 분할해야 합니다… 이를 통해 자연스러운 음성 출력이 가능해집니다… 실시간 스트리밍 기술과 결합하면 더욱 빠른 응답을 제공할 수 있습니다… 음성 합성 기술은 접근성 도구부터 AI 어시스턴트까지 다양하게 활용됩니다… 모든 것이 제대로 작동하는지 확인해 봅시다…",
|
|
453
|
+
label: "Korean ellipsis punctuation test (…) - 350+ chars",
|
|
454
|
+
category: "Multilingual Punctuation Test",
|
|
455
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ko,
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
// Japanese text WITH CJK punctuation (。!?) - tests CJK punctuation splitting
|
|
459
|
+
// Text length: ~320 characters (exceeds 300 char limit)
|
|
460
|
+
text: "日本語のテキストは通常スペースを含まないため特別な処理が必要です。このテストは日本語の句読点で正しく分割されることを確認します。自然言語処理技術の発展により音声合成の品質は大幅に向上しました。特にディープラーニングを活用した最新のテキスト音声変換システムは人間の発話に非常に近い自然な音声を生成できます。スペースがない言語では句読点での分割が重要です。このSDKはそのような状況を自動的に検出して適切に処理します。リアルタイムストリーミング技術と組み合わせることで待ち時間を大幅に短縮できます。これにより日本語でも問題なく長いテキストを音声に変換することができます。音声合成技術の未来はとても明るいです。",
|
|
461
|
+
label: "Japanese CJK punctuation test (。) - 320+ chars",
|
|
462
|
+
category: "Multilingual Punctuation Test",
|
|
463
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ja,
|
|
464
|
+
},
|
|
465
|
+
// Pronunciation Dictionary Tests
|
|
466
|
+
{
|
|
467
|
+
// Basic pronunciation dictionary test with partial_match=true/false
|
|
468
|
+
text: "The CEO of OpenAI announced that GPT models are improving. Dr. Smith from MIT said AI research is accelerating.",
|
|
469
|
+
label: "Pronunciation dictionary (partial_match=true/false)",
|
|
470
|
+
category: "Pronunciation Dictionary Test",
|
|
471
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.En,
|
|
472
|
+
pronunciationDictionary: [
|
|
473
|
+
// partial_match=false: exact word boundary match
|
|
474
|
+
{ text: "CEO", pronunciation: "Chief Executive Officer", partial_match: false },
|
|
475
|
+
{ text: "MIT", pronunciation: "Massachusetts Institute of Technology", partial_match: false },
|
|
476
|
+
{ text: "AI", pronunciation: "Artificial Intelligence", partial_match: false },
|
|
477
|
+
// partial_match=true: substring match
|
|
478
|
+
{ text: "GPT", pronunciation: "Generative Pre-trained Transformer", partial_match: true },
|
|
479
|
+
{ text: "Dr.", pronunciation: "Doctor", partial_match: true },
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
// Pronunciation dictionary causing text expansion to exceed 300 chars (triggers chunking)
|
|
484
|
+
// Original text: ~190 chars, After expansion: 400+ chars
|
|
485
|
+
text: "AI and ML are revolutionizing tech. The CEO discussed GPT advancements. Dr. Kim from MIT explained how NLP and CV work together. AWS and GCP provide cloud AI services.",
|
|
486
|
+
label: "Pronunciation dictionary + Long text chunking (~190 chars -> 400+ chars)",
|
|
487
|
+
category: "Pronunciation Dictionary + Chunking Test",
|
|
488
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.En,
|
|
489
|
+
pronunciationDictionary: [
|
|
490
|
+
// partial_match=false: exact word boundary matches
|
|
491
|
+
{ text: "AI", pronunciation: "Artificial Intelligence", partial_match: false },
|
|
492
|
+
{ text: "ML", pronunciation: "Machine Learning", partial_match: false },
|
|
493
|
+
{ text: "CEO", pronunciation: "Chief Executive Officer", partial_match: false },
|
|
494
|
+
{ text: "MIT", pronunciation: "Massachusetts Institute of Technology", partial_match: false },
|
|
495
|
+
{ text: "NLP", pronunciation: "Natural Language Processing", partial_match: false },
|
|
496
|
+
{ text: "CV", pronunciation: "Computer Vision", partial_match: false },
|
|
497
|
+
{ text: "AWS", pronunciation: "Amazon Web Services", partial_match: false },
|
|
498
|
+
{ text: "GCP", pronunciation: "Google Cloud Platform", partial_match: false },
|
|
499
|
+
// partial_match=true: substring matches
|
|
500
|
+
{ text: "GPT", pronunciation: "Generative Pre-trained Transformer", partial_match: true },
|
|
501
|
+
{ text: "Dr.", pronunciation: "Doctor", partial_match: true },
|
|
502
|
+
{ text: "tech", pronunciation: "technology", partial_match: true },
|
|
503
|
+
],
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
// Korean pronunciation dictionary test
|
|
507
|
+
text: "SK와 LG의 CEO가 AI와 ML 기술에 대해 발표했습니다. Dr. 김 박사가 MIT에서 NLP 연구 성과를 공개했습니다.",
|
|
508
|
+
label: "Korean pronunciation dictionary test",
|
|
509
|
+
category: "Pronunciation Dictionary Test",
|
|
510
|
+
language: models.APIConvertTextToSpeechUsingCharacterRequestLanguage.Ko,
|
|
511
|
+
pronunciationDictionary: [
|
|
512
|
+
{ text: "SK", pronunciation: "에스케이", partial_match: false },
|
|
513
|
+
{ text: "LG", pronunciation: "엘지", partial_match: false },
|
|
514
|
+
{ text: "CEO", pronunciation: "최고경영자", partial_match: false },
|
|
515
|
+
{ text: "AI", pronunciation: "인공지능", partial_match: false },
|
|
516
|
+
{ text: "ML", pronunciation: "머신러닝", partial_match: false },
|
|
517
|
+
{ text: "MIT", pronunciation: "매사추세츠 공과대학교", partial_match: false },
|
|
518
|
+
{ text: "NLP", pronunciation: "자연어처리", partial_match: false },
|
|
519
|
+
{ text: "Dr.", pronunciation: "닥터", partial_match: true },
|
|
520
|
+
],
|
|
521
|
+
},
|
|
428
522
|
];
|
|
429
523
|
|
|
430
524
|
for (let i = 0; i < scenarios.length; i++) {
|
|
@@ -472,7 +566,8 @@ async function simpleDemo(): Promise<void> {
|
|
|
472
566
|
const success = await simpleStreamingTts(
|
|
473
567
|
voiceId,
|
|
474
568
|
scenario.text,
|
|
475
|
-
scenario.language
|
|
569
|
+
scenario.language,
|
|
570
|
+
scenario.pronunciationDictionary
|
|
476
571
|
);
|
|
477
572
|
|
|
478
573
|
if (!success) {
|
|
@@ -497,6 +592,15 @@ async function simpleDemo(): Promise<void> {
|
|
|
497
592
|
console.log(
|
|
498
593
|
" • Character-based chunking: Japanese/Chinese text without spaces"
|
|
499
594
|
);
|
|
595
|
+
console.log("\n🌍 Multilingual punctuation tests:");
|
|
596
|
+
console.log(" • Ellipsis: English (… ‥)");
|
|
597
|
+
console.log(" • Korean ellipsis: Korean (…)");
|
|
598
|
+
console.log(" • CJK punctuation: Japanese (。)");
|
|
599
|
+
console.log("\n📖 Pronunciation dictionary tests:");
|
|
600
|
+
console.log(" • partial_match=false: Word boundary matching (CEO, MIT, AI)");
|
|
601
|
+
console.log(" • partial_match=true: Substring matching (GPT, Dr., tech)");
|
|
602
|
+
console.log(" • Long text chunking: Text expansion exceeding 300 chars");
|
|
603
|
+
console.log(" • Korean pronunciation: SK, LG, CEO, AI, ML, MIT, NLP");
|
|
500
604
|
}
|
|
501
605
|
|
|
502
606
|
/**
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Behavior tests for applyPronunciationDictionary().
|
|
4
|
+
*
|
|
5
|
+
* Run:
|
|
6
|
+
* npx ts-node custom_test/test_pronunciation_dictionary.ts
|
|
7
|
+
* # or after build:
|
|
8
|
+
* node dist/custom_test/test_pronunciation_dictionary.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
applyPronunciationDictionary,
|
|
13
|
+
PronunciationDictionaryValidationError,
|
|
14
|
+
type PronunciationDictionaryEntry,
|
|
15
|
+
} from "../src/lib/custom_utils/pronunciation_utils.js";
|
|
16
|
+
|
|
17
|
+
function assertEqual(actual: string, expected: string, message: string): void {
|
|
18
|
+
if (actual !== expected) {
|
|
19
|
+
throw new Error(`${message}\nexpected=${expected}\nactual=${actual}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertThrows(fn: () => void, message: string): void {
|
|
24
|
+
let threw = false;
|
|
25
|
+
try {
|
|
26
|
+
fn();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
threw = true;
|
|
29
|
+
if (!(e instanceof PronunciationDictionaryValidationError)) {
|
|
30
|
+
throw new Error(`${message}\nexpected PronunciationDictionaryValidationError`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!threw) {
|
|
34
|
+
throw new Error(`${message}\nexpected to throw`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function runTests(): void {
|
|
39
|
+
const d = (entries: PronunciationDictionaryEntry[]) => entries;
|
|
40
|
+
|
|
41
|
+
assertEqual(applyPronunciationDictionary("hello", undefined), "hello", "none returns original");
|
|
42
|
+
assertEqual(applyPronunciationDictionary("hello", []), "hello", "empty returns original");
|
|
43
|
+
|
|
44
|
+
assertEqual(
|
|
45
|
+
applyPronunciationDictionary("This is Supertone.", d([{ text: "Supertone", pronunciation: "super tone", partial_match: false }])),
|
|
46
|
+
"This is super tone.",
|
|
47
|
+
"basic exact match"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assertEqual(
|
|
51
|
+
applyPronunciationDictionary("K-TTS is different from TTSAPI.", d([{ text: "TTS", pronunciation: "text to speech", partial_match: true }])),
|
|
52
|
+
"K-text to speech is different from text to speechAPI.",
|
|
53
|
+
"partial match substrings"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
assertEqual(
|
|
57
|
+
applyPronunciationDictionary("TTS and TTS and TTS.", d([{ text: "TTS", pronunciation: "text to speech", partial_match: true }])),
|
|
58
|
+
"text to speech and text to speech and text to speech.",
|
|
59
|
+
"multiple occurrences"
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
assertEqual(
|
|
63
|
+
applyPronunciationDictionary('He said, "Supertone", (Supertone)!', d([{ text: "Supertone", pronunciation: "super tone", partial_match: false }])),
|
|
64
|
+
'He said, "super tone", (super tone)!',
|
|
65
|
+
"punctuation boundaries"
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
assertEqual(
|
|
69
|
+
applyPronunciationDictionary("API_test API test_API", d([{ text: "API", pronunciation: "A P I", partial_match: false }])),
|
|
70
|
+
"API_test A P I test_API",
|
|
71
|
+
"word boundary underscore"
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
assertEqual(
|
|
75
|
+
applyPronunciationDictionary("C++ is old, C++11 is newer.", d([{ text: "C++", pronunciation: "cplusplus", partial_match: false }])),
|
|
76
|
+
"cplusplus is old, C++11 is newer.",
|
|
77
|
+
"numbers break boundary"
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
assertEqual(
|
|
81
|
+
applyPronunciationDictionary("a(b)c a(b)c", d([{ text: "a(b)c", pronunciation: "X", partial_match: true }])),
|
|
82
|
+
"X X",
|
|
83
|
+
"regex meta chars"
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
assertEqual(
|
|
87
|
+
applyPronunciationDictionary("aaaa", d([{ text: "aa", pronunciation: "b", partial_match: true }])),
|
|
88
|
+
"bb",
|
|
89
|
+
"non-overlapping left-to-right"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
assertEqual(
|
|
93
|
+
applyPronunciationDictionary("AAAA", d([
|
|
94
|
+
{ text: "AA", pronunciation: "B", partial_match: true },
|
|
95
|
+
{ text: "A", pronunciation: "C", partial_match: true },
|
|
96
|
+
])),
|
|
97
|
+
"BB",
|
|
98
|
+
"order priority AA then A"
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
assertEqual(
|
|
102
|
+
applyPronunciationDictionary("AAAA", d([
|
|
103
|
+
{ text: "A", pronunciation: "C", partial_match: true },
|
|
104
|
+
{ text: "AA", pronunciation: "B", partial_match: true },
|
|
105
|
+
])),
|
|
106
|
+
"CCCC",
|
|
107
|
+
"order priority A then AA"
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
assertEqual(
|
|
111
|
+
applyPronunciationDictionary("NY is not New Jersey.", d([
|
|
112
|
+
{ text: "NY", pronunciation: "New York", partial_match: false },
|
|
113
|
+
{ text: "New", pronunciation: "Old", partial_match: false },
|
|
114
|
+
])),
|
|
115
|
+
"New York is not Old Jersey.",
|
|
116
|
+
"no resubstitution inside pronunciation"
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
assertEqual(
|
|
120
|
+
applyPronunciationDictionary("이번 APEC 은 한국에서 열립니다", d([
|
|
121
|
+
{ text: "AP", pronunciation: "에이피", partial_match: true },
|
|
122
|
+
{ text: "APEC", pronunciation: "에이팩", partial_match: true },
|
|
123
|
+
])),
|
|
124
|
+
"이번 에이피EC 은 한국에서 열립니다",
|
|
125
|
+
"order AP then APEC"
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
assertEqual(
|
|
129
|
+
applyPronunciationDictionary("이번 APEC 은 한국에서 열립니다", d([
|
|
130
|
+
{ text: "APEC", pronunciation: "에이팩", partial_match: true },
|
|
131
|
+
{ text: "AP", pronunciation: "에이피", partial_match: true },
|
|
132
|
+
])),
|
|
133
|
+
"이번 에이팩 은 한국에서 열립니다",
|
|
134
|
+
"order APEC then AP"
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
assertEqual(
|
|
138
|
+
applyPronunciationDictionary("Supertone tone", d([
|
|
139
|
+
{ text: "Supertone", pronunciation: "super tone", partial_match: false },
|
|
140
|
+
{ text: "tone", pronunciation: "TONE", partial_match: false },
|
|
141
|
+
])),
|
|
142
|
+
"super tone TONE",
|
|
143
|
+
"no resubstitution across rules"
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
assertEqual(
|
|
147
|
+
applyPronunciationDictionary("TTS와 TTSAPI는 다릅니다.", d([{ text: "TTS", pronunciation: "text to speech", partial_match: true }])),
|
|
148
|
+
"text to speech와 text to speechAPI는 다릅니다.",
|
|
149
|
+
"Korean partial match"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
assertEqual(
|
|
153
|
+
applyPronunciationDictionary("東京TTS東京", d([{ text: "TTS", pronunciation: "text to speech", partial_match: false }])),
|
|
154
|
+
"東京TTS東京",
|
|
155
|
+
"no boundary in Japanese exact"
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
assertEqual(
|
|
159
|
+
applyPronunciationDictionary("これは「TTS」です。", d([{ text: "TTS", pronunciation: "text to speech", partial_match: false }])),
|
|
160
|
+
"これは「text to speech」です。",
|
|
161
|
+
"boundary by punctuation Japanese"
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
assertEqual(
|
|
165
|
+
applyPronunciationDictionary("東京TTS東京", d([{ text: "TTS", pronunciation: "text to speech", partial_match: true }])),
|
|
166
|
+
"東京text to speech東京",
|
|
167
|
+
"partial match Japanese"
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
assertEqual(
|
|
171
|
+
applyPronunciationDictionary(`X \uE000PD0\uE001 Supertone`, d([{ text: "Supertone", pronunciation: "super tone", partial_match: false }])),
|
|
172
|
+
`X \uE000PD0\uE001 super tone`,
|
|
173
|
+
"token collision safe"
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
assertEqual(
|
|
177
|
+
applyPronunciationDictionary("TTS와 TTS.", d([{ text: "TTS", pronunciation: "text to speech", partial_match: false }])),
|
|
178
|
+
"TTS와 text to speech.",
|
|
179
|
+
"unicode boundary note"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
assertThrows(
|
|
183
|
+
() => applyPronunciationDictionary("hi", {} as any),
|
|
184
|
+
"validation: dictionary must be array"
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
assertThrows(
|
|
188
|
+
() => applyPronunciationDictionary("hi", ["not-an-object"] as any),
|
|
189
|
+
"validation: entry must be object"
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
assertThrows(
|
|
193
|
+
() => applyPronunciationDictionary("hi", [{ text: 1, pronunciation: "b", partial_match: true }] as any),
|
|
194
|
+
"validation: text type"
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
assertThrows(
|
|
198
|
+
() => applyPronunciationDictionary("hi", [{ text: "a", pronunciation: 1, partial_match: true }] as any),
|
|
199
|
+
"validation: pronunciation type"
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
assertThrows(
|
|
203
|
+
() => applyPronunciationDictionary("hi", [{ text: "a", pronunciation: "b" }] as any),
|
|
204
|
+
"validation: missing partial_match"
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
assertThrows(
|
|
208
|
+
() => applyPronunciationDictionary("hi", [{ text: "a", pronunciation: "b", partial_match: "true" }] as any),
|
|
209
|
+
"validation: partial_match type"
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
assertThrows(
|
|
213
|
+
() => applyPronunciationDictionary("hi", [{ text: "", pronunciation: "b", partial_match: true }] as any),
|
|
214
|
+
"validation: empty text"
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
assertThrows(
|
|
218
|
+
() => applyPronunciationDictionary("hi", [{ text: "a", pronunciation: "", partial_match: true }] as any),
|
|
219
|
+
"validation: empty pronunciation"
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
console.log("OK: applyPronunciationDictionary tests passed");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
runTests();
|
|
226
|
+
|
|
227
|
+
|