Commit bdd7d0c1fdc0ad20ad48bcc11455808f2a3bc55b

Authored by xiaoxin
1 parent 128c38ca

【代码优化】AI:音乐生成产物上传至内部文件服务

yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicGenerateModeEnum.java
@@ -15,8 +15,8 @@ import java.util.Arrays; @@ -15,8 +15,8 @@ import java.util.Arrays;
15 @Getter 15 @Getter
16 public enum AiMusicGenerateModeEnum implements IntArrayValuable { 16 public enum AiMusicGenerateModeEnum implements IntArrayValuable {
17 17
18 - LYRIC(1, "歌词模式"),  
19 - DESCRIPTION(2, "描述模式"); 18 + DESCRIPTION(1, "描述模式"),
  19 + LYRIC(2, "歌词模式");
20 20
21 /** 21 /**
22 * 模式 22 * 模式
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java
@@ -20,21 +20,20 @@ public class AiSunoGenerateReqVO { @@ -20,21 +20,20 @@ public class AiSunoGenerateReqVO {
20 * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈 20 * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈
21 * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题; 21 * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题;
22 */ 22 */
23 - @Schema(description = "生成模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 23 + @Schema(description = "生成模式 1.描述模式 2. 歌词模式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
24 @NotNull(message = "生成模式不能为空") 24 @NotNull(message = "生成模式不能为空")
25 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 25 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举
26 26
27 - @Schema(description = "用于生成音乐音频的提示", requiredMode = Schema.RequiredMode.REQUIRED, 27 + @Schema(description = "用于生成音乐音频的提示 1.描述模式:音乐/歌词说明 2.歌词模式:歌词", requiredMode = Schema.RequiredMode.REQUIRED,
28 example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") 28 example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
29 private String prompt; 29 private String prompt;
30 30
31 @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") 31 @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true")
32 private Boolean makeInstrumental; 32 private Boolean makeInstrumental;
33 33
34 - // TODO @xin:看了下这个字段,发现最终还是 model 合适点;因为它其实是模型  
35 - @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") 34 + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5")
36 @NotEmpty(message = "模型不能为空") 35 @NotEmpty(message = "模型不能为空")
37 - private String modelVersion; // 参见 AiModelEnum 枚举 36 + private String model; // 参见 AiModelEnum 枚举
38 37
39 @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") 38 @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]")
40 private List<String> tags; 39 private List<String> tags;
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.service.music; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.ai.service.music;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.text.StrPool; 4 import cn.hutool.core.text.StrPool;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.http.HttpUtil;
6 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 7 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; 9 import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO;
@@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; @@ -12,6 +13,7 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO;
12 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; 13 import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
13 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; 14 import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
14 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; 15 import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum;
  16 +import cn.iocoder.yudao.module.infra.api.file.FileApi;
15 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
16 import lombok.extern.slf4j.Slf4j; 18 import lombok.extern.slf4j.Slf4j;
17 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
@@ -38,6 +40,9 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -38,6 +40,9 @@ public class AiMusicServiceImpl implements AiMusicService {
38 @Resource 40 @Resource
39 private AiMusicMapper musicMapper; 41 private AiMusicMapper musicMapper;
40 42
  43 + @Resource
  44 + private FileApi fileApi;
  45 +
41 @Override 46 @Override
42 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { 47 public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
43 // 1. 调用 Suno 生成音乐 48 // 1. 调用 Suno 生成音乐
@@ -45,12 +50,12 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -45,12 +50,12 @@ public class AiMusicServiceImpl implements AiMusicService {
45 if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { 50 if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) {
46 // 1.1 歌词模式 51 // 1.1 歌词模式
47 SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( 52 SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
48 - reqVO.getPrompt(), reqVO.getModelVersion(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); 53 + reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle());
49 musicDataList = sunoApi.customGenerate(generateRequest); 54 musicDataList = sunoApi.customGenerate(generateRequest);
50 } else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { 55 } else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
51 // 1.2 描述模式 56 // 1.2 描述模式
52 SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( 57 SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
53 - reqVO.getPrompt(), reqVO.getModelVersion(), reqVO.getMakeInstrumental()); 58 + reqVO.getPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental());
54 musicDataList = sunoApi.generate(generateRequest); 59 musicDataList = sunoApi.generate(generateRequest);
55 } else { 60 } else {
56 throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); 61 throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO));
@@ -90,23 +95,6 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -90,23 +95,6 @@ public class AiMusicServiceImpl implements AiMusicService {
90 return streamingTask.size(); 95 return streamingTask.size();
91 } 96 }
92 97
93 - /**  
94 - * 构建 AiMusicDO 集合  
95 - *  
96 - * @param musicList suno 音乐任务列表  
97 - * @return AiMusicDO 集合  
98 - */  
99 - private static List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {  
100 - // TODO @xin:成功的情况下,需要下载到自己的文件服务器。参考图片的处理  
101 - return convertList(musicList, musicData -> new AiMusicDO()  
102 - .setTaskId(musicData.id()).setModel(musicData.modelName())  
103 - .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt())  
104 - .setAudioUrl(musicData.audioUrl()).setVideoUrl(musicData.videoUrl()).setImageUrl(musicData.imageUrl())  
105 - .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))  
106 - .setStatus(Objects.equals("complete", musicData.status()) ?  
107 - AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));  
108 - }  
109 -  
110 @Override 98 @Override
111 public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) { 99 public void updateMusicPublicStatus(AiMusicUpdatePublicStatusReqVO updateReqVO) {
112 // 校验存在 100 // 校验存在
@@ -134,4 +122,33 @@ public class AiMusicServiceImpl implements AiMusicService { @@ -134,4 +122,33 @@ public class AiMusicServiceImpl implements AiMusicService {
134 return musicMapper.selectPage(pageReqVO); 122 return musicMapper.selectPage(pageReqVO);
135 } 123 }
136 124
  125 + /**
  126 + * 构建 AiMusicDO 集合
  127 + *
  128 + * @param musicList suno 音乐任务列表
  129 + * @return AiMusicDO 集合
  130 + */
  131 + private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
  132 + return convertList(musicList, musicData -> new AiMusicDO()
  133 + .setTaskId(musicData.id()).setModel(musicData.modelName())
  134 + .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt())
  135 + .setAudioUrl(createFile(musicData.audioUrl())).setVideoUrl(createFile(musicData.videoUrl())).setImageUrl(createFile(musicData.imageUrl()))
  136 + .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA))
  137 + .setStatus(Objects.equals("complete", musicData.status()) ?
  138 + AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus()));
  139 + }
  140 +
  141 + /**
  142 + * 将生成的音频文件上传到文件服务器
  143 + *
  144 + * @param url 音频文件地址
  145 + * @return 内部文件地址
  146 + */
  147 + private String createFile(String url) {
  148 + if (StrUtil.isBlank(url)) {
  149 + return null;
  150 + }
  151 + byte[] bytes = HttpUtil.downloadBytes(url);
  152 + return fileApi.createFile(bytes);
  153 + }
137 } 154 }