Skip to content

Commit 69e3bd0

Browse files
authored
Merge pull request #80 from codemate-oj/feat/tts-api
feat: add tts api
2 parents 4fa958e + 509fe93 commit 69e3bd0

2 files changed

Lines changed: 147 additions & 0 deletions

File tree

packages/codemate-plugin/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
"packageManager": "yarn@4.0.2",
44
"dependencies": {
55
"alipay-sdk": "^4.11.0",
6+
"cos-nodejs-sdk-v5": "^2.14.6",
67
"crypto-js": "^4.2.0",
78
"fs-extra": "^11.2.0",
89
"hydrooj": "workspace:^",
910
"lodash": "^4.17.21",
1011
"lru-cache": "^10.2.0",
1112
"nanoid": "^5.0.6",
1213
"tencentcloud-sdk-nodejs-captcha": "^4.0.850",
14+
"tencentcloud-sdk-nodejs-tts": "^4.0.1003",
1315
"wechatpay-node-v3": "2.1.8"
1416
},
1517
"module": "api.ts",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import COS from 'cos-nodejs-sdk-v5';
2+
import * as TencentCloudSDK from 'tencentcloud-sdk-nodejs-tts';
3+
import type { Client as TtsClientType } from 'tencentcloud-sdk-nodejs-tts/tencentcloud/services/tts/v20190823/tts_client';
4+
import { Context, db, Handler, md5, nanoid, ObjectId, param, SettingModel, SystemModel, Types } from 'hydrooj';
5+
6+
const TYPE_TTS_DATA = 110;
7+
const VOICE_TYPE = 601009;
8+
9+
export interface TtsDataDoc {
10+
docType: 110;
11+
docId: ObjectId;
12+
domainId: string;
13+
fileId: string;
14+
text: string;
15+
textHash: string;
16+
voiceType: number;
17+
}
18+
19+
declare module 'hydrooj' {
20+
interface DocType {
21+
[TYPE_TTS_DATA]: TtsDataDoc;
22+
}
23+
24+
interface Lib {
25+
tts: TtsClientType;
26+
tts_cos: COS;
27+
}
28+
29+
interface Collections {
30+
tts: TtsDataDoc;
31+
}
32+
}
33+
34+
const coll = db.collection('tts');
35+
36+
const getFileKey = (fileId: string) => `${fileId}.mp3`;
37+
38+
class TtsHandler extends Handler {
39+
@param('text', Types.String)
40+
async get(domainId: string, text: string) {
41+
const textMd5 = md5(text);
42+
const doc = await coll.findOne<TtsDataDoc>({ docType: TYPE_TTS_DATA, domainId, textHash: textMd5 });
43+
const bucket = await SystemModel.get('tts.bucket');
44+
const region = await SystemModel.get('tts.region');
45+
46+
if (doc) {
47+
const url = await new Promise((resolve, reject) => {
48+
global.Hydro.lib.tts_cos.getObjectUrl(
49+
{
50+
Bucket: bucket,
51+
Region: region,
52+
Key: getFileKey(doc.fileId),
53+
Sign: true,
54+
},
55+
(err, data) => (err ? reject(err) : resolve(data.Url)),
56+
);
57+
});
58+
59+
this.response.body = {
60+
audioUrl: url,
61+
};
62+
63+
return;
64+
}
65+
66+
await this.limitRate('tts.generate', 15, 3, true);
67+
68+
const fileId = nanoid();
69+
70+
const res = await global.Hydro.lib.tts.TextToVoice({
71+
Text: text,
72+
SessionId: fileId,
73+
Codec: 'mp3',
74+
VoiceType: VOICE_TYPE,
75+
});
76+
77+
const buffer = Buffer.from(res.Audio, 'base64');
78+
await new Promise((resolve, reject) => {
79+
global.Hydro.lib.tts_cos.putObject(
80+
{
81+
Bucket: bucket,
82+
Region: region,
83+
Key: getFileKey(fileId),
84+
Body: buffer,
85+
},
86+
(err) => (err ? reject(err) : resolve(1)),
87+
);
88+
});
89+
90+
await coll.insertOne({
91+
docType: TYPE_TTS_DATA,
92+
docId: new ObjectId(),
93+
domainId,
94+
fileId,
95+
text,
96+
textHash: textMd5,
97+
voiceType: VOICE_TYPE,
98+
});
99+
100+
const url = await new Promise((resolve, reject) => {
101+
global.Hydro.lib.tts_cos.getObjectUrl(
102+
{
103+
Bucket: bucket,
104+
Region: region,
105+
Key: getFileKey(fileId),
106+
Sign: true,
107+
},
108+
(err, data) => (err ? reject(err) : resolve(data.Url)),
109+
);
110+
});
111+
112+
this.response.body = {
113+
audioUrl: url,
114+
};
115+
}
116+
}
117+
118+
export async function apply(ctx: Context) {
119+
ctx.Route('tts', '/tts', TtsHandler);
120+
121+
ctx.inject(['setting'], (c) => {
122+
c.setting.SystemSetting(SettingModel.Setting('setting_secrets', 'tts.secretId', '', 'text', 'TTS SecretId'));
123+
c.setting.SystemSetting(SettingModel.Setting('setting_secrets', 'tts.secretKey', '', 'text', 'TTS SecretKey'));
124+
c.setting.SystemSetting(SettingModel.Setting('setting_secrets', 'tts.bucket', '', 'text', 'TTS COS bucket'));
125+
c.setting.SystemSetting(SettingModel.Setting('setting_secrets', 'tts.region', '', 'text', 'TTS COS region'));
126+
});
127+
128+
global.Hydro.lib.tts = new TencentCloudSDK.tts.v20190823.Client({
129+
credential: {
130+
secretId: await SystemModel.get('tts.secretId'),
131+
secretKey: await SystemModel.get('tts.secretKey'),
132+
},
133+
region: '',
134+
profile: {
135+
httpProfile: {
136+
endpoint: 'tts.tencentcloudapi.com',
137+
},
138+
},
139+
});
140+
141+
global.Hydro.lib.tts_cos = new COS({
142+
SecretId: await SystemModel.get('tts.secretId'),
143+
SecretKey: await SystemModel.get('tts.secretKey'),
144+
});
145+
}

0 commit comments

Comments
 (0)