@dofe/infra-clients 0.1.17 → 0.1.18

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.
Files changed (80) hide show
  1. package/dist/index.d.ts +0 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +0 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/internal/sms/sms-aliyun.client.d.ts +2 -1
  6. package/dist/internal/sms/sms-aliyun.client.d.ts.map +1 -1
  7. package/dist/internal/sms/sms-volcengine.client.d.ts +2 -2
  8. package/package.json +72 -7
  9. package/dist/internal/file-cdn/dto/file-cdn.dto.d.ts +0 -78
  10. package/dist/internal/file-cdn/dto/file-cdn.dto.d.ts.map +0 -1
  11. package/dist/internal/file-cdn/dto/file-cdn.dto.js +0 -70
  12. package/dist/internal/file-cdn/dto/file-cdn.dto.js.map +0 -1
  13. package/dist/internal/file-cdn/file-cdn.client.d.ts +0 -283
  14. package/dist/internal/file-cdn/file-cdn.client.d.ts.map +0 -1
  15. package/dist/internal/file-cdn/file-cdn.client.js +0 -526
  16. package/dist/internal/file-cdn/file-cdn.client.js.map +0 -1
  17. package/dist/internal/file-cdn/file-cdn.module.d.ts +0 -3
  18. package/dist/internal/file-cdn/file-cdn.module.d.ts.map +0 -1
  19. package/dist/internal/file-cdn/file-cdn.module.js +0 -32
  20. package/dist/internal/file-cdn/file-cdn.module.js.map +0 -1
  21. package/dist/internal/file-cdn/index.d.ts +0 -35
  22. package/dist/internal/file-cdn/index.d.ts.map +0 -1
  23. package/dist/internal/file-cdn/index.js +0 -54
  24. package/dist/internal/file-cdn/index.js.map +0 -1
  25. package/dist/internal/openspeech/index.d.ts +0 -35
  26. package/dist/internal/openspeech/index.d.ts.map +0 -1
  27. package/dist/internal/openspeech/index.js +0 -56
  28. package/dist/internal/openspeech/index.js.map +0 -1
  29. package/dist/internal/openspeech/openspeech.client.d.ts +0 -304
  30. package/dist/internal/openspeech/openspeech.client.d.ts.map +0 -1
  31. package/dist/internal/openspeech/openspeech.client.js +0 -405
  32. package/dist/internal/openspeech/openspeech.client.js.map +0 -1
  33. package/dist/internal/openspeech/openspeech.factory.d.ts +0 -247
  34. package/dist/internal/openspeech/openspeech.factory.d.ts.map +0 -1
  35. package/dist/internal/openspeech/openspeech.factory.js +0 -406
  36. package/dist/internal/openspeech/openspeech.factory.js.map +0 -1
  37. package/dist/internal/openspeech/openspeech.module.d.ts +0 -45
  38. package/dist/internal/openspeech/openspeech.module.d.ts.map +0 -1
  39. package/dist/internal/openspeech/openspeech.module.js +0 -68
  40. package/dist/internal/openspeech/openspeech.module.js.map +0 -1
  41. package/dist/internal/openspeech/providers/aliyun.provider.d.ts +0 -125
  42. package/dist/internal/openspeech/providers/aliyun.provider.d.ts.map +0 -1
  43. package/dist/internal/openspeech/providers/aliyun.provider.js +0 -274
  44. package/dist/internal/openspeech/providers/aliyun.provider.js.map +0 -1
  45. package/dist/internal/openspeech/providers/base.provider.d.ts +0 -98
  46. package/dist/internal/openspeech/providers/base.provider.d.ts.map +0 -1
  47. package/dist/internal/openspeech/providers/base.provider.js +0 -87
  48. package/dist/internal/openspeech/providers/base.provider.js.map +0 -1
  49. package/dist/internal/openspeech/providers/index.d.ts +0 -10
  50. package/dist/internal/openspeech/providers/index.d.ts.map +0 -1
  51. package/dist/internal/openspeech/providers/index.js +0 -26
  52. package/dist/internal/openspeech/providers/index.js.map +0 -1
  53. package/dist/internal/openspeech/providers/volcengine-streaming.provider.d.ts +0 -291
  54. package/dist/internal/openspeech/providers/volcengine-streaming.provider.d.ts.map +0 -1
  55. package/dist/internal/openspeech/providers/volcengine-streaming.provider.js +0 -1358
  56. package/dist/internal/openspeech/providers/volcengine-streaming.provider.js.map +0 -1
  57. package/dist/internal/openspeech/providers/volcengine.provider.d.ts +0 -144
  58. package/dist/internal/openspeech/providers/volcengine.provider.d.ts.map +0 -1
  59. package/dist/internal/openspeech/providers/volcengine.provider.js +0 -337
  60. package/dist/internal/openspeech/providers/volcengine.provider.js.map +0 -1
  61. package/dist/internal/openspeech/types.d.ts +0 -408
  62. package/dist/internal/openspeech/types.d.ts.map +0 -1
  63. package/dist/internal/openspeech/types.js +0 -11
  64. package/dist/internal/openspeech/types.js.map +0 -1
  65. package/dist/internal/volcengine-tts/dto/tts.dto.d.ts +0 -52
  66. package/dist/internal/volcengine-tts/dto/tts.dto.d.ts.map +0 -1
  67. package/dist/internal/volcengine-tts/dto/tts.dto.js +0 -59
  68. package/dist/internal/volcengine-tts/dto/tts.dto.js.map +0 -1
  69. package/dist/internal/volcengine-tts/index.d.ts +0 -4
  70. package/dist/internal/volcengine-tts/index.d.ts.map +0 -1
  71. package/dist/internal/volcengine-tts/index.js +0 -20
  72. package/dist/internal/volcengine-tts/index.js.map +0 -1
  73. package/dist/internal/volcengine-tts/volcengine-tts.client.d.ts +0 -104
  74. package/dist/internal/volcengine-tts/volcengine-tts.client.d.ts.map +0 -1
  75. package/dist/internal/volcengine-tts/volcengine-tts.client.js +0 -690
  76. package/dist/internal/volcengine-tts/volcengine-tts.client.js.map +0 -1
  77. package/dist/internal/volcengine-tts/volcengine-tts.module.d.ts +0 -3
  78. package/dist/internal/volcengine-tts/volcengine-tts.module.d.ts.map +0 -1
  79. package/dist/internal/volcengine-tts/volcengine-tts.module.js +0 -34
  80. package/dist/internal/volcengine-tts/volcengine-tts.module.js.map +0 -1
