|
|
@ -1,6 +1,9 @@ |
|
|
package org.jeecg.modules.applet.service.impl; |
|
|
package org.jeecg.modules.applet.service.impl; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSON; |
|
|
|
|
|
import com.alibaba.fastjson.JSONArray; |
|
|
|
|
|
import com.alibaba.fastjson.JSONObject; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.tencentcloudapi.common.Credential; |
|
|
import com.tencentcloudapi.common.Credential; |
|
|
import com.tencentcloudapi.common.profile.ClientProfile; |
|
|
import com.tencentcloudapi.common.profile.ClientProfile; |
|
|
@ -8,6 +11,10 @@ import com.tencentcloudapi.common.profile.HttpProfile; |
|
|
import com.tencentcloudapi.tts.v20190823.TtsClient; |
|
|
import com.tencentcloudapi.tts.v20190823.TtsClient; |
|
|
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceRequest; |
|
|
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceRequest; |
|
|
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceResponse; |
|
|
import com.tencentcloudapi.tts.v20190823.models.TextToVoiceResponse; |
|
|
|
|
|
import com.tencentcloudapi.tts.v20190823.models.CreateTtsTaskRequest; |
|
|
|
|
|
import com.tencentcloudapi.tts.v20190823.models.CreateTtsTaskResponse; |
|
|
|
|
|
import com.tencentcloudapi.tts.v20190823.models.DescribeTtsTaskStatusRequest; |
|
|
|
|
|
import com.tencentcloudapi.tts.v20190823.models.DescribeTtsTaskStatusResponse; |
|
|
import lombok.extern.log4j.Log4j2; |
|
|
import lombok.extern.log4j.Log4j2; |
|
|
import org.jeecg.common.exception.JeecgBootException; |
|
|
import org.jeecg.common.exception.JeecgBootException; |
|
|
import org.jeecg.common.system.util.AppletUserUtil; |
|
|
import org.jeecg.common.system.util.AppletUserUtil; |
|
|
@ -15,6 +22,10 @@ import org.jeecg.common.util.oss.OssBootUtil; |
|
|
import org.jeecg.modules.applet.entity.TtsVo; |
|
|
import org.jeecg.modules.applet.entity.TtsVo; |
|
|
import org.jeecg.modules.applet.service.AppletApiTTService; |
|
|
import org.jeecg.modules.applet.service.AppletApiTTService; |
|
|
import org.jeecg.modules.common.IdUtils; |
|
|
import org.jeecg.modules.common.IdUtils; |
|
|
|
|
|
import org.jeecg.modules.demo.appletCoursePage.entity.AppletCoursePage; |
|
|
|
|
|
import org.jeecg.modules.demo.appletCoursePage.service.IAppletCoursePageService; |
|
|
|
|
|
import org.jeecg.modules.demo.appletCoursePageWord.entity.AppletCoursePageWord; |
|
|
|
|
|
import org.jeecg.modules.demo.appletCoursePageWord.service.IAppletCoursePageWordService; |
|
|
import org.jeecg.modules.demo.appletTtsPlayLog.service.IAppletTtsPlayLogService; |
|
|
import org.jeecg.modules.demo.appletTtsPlayLog.service.IAppletTtsPlayLogService; |
|
|
import org.jeecg.modules.demo.appletTtsTimbre.entity.AppletTtsTimbre; |
|
|
import org.jeecg.modules.demo.appletTtsTimbre.entity.AppletTtsTimbre; |
|
|
import org.jeecg.modules.demo.appletTtsTimbre.service.IAppletTtsTimbreService; |
|
|
import org.jeecg.modules.demo.appletTtsTimbre.service.IAppletTtsTimbreService; |
|
|
@ -28,6 +39,10 @@ import org.springframework.stereotype.Service; |
|
|
|
|
|
|
|
|
import java.util.List; |
|
|
import java.util.List; |
|
|
import java.util.UUID; |
|
|
import java.util.UUID; |
|
|
|
|
|
import java.util.Base64; |
|
|
|
|
|
import java.net.URL; |
|
|
|
|
|
import java.io.InputStream; |
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
|
|
|
|
@Log4j2 |
|
|
@Log4j2 |
|
|
@Service |
|
|
@Service |
|
|
@ -37,6 +52,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
private String secretId; |
|
|
private String secretId; |
|
|
@Value("${tencent.secretKey}") |
|
|
@Value("${tencent.secretKey}") |
|
|
private String secretKey; |
|
|
private String secretKey; |
|
|
|
|
|
@Value("${tencent.TtscallbackUrl}") |
|
|
|
|
|
private String TtscallbackUrl; |
|
|
|
|
|
|
|
|
@Autowired |
|
|
@Autowired |
|
|
private IAppletTtsTimbreService appletTtsTimbreService; |
|
|
private IAppletTtsTimbreService appletTtsTimbreService; |
|
|
@ -44,6 +61,10 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
private IAppletTtsPlayLogService appletTtsPlayLogService; |
|
|
private IAppletTtsPlayLogService appletTtsPlayLogService; |
|
|
@Autowired |
|
|
@Autowired |
|
|
private IAppletTtsCacheService appletTtsCacheService; |
|
|
private IAppletTtsCacheService appletTtsCacheService; |
|
|
|
|
|
@Autowired |
|
|
|
|
|
private IAppletCoursePageService appletCoursePageService; |
|
|
|
|
|
@Autowired |
|
|
|
|
|
private IAppletCoursePageWordService appletCoursePageWordService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public TtsVo textToVoice(String text, Float speed, Integer voiceType, Float volume, String codec) { |
|
|
public TtsVo textToVoice(String text, Float speed, Integer voiceType, Float volume, String codec) { |
|
|
@ -208,6 +229,397 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public String createLongTextTtsTask(String text, Float speed, Integer voiceType, Float volume, String codec, String callbackUrl) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 先检查是否存在生成中的任务(相同文本与参数) |
|
|
|
|
|
LambdaQueryWrapper<AppletTtsCache> generatingWrapper = new LambdaQueryWrapper<>(); |
|
|
|
|
|
generatingWrapper.eq(AppletTtsCache::getText, text) |
|
|
|
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
|
|
|
.eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null) |
|
|
|
|
|
.eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null) |
|
|
|
|
|
.eq(AppletTtsCache::getState, 0); |
|
|
|
|
|
|
|
|
|
|
|
AppletTtsCache generating = appletTtsCacheService.getOne(generatingWrapper); |
|
|
|
|
|
if (generating != null && generating.getTaskId() != null) { |
|
|
|
|
|
log.info("已有长文本TTS任务正在生成中,直接返回任务ID: {}", generating.getTaskId()); |
|
|
|
|
|
return generating.getTaskId(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 再检查是否已有成功缓存 |
|
|
|
|
|
LambdaQueryWrapper<AppletTtsCache> successWrapper = new LambdaQueryWrapper<>(); |
|
|
|
|
|
successWrapper.eq(AppletTtsCache::getText, text) |
|
|
|
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
|
|
|
.eq(volume != null, AppletTtsCache::getVolume, volume != null ? volume.doubleValue() : null) |
|
|
|
|
|
.eq(speed != null, AppletTtsCache::getSpeed, speed != null ? speed.doubleValue() : null) |
|
|
|
|
|
.eq(AppletTtsCache::getSuccess, "Y") |
|
|
|
|
|
.eq(AppletTtsCache::getState, 1); |
|
|
|
|
|
AppletTtsCache successCache = appletTtsCacheService.getOne(successWrapper); |
|
|
|
|
|
if (successCache != null && successCache.getTaskId() != null) { |
|
|
|
|
|
log.info("长文本TTS已完成,返回任务ID: {}", successCache.getTaskId()); |
|
|
|
|
|
return successCache.getTaskId(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 调用腾讯云异步创建任务 |
|
|
|
|
|
Credential cred = new Credential(secretId, secretKey); |
|
|
|
|
|
HttpProfile httpProfile = new HttpProfile(); |
|
|
|
|
|
httpProfile.setEndpoint("tts.tencentcloudapi.com"); |
|
|
|
|
|
ClientProfile clientProfile = new ClientProfile(); |
|
|
|
|
|
clientProfile.setHttpProfile(httpProfile); |
|
|
|
|
|
TtsClient client = new TtsClient(cred, "", clientProfile); |
|
|
|
|
|
|
|
|
|
|
|
CreateTtsTaskRequest req = new CreateTtsTaskRequest(); |
|
|
|
|
|
req.setText(text); |
|
|
|
|
|
if (speed != null) req.setSpeed(speed); |
|
|
|
|
|
if (voiceType != null) req.setVoiceType(voiceType.longValue()); |
|
|
|
|
|
if (volume != null) req.setVolume(volume); |
|
|
|
|
|
if (codec != null && !codec.isEmpty()) { |
|
|
|
|
|
req.setCodec(codec); |
|
|
|
|
|
} else { |
|
|
|
|
|
req.setCodec("wav"); |
|
|
|
|
|
} |
|
|
|
|
|
req.setModelType(1L); |
|
|
|
|
|
req.setPrimaryLanguage(2L); |
|
|
|
|
|
req.setSampleRate(16000L); |
|
|
|
|
|
// 启用时间戳字幕 |
|
|
|
|
|
try { |
|
|
|
|
|
req.getClass().getMethod("setEnableSubtitle", Boolean.class).invoke(req, Boolean.TRUE); |
|
|
|
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
if (callbackUrl != null && !callbackUrl.isEmpty()) { |
|
|
|
|
|
req.setCallbackUrl(callbackUrl); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
CreateTtsTaskResponse resp = client.CreateTtsTask(req); |
|
|
|
|
|
String taskId = resp.getData() != null ? resp.getData().getTaskId() : null; |
|
|
|
|
|
if (taskId == null || taskId.isEmpty()) { |
|
|
|
|
|
throw new JeecgBootException("创建长文本TTS任务失败,未返回任务ID"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 写入缓存,标记生成中 |
|
|
|
|
|
AppletTtsCache cache = new AppletTtsCache(); |
|
|
|
|
|
cache.setTaskId(taskId); |
|
|
|
|
|
cache.setText(text); |
|
|
|
|
|
cache.setVoiceType(voiceType); |
|
|
|
|
|
cache.setVolume(volume != null ? volume.doubleValue() : null); |
|
|
|
|
|
cache.setSpeed(speed != null ? speed.doubleValue() : null); |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.save(cache); |
|
|
|
|
|
|
|
|
|
|
|
log.info("长文本TTS任务创建成功,taskId: {}", taskId); |
|
|
|
|
|
return taskId; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("创建长文本TTS任务失败: {}", e.getMessage(), e); |
|
|
|
|
|
throw new JeecgBootException("创建长文本TTS任务失败: " + e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public AppletTtsCache queryLongTextTtsTaskStatus(String taskId) { |
|
|
|
|
|
try { |
|
|
|
|
|
Credential cred = new Credential(secretId, secretKey); |
|
|
|
|
|
HttpProfile httpProfile = new HttpProfile(); |
|
|
|
|
|
httpProfile.setEndpoint("tts.tencentcloudapi.com"); |
|
|
|
|
|
ClientProfile clientProfile = new ClientProfile(); |
|
|
|
|
|
clientProfile.setHttpProfile(httpProfile); |
|
|
|
|
|
TtsClient client = new TtsClient(cred, "", clientProfile); |
|
|
|
|
|
|
|
|
|
|
|
DescribeTtsTaskStatusRequest req = new DescribeTtsTaskStatusRequest(); |
|
|
|
|
|
req.setTaskId(taskId); |
|
|
|
|
|
DescribeTtsTaskStatusResponse resp = client.DescribeTtsTaskStatus(req); |
|
|
|
|
|
|
|
|
|
|
|
Long status = resp.getData() != null ? resp.getData().getStatus() : null; // 0等待 1处理中 2成功 3失败 |
|
|
|
|
|
String audioUrl = resp.getData() != null ? resp.getData().getResultUrl() : null; |
|
|
|
|
|
|
|
|
|
|
|
AppletTtsCache cache = appletTtsCacheService |
|
|
|
|
|
.lambdaQuery() |
|
|
|
|
|
.eq(AppletTtsCache::getTaskId, taskId) |
|
|
|
|
|
.one(); |
|
|
|
|
|
if (cache == null) { |
|
|
|
|
|
cache = new AppletTtsCache(); |
|
|
|
|
|
cache.setTaskId(taskId); |
|
|
|
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
appletTtsCacheService.save(cache); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (status == null || status == 0 || status == 1) { |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
cache.setUpdateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.updateById(cache); |
|
|
|
|
|
return cache; |
|
|
|
|
|
} else if (status == 2) { |
|
|
|
|
|
// 成功,处理音频 |
|
|
|
|
|
byte[] audioData = null; |
|
|
|
|
|
if (audioUrl != null && !audioUrl.isEmpty()) { |
|
|
|
|
|
audioData = downloadBytes(audioUrl); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (audioData != null) { |
|
|
|
|
|
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav"; |
|
|
|
|
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData); |
|
|
|
|
|
String uploadedUrl = OssBootUtil.upload(inputStream, "tts/" + fileName); |
|
|
|
|
|
cache.setAudioId(uploadedUrl != null ? uploadedUrl : audioUrl); |
|
|
|
|
|
|
|
|
|
|
|
Double realDuration = AudioDurationUtil.calculateDuration(audioData); |
|
|
|
|
|
if (realDuration != null) { |
|
|
|
|
|
cache.setDuration(realDuration); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
cache.setAudioId(audioUrl); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 解析时间戳 |
|
|
|
|
|
String timestampsJson = null; |
|
|
|
|
|
try { |
|
|
|
|
|
timestampsJson = JSON.toJSONString(resp.getData().getSubtitles()); |
|
|
|
|
|
cache.setTimestamps(timestampsJson); |
|
|
|
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
|
|
|
|
|
|
cache.setSuccess("Y"); |
|
|
|
|
|
cache.setState(1); |
|
|
|
|
|
cache.setUpdateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.updateById(cache); |
|
|
|
|
|
return cache; |
|
|
|
|
|
} else { |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
cache.setUpdateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.updateById(cache); |
|
|
|
|
|
return cache; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("查询长文本TTS任务状态失败: {}", e.getMessage(), e); |
|
|
|
|
|
throw new JeecgBootException("查询长文本TTS任务状态失败: " + e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 按页面创建长文本TTS任务(异步),仅接收页面ID与音色ID |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public String createLongTextTtsTaskByPage(String pageId, Integer voiceType) { |
|
|
|
|
|
String text = extractTextByPageId(pageId); |
|
|
|
|
|
if (text == null || text.trim().isEmpty()) { |
|
|
|
|
|
throw new JeecgBootException("页面内容为空,无法创建TTS任务"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 检查是否存在相同页面与音色的生成中任务 |
|
|
|
|
|
LambdaQueryWrapper<AppletTtsCache> generatingWrapper = new LambdaQueryWrapper<>(); |
|
|
|
|
|
generatingWrapper.eq(AppletTtsCache::getPageId, pageId) |
|
|
|
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
|
|
|
.eq(AppletTtsCache::getState, 0); |
|
|
|
|
|
AppletTtsCache generating = appletTtsCacheService.getOne(generatingWrapper); |
|
|
|
|
|
if (generating != null && generating.getTaskId() != null) { |
|
|
|
|
|
log.info("页面长文本TTS任务生成中,返回任务ID: {}", generating.getTaskId()); |
|
|
|
|
|
return generating.getTaskId(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已有成功缓存 |
|
|
|
|
|
LambdaQueryWrapper<AppletTtsCache> successWrapper = new LambdaQueryWrapper<>(); |
|
|
|
|
|
successWrapper.eq(AppletTtsCache::getPageId, pageId) |
|
|
|
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
|
|
|
.eq(AppletTtsCache::getSuccess, "Y") |
|
|
|
|
|
.eq(AppletTtsCache::getState, 1); |
|
|
|
|
|
AppletTtsCache successCache = appletTtsCacheService.getOne(successWrapper); |
|
|
|
|
|
if (successCache != null && successCache.getTaskId() != null) { |
|
|
|
|
|
log.info("页面长文本TTS已完成,返回任务ID: {}", successCache.getTaskId()); |
|
|
|
|
|
return successCache.getTaskId(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建腾讯云任务 |
|
|
|
|
|
Credential cred = new Credential(secretId, secretKey); |
|
|
|
|
|
HttpProfile httpProfile = new HttpProfile(); |
|
|
|
|
|
httpProfile.setEndpoint("tts.tencentcloudapi.com"); |
|
|
|
|
|
ClientProfile clientProfile = new ClientProfile(); |
|
|
|
|
|
clientProfile.setHttpProfile(httpProfile); |
|
|
|
|
|
TtsClient client = new TtsClient(cred, "", clientProfile); |
|
|
|
|
|
|
|
|
|
|
|
CreateTtsTaskRequest req = new CreateTtsTaskRequest(); |
|
|
|
|
|
req.setText(text); |
|
|
|
|
|
if (voiceType != null) req.setVoiceType(voiceType.longValue()); |
|
|
|
|
|
req.setCodec("wav"); |
|
|
|
|
|
req.setModelType(1L); |
|
|
|
|
|
req.setPrimaryLanguage(2L); |
|
|
|
|
|
req.setSampleRate(16000L); |
|
|
|
|
|
req.setEnableSubtitle(true); |
|
|
|
|
|
req.setCallbackUrl(TtscallbackUrl); |
|
|
|
|
|
|
|
|
|
|
|
CreateTtsTaskResponse resp = client.CreateTtsTask(req); |
|
|
|
|
|
String taskId = resp.getData() != null ? resp.getData().getTaskId() : null; |
|
|
|
|
|
if (taskId == null || taskId.isEmpty()) { |
|
|
|
|
|
throw new JeecgBootException("创建页面长文本TTS任务失败,未返回任务ID"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 写入缓存,标记生成中 |
|
|
|
|
|
AppletTtsCache cache = new AppletTtsCache(); |
|
|
|
|
|
cache.setTaskId(taskId); |
|
|
|
|
|
cache.setText(text); |
|
|
|
|
|
cache.setVoiceType(voiceType); |
|
|
|
|
|
cache.setPageId(pageId); |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.save(cache); |
|
|
|
|
|
|
|
|
|
|
|
log.info("页面长文本TTS任务创建成功,taskId: {}", taskId); |
|
|
|
|
|
return taskId; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("创建页面长文本TTS任务失败: {}", e.getMessage(), e); |
|
|
|
|
|
throw new JeecgBootException("创建页面长文本TTS任务失败: " + e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 根据页面ID提取页面文本内容 |
|
|
|
|
|
*/ |
|
|
|
|
|
@Override |
|
|
|
|
|
public String extractTextByPageId(String pageId) { |
|
|
|
|
|
// 旧实现(按标题、content纯文本及重点单词拼接)已注释: |
|
|
|
|
|
// AppletCoursePage page = appletCoursePageService.getById(pageId); |
|
|
|
|
|
// if (page == null) { |
|
|
|
|
|
// throw new JeecgBootException("未找到页面: " + pageId); |
|
|
|
|
|
// } |
|
|
|
|
|
// StringBuilder oldSb = new StringBuilder(); |
|
|
|
|
|
// if (page.getTitle() != null) { |
|
|
|
|
|
// oldSb.append(page.getTitle()).append("\n"); |
|
|
|
|
|
// } |
|
|
|
|
|
// if (page.getContent() != null) { |
|
|
|
|
|
// oldSb.append(stripHtml(page.getContent())).append("\n"); |
|
|
|
|
|
// } |
|
|
|
|
|
// List<AppletCoursePageWord> oldWords = appletCoursePageWordService |
|
|
|
|
|
// .lambdaQuery() |
|
|
|
|
|
// .eq(AppletCoursePageWord::getPageId, pageId) |
|
|
|
|
|
// .list(); |
|
|
|
|
|
// if (oldWords != null && !oldWords.isEmpty()) { |
|
|
|
|
|
// for (AppletCoursePageWord w : oldWords) { |
|
|
|
|
|
// if (w.getWord() != null && !w.getWord().isEmpty()) { |
|
|
|
|
|
// oldSb.append(w.getWord()).append(". "); |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
|
|
|
// return oldSb.toString().trim(); |
|
|
|
|
|
|
|
|
|
|
|
// 新实现:按页面编辑所定义的 JSON 结构解析页面内容,仅提取文本组件 |
|
|
|
|
|
AppletCoursePage page = appletCoursePageService.getById(pageId); |
|
|
|
|
|
if (page == null) { |
|
|
|
|
|
throw new JeecgBootException("未找到页面: " + pageId); |
|
|
|
|
|
} |
|
|
|
|
|
String content = page.getContent(); |
|
|
|
|
|
if (content == null || content.trim().isEmpty()) { |
|
|
|
|
|
return ""; |
|
|
|
|
|
} |
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
|
|
|
try { |
|
|
|
|
|
// content 期望是数组:[{ type: 'text'|'image'|'video', content?: string, language?: 'zh'|'en', ... }] |
|
|
|
|
|
JSONArray arr = JSON.parseArray(content); |
|
|
|
|
|
for (int i = 0; i < arr.size(); i++) { |
|
|
|
|
|
JSONObject obj = arr.getJSONObject(i); |
|
|
|
|
|
if (obj == null) continue; |
|
|
|
|
|
String type = obj.getString("type"); |
|
|
|
|
|
if ("text".equalsIgnoreCase(type)) { |
|
|
|
|
|
String text = obj.getString("content"); |
|
|
|
|
|
if (text != null && !text.trim().isEmpty()) { |
|
|
|
|
|
sb.append(text.trim()).append("\n"); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return sb.toString().trim(); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// 若不是数组,尝试作为单个对象或直接文本 |
|
|
|
|
|
try { |
|
|
|
|
|
JSONObject obj = JSON.parseObject(content); |
|
|
|
|
|
String type = obj.getString("type"); |
|
|
|
|
|
if ("text".equalsIgnoreCase(type)) { |
|
|
|
|
|
String text = obj.getString("content"); |
|
|
|
|
|
return text != null ? text : ""; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
// 作为纯文本返回 |
|
|
|
|
|
return content; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private String stripHtml(String html) { |
|
|
|
|
|
try { |
|
|
|
|
|
String noTag = html.replaceAll("<[^>]*>", " "); |
|
|
|
|
|
noTag = noTag.replace(" ", " "); |
|
|
|
|
|
return noTag.replaceAll("\\s+", " ").trim(); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
return html; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public boolean handleTtsCallback(String taskId, String audioBase64, Integer sampleRate, String codec, boolean success, String message) { |
|
|
|
|
|
try { |
|
|
|
|
|
AppletTtsCache cache = appletTtsCacheService |
|
|
|
|
|
.lambdaQuery() |
|
|
|
|
|
.eq(AppletTtsCache::getTaskId, taskId) |
|
|
|
|
|
.one(); |
|
|
|
|
|
if (cache == null) { |
|
|
|
|
|
cache = new AppletTtsCache(); |
|
|
|
|
|
cache.setTaskId(taskId); |
|
|
|
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (success) { |
|
|
|
|
|
byte[] audioData = null; |
|
|
|
|
|
if (audioBase64 != null && !audioBase64.isEmpty()) { |
|
|
|
|
|
audioData = Base64.getDecoder().decode(audioBase64); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String fileSuffix = (codec != null && !codec.isEmpty()) ? codec : "wav"; |
|
|
|
|
|
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + "." + fileSuffix; |
|
|
|
|
|
String uploadedUrl = null; |
|
|
|
|
|
if (audioData != null) { |
|
|
|
|
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData); |
|
|
|
|
|
uploadedUrl = OssBootUtil.upload(inputStream, "tts/" + fileName); |
|
|
|
|
|
Double realDuration = AudioDurationUtil.calculateDuration(audioData); |
|
|
|
|
|
if (realDuration != null) { |
|
|
|
|
|
cache.setDuration(realDuration); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
cache.setAudioId(uploadedUrl); |
|
|
|
|
|
cache.setSuccess("Y"); |
|
|
|
|
|
cache.setState(1); |
|
|
|
|
|
} else { |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
} |
|
|
|
|
|
cache.setUpdateTime(new java.util.Date()); |
|
|
|
|
|
if (cache.getId() == null) { |
|
|
|
|
|
appletTtsCacheService.save(cache); |
|
|
|
|
|
} else { |
|
|
|
|
|
appletTtsCacheService.updateById(cache); |
|
|
|
|
|
} |
|
|
|
|
|
log.info("TTS回调处理完成,taskId: {}, success: {}, message: {}", taskId, success, message); |
|
|
|
|
|
return true; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("处理TTS回调失败: {}", e.getMessage(), e); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private byte[] downloadBytes(String url) { |
|
|
|
|
|
try (InputStream in = new URL(url).openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
|
|
|
|
|
byte[] buf = new byte[8192]; |
|
|
|
|
|
int len; |
|
|
|
|
|
while ((len = in.read(buf)) != -1) { |
|
|
|
|
|
baos.write(buf, 0, len); |
|
|
|
|
|
} |
|
|
|
|
|
return baos.toByteArray(); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.warn("下载音频失败: {}", e.getMessage()); |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 保存TTS播放日志 |
|
|
* 保存TTS播放日志 |
|
|
|