@jokio/sdk 0.1.9 → 0.1.11
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 +1 -1
- package/dist/auth.service.d.ts +25 -0
- package/dist/common/playAudioFromBuffer.d.ts +1 -0
- package/dist/crypto.service.d.ts +9 -0
- package/dist/index.d.ts +18 -0
- package/dist/jokio.sdk.es.js +8534 -0
- package/dist/nats.service.d.ts +45 -0
- package/dist/tts.service.d.ts +13 -0
- package/dist/voicevoxTts.d.ts +11 -0
- package/package.json +11 -4
- package/.prettierrc +0 -12
- package/package-lock.json +0 -1062
- package/src/auth.service.ts +0 -152
- package/src/common/playAudioFromBuffer.ts +0 -15
- package/src/crypto.service.ts +0 -100
- package/src/index.ts +0 -39
- package/src/nats.service.ts +0 -348
- package/src/tts.service.ts +0 -153
- package/src/voicevoxTts.ts +0 -94
- package/tsconfig.json +0 -14
- package/vite.config.ts +0 -23
- /package/{test → dist}/index.html +0 -0
package/src/tts.service.ts
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { playAudioFromBuffer } from './common/playAudioFromBuffer'
|
|
2
|
-
|
|
3
|
-
export class EdgeTtsService {
|
|
4
|
-
async getAudioBuffer(
|
|
5
|
-
text: string,
|
|
6
|
-
voice: string,
|
|
7
|
-
options?: { pitch?: string; rate?: string; volume?: string },
|
|
8
|
-
): Promise<ArrayBuffer> {
|
|
9
|
-
const audioChunks: Uint8Array[] = []
|
|
10
|
-
const reqId = crypto.randomUUID() // Supported in most modern browsers
|
|
11
|
-
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
const ws = new WebSocket(
|
|
14
|
-
`${Constants.WSS_URL}?trustedclienttoken=${Constants.TRUSTED_CLIENT_TOKEN}&ConnectionId=${reqId}`,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
ws.binaryType = 'arraybuffer'
|
|
18
|
-
|
|
19
|
-
const SSML_text = getSSML(text, voice, options)
|
|
20
|
-
|
|
21
|
-
ws.addEventListener('open', () => {
|
|
22
|
-
const configMessage = buildTTSConfigMessage()
|
|
23
|
-
ws.send(configMessage)
|
|
24
|
-
|
|
25
|
-
const speechMessage =
|
|
26
|
-
`X-RequestId:${reqId}\r\n` +
|
|
27
|
-
`Content-Type:application/ssml+xml\r\n` +
|
|
28
|
-
`X-Timestamp:${new Date().toISOString()}Z\r\n` +
|
|
29
|
-
`Path:ssml\r\n\r\n` +
|
|
30
|
-
SSML_text
|
|
31
|
-
|
|
32
|
-
ws.send(speechMessage)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
ws.addEventListener('message', event => {
|
|
36
|
-
const data = event.data
|
|
37
|
-
|
|
38
|
-
if (data instanceof ArrayBuffer) {
|
|
39
|
-
const buffer = new Uint8Array(data)
|
|
40
|
-
const needle = new TextEncoder().encode('Path:audio\r\n')
|
|
41
|
-
|
|
42
|
-
const index = findSubarray(buffer, needle)
|
|
43
|
-
if (index !== -1) {
|
|
44
|
-
const audioData = buffer.subarray(index + needle.length)
|
|
45
|
-
audioChunks.push(audioData)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const textData = new TextDecoder().decode(buffer)
|
|
49
|
-
if (textData.includes('Path:turn.end')) {
|
|
50
|
-
ws.close()
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
if (data.includes('Path:turn.end')) {
|
|
54
|
-
ws.close()
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
ws.addEventListener('close', () => {
|
|
60
|
-
if (!audioChunks.length) {
|
|
61
|
-
reject('No audio data available to save.')
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Combine all Uint8Arrays into one ArrayBuffer
|
|
66
|
-
const totalLength = audioChunks.reduce(
|
|
67
|
-
(acc, chunk) => acc + chunk.length,
|
|
68
|
-
0,
|
|
69
|
-
)
|
|
70
|
-
const finalBuffer = new Uint8Array(totalLength)
|
|
71
|
-
let offset = 0
|
|
72
|
-
for (const chunk of audioChunks) {
|
|
73
|
-
finalBuffer.set(chunk, offset)
|
|
74
|
-
offset += chunk.length
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
resolve(finalBuffer.buffer) // ArrayBuffer
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
ws.addEventListener('error', err => {
|
|
81
|
-
reject(err)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async getAudio(
|
|
87
|
-
text: string,
|
|
88
|
-
voice: string,
|
|
89
|
-
options?: { pitch?: string; rate?: string; volume?: string },
|
|
90
|
-
) {
|
|
91
|
-
return this.getAudioBuffer(text, voice, options).then(x =>
|
|
92
|
-
playAudioFromBuffer(x),
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async getVoices(): Promise<any[]> {
|
|
97
|
-
const response = await fetch(
|
|
98
|
-
`${Constants.VOICES_URL}?trustedclienttoken=${Constants.TRUSTED_CLIENT_TOKEN}`,
|
|
99
|
-
)
|
|
100
|
-
const data = await response.json()
|
|
101
|
-
return data.map((voice: any) => ({
|
|
102
|
-
id: voice.ShortName,
|
|
103
|
-
locale: voice.Locale,
|
|
104
|
-
description: voice.FriendlyName,
|
|
105
|
-
}))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const Constants = {
|
|
110
|
-
TRUSTED_CLIENT_TOKEN: '6A5AA1D4EAFF4E9FB37E23D68491D6F4',
|
|
111
|
-
WSS_URL:
|
|
112
|
-
'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1',
|
|
113
|
-
VOICES_URL:
|
|
114
|
-
'https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list',
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const buildTTSConfigMessage = (): string => {
|
|
118
|
-
return (
|
|
119
|
-
`X-Timestamp:${new Date().toISOString()}Z\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n` +
|
|
120
|
-
`{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":false,"wordBoundaryEnabled":true},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}`
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const getSSML = (
|
|
125
|
-
text: string,
|
|
126
|
-
voice: string,
|
|
127
|
-
options: any = {},
|
|
128
|
-
): string => {
|
|
129
|
-
options.pitch = options.pitch?.replace('hz', 'Hz')
|
|
130
|
-
const pitch = options.pitch || '0Hz' // this.validatePitch(options.pitch || '0Hz');
|
|
131
|
-
const rate = options.rate || '0%' // this.validateRate(options.rate || '0%');
|
|
132
|
-
const volume = options.volume || '0%' // this.validateVolume(options.volume || '0%');
|
|
133
|
-
|
|
134
|
-
return `<speak version='1.0' xml:lang='en-US'><voice name='${voice}'><prosody pitch='${pitch}' rate='${rate}' volume='${volume}'>${text}</prosody></voice></speak>`
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Helper: Finds a subarray inside another Uint8Array
|
|
138
|
-
function findSubarray(
|
|
139
|
-
haystack: Uint8Array,
|
|
140
|
-
needle: Uint8Array,
|
|
141
|
-
): number {
|
|
142
|
-
for (let i = 0; i <= haystack.length - needle.length; i++) {
|
|
143
|
-
let match = true
|
|
144
|
-
for (let j = 0; j < needle.length; j++) {
|
|
145
|
-
if (haystack[i + j] !== needle[j]) {
|
|
146
|
-
match = false
|
|
147
|
-
break
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (match) return i
|
|
151
|
-
}
|
|
152
|
-
return -1
|
|
153
|
-
}
|
package/src/voicevoxTts.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { playAudioFromBuffer } from './common/playAudioFromBuffer'
|
|
2
|
-
|
|
3
|
-
type Config = {
|
|
4
|
-
voicevoxUrl: string
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export class VoicevoxTtsService {
|
|
8
|
-
constructor(private config: Config) {}
|
|
9
|
-
|
|
10
|
-
async getAudioBuffer(text: string, voice: string) {
|
|
11
|
-
const audioQueryResponse = await fetch(
|
|
12
|
-
`${
|
|
13
|
-
this.config.voicevoxUrl
|
|
14
|
-
}/audio_query?text=${encodeURIComponent(text)}&speaker=${
|
|
15
|
-
voice || 3
|
|
16
|
-
}`,
|
|
17
|
-
{
|
|
18
|
-
method: 'POST',
|
|
19
|
-
headers: {
|
|
20
|
-
Accept: 'application/json',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
if (!audioQueryResponse.ok) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Audio query failed: ${audioQueryResponse.statusText}`,
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const audioQuery = await audioQueryResponse.json()
|
|
32
|
-
|
|
33
|
-
// Step 2: Synthesize the speech
|
|
34
|
-
const synthesisResponse = await fetch(
|
|
35
|
-
`${this.config.voicevoxUrl}/synthesis?speaker=${voice}`,
|
|
36
|
-
{
|
|
37
|
-
method: 'POST',
|
|
38
|
-
headers: {
|
|
39
|
-
Accept: 'audio/wav',
|
|
40
|
-
'Content-Type': 'application/json',
|
|
41
|
-
},
|
|
42
|
-
body: JSON.stringify(audioQuery),
|
|
43
|
-
},
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if (!synthesisResponse.body) {
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const reader = synthesisResponse.body.getReader()
|
|
51
|
-
const chunks = []
|
|
52
|
-
|
|
53
|
-
// Step 1: Read all the chunks
|
|
54
|
-
while (true) {
|
|
55
|
-
const { done, value } = await reader.read()
|
|
56
|
-
if (done) break
|
|
57
|
-
chunks.push(value)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Step 2: Concatenate into one Uint8Array
|
|
61
|
-
const totalLength = chunks.reduce(
|
|
62
|
-
(acc, chunk) => acc + chunk.length,
|
|
63
|
-
0,
|
|
64
|
-
)
|
|
65
|
-
const audioData = new Uint8Array(totalLength)
|
|
66
|
-
let offset = 0
|
|
67
|
-
for (const chunk of chunks) {
|
|
68
|
-
audioData.set(chunk, offset)
|
|
69
|
-
offset += chunk.length
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return audioData.buffer
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async getAudio(text: string, voice: string) {
|
|
76
|
-
return this.getAudioBuffer(text, voice).then(x =>
|
|
77
|
-
x ? playAudioFromBuffer(x) : null,
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async getVoices(): Promise<any[]> {
|
|
82
|
-
const speakers = await fetch(
|
|
83
|
-
`${this.config.voicevoxUrl}/speakers`,
|
|
84
|
-
).then(x => x.json())
|
|
85
|
-
|
|
86
|
-
return speakers.flatMap((x: any) =>
|
|
87
|
-
x.styles.map((y: any) => ({
|
|
88
|
-
id: y.id.toString(),
|
|
89
|
-
locale: 'ja-JP',
|
|
90
|
-
description: `${x.name} - ${y.name}`,
|
|
91
|
-
})),
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"emitDeclarationOnly": true,
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"moduleResolution": "Node",
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src"]
|
|
14
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import { defineConfig } from 'vite'
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
publicDir: "test",
|
|
6
|
-
server: {
|
|
7
|
-
open: '/index.html' // or 'test/index.html' if kept in /test
|
|
8
|
-
},
|
|
9
|
-
build: {
|
|
10
|
-
lib: {
|
|
11
|
-
entry: path.resolve(__dirname, 'src/index.ts'),
|
|
12
|
-
name: '@jokio/sdk',
|
|
13
|
-
fileName: format => `jokio.sdk.${format}.js`,
|
|
14
|
-
},
|
|
15
|
-
// rollupOptions: {
|
|
16
|
-
// output: {
|
|
17
|
-
// globals: {
|
|
18
|
-
// // Example: react: 'React'
|
|
19
|
-
// },
|
|
20
|
-
// },
|
|
21
|
-
// },
|
|
22
|
-
},
|
|
23
|
-
})
|
|
File without changes
|