Skip to content

Commit 5571299

Browse files
committed
feat: add azure api
1 parent dbe3054 commit 5571299

File tree

8 files changed

+162
-31
lines changed

8 files changed

+162
-31
lines changed

electron/main/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { release } from "os";
33
import { join } from "path";
44
import api from "../utils/api";
55
import edgeApi from "../utils/edge-api";
6-
6+
import azureApi from "../utils/azure-api";
77
import logger from "../utils/log";
88

99
// Disable GPU Acceleration for Windows 7
@@ -183,6 +183,11 @@ ipcMain.handle("edgeApi", async (event, ssml) => {
183183
return res;
184184
});
185185

186+
ipcMain.handle("azureApi", async (event, ssml, key, region) => {
187+
const res = azureApi(ssml, key, region)
188+
return res;
189+
});
190+
186191
ipcMain.handle("openFolderSelector", async (event) => {
187192
const path = dialog.showOpenDialogSync(win, {
188193
defaultPath: app.getPath("desktop"),

electron/utils/azure-api.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import logger from "../utils/log";
2+
3+
const azureApi = (ssml: string, key: string, region: string) => {
4+
var sdk = require("microsoft-cognitiveservices-speech-sdk");
5+
const speechConfig = sdk.SpeechConfig.fromSubscription(key, region);
6+
speechConfig.setProperty(sdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");
7+
var audio_config = sdk.AudioConfig.fromDefaultSpeakerOutput();
8+
var speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, audio_config);
9+
return new Promise((resolve, reject) => {
10+
speechSynthesizer.speakSsmlAsync(
11+
ssml,
12+
(result: any) => {
13+
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
14+
logger.info(`Speech synthesized to speaker for text [${ssml}]`);
15+
resolve(Buffer.from(result.audioData));
16+
} else {
17+
logger.info("Speech synthesis canceled, " + result.errorDetails + "\nDid you update the subscription info?");
18+
reject(result);
19+
}
20+
speechSynthesizer.close();
21+
speechSynthesizer = null;
22+
},
23+
(err: any) => {
24+
logger.info("Error synthesizing. " + err);
25+
speechSynthesizer.close();
26+
speechSynthesizer = null;
27+
}
28+
);
29+
}
30+
);
31+
}
32+
33+
export default azureApi;

src/components/configpage/ConfigPage.vue

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@
1414
</template>
1515
</el-input>
1616
</el-form-item>
17+
<el-form-item label="SpeechKey">
18+
<el-input
19+
v-model="config.speechKey"
20+
size="small"
21+
class="input-path"
22+
@change="setSpeechKey"
23+
password
24+
/>
25+
</el-form-item>
26+
<el-form-item label="ServiceRegion">
27+
<el-input
28+
v-model="config.serviceRegion"
29+
size="small"
30+
class="input-path"
31+
@change="setServiceRegion"
32+
/>
33+
</el-form-item>
1734
<el-form-item label="自动播放(仅单文本模式)">
1835
<el-switch
1936
v-model="config.autoplay"
@@ -206,6 +223,24 @@ const updateTitleStyle = () => {
206223
duration: 2000,
207224
});
208225
};
226+
227+
const setSpeechKey = () => {
228+
ttsStore.setSpeechKey();
229+
ElMessage({
230+
message: "保存成功,请点击“刷新配置”立即应用。。",
231+
type: "success",
232+
duration: 2000,
233+
});
234+
};
235+
236+
const setServiceRegion = () => {
237+
ttsStore.setServiceRegion();
238+
ElMessage({
239+
message: "保存成功,请点击“刷新配置”立即应用。。",
240+
type: "success",
241+
duration: 2000,
242+
});
243+
};
209244
</script>
210245

211246
<style scoped>
@@ -226,8 +261,9 @@ const updateTitleStyle = () => {
226261
.el-form {
227262
margin-top: 7px;
228263
border-right: 1px solid #dcdfe6;
229-
width: calc(100% - 410px);
264+
width: calc(100% - 395px);
230265
padding-left: 10px;
266+
overflow-x: scroll;
231267
}
232268
:deep(.input-path .el-input-group__append) {
233269
display: inline-flex;
@@ -265,7 +301,7 @@ const updateTitleStyle = () => {
265301
.btns {
266302
width: 100%;
267303
box-sizing: border-box;
268-
padding-right: 10px;
304+
padding-right: 2px;
269305
}
270306
:deep(.btns .el-form-item__content) {
271307
justify-content: space-between;

src/components/main/MainOptions.vue

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22
<div class="options">
33
<el-form :model="formConfig" label-width="120px" label-position="top">
44
<el-form-item label="接口">
5-
<el-switch
6-
style="display: block"
5+
<el-select
76
v-model="formConfig.api"
8-
active-color="#13ce66"
9-
inactive-color="#ff4949"
10-
active-text="微软TTS"
11-
inactive-text="Edge朗读"
7+
placeholder="选择接口"
128
@change="apiChange"
13-
:disabled="apiDisable">
14-
</el-switch>
9+
>
10+
<el-option
11+
v-for="item in oc.apiSelect"
12+
:key="item.value"
13+
:label="item.label"
14+
:value="item.value"
15+
/>
16+
</el-select>
1517
</el-form-item>
1618
<el-form-item label="语言">
1719
<el-select-v2
@@ -55,7 +57,7 @@
5557
<el-select
5658
v-model="formConfig.voiceStyleSelect"
5759
placeholder="选择说话风格"
58-
:disabled="!formConfig.api"
60+
:disabled="apiEdge"
5961
>
6062
<el-option
6163
v-for="item in voiceStyleSelectList"
@@ -73,7 +75,7 @@
7375
</el-select>
7476
</el-form-item>
7577
<el-form-item label="角色扮演">
76-
<el-select v-model="formConfig.role" placeholder="选择角色" :disabled="!formConfig.api">
78+
<el-select v-model="formConfig.role" placeholder="选择角色" :disabled="apiEdge">
7779
<el-option
7880
v-for="item in rolePlayList"
7981
:key="item"
@@ -161,19 +163,31 @@ const {
161163
const Store = require("electron-store");
162164
const store = new Store();
163165
164-
if (!formConfig.value.hasOwnProperty("api")) {
165-
formConfig.value.api = true;
166-
}
166+
// if (!formConfig.value.hasOwnProperty("api")) {
167+
// formConfig.value.api = true;
168+
// }
169+
170+
const apiEdge = ref(false);
167171
168-
const apiChange = (res:boolean) => {
169-
if (!res) {
172+
const apiChange = (res:number) => {
173+
if (res === 2) {
174+
apiEdge.value = true;
170175
ElMessage({
171176
message: `edge接口不支持自动切片,最长支持文本长度未知。请根据自身需求手动预处理文本。`,
172177
type: "warning",
173178
duration: 4000,
174179
});
180+
} else {
181+
if (res === 3 && (config.value.speechKey === "" || config.value.serviceRegion === "")) {
182+
ElMessage({
183+
message: `请先配置Azure的Speech服务密钥和区域。`,
184+
type: "warning",
185+
duration: 4000,
186+
});
187+
return;
188+
}
189+
apiEdge.value = false;
175190
}
176-
177191
}
178192
179193
const audition = (value: string) => {

src/components/main/options-config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,24 @@ const findVoicesByLocaleName = (localeName: any) => {
207207
return voices;
208208
};
209209

210+
const apiSelect = [
211+
{
212+
value: 1,
213+
label: "Microsoft Speech API",
214+
},
215+
{
216+
value: 2,
217+
label: "Edge Speech API",
218+
},
219+
{
220+
value: 3,
221+
label: "Azure Speech API",
222+
},
223+
];
224+
210225
export const optionsConfig = {
211226
voicesList,
212227
languageSelect,
213228
findVoicesByLocaleName,
229+
apiSelect,
214230
};

src/global/initLocalStore.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export default async function initStore() {
2121
role: "",
2222
speed: 1.0,
2323
pitch: 1.0,
24+
api: 1,
25+
specchKey: "",
26+
serviceRegion: "",
2427
});
2528

2629
if (!store.has("savePath")) {

src/store/play.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ async function getTTSData(
99
role: string,
1010
rate = 0,
1111
pitch = 0,
12-
api = true
12+
api: number,
13+
key: string,
14+
region: string
1315
) {
1416
let SSML = "";
15-
if (inps.activeIndex == "1" && api) {
17+
if (inps.activeIndex == "1" && (api == 1 || api == 3)) {
1618
SSML = `
1719
<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
1820
<voice name="${voice}">
@@ -27,7 +29,7 @@ async function getTTSData(
2729
</speak>
2830
`;
2931
}
30-
else if(inps.activeIndex == "1" && !api) {
32+
else if (inps.activeIndex == "1" && api == 2) {
3133
SSML = `
3234
<speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
3335
<voice name="${voice}">
@@ -43,12 +45,15 @@ async function getTTSData(
4345
}
4446
ipcRenderer.send("log.info", SSML);
4547
console.log(SSML);
46-
if (api) {
48+
if (api == 1) {
4749
const result = await ipcRenderer.invoke("speech", SSML);
4850
return result;
49-
} else {
51+
} else if (api == 2) {
5052
const result = await ipcRenderer.invoke("edgeApi", SSML);
5153
return result;
54+
} else {
55+
const result = await ipcRenderer.invoke("azureApi", SSML, key, region);
56+
return result;
5257
}
5358
}
5459
export default getTTSData;

src/store/store.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export const useTtsStore = defineStore("ttsStore", {
3535
autoplay: store.get("autoplay"),
3636
updateNotification: store.get("updateNotification"),
3737
titleStyle: store.get("titleStyle"),
38+
api: store.get("api"),
39+
speechKey: store.get("speechKey"),
40+
serviceRegion: store.get("serviceRegion"),
3841
},
3942
isLoading: false,
4043
currMp3Buffer: Buffer.alloc(0),
@@ -90,6 +93,12 @@ export const useTtsStore = defineStore("ttsStore", {
9093
setAutoPlay() {
9194
store.set("autoplay", this.config.autoplay);
9295
},
96+
setSpeechKey() {
97+
store.set("speechKey", this.config.speechKey);
98+
},
99+
setServiceRegion() {
100+
store.set("serviceRegion", this.config.serviceRegion);
101+
},
93102
addFormConfig() {
94103
this.config.formConfigJson[this.currConfigName] = this.formConfig;
95104
this.genFormConfig();
@@ -124,7 +133,7 @@ export const useTtsStore = defineStore("ttsStore", {
124133
? this.inputs.inputValue
125134
: this.inputs.ssmlValue,
126135
};
127-
if (this.page.tabIndex == "1" && this.formConfig.api && this.inputs.inputValue.length > 400) {
136+
if (this.page.tabIndex == "1" && this.formConfig.api == 1 && this.inputs.inputValue.length > 400) {
128137
const delimiters = ",。?,.?".split("");
129138
const maxSize = 300;
130139
ipcRenderer.send("log.info", "字数过多,正在对文本切片。。。");
@@ -165,7 +174,9 @@ export const useTtsStore = defineStore("ttsStore", {
165174
this.formConfig.role,
166175
(this.formConfig.speed - 1) * 100,
167176
(this.formConfig.pitch - 1) * 50,
168-
this.formConfig.api
177+
this.formConfig.api,
178+
this.config.speechKey,
179+
this.config.serviceRegion
169180
);
170181
this.currMp3Buffer = Buffer.concat([this.currMp3Buffer, buffers]);
171182
ipcRenderer.send(
@@ -206,7 +217,9 @@ export const useTtsStore = defineStore("ttsStore", {
206217
this.formConfig.role,
207218
(this.formConfig.speed - 1) * 100,
208219
(this.formConfig.pitch - 1) * 50,
209-
this.formConfig.api
220+
this.formConfig.api,
221+
this.config.speechKey,
222+
this.config.serviceRegion
210223
)
211224
.then((mp3buffer: any) => {
212225
this.currMp3Buffer = mp3buffer;
@@ -261,7 +274,7 @@ export const useTtsStore = defineStore("ttsStore", {
261274
inps.inputValue = datastr;
262275
let buffer = Buffer.alloc(0);
263276

264-
if (datastr.length > 400 && this.formConfig.api) {
277+
if (datastr.length > 400 && this.formConfig.api == 1) {
265278
const delimiters = ",。?,.? ".split("");
266279
const maxSize = 300;
267280
ipcRenderer.send("log.info", "字数过多,正在对文本切片。。。");
@@ -302,7 +315,9 @@ export const useTtsStore = defineStore("ttsStore", {
302315
this.formConfig.role,
303316
(this.formConfig.speed - 1) * 100,
304317
(this.formConfig.pitch - 1) * 50,
305-
this.formConfig.api
318+
this.formConfig.api,
319+
this.config.speechKey,
320+
this.config.serviceRegion
306321
);
307322
buffer = Buffer.concat([buffer, buffers]);
308323
ipcRenderer.send(
@@ -347,7 +362,9 @@ export const useTtsStore = defineStore("ttsStore", {
347362
this.formConfig.role,
348363
(this.formConfig.speed - 1) * 100,
349364
(this.formConfig.pitch - 1) * 50,
350-
this.formConfig.api
365+
this.formConfig.api,
366+
this.config.speechKey,
367+
this.config.serviceRegion
351368
)
352369
.then((mp3buffer: any) => {
353370
fs.writeFileSync(filePath, mp3buffer);
@@ -410,7 +427,9 @@ export const useTtsStore = defineStore("ttsStore", {
410427
this.formConfig.role,
411428
(this.formConfig.speed - 1) * 100,
412429
(this.formConfig.pitch - 1) * 50,
413-
this.formConfig.api
430+
this.formConfig.api,
431+
this.config.speechKey,
432+
this.config.serviceRegion
414433
)
415434
.then((mp3buffer: any) => {
416435
this.currMp3Buffer = mp3buffer;

0 commit comments

Comments
 (0)