diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java index 39ceb1e17..f1298cf56 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/music/AiMusicStatusEnum.java @@ -16,7 +16,8 @@ import java.util.Arrays; public enum AiMusicStatusEnum implements IntArrayValuable { IN_PROGRESS(10, "进行中"), - SUCCESS(20, "已完成"); + SUCCESS(20, "已完成"), + FAIL(30, "已失败"); /** * 状态 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java index d73a542dc..ce88c6ad9 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java @@ -38,10 +38,6 @@ public class AiMusicController { @PostMapping("/generate") @Operation(summary = "音乐生成") public CommonResult> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { - if (true) { - musicService.syncMusic(); - return null; - } return success(musicService.generateMusic(getLoginUserId(), reqVO)); } @@ -64,12 +60,11 @@ public class AiMusicController { return success(BeanUtils.toBean(music, AiMusicRespVO.class)); } - // TODO @xin:这个搞成 updateMy ,修改【我的】音乐。方便后续支持其它字段;另外,需要校验下,更新的音乐,是不是我的! - @PostMapping("/updateTitle-my") + @PostMapping("/update-my") @Operation(summary = "修改【我的】音乐 目前只支持修改标题") @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") - public CommonResult updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { - musicService.updateMusicTitle(updateReqVO); + public CommonResult updateMy(AiMusicUpdateReqVO updateReqVO) { + musicService.updateMyMusic(updateReqVO, getLoginUserId()); return success(true); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java index 447bc9765..5b7f838f2 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateReqVO.java @@ -15,4 +15,7 @@ public class AiMusicUpdateReqVO { @Schema(description = "是否发布", example = "true") private Boolean publicStatus; + @Schema(description = "音乐名称", example = "夜空中最亮的星") + private String title; + } \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateTitleReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateTitleReqVO.java deleted file mode 100644 index a5d272bca..000000000 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateTitleReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.ai.controller.admin.music.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - AI 音乐修改名称 Request VO") -@Data -public class AiMusicUpdateTitleReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") - private Long id; - - @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") - @NotNull(message = "音乐名称不能为空") - private String title; - -} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java index a0e7a0738..0222abbbc 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java @@ -24,10 +24,10 @@ public class AiSunoGenerateReqVO { @NotNull(message = "生成模式不能为空") private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 - // TODO @xin:方案一:prompt => lyric 歌词;gptDescriptionPrompt => description 描述(db 那字段也改下,避免和 gpt 直接耦合);这样搞完后,会更统一好理解一点 - // TODO @xin:方案二:还是之前的做法,都用 prompt;不过最终 gptDescriptionPrompt 还是存储 description 算描述。可以微信一起讨论下。 @Schema(description = "用于生成音乐音频的歌词提示", example = """ + 1.描述模式:创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。 + 2.歌词模式: [Verse] 阳光下奔跑 多么欢快 假期就要来 心都飞起来 @@ -39,11 +39,7 @@ public class AiSunoGenerateReqVO { 日子太短暂 别再等待 马上放假了 梦想起飞 """) - private String prompt; // 歌词模式用 - - @Schema(description = "用于生成音乐音频的描述", - example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") - private String gptDescriptionPrompt; // 描述模式用 + private String prompt; @Schema(description = "是否纯音乐", example = "true") private Boolean makeInstrumental; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java index 76a810f82..8a6cbe828 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java @@ -75,11 +75,7 @@ public class AiMusicDO extends BaseDO { /** * 描述词 */ - private String gptDescriptionPrompt; - /** - * 提示词 - */ - private String prompt; + private String description; /** * 平台 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java index 5a03e525d..7d1541ccb 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java @@ -38,11 +38,11 @@ public interface AiMusicService { void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); /** - * 更新音乐名称 + * 更新我的音乐 * * @param updateReqVO 更新信息 */ - void updateMusicTitle(@Valid AiMusicUpdateTitleReqVO updateReqVO); + void updateMyMusic(@Valid AiMusicUpdateReqVO updateReqVO, Long userId); /** * 删除AI 音乐 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java index 7aa04d572..a17fc19b5 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java @@ -3,11 +3,14 @@ package cn.iocoder.yudao.module.ai.service.music; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdateReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; @@ -48,18 +51,18 @@ public class AiMusicServiceImpl implements AiMusicService { public List generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { // 1. 调用 Suno 生成音乐 SunoApi sunoApi = apiKeyService.getSunoApi(); - // TODO @xin:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 + // TODO 芋艿:这两个貌似一直没跑成功,你那可以么?用的请求是 AiMusicController.http 的 --xin:大部分ok的,补充了error_message List musicDataList; - if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { - // 1.1 歌词模式 + if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { + // 1.1 描述模式 + SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( + reqVO.getPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental()); + musicDataList = sunoApi.generate(generateRequest); + } else if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { + // 1.2 歌词模式 SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); musicDataList = sunoApi.customGenerate(generateRequest); - } else if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) { - // 1.2 描述模式 - SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest( - reqVO.getGptDescriptionPrompt(), reqVO.getModel(), reqVO.getMakeInstrumental()); - musicDataList = sunoApi.generate(generateRequest); } else { throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); } @@ -108,9 +111,12 @@ public class AiMusicServiceImpl implements AiMusicService { } @Override - public void updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { - // 校验存在 - validateMusicExists(updateReqVO.getId()); + public void updateMyMusic(AiMusicUpdateReqVO updateReqVO, Long userId) { + // 校验音乐是否存在 + AiMusicDO musicDO = validateMusicExists(updateReqVO.getId()); + if (ObjUtil.notEqual(musicDO.getUserId(), userId)) { + throw exception(MUSIC_NOT_EXISTS); + } // 更新 musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle())); } @@ -156,29 +162,38 @@ public class AiMusicServiceImpl implements AiMusicService { * @return AiMusicDO 集合 */ private List buildMusicDOList(List musicList) { - // TODO @xin:它有 status = error 状态,表示失败噢。 - return convertList(musicList, musicData -> new AiMusicDO() - .setTaskId(musicData.id()).setModel(musicData.modelName()) - .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) - // TODO @xin:只有在完成的状态,在下载文件 - .setAudioUrl(downloadFile(musicData.audioUrl())) - .setVideoUrl(downloadFile(musicData.videoUrl())) - .setImageUrl(downloadFile(musicData.imageUrl())) - .setTitle(musicData.title()).setDuration(musicData.duration()) - .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) - .setStatus(Objects.equals("complete", musicData.status()) ? - AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); + return convertList(musicList, musicData -> { + Integer status; + if (Objects.equals("complete", musicData.status())) { + status = AiMusicStatusEnum.SUCCESS.getStatus(); + } else if (Objects.equals("error", musicData.status())) { + status = AiMusicStatusEnum.FAIL.getStatus(); + } else { + status = AiMusicStatusEnum.IN_PROGRESS.getStatus(); + } + return new AiMusicDO() + .setTaskId(musicData.id()).setModel(musicData.modelName()) + .setDescription(musicData.gptDescriptionPrompt()) + .setAudioUrl(downloadFile(status, musicData.audioUrl())) + .setVideoUrl(downloadFile(status, musicData.videoUrl())) + .setImageUrl(downloadFile(status, musicData.imageUrl())) + .setTitle(musicData.title()).setDuration(musicData.duration()) + .setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) + .setErrorMessage(musicData.errorMessage()) + .setStatus(status); + }); } /** - * 将生成的音频文件上传到文件服务器 + * 音乐生成好后,将音频文件上传到文件服务器 * - * @param url 音频文件地址 + * @param status 音乐状态 + * @param url 音频文件地址 * @return 内部文件地址 */ - private String downloadFile(String url) { - if (StrUtil.isBlank(url)) { - return null; + private String downloadFile(Integer status, String url) { + if (StrUtil.isBlank(url) || ObjectUtil.notEqual(status, AiMusicStatusEnum.SUCCESS.getStatus())) { + return url; } try { byte[] bytes = HttpUtil.downloadBytes(url); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java index 27d2fa498..de33635f7 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java @@ -175,6 +175,7 @@ public class SunoApi { @JsonProperty("model_name") String modelName, String status, @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, + @JsonProperty("error_message") String errorMessage, String prompt, String type, String tags, diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java index 4fd81cf89..e5bf6be38 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java @@ -17,8 +17,8 @@ public class SunoTests { @Before public void setup() { -// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; - String url = "http://127.0.0.1:3001"; + String url = "https://suno-55ishh05u-status2xxs-projects.vercel.app"; +// String url = "http://127.0.0.1:3001"; this.sunoApi = new SunoApi(url); }