@@ -1,690 +0,0 @@
1
- "use strict";
2
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
- return c > 3 && r && Object.defineProperty(target, key, r), r;
7
- };
8
- var __metadata = (this && this.__metadata) || function (k, v) {
9
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
- };
11
- var __param = (this && this.__param) || function (paramIndex, decorator) {
12
- return function (target, key) { decorator(target, key, paramIndex); }
13
- };
14
- Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.VolcengineTtsClient = void 0;
16
- const common_1 = require("@nestjs/common");
17
- const config_1 = require("@nestjs/config");
18
- const axios_1 = require("@nestjs/axios");
19
- const nest_winston_1 = require("nest-winston");
20
- const winston_1 = require("winston");
21
- const rxjs_1 = require("rxjs");
22
- const tos_sdk_1 = require("@volcengine/tos-sdk");
23
- const music_metadata_1 = require("music-metadata");
24
- const openapi_1 = require("@volcengine/openapi");
25
- const infra_common_1 = require("@dofe/infra-common");
26
- const infra_common_2 = require("@dofe/infra-common");
27
- const infra_shared_services_1 = require("@dofe/infra-shared-services");
28
- const infra_utils_1 = require("@dofe/infra-utils");
29
- /**
30
- * Volcengine TTS服务
31
- * 基于字节跳动TTS API实现语音合成功能
32
- */
33
- const hotList = [
34
- 'ICL_zh_male_BV144_paoxiaoge_v1_tob',
35
- 'zh_male_sunwukong_mars_bigtts',
36
- 'zh_male_xionger_mars_bigtts',
37
- 'zh_male_zhubajie_mars_bigtts',
38
- ];
39
- let VolcengineTtsClient = class VolcengineTtsClient {
40
- configService;
41
- httpService;
42
- fileApi;
43
- logger;
44
- ttsConfig;
45
- ttsUrl;
46
- tosClient = null;
47
- cloudUrl = '';
48
- constructor(configService, httpService, fileApi, logger) {
49
- this.configService = configService;
50
- this.httpService = httpService;
51
- this.fileApi = fileApi;
52
- this.logger = logger;
53
- const config = (0, infra_common_1.getKeysConfig)()?.tts;
54
- // console.log('techwu config', config);
55
- if (!config || !config.volcengine) {
56
- throw new infra_common_2.FeatureNotConfiguredError('tts', 'keys.tts');
57
- }
58
- const volcengineConfig = config.volcengine;
59
- const storageConfig = (0, infra_common_1.getKeysConfig)()?.storage?.tos;
60
- // 从 buckets 配置中读取 TOS bucket 信息
61
- const bucketConfigs = this.configService.getOrThrow('buckets');
62
- const tosBucket = bucketConfigs.find((b) => b.vendor === 'tos' && b.bucket === volcengineConfig.bucket);
63
- if (!tosBucket) {
64
- throw new Error(`TOS bucket "${volcengineConfig.bucket}" not found in buckets configuration`);
65
- }
66
- if (!storageConfig ||
67
- !storageConfig.accessKey ||
68
- !storageConfig.secretKey) {
69
- throw new Error('TOS storage credentials not found in keys/config.json');
70
- }
71
- // 构建 TOS 配置
72
- const tosConfig = {
73
- region: tosBucket.region || 'cn-shanghai',
74
- endpoint: this.extractTosEndpoint(tosBucket.tosEndpoint || tosBucket.endpoint),
75
- bucket: tosBucket.bucket,
76
- accessKeyId: storageConfig.accessKey,
77
- accessKeySecret: storageConfig.secretKey,
78
- };
79
- this.ttsConfig = {
80
- endpoint: volcengineConfig.endpoint ||
81
- 'https://openspeech.bytedance.com/api/v3/tts/unidirectional',
82
- apiKey: volcengineConfig.apiKey || '',
83
- resourceId: volcengineConfig.resourceId || '',
84
- region: volcengineConfig.region || 'cn-shanghai',
85
- accessKey: volcengineConfig.accessKey || '',
86
- secretKey: volcengineConfig.secretKey || '',
87
- tos: tosConfig,
88
- };
89
- this.ttsUrl = this.ttsConfig.endpoint;
90
- this.validateConfiguration();
91
- this.initializeTOS();
92
- }
93
- /**
94
- * 从 endpoint URL 中提取 TOS endpoint 域名
95
- * 例如: https://tos-s3-cn-shanghai.volces.com -> tos-s3-cn-shanghai.volces.com
96
- */
97
- extractTosEndpoint(endpointUrl) {
98
- if (!endpointUrl) {
99
- return 'tos-cn-shanghai.volces.com';
100
- }
101
- // 移除协议前缀
102
- return endpointUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
103
- }
104
- /**
105
- * 初始化火山云TOS客户端
106
- */
107
- initializeTOS() {
108
- if (!this.ttsConfig.tos) {
109
- this.logger.warn('火山云TOS配置未提供,云存储功能将无法使用');
110
- return;
111
- }
112
- try {
113
- const { region, endpoint, bucket, accessKeyId, accessKeySecret } = this.ttsConfig.tos;
114
- if (!accessKeyId || !accessKeySecret) {
115
- this.logger.warn('火山云TOS配置不完整,云存储功能可能无法使用');
116
- this.tosClient = null;
117
- return;
118
- }
119
- this.cloudUrl = `https://${bucket}.${endpoint}`;
120
- this.tosClient = new tos_sdk_1.TosClient({
121
- region,
122
- endpoint,
123
- accessKeyId,
124
- accessKeySecret,
125
- });
126
- if (infra_utils_1.environmentUtil.isProduction()) {
127
- this.logger.info('VolcengineTtsClient module initialized TOS client', {
128
- region,
129
- endpoint,
130
- bucket,
131
- accessKeyId,
132
- accessKeySecret,
133
- });
134
- }
135
- else {
136
- this.logger.debug('VolcengineTtsClient module initialized TOS client', {
137
- region,
138
- endpoint,
139
- bucket,
140
- accessKeyId,
141
- accessKeySecret,
142
- });
143
- }
144
- }
145
- catch (error) {
146
- this.logger.error(`火山云TOS客户端初始化失败: ${error.message}`);
147
- this.tosClient = null;
148
- }
149
- }
150
- /**
151
- * 验证配置信息
152
- */
153
- validateConfiguration() {
154
- const missingVars = [];
155
- if (!this.ttsConfig.apiKey) {
156
- missingVars.push('apiKey');
157
- }
158
- if (!this.ttsConfig.resourceId) {
159
- missingVars.push('resourceId');
160
- }
161
- if (missingVars.length > 0) {
162
- this.logger.warn(`缺少TTS配置项: ${missingVars.join(', ')}`);
163
- this.logger.warn('TTS服务可能无法正常工作,请检查配置文件');
164
- }
165
- else {
166
- if (infra_utils_1.environmentUtil.isProduction()) {
167
- this.logger.info('TTS configuration validated successfully');
168
- }
169
- }
170
- }
171
- /**
172
- * 安全解析JSON数据
173
- */
174
- safeJsonParse(jsonString) {
175
- try {
176
- return JSON.parse(jsonString);
177
- }
178
- catch (error) {
179
- this.logger.debug(`JSON解析失败: ${error.message}, 数据: ${jsonString.substring(0, 50)}...`);
180
- return null;
181
- }
182
- }
183
- /**
184
- * 获取音频时长(毫秒)
185
- */
186
- async getAudioDuration(audioData) {
187
- try {
188
- const metadata = await (0, music_metadata_1.parseBuffer)(audioData, {
189
- mimeType: 'audio/mpeg',
190
- });
191
- return (metadata.format.duration || 0) * 1000; // 转换为毫秒
192
- }
193
- catch (error) {
194
- this.logger.warn(`获取音频时长失败: ${error.message}`);
195
- return 0;
196
- }
197
- }
198
- /**
199
- * 直接上传音频数据到火山云TOS
200
- */
201
- async uploadAudioToCloud(audioData, fileName) {
202
- try {
203
- this.logger.info(`开始上传音频数据到火山云TOS: ${fileName}`);
204
- if (!this.tosClient || !this.ttsConfig.tos) {
205
- throw new Error('火山云TOS客户端未初始化');
206
- }
207
- // 生成TOS对象键
208
- const objectKey = `tts/${Date.now()}/${fileName}`;
209
- // 上传到火山云TOS
210
- await this.tosClient.putObject({
211
- bucket: this.ttsConfig.tos.bucket,
212
- key: objectKey,
213
- body: audioData,
214
- contentType: 'audio/mpeg',
215
- });
216
- return {
217
- success: true,
218
- cloudUrl: `${this.cloudUrl}/${objectKey}`,
219
- s3Uri: `tos://${this.ttsConfig.tos.bucket}/${objectKey}`,
220
- };
221
- }
222
- catch (error) {
223
- this.logger.error(`上传音频数据到火山云TOS失败: ${error.message}`);
224
- return {
225
- success: false,
226
- error: error.message,
227
- };
228
- }
229
- }
230
- /**
231
- * 语音合成主方法
232
- */
233
- async textToSpeech(request) {
234
- try {
235
- // 构建请求头
236
- const headers = this.buildHeaders();
237
- // 构建请求体
238
- const payload = {
239
- req_params: {
240
- text: request.text,
241
- model: 'seed-tts-1.1',
242
- speaker: request.speaker ||
243
- (await this.getRandomVoice('🔥热门推荐')).voice.id ||
244
- 'zh_male_beijingxiaoye_emo_v2_mars_bigtts',
245
- additions: JSON.stringify({
246
- disable_markdown_filter: true,
247
- enable_language_detector: true,
248
- enable_latex_tn: true,
249
- disable_default_bit_rate: true,
250
- max_length_to_filter_parenthesis: 0,
251
- cache_config: {
252
- text_type: 1,
253
- use_cache: true,
254
- },
255
- post_process: {
256
- pitch: request.pitch || 0,
257
- },
258
- }),
259
- audio_params: {
260
- format: 'mp3',
261
- sample_rate: 32000,
262
- speech_rate: request.speech_rate || 0,
263
- loudness_rate: request.loudness_rate || 0,
264
- },
265
- },
266
- };
267
- // 执行TTS请求
268
- const result = await this.executeTtsRequest(headers, payload);
269
- this.logger.info(`TTS合成完成,云存储URL: ${result.audio}`);
270
- return { ...result, text: request.text };
271
- }
272
- catch (error) {
273
- this.logger.error(`TTS合成失败: ${error.message}`, error.stack);
274
- return {
275
- success: false,
276
- error: error.message,
277
- };
278
- }
279
- }
280
- /**
281
- * 构建请求头
282
- */
283
- buildHeaders() {
284
- return {
285
- 'x-api-key': this.ttsConfig.apiKey,
286
- 'X-Api-Resource-Id': this.ttsConfig.resourceId,
287
- Connection: 'keep-alive',
288
- 'Content-Type': 'application/json',
289
- };
290
- }
291
- /**
292
- * 执行TTS请求
293
- */
294
- async executeTtsRequest(headers, payload) {
295
- try {
296
- this.logger.info('发送TTS请求到字节跳动API');
297
- const response = await (0, rxjs_1.firstValueFrom)(this.httpService.post(this.ttsUrl, payload, {
298
- headers,
299
- responseType: 'stream',
300
- }));
301
- // 获取日志ID
302
- const logId = response.headers['x-tt-logid'];
303
- this.logger.info(`请求日志ID: ${logId}`);
304
- // 处理流式响应
305
- return await this.processStreamResponse(response.data, logId);
306
- }
307
- catch (error) {
308
- this.logger.error(`TTS API请求失败: ${error.message}`);
309
- if (error.response) {
310
- this.logger.error('响应状态:', error.response.status);
311
- this.logger.error('响应头:', error.response.headers);
312
- this.logger.error('响应数据:', error.response.data);
313
- }
314
- throw new common_1.BadGatewayException(`TTS API请求失败: ${error.message}`);
315
- }
316
- }
317
- /**
318
- * 处理流式响应
319
- */
320
- async processStreamResponse(stream, logId) {
321
- return new Promise((resolve, reject) => {
322
- let audioData = Buffer.alloc(0);
323
- let totalAudioSize = 0;
324
- let hasError = false;
325
- let errorMessage = '';
326
- let buffer = ''; // 用于缓存不完整的数据
327
- stream.on('data', (chunk) => {
328
- try {
329
- // 将新数据添加到缓冲区
330
- buffer += chunk.toString();
331
- // 按行分割数据
332
- const lines = buffer.split('\n');
333
- // 保留最后一个可能不完整的行
334
- buffer = lines.pop() || '';
335
- for (const line of lines) {
336
- if (!line.trim())
337
- continue;
338
- const data = this.safeJsonParse(line);
339
- if (!data) {
340
- continue;
341
- }
342
- // 处理音频数据
343
- if (data.code === 0 && data.data) {
344
- const audioChunk = Buffer.from(data.data, 'base64');
345
- audioData = Buffer.concat([audioData, audioChunk]);
346
- totalAudioSize += audioChunk.length;
347
- continue;
348
- }
349
- // 处理句子信息
350
- if (data.code === 0 && data.sentence) {
351
- continue;
352
- }
353
- // 处理完成信号
354
- if (data.code === 20000000) {
355
- this.logger.info('TTS合成完成');
356
- break;
357
- }
358
- // 处理错误
359
- if (data.code > 0) {
360
- hasError = true;
361
- errorMessage = data.message || `错误码: ${data.code}`;
362
- this.logger.error(`TTS API错误: ${errorMessage}`);
363
- break;
364
- }
365
- }
366
- }
367
- catch (parseError) {
368
- this.logger.error(`解析响应数据失败: ${parseError.message}`);
369
- }
370
- });
371
- stream.on('end', async () => {
372
- this.logger.info(`流式响应结束,总音频大小: ${totalAudioSize} bytes`);
373
- // 处理缓冲区中剩余的数据
374
- if (buffer.trim()) {
375
- this.logger.debug(`处理缓冲区剩余数据: ${buffer.substring(0, 100)}...`);
376
- const data = this.safeJsonParse(buffer);
377
- if (data) {
378
- // 处理音频数据
379
- if (data.code === 0 && data.data) {
380
- const audioChunk = Buffer.from(data.data, 'base64');
381
- audioData = Buffer.concat([audioData, audioChunk]);
382
- totalAudioSize += audioChunk.length;
383
- this.logger.debug(`收到缓冲区音频数据块,大小: ${audioChunk.length} bytes`);
384
- }
385
- // 处理完成信号
386
- if (data.code === 20000000) {
387
- this.logger.info('TTS合成完成(缓冲区)');
388
- }
389
- // 处理错误
390
- if (data.code > 0) {
391
- hasError = true;
392
- errorMessage = data.message || `错误码: ${data.code}`;
393
- this.logger.error(`TTS API错误(缓冲区): ${errorMessage}`);
394
- }
395
- }
396
- }
397
- if (hasError) {
398
- this.logger.error(`TTS处理过程中发生错误: ${errorMessage}`);
399
- resolve({
400
- success: false,
401
- error: errorMessage,
402
- });
403
- return;
404
- }
405
- if (audioData.length === 0) {
406
- this.logger.warn('未收到任何音频数据');
407
- resolve({
408
- success: false,
409
- error: '未收到音频数据',
410
- });
411
- return;
412
- }
413
- this.logger.info(`准备上传音频数据到云存储,大小: ${audioData.length} bytes`);
414
- // 生成文件名
415
- const fileName = `tts_${Date.now()}_${logId || 'unknown'}.mp3`;
416
- const audioDuration = await this.getAudioDuration(audioData);
417
- // 直接上传到云存储
418
- this.uploadAudioToCloud(audioData, fileName)
419
- .then((cloudResult) => {
420
- if (cloudResult.success) {
421
- this.logger.info('音频数据上传到云存储成功');
422
- resolve({
423
- success: true,
424
- audio: cloudResult.cloudUrl,
425
- duration: audioDuration,
426
- });
427
- }
428
- else {
429
- this.logger.error(`云存储上传失败: ${cloudResult.error}`);
430
- resolve({
431
- success: false,
432
- error: cloudResult.error,
433
- });
434
- }
435
- })
436
- .catch((error) => {
437
- this.logger.error(`云存储上传异常: ${error.message}`);
438
- resolve({
439
- success: false,
440
- error: error.message,
441
- });
442
- });
443
- });
444
- stream.on('error', (error) => {
445
- this.logger.error(`流处理错误: ${error.message}`);
446
- reject(error);
447
- });
448
- });
449
- }
450
- /**
451
- * 获取音色列表
452
- */
453
- async getVoiceList() {
454
- const result = await this.voiceList();
455
- // 统计总的声音数量
456
- let totalVoices = 0;
457
- const voiceIds = new Set();
458
- result.forEach((category) => {
459
- category.groups.forEach((group) => {
460
- // 统计声音ID
461
- group.items.forEach((item) => {
462
- voiceIds.add(item.id);
463
- totalVoices++;
464
- });
465
- });
466
- });
467
- return result;
468
- }
469
- /**
470
- * 随机获取一个音色
471
- */
472
- async getRandomVoice(category, gender) {
473
- const voiceData = await this.voiceList();
474
- // 如果指定了分类,先过滤出该分类的数据
475
- let filteredData = voiceData;
476
- if (category) {
477
- filteredData = voiceData.filter((item) => item.Category === category);
478
- if (filteredData.length === 0) {
479
- throw new Error(`未找到分类 "${category}" 的音色数据`);
480
- }
481
- }
482
- // 收集所有可用的音色
483
- const allVoices = [];
484
- filteredData.forEach((categoryItem) => {
485
- categoryItem.groups.forEach((group) => {
486
- // 如果指定了性别,只收集该性别的音色
487
- if (gender && group.gender_type !== gender) {
488
- return;
489
- }
490
- allVoices.push({
491
- ...group,
492
- category: categoryItem.Category,
493
- });
494
- });
495
- });
496
- if (allVoices.length === 0) {
497
- throw new Error('没有可用的音色数据');
498
- }
499
- // 随机选择一个性别分组
500
- const randomGroup = allVoices[Math.floor(Math.random() * allVoices.length)];
501
- // 从该分组中随机选择一个音色
502
- const randomVoice = randomGroup.items[Math.floor(Math.random() * randomGroup.items.length)];
503
- return {
504
- category: randomGroup.category,
505
- gender_title: randomGroup.gender_title,
506
- gender_type: randomGroup.gender_type,
507
- voice: randomVoice,
508
- };
509
- }
510
- /**
511
- * 获取音色列表(原始数据)
512
- */
513
- async voiceList() {
514
- const list = await this.volcengineApi({
515
- params: {
516
- Action: 'ListBigModelTTSTimbres',
517
- Version: '2025-05-20',
518
- },
519
- body: {},
520
- method: 'POST',
521
- Service: 'speech_saas_prod',
522
- }).then((res) => {
523
- return res.Result?.Timbres ?? [];
524
- });
525
- // 用于存储按分类和性别分组的数据
526
- const categoryMap = new Map();
527
- // 遍历所有声音数据
528
- list.forEach((speaker) => {
529
- const speakerId = speaker.SpeakerID;
530
- speaker.TimbreInfos.forEach((timbre) => {
531
- const gender = timbre.Gender;
532
- const speakerName = timbre.SpeakerName;
533
- // 检查是否包含"多语种"分类,如果有则跳过整个声音
534
- const hasMultiLanguage = timbre.Categories.some((categoryInfo) => categoryInfo.Category === '多语种');
535
- if (hasMultiLanguage) {
536
- return; // 跳过包含"多语种"分类的声音
537
- }
538
- // 遍历每个声音的分类
539
- timbre.Categories.forEach((categoryInfo) => {
540
- const category = categoryInfo.Category;
541
- // 如果分类不存在,创建新的分类,并初始化男女两个性别分组
542
- if (!categoryMap.has(category)) {
543
- categoryMap.set(category, {
544
- Category: category,
545
- groups: [
546
- {
547
- gender_title: '女声',
548
- gender_type: 'female',
549
- items: [],
550
- },
551
- {
552
- gender_title: '男声',
553
- gender_type: 'male',
554
- items: [],
555
- },
556
- ],
557
- });
558
- }
559
- const categoryData = categoryMap.get(category);
560
- // 根据性别找到对应的分组
561
- const genderGroup = categoryData.groups.find((group) => (gender === '女' && group.gender_type === 'female') ||
562
- (gender === '男' && group.gender_type === 'male'));
563
- if (genderGroup) {
564
- // 遍历情感信息,获取演示URL
565
- timbre.Emotions.forEach((emotion) => {
566
- // 检查是否已存在相同的声音项(避免重复)
567
- const existingItem = genderGroup.items.find((item) => item.id === speakerId && item.voice_url === emotion.DemoURL);
568
- if (!existingItem) {
569
- genderGroup.items.push({
570
- id: speakerId,
571
- name: speakerName,
572
- voice_url: emotion.DemoURL,
573
- emotion: emotion.Emotion,
574
- emotion_type: emotion.EmotionType,
575
- demo_text: emotion.DemoText,
576
- });
577
- }
578
- });
579
- }
580
- });
581
- });
582
- });
583
- // 转换为数组格式
584
- const result = Array.from(categoryMap.values());
585
- // 创建热门分组
586
- const hotGroup = this.createHotGroup(list);
587
- // 将热门分组添加到结果数组的开头
588
- return [hotGroup, ...result];
589
- }
590
- /**
591
- * 创建热门分组
592
- */
593
- createHotGroup(list) {
594
- const hotItems = [];
595
- // 遍历所有声音数据,筛选出热门音色
596
- list.forEach((speaker) => {
597
- const speakerId = speaker.SpeakerID;
598
- speaker.TimbreInfos.forEach((timbre) => {
599
- const gender = timbre.Gender;
600
- const speakerName = timbre.SpeakerName;
601
- // 检查是否包含"多语种"分类,如果有则跳过
602
- const hasMultiLanguage = timbre.Categories.some((categoryInfo) => categoryInfo.Category === '多语种');
603
- if (hasMultiLanguage) {
604
- return;
605
- }
606
- // 检查是否在热门列表中
607
- const isHot = hotList.includes(speakerId);
608
- if (!isHot) {
609
- return;
610
- }
611
- // 遍历情感信息,获取演示URL
612
- timbre.Emotions.forEach((emotion) => {
613
- // 检查是否已存在相同的声音项(避免重复)
614
- const existingItem = hotItems.find((item) => item.id === speakerId && item.voice_url === emotion.DemoURL);
615
- if (!existingItem) {
616
- hotItems.push({
617
- id: speakerId,
618
- name: speakerName,
619
- voice_url: emotion.DemoURL,
620
- emotion: emotion.Emotion,
621
- emotion_type: emotion.EmotionType,
622
- demo_text: emotion.DemoText,
623
- gender: gender === '女' ? 'female' : 'male',
624
- gender_title: gender === '女' ? '女声' : '男声',
625
- });
626
- }
627
- });
628
- });
629
- });
630
- // 按照 hotList 的顺序排序热门音色
631
- const sortedHotItems = hotItems.sort((a, b) => {
632
- const indexA = hotList.indexOf(a.id);
633
- const indexB = hotList.indexOf(b.id);
634
- return indexA - indexB;
635
- });
636
- return {
637
- Category: '🔥热门推荐',
638
- groups: [
639
- {
640
- gender_title: '热门音色',
641
- gender_type: 'hot',
642
- items: sortedHotItems,
643
- },
644
- ],
645
- };
646
- }
647
- /**
648
- * 调用火山引擎 API
649
- */
650
- async volcengineApi({ body = {}, params, method = 'POST', Service, }) {
651
- const baseUrl = 'https://open.volcengineapi.com';
652
- const openApiRequestData = {
653
- region: this.ttsConfig.region,
654
- method,
655
- params: params,
656
- headers: {},
657
- body: JSON.stringify(body),
658
- };
659
- const signer = new openapi_1.Signer(openApiRequestData, Service);
660
- // 签名
661
- signer.addAuthorization({
662
- accessKeyId: this.ttsConfig.accessKey,
663
- secretKey: this.ttsConfig.secretKey,
664
- });
665
- try {
666
- const response = await (0, rxjs_1.firstValueFrom)(this.httpService.request({
667
- url: baseUrl,
668
- headers: openApiRequestData.headers,
669
- params: openApiRequestData.params,
670
- method: openApiRequestData.method,
671
- data: body,
672
- }));
673
- return response.data;
674
- }
675
- catch (error) {
676
- this.logger.error(`火山引擎API调用失败: ${error.message}`);
677
- return undefined;
678
- }
679
- }
680
- };
681
- exports.VolcengineTtsClient = VolcengineTtsClient;
682
- exports.VolcengineTtsClient = VolcengineTtsClient = __decorate([
683
- (0, common_1.Injectable)(),
684
- __param(3, (0, common_1.Inject)(nest_winston_1.WINSTON_MODULE_PROVIDER)),
685
- __metadata("design:paramtypes", [config_1.ConfigService,
686
- axios_1.HttpService,
687
- infra_shared_services_1.FileStorageService,
688
- winston_1.Logger])
689
- ], VolcengineTtsClient);
690
- //# sourceMappingURL=volcengine-tts.client.js.map