Files
smartDriveEE/src/main/java/com/rj/service/SiliconFlowTtsService.java
2025-10-06 12:17:34 +08:00

362 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.rj.service;
import com.rj.config.SiliconFlowConfig;
import com.rj.dto.SiliconFlowTtsRequest;
import com.rj.dto.SiliconFlowTtsResponse;
import com.rj.entity.TtsRequestLog;
import com.rj.utils.MinIOUrlGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* SiliconFlow TTS 服务类
*
* @author rj
* @date 2025-01-02
*/
@Slf4j
@Service
public class SiliconFlowTtsService {
@Autowired
private SiliconFlowConfig config;
@Autowired
private RestTemplate restTemplate;
@Autowired
private com.rj.service.MinIOService minioService;
@Autowired
private ITtsRequestLogService ttsRequestLogService;
@Autowired
private MinIOUrlGenerator urlGenerator;
private static final String TTS_ENDPOINT = "/audio/speech";
/**
* 文本转语音
*
* @param request TTS请求
* @return TTS响应
*/
public SiliconFlowTtsResponse textToSpeech(SiliconFlowTtsRequest request) {
long startTime = System.currentTimeMillis();
TtsRequestLog requestLog = new TtsRequestLog();
try {
if (!config.isEnabled()) {
return SiliconFlowTtsResponse.error("SiliconFlow服务未启用");
}
if (request.getModel() == null || request.getModel().trim().isEmpty()) {
request.setModel(config.getDefaultTtsModel());
}
// 确保voice参数正确设置
if (request.getVoice() == null || request.getVoice().trim().isEmpty()) {
// 根据模型设置默认voice参数 FunAudioLLM/CosyVoice2-0.5B:claire
String model = request.getModel();
if (model != null && model.contains("fnlp/MOSS-TTSD-v0.5")) {
request.setVoice("fnlp/MOSS-TTSD-v0.5:claire");
} else {
request.setVoice("FunAudioLLM/CosyVoice2-0.5B:claire"); // 默认使用claire
}
}
if (request.getInput() == null || request.getInput().trim().isEmpty()) {
return SiliconFlowTtsResponse.error("输入文本不能为空");
}
String url = config.getBaseUrl() + TTS_ENDPOINT;
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(config.getApiKey());
// 创建请求实体
HttpEntity<SiliconFlowTtsRequest> requestEntity = new HttpEntity<>(request, headers);
// 详细记录请求参数
log.info("发送TTS请求到: {}", url);
log.info("TTS请求参数 - 模型: {}, 输入文本: {}, 输入长度: {}, 语音: {}, 语速: {}, 音调: {}, 音量: {}, 格式: {}, 采样率: {}",
request.getModel(),
request.getInput(),
request.getInput().length(),
request.getVoice(),
request.getSpeed(),
request.getPitch(),
request.getVolume(),
request.getFormat(),
request.getSampleRate()
);
// 发送请求 - SiliconFlow API返回音频流不是JSON
ResponseEntity<byte[]> response = restTemplate.exchange(
url,
HttpMethod.POST,
requestEntity,
byte[].class
);
if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
byte[] audioBytes = response.getBody();
String audioBase64 = Base64.getEncoder().encodeToString(audioBytes);
// 估算音频时长MP3格式
double estimatedDuration = calculateAudioDuration(audioBytes.length, "mp3");
// 上传到MinIO
String fileName = uploadAudioToMinIO(audioBytes, request.getInput());
// 生成MinIO直接访问URL
String minioUrl = null;
if (fileName != null) {
minioUrl = minioService.getFileUrl(fileName);
}
// 生成临时访问URL15分钟有效
MinIOUrlGenerator.UrlInfo tempUrlInfo = null;
if (fileName != null) {
tempUrlInfo = urlGenerator.generateShortTermUrl(fileName);
}
SiliconFlowTtsResponse ttsResponse = new SiliconFlowTtsResponse();
ttsResponse.setSuccess(true);
ttsResponse.setAudio(audioBase64);
ttsResponse.setFormat("mp3"); // SiliconFlow默认返回mp3格式
ttsResponse.setSampleRate(44100); // 默认采样率
ttsResponse.setDuration(estimatedDuration);
ttsResponse.setMinioUrl(minioUrl); // 直接MinIO访问URL
// 添加临时访问URL短链
if (tempUrlInfo != null) {
ttsResponse.setShortUrl(tempUrlInfo.getUrl());
ttsResponse.setShortUrlExpireTime(tempUrlInfo.getExpiresAt());
}
// 保存请求日志到数据库
saveTtsRequestLog(requestLog, request, ttsResponse, startTime, null, fileName);
log.info("TTS请求成功音频数据长度: {} bytes, 估算时长: {:.2f}秒, MinIO URL: {}, 临时URL: {}",
audioBytes.length, estimatedDuration, minioUrl, tempUrlInfo != null ? tempUrlInfo.getUrl() : "");
return ttsResponse;
} else {
log.error("TTS请求失败状态码: {}", response.getStatusCode());
return SiliconFlowTtsResponse.error("TTS请求失败状态码: " + response.getStatusCode());
}
} catch (Exception e) {
log.error("TTS请求异常: {}", e.getMessage(), e);
// 保存错误日志到数据库
SiliconFlowTtsResponse errorResponse = SiliconFlowTtsResponse.error("TTS请求异常: " + e.getMessage());
saveTtsRequestLog(requestLog, request, errorResponse, startTime, e.getMessage(), null);
return errorResponse;
}
}
/**
* 简单文本转语音
*
* @param text 要转换的文本
* @return TTS响应
*/
public SiliconFlowTtsResponse textToSpeech(String text) {
SiliconFlowTtsRequest request = new SiliconFlowTtsRequest();
request.setModel(config.getDefaultTtsModel());
request.setInput(text);
// voice参数会在textToSpeech方法中自动设置
return textToSpeech(request);
}
/**
* 文本转语音并返回音频字节数组
*
* @param request TTS请求
* @return 音频字节数组
*/
public byte[] textToSpeechBytes(SiliconFlowTtsRequest request) {
SiliconFlowTtsResponse response = textToSpeech(request);
if (response.isSuccess() && response.getAudio() != null) {
try {
return Base64.getDecoder().decode(response.getAudio());
} catch (Exception e) {
log.error("Base64解码失败: {}", e.getMessage());
return null;
}
}
return null;
}
/**
* 简单文本转语音并返回音频字节数组
*
* @param text 要转换的文本
* @return 音频字节数组
*/
public byte[] textToSpeechBytes(String text) {
SiliconFlowTtsRequest request = new SiliconFlowTtsRequest();
request.setModel(config.getDefaultTtsModel());
request.setInput(text);
// voice参数会在textToSpeech方法中自动设置
return textToSpeechBytes(request);
}
/**
* 检查服务状态
*
* @return 是否可用
*/
public boolean isServiceAvailable() {
return config.isEnabled() && config.getApiKey() != null && !config.getApiKey().trim().isEmpty();
}
/**
* 计算音频时长
*
* @param audioSizeBytes 音频文件大小(字节)
* @param format 音频格式
* @return 估算的音频时长(秒)
*/
private double calculateAudioDuration(long audioSizeBytes, String format) {
if (audioSizeBytes <= 0) {
return 0.0;
}
// 根据格式和文件大小估算时长
// 这是一个粗略的估算,实际时长可能因编码参数而异
double estimatedDuration;
switch (format.toLowerCase()) {
case "mp3":
// MP3 128kbps 大约 16KB/秒,但实际可能因编码而异
// 使用更保守的估算:约 20KB/秒
estimatedDuration = audioSizeBytes / 20000.0;
break;
case "wav":
// WAV 44.1kHz 16bit 立体声 = 176KB/秒
estimatedDuration = audioSizeBytes / 176000.0;
break;
case "opus":
// Opus 通常 64kbps约 8KB/秒
estimatedDuration = audioSizeBytes / 8000.0;
break;
default:
// 默认估算
estimatedDuration = audioSizeBytes / 20000.0;
break;
}
// 确保时长在合理范围内
return Math.max(0.1, Math.min(estimatedDuration, 3600.0)); // 0.1秒到1小时
}
/**
* 上传音频到MinIO
*
* @param audioBytes 音频字节数组
* @param inputText 输入文本(用于生成文件名)
* @return 文件名
*/
private String uploadAudioToMinIO(byte[] audioBytes, String inputText) {
try {
// 生成文件名tts_时间戳_文本hash.mp3
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String textHash = String.valueOf(inputText.hashCode()).replace("-", "");
String fileName = String.format("tts_%s_%s.mp3", timestamp, textHash);
// 上传到MinIO
minioService.uploadFile(audioBytes, fileName, "audio/mpeg");
log.info("音频上传到MinIO成功: {}, 大小: {} bytes", fileName, audioBytes.length);
return fileName;
} catch (Exception e) {
log.error("音频上传到MinIO失败: {}", e.getMessage(), e);
return null; // 上传失败不影响TTS功能
}
}
/**
* 保存TTS请求日志到数据库
*
* @param requestLog 请求日志对象
* @param request TTS请求
* @param response TTS响应
* @param startTime 开始时间
* @param errorMessage 错误信息
* @param audioFileName 音频文件名
*/
private void saveTtsRequestLog(TtsRequestLog requestLog, SiliconFlowTtsRequest request,
SiliconFlowTtsResponse response, long startTime, String errorMessage, String audioFileName) {
try {
long processingTime = System.currentTimeMillis() - startTime;
// 设置请求参数
requestLog.setRequestTime(LocalDateTime.now());
requestLog.setModel(request.getModel());
requestLog.setInputText(request.getInput());
requestLog.setInputLength(request.getInput() != null ? request.getInput().length() : 0);
requestLog.setVoice(request.getVoice());
requestLog.setSpeed(request.getSpeed());
requestLog.setPitch(request.getPitch());
requestLog.setVolume(request.getVolume());
requestLog.setFormat(request.getFormat());
requestLog.setSampleRate(request.getSampleRate());
requestLog.setCreatorName(request.getCreatorName());
requestLog.setCreatorPhone(request.getCreatorPhone());
requestLog.setAudioName(request.getAudioName());
// 设置响应信息
requestLog.setStatus(response.isSuccess() ? "SUCCESS" : "FAILED");
requestLog.setDuration(response.getDuration());
requestLog.setMinioUrl(response.getMinioUrl()); // 临时访问URL
requestLog.setAudioName(audioFileName); // 音频文件名
requestLog.setShortUrl(response.getShortUrl()); // 短链URL与MinIO URL相同
requestLog.setShortUrlExpireTime(response.getShortUrlExpireTime());
requestLog.setProcessingTimeMs(processingTime);
requestLog.setCreateTime(LocalDateTime.now());
requestLog.setUpdateTime(LocalDateTime.now());
// 设置音频文件大小
if (response.getAudio() != null) {
try {
byte[] audioBytes = Base64.getDecoder().decode(response.getAudio());
requestLog.setAudioSizeBytes((long) audioBytes.length);
} catch (Exception e) {
log.warn("计算音频文件大小失败: {}", e.getMessage());
}
}
// 设置错误信息
if (errorMessage != null) {
requestLog.setErrorMessage(errorMessage);
} else if (!response.isSuccess()) {
requestLog.setErrorMessage(response.getMessage());
requestLog.setErrorCode(response.getErrorCode());
}
// 保存到数据库
ttsRequestLogService.saveTtsRequestLog(requestLog);
log.info("TTS请求日志已保存: ID={}, 状态={}, 处理时间={}ms",
requestLog.getId(), requestLog.getStatus(), processingTime);
} catch (Exception e) {
log.error("保存TTS请求日志失败: {}", e.getMessage(), e);
}
}
}