|
|
@ -26,6 +26,7 @@ import org.jeecg.modules.demo.appletCoursePage.entity.AppletCoursePage; |
|
|
import org.jeecg.modules.demo.appletCoursePage.service.IAppletCoursePageService; |
|
|
import org.jeecg.modules.demo.appletCoursePage.service.IAppletCoursePageService; |
|
|
import org.jeecg.modules.demo.appletCoursePageWord.entity.AppletCoursePageWord; |
|
|
import org.jeecg.modules.demo.appletCoursePageWord.entity.AppletCoursePageWord; |
|
|
import org.jeecg.modules.demo.appletCoursePageWord.service.IAppletCoursePageWordService; |
|
|
import org.jeecg.modules.demo.appletCoursePageWord.service.IAppletCoursePageWordService; |
|
|
|
|
|
import org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog; |
|
|
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; |
|
|
@ -35,11 +36,11 @@ import org.jeecg.modules.applet.util.AudioDurationUtil; |
|
|
import java.io.ByteArrayInputStream; |
|
|
import java.io.ByteArrayInputStream; |
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
|
|
import org.springframework.data.redis.core.RedisTemplate; |
|
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
|
|
import java.util.UUID; |
|
|
|
|
|
import java.util.Base64; |
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
import java.net.URL; |
|
|
import java.net.URL; |
|
|
import java.io.InputStream; |
|
|
import java.io.InputStream; |
|
|
import java.io.ByteArrayOutputStream; |
|
|
import java.io.ByteArrayOutputStream; |
|
|
@ -65,6 +66,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
private IAppletCoursePageService appletCoursePageService; |
|
|
private IAppletCoursePageService appletCoursePageService; |
|
|
@Autowired |
|
|
@Autowired |
|
|
private IAppletCoursePageWordService appletCoursePageWordService; |
|
|
private IAppletCoursePageWordService appletCoursePageWordService; |
|
|
|
|
|
@Autowired |
|
|
|
|
|
private RedisTemplate<String, Object> redisTemplate; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
@ -78,23 +81,23 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
// 1. 先查询缓存数据库,看是否已经有相同参数的音频 |
|
|
// 1. 先查询缓存数据库,看是否已经有相同参数的音频 |
|
|
LambdaQueryWrapper<AppletTtsCache> queryWrapper = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<AppletTtsCache> queryWrapper = new LambdaQueryWrapper<>(); |
|
|
queryWrapper.eq(AppletTtsCache::getText, text) |
|
|
queryWrapper.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::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"); |
|
|
|
|
|
|
|
|
AppletTtsCache existingCache = appletTtsCacheService.getOne(queryWrapper); |
|
|
AppletTtsCache existingCache = appletTtsCacheService.getOne(queryWrapper); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (existingCache != null) { |
|
|
if (existingCache != null) { |
|
|
// 缓存命中,直接返回缓存的音频ID |
|
|
// 缓存命中,直接返回缓存的音频ID |
|
|
log.info("TTS缓存命中,直接返回缓存音频,audioId: {}", existingCache.getAudioId()); |
|
|
log.info("TTS缓存命中,直接返回缓存音频,audioId: {}", existingCache.getAudioId()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 记录播放日志 |
|
|
// 记录播放日志 |
|
|
long endTime = System.currentTimeMillis(); |
|
|
long endTime = System.currentTimeMillis(); |
|
|
double elapsedTime = (endTime - startTime) / 1000.0; |
|
|
double elapsedTime = (endTime - startTime) / 1000.0; |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, true, existingCache.getId()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, true, existingCache.getId()); |
|
|
|
|
|
|
|
|
return TtsVo.builder() |
|
|
return TtsVo.builder() |
|
|
.url(existingCache.getAudioId()) |
|
|
.url(existingCache.getAudioId()) |
|
|
.time(existingCache.getDuration()) |
|
|
.time(existingCache.getDuration()) |
|
|
@ -103,7 +106,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
|
|
|
|
|
|
// 2. 缓存未命中,调用腾讯云TTS接口生成音频 |
|
|
// 2. 缓存未命中,调用腾讯云TTS接口生成音频 |
|
|
log.info("TTS缓存未命中,调用腾讯云接口生成音频"); |
|
|
log.info("TTS缓存未命中,调用腾讯云接口生成音频"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建认证对象 |
|
|
// 创建认证对象 |
|
|
Credential cred = new Credential(secretId, secretKey); |
|
|
Credential cred = new Credential(secretId, secretKey); |
|
|
|
|
|
|
|
|
@ -160,7 +163,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
// 3. 将音频数据上传到OSS |
|
|
// 3. 将音频数据上传到OSS |
|
|
byte[] audioData = java.util.Base64.getDecoder().decode(audioBase64); |
|
|
byte[] audioData = java.util.Base64.getDecoder().decode(audioBase64); |
|
|
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav"; |
|
|
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用ByteArrayInputStream上传到OSS |
|
|
// 使用ByteArrayInputStream上传到OSS |
|
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData); |
|
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData); |
|
|
String audioUrl = OssBootUtil.upload(inputStream, "tts/" + fileName); |
|
|
String audioUrl = OssBootUtil.upload(inputStream, "tts/" + fileName); |
|
|
@ -200,7 +203,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
|
|
|
|
|
|
// 记录成功的TTS调用日志 |
|
|
// 记录成功的TTS调用日志 |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, true, cacheId); |
|
|
|
|
|
|
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, true, cacheId); |
|
|
|
|
|
|
|
|
log.info("TTS调用成功,文本长度: {}, 耗时: {}秒", text != null ? text.length() : 0, elapsedTime); |
|
|
log.info("TTS调用成功,文本长度: {}, 耗时: {}秒", text != null ? text.length() : 0, elapsedTime); |
|
|
return TtsVo.builder() |
|
|
return TtsVo.builder() |
|
|
@ -210,7 +213,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
} else { |
|
|
} else { |
|
|
// 记录失败的TTS调用日志 |
|
|
// 记录失败的TTS调用日志 |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, false, null); |
|
|
|
|
|
|
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, false, null); |
|
|
|
|
|
|
|
|
log.warn("TTS返回的音频数据为空"); |
|
|
log.warn("TTS返回的音频数据为空"); |
|
|
return null; |
|
|
return null; |
|
|
@ -223,22 +226,98 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
|
|
|
|
|
|
// 记录失败的TTS调用日志 |
|
|
// 记录失败的TTS调用日志 |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
savePlayLog(userId, text, voiceType, volume != null ? volume.doubleValue() : null, |
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, false, null); |
|
|
|
|
|
|
|
|
speed != null ? speed.doubleValue() : null, elapsedTime, false, null); |
|
|
|
|
|
|
|
|
log.error("调用腾讯云TTS接口失败: {}", e.getMessage(), e); |
|
|
log.error("调用腾讯云TTS接口失败: {}", e.getMessage(), e); |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public boolean handleLongTextTtsCallback(String taskId, String resultUrl, Long status, String statusStr, String subtitlesJson) { |
|
|
|
|
|
// Redis锁的key |
|
|
|
|
|
String lockKey = "tts_callback_lock:" + taskId; |
|
|
|
|
|
// 锁的过期时间(秒) |
|
|
|
|
|
int lockExpireTime = 300; // 5分钟 |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 尝试获取Redis分布式锁 |
|
|
|
|
|
Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", lockExpireTime, TimeUnit.SECONDS); |
|
|
|
|
|
if (!lockAcquired) { |
|
|
|
|
|
log.warn("TTS回调处理已在进行中,跳过重复处理,taskId: {}", taskId); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.info("获取TTS回调处理锁成功,开始处理,taskId: {}", taskId); |
|
|
|
|
|
|
|
|
|
|
|
// 检查数据库中是否已经处理完成 |
|
|
|
|
|
AppletTtsCache cache = appletTtsCacheService |
|
|
|
|
|
.lambdaQuery() |
|
|
|
|
|
.eq(AppletTtsCache::getTaskId, taskId) |
|
|
|
|
|
.one(); |
|
|
|
|
|
if (cache == null) { |
|
|
|
|
|
log.error("未找到对应的TTS缓存记录,taskId: {}", taskId); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果已经处理完成(成功状态且state=1),直接返回 |
|
|
|
|
|
if ("Y".equals(cache.getSuccess()) && cache.getState() != null && cache.getState() == 1) { |
|
|
|
|
|
log.info("TTS任务已处理完成,无需重复处理,taskId: {}, audioId: {}", taskId, cache.getAudioId()); |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存字幕信息 |
|
|
|
|
|
if (subtitlesJson != null && !subtitlesJson.isEmpty()) { |
|
|
|
|
|
cache.setSubtitlesJson(subtitlesJson); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 判断是否成功 (Status == 2 表示成功) |
|
|
|
|
|
if (status != null && status == 2) { |
|
|
|
|
|
// 使用统一的音频处理方法 |
|
|
|
|
|
AudioProcessResult audioResult = processAndUploadAudio(resultUrl, taskId); |
|
|
|
|
|
|
|
|
|
|
|
cache.setAudioId(audioResult.getAudioUrl()); |
|
|
|
|
|
if (audioResult.getDuration() != null) { |
|
|
|
|
|
cache.setDuration(audioResult.getDuration()); |
|
|
|
|
|
} |
|
|
|
|
|
cache.setSuccess("Y"); |
|
|
|
|
|
cache.setState(1); |
|
|
|
|
|
} else { |
|
|
|
|
|
cache.setSuccess("N"); |
|
|
|
|
|
cache.setState(0); |
|
|
|
|
|
log.warn("TTS任务失败,taskId: {}, status: {}, statusStr: {}", taskId, status, statusStr); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
cache.setUpdateTime(new java.util.Date()); |
|
|
|
|
|
appletTtsCacheService.updateById(cache); |
|
|
|
|
|
|
|
|
|
|
|
log.info("长文本TTS回调处理完成,taskId: {}, success: {}, status: {}", taskId, cache.getSuccess(), status); |
|
|
|
|
|
return true; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("处理长文本TTS回调失败: {}", e.getMessage(), e); |
|
|
|
|
|
return false; |
|
|
|
|
|
} finally { |
|
|
|
|
|
// 释放Redis锁 |
|
|
|
|
|
try { |
|
|
|
|
|
redisTemplate.delete(lockKey); |
|
|
|
|
|
log.info("释放TTS回调处理锁,taskId: {}", taskId); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("释放TTS回调处理锁失败,taskId: {}, error: {}", taskId, e.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public String createLongTextTtsTask(String text, Float speed, Integer voiceType, Float volume, String codec, String callbackUrl) { |
|
|
public String createLongTextTtsTask(String text, Float speed, Integer voiceType, Float volume, String codec, String callbackUrl) { |
|
|
try { |
|
|
try { |
|
|
// 先检查是否存在生成中的任务(相同文本与参数) |
|
|
|
|
|
|
|
|
// 将文章内容去除HTML并计算哈希,用于缓存唯一标识 |
|
|
|
|
|
String normalized = stripHtml(text); |
|
|
|
|
|
String textHash = String.valueOf(normalized.hashCode()); |
|
|
|
|
|
|
|
|
|
|
|
// 先检查是否存在生成中的任务(相同文本哈希与音色) |
|
|
LambdaQueryWrapper<AppletTtsCache> generatingWrapper = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<AppletTtsCache> generatingWrapper = new LambdaQueryWrapper<>(); |
|
|
generatingWrapper.eq(AppletTtsCache::getText, text) |
|
|
|
|
|
|
|
|
generatingWrapper.eq(AppletTtsCache::getText, textHash) |
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
.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); |
|
|
.eq(AppletTtsCache::getState, 0); |
|
|
|
|
|
|
|
|
AppletTtsCache generating = appletTtsCacheService.getOne(generatingWrapper); |
|
|
AppletTtsCache generating = appletTtsCacheService.getOne(generatingWrapper); |
|
|
@ -249,10 +328,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
|
|
|
|
|
|
// 再检查是否已有成功缓存 |
|
|
// 再检查是否已有成功缓存 |
|
|
LambdaQueryWrapper<AppletTtsCache> successWrapper = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<AppletTtsCache> successWrapper = new LambdaQueryWrapper<>(); |
|
|
successWrapper.eq(AppletTtsCache::getText, text) |
|
|
|
|
|
|
|
|
successWrapper.eq(AppletTtsCache::getText, textHash) |
|
|
.eq(AppletTtsCache::getVoiceType, voiceType) |
|
|
.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::getSuccess, "Y") |
|
|
.eq(AppletTtsCache::getState, 1); |
|
|
.eq(AppletTtsCache::getState, 1); |
|
|
AppletTtsCache successCache = appletTtsCacheService.getOne(successWrapper); |
|
|
AppletTtsCache successCache = appletTtsCacheService.getOne(successWrapper); |
|
|
@ -285,7 +362,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
// 启用时间戳字幕 |
|
|
// 启用时间戳字幕 |
|
|
try { |
|
|
try { |
|
|
req.getClass().getMethod("setEnableSubtitle", Boolean.class).invoke(req, Boolean.TRUE); |
|
|
req.getClass().getMethod("setEnableSubtitle", Boolean.class).invoke(req, Boolean.TRUE); |
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
|
|
|
} catch (Exception ignore) { |
|
|
|
|
|
} |
|
|
if (callbackUrl != null && !callbackUrl.isEmpty()) { |
|
|
if (callbackUrl != null && !callbackUrl.isEmpty()) { |
|
|
req.setCallbackUrl(callbackUrl); |
|
|
req.setCallbackUrl(callbackUrl); |
|
|
} |
|
|
} |
|
|
@ -296,10 +374,10 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
throw new JeecgBootException("创建长文本TTS任务失败,未返回任务ID"); |
|
|
throw new JeecgBootException("创建长文本TTS任务失败,未返回任务ID"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 写入缓存,标记生成中 |
|
|
|
|
|
|
|
|
// 写入缓存,标记生成中(text字段保存哈希) |
|
|
AppletTtsCache cache = new AppletTtsCache(); |
|
|
AppletTtsCache cache = new AppletTtsCache(); |
|
|
cache.setTaskId(taskId); |
|
|
cache.setTaskId(taskId); |
|
|
cache.setText(text); |
|
|
|
|
|
|
|
|
cache.setText(textHash); |
|
|
cache.setVoiceType(voiceType); |
|
|
cache.setVoiceType(voiceType); |
|
|
cache.setVolume(volume != null ? volume.doubleValue() : null); |
|
|
cache.setVolume(volume != null ? volume.doubleValue() : null); |
|
|
cache.setSpeed(speed != null ? speed.doubleValue() : null); |
|
|
cache.setSpeed(speed != null ? speed.doubleValue() : null); |
|
|
@ -308,7 +386,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
cache.setCreateTime(new java.util.Date()); |
|
|
appletTtsCacheService.save(cache); |
|
|
appletTtsCacheService.save(cache); |
|
|
|
|
|
|
|
|
log.info("长文本TTS任务创建成功,taskId: {}", taskId); |
|
|
|
|
|
|
|
|
log.info("长文本TTS任务创建成功,taskId: {},textHash: {}", taskId, textHash); |
|
|
return taskId; |
|
|
return taskId; |
|
|
} catch (Exception e) { |
|
|
} catch (Exception e) { |
|
|
log.error("创建长文本TTS任务失败: {}", e.getMessage(), e); |
|
|
log.error("创建长文本TTS任务失败: {}", e.getMessage(), e); |
|
|
@ -352,32 +430,21 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
appletTtsCacheService.updateById(cache); |
|
|
appletTtsCacheService.updateById(cache); |
|
|
return cache; |
|
|
return cache; |
|
|
} else if (status == 2) { |
|
|
} 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); |
|
|
|
|
|
|
|
|
// 成功,使用统一的音频处理方法 |
|
|
|
|
|
AudioProcessResult audioResult = processAndUploadAudio(audioUrl, taskId); |
|
|
|
|
|
|
|
|
Double realDuration = AudioDurationUtil.calculateDuration(audioData); |
|
|
|
|
|
if (realDuration != null) { |
|
|
|
|
|
cache.setDuration(realDuration); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
cache.setAudioId(audioUrl); |
|
|
|
|
|
|
|
|
cache.setAudioId(audioResult.getAudioUrl()); |
|
|
|
|
|
if (audioResult.getDuration() != null) { |
|
|
|
|
|
cache.setDuration(audioResult.getDuration()); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 解析时间戳 |
|
|
// 解析时间戳 |
|
|
String timestampsJson = null; |
|
|
String timestampsJson = null; |
|
|
try { |
|
|
try { |
|
|
timestampsJson = JSON.toJSONString(resp.getData().getSubtitles()); |
|
|
timestampsJson = JSON.toJSONString(resp.getData().getSubtitles()); |
|
|
cache.setTimestamps(timestampsJson); |
|
|
|
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
|
|
|
cache.setSubtitlesJson(timestampsJson); |
|
|
|
|
|
} catch (Exception ignore) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
cache.setSuccess("Y"); |
|
|
cache.setSuccess("Y"); |
|
|
cache.setState(1); |
|
|
cache.setState(1); |
|
|
@ -538,7 +605,8 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
String text = obj.getString("content"); |
|
|
String text = obj.getString("content"); |
|
|
return text != null ? text : ""; |
|
|
return text != null ? text : ""; |
|
|
} |
|
|
} |
|
|
} catch (Exception ignore) {} |
|
|
|
|
|
|
|
|
} catch (Exception ignore) { |
|
|
|
|
|
} |
|
|
// 作为纯文本返回 |
|
|
// 作为纯文本返回 |
|
|
return content; |
|
|
return content; |
|
|
} |
|
|
} |
|
|
@ -607,6 +675,31 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public Map<Integer, Object> selectMapByHtml(String content) { |
|
|
|
|
|
HashMap<Integer, Object> map = new HashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
int i = stripHtml(content).hashCode(); |
|
|
|
|
|
String textHash = String.valueOf(i); |
|
|
|
|
|
|
|
|
|
|
|
List<AppletTtsTimbre> list = appletTtsTimbreService |
|
|
|
|
|
.lambdaQuery() |
|
|
|
|
|
.select(AppletTtsTimbre::getVoiceType) |
|
|
|
|
|
.list(); |
|
|
|
|
|
|
|
|
|
|
|
for (AppletTtsTimbre timbre : list) { |
|
|
|
|
|
AppletTtsCache one = appletTtsCacheService.lambdaQuery() |
|
|
|
|
|
.eq(AppletTtsCache::getText, textHash) |
|
|
|
|
|
.eq(AppletTtsCache::getVoiceType, timbre.getVoiceType()) |
|
|
|
|
|
.select(AppletTtsCache::getAudioId) |
|
|
|
|
|
.one(); |
|
|
|
|
|
|
|
|
|
|
|
map.put(timbre.getVoiceType(), one != null ? one.getAudioId() : null); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return map; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private byte[] downloadBytes(String url) { |
|
|
private byte[] downloadBytes(String url) { |
|
|
try (InputStream in = new URL(url).openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
|
|
try (InputStream in = new URL(url).openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { |
|
|
byte[] buf = new byte[8192]; |
|
|
byte[] buf = new byte[8192]; |
|
|
@ -620,16 +713,14 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
return null; |
|
|
return null; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 保存TTS播放日志 |
|
|
* 保存TTS播放日志 |
|
|
*/ |
|
|
*/ |
|
|
private void savePlayLog(String userId, String text, Integer voiceType, Double volume, |
|
|
|
|
|
Double speed, Double elapsedTime, boolean success, String cacheId) { |
|
|
|
|
|
|
|
|
private void savePlayLog(String userId, String text, Integer voiceType, Double volume, |
|
|
|
|
|
Double speed, Double elapsedTime, boolean success, String cacheId) { |
|
|
try { |
|
|
try { |
|
|
org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog playLog = |
|
|
|
|
|
new org.jeecg.modules.demo.appletTtsPlayLog.entity.AppletTtsPlayLog(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AppletTtsPlayLog playLog = new AppletTtsPlayLog(); |
|
|
playLog.setUserId(userId); |
|
|
playLog.setUserId(userId); |
|
|
playLog.setVoicetype(voiceType); |
|
|
playLog.setVoicetype(voiceType); |
|
|
playLog.setVolume(volume); |
|
|
playLog.setVolume(volume); |
|
|
@ -640,7 +731,7 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
playLog.setCreateTime(new java.util.Date()); |
|
|
playLog.setCreateTime(new java.util.Date()); |
|
|
|
|
|
|
|
|
appletTtsPlayLogService.save(playLog); |
|
|
appletTtsPlayLogService.save(playLog); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("TTS播放日志保存成功,用户: {}, 结果: {}, 缓存ID: {}", userId, success ? "成功" : "失败", cacheId); |
|
|
log.debug("TTS播放日志保存成功,用户: {}, 结果: {}, 缓存ID: {}", userId, success ? "成功" : "失败", cacheId); |
|
|
} catch (Exception e) { |
|
|
} catch (Exception e) { |
|
|
log.error("保存TTS播放日志失败: {}", e.getMessage(), e); |
|
|
log.error("保存TTS播放日志失败: {}", e.getMessage(), e); |
|
|
@ -653,4 +744,88 @@ public class AppletApiTTServiceImpl implements AppletApiTTService { |
|
|
return appletTtsTimbreService.list(); |
|
|
return appletTtsTimbreService.list(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
public void generateLongTextForArticleContentAllTimbres(String content) { |
|
|
|
|
|
try { |
|
|
|
|
|
List<AppletTtsTimbre> timbres = appletTtsTimbreService |
|
|
|
|
|
.lambdaQuery() |
|
|
|
|
|
.eq(AppletTtsTimbre::getStatus, "Y") |
|
|
|
|
|
.select(AppletTtsTimbre::getVoiceType) |
|
|
|
|
|
.list(); |
|
|
|
|
|
String callbackUrl = TtscallbackUrl; |
|
|
|
|
|
for (AppletTtsTimbre timbre : timbres) { |
|
|
|
|
|
try { |
|
|
|
|
|
createLongTextTtsTask(content, null, timbre.getVoiceType(), null, "wav", callbackUrl); |
|
|
|
|
|
} catch (Exception ex) { |
|
|
|
|
|
log.warn("创建文章内容长文本TTS任务失败 voiceType {}: {}", timbre.getVoiceType(), ex.getMessage()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("遍历音色创建文章长文本TTS任务失败: {}", e.getMessage(), e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 统一的音频处理方法:下载音频、上传到OSS、计算时长 |
|
|
|
|
|
* |
|
|
|
|
|
* @param audioUrl 音频URL |
|
|
|
|
|
* @param taskId 任务ID(用于日志) |
|
|
|
|
|
* @return AudioProcessResult 包含上传后的URL和音频时长 |
|
|
|
|
|
*/ |
|
|
|
|
|
private AudioProcessResult processAndUploadAudio(String audioUrl, String taskId) { |
|
|
|
|
|
if (audioUrl == null || audioUrl.isEmpty()) { |
|
|
|
|
|
log.warn("音频URL为空,taskId: {}", taskId); |
|
|
|
|
|
return new AudioProcessResult(null, null); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 下载音频数据 |
|
|
|
|
|
byte[] audioData = downloadBytes(audioUrl); |
|
|
|
|
|
if (audioData == null || audioData.length == 0) { |
|
|
|
|
|
log.error("音频下载失败或数据为空,taskId: {}, audioUrl: {}", taskId, audioUrl); |
|
|
|
|
|
return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 生成文件名并上传到OSS |
|
|
|
|
|
String fileName = IdUtils.generateNo("TTS_") + System.currentTimeMillis() + ".wav"; |
|
|
|
|
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioData); |
|
|
|
|
|
String uploadedUrl = OssBootUtil.upload(inputStream, "tts/" + fileName); |
|
|
|
|
|
|
|
|
|
|
|
if (uploadedUrl == null) { |
|
|
|
|
|
log.error("音频上传到OSS失败,taskId: {}", taskId); |
|
|
|
|
|
return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算音频时长 |
|
|
|
|
|
// Double audioDuration = AudioDurationUtil.calculateDuration(audioData); |
|
|
|
|
|
|
|
|
|
|
|
log.info("音频处理成功,taskId: {}, uploadedUrl: {}, duration: {}", taskId, uploadedUrl, null); |
|
|
|
|
|
return new AudioProcessResult(uploadedUrl, null); |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("音频处理失败,taskId: {}, audioUrl: {}, error: {}", taskId, audioUrl, e.getMessage()); |
|
|
|
|
|
return new AudioProcessResult(audioUrl, null); // 返回原始URL作为兜底 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 音频处理结果封装类 |
|
|
|
|
|
*/ |
|
|
|
|
|
private static class AudioProcessResult { |
|
|
|
|
|
private final String audioUrl; |
|
|
|
|
|
private final Double duration; |
|
|
|
|
|
|
|
|
|
|
|
public AudioProcessResult(String audioUrl, Double duration) { |
|
|
|
|
|
this.audioUrl = audioUrl; |
|
|
|
|
|
this.duration = duration; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public String getAudioUrl() { |
|
|
|
|
|
return audioUrl; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Double getDuration() { |
|
|
|
|
|
return duration; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |