Commit bdd7d0c1fdc0ad20ad48bcc11455808f2a3bc55b
1 parent
128c38ca
【代码优化】AI:音乐生成产物上传至内部文件服务
Showing
3 changed files
with
42 additions
and
26 deletions
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 | } |