Commit 2e9915b77baef6b151fe5edbf0dbfdb19ce77b57
Committed by
Gitee
!7 【增加】AI:我的音乐
Merge pull request !7 from 小新/master-jdk21-ai
Showing
11 changed files
with
219 additions
and
59 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/AiMusicController.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.util.ObjUtil; | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 6 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 7 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicRespVO; | ||
| 8 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; | ||
| 9 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 7 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; |
| 10 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 11 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; | 9 | import cn.iocoder.yudao.module.ai.service.music.AiMusicService; |
| 12 | import io.swagger.v3.oas.annotations.Operation; | 10 | import io.swagger.v3.oas.annotations.Operation; |
| @@ -30,13 +28,39 @@ public class AiMusicController { | @@ -30,13 +28,39 @@ public class AiMusicController { | ||
| 30 | @Resource | 28 | @Resource |
| 31 | private AiMusicService musicService; | 29 | private AiMusicService musicService; |
| 32 | 30 | ||
| 33 | - // TODO @xin:一个接口,获得【我的】音乐分页,参考 获得【我的】聊天角色分页 来写;用于我自己生成的列表,和音乐广场 | 31 | + @GetMapping("/my-page") |
| 32 | + @Operation(summary = "获得【我的】音乐分页") | ||
| 33 | + public CommonResult<PageResult<AiMusicRespVO>> getMusicMyPage(@Valid AiMusicPageReqVO pageReqVO) { | ||
| 34 | + PageResult<AiMusicDO> pageResult = musicService.getMusicMyPage(pageReqVO, getLoginUserId()); | ||
| 35 | + return success(BeanUtils.toBean(pageResult, AiMusicRespVO.class)); | ||
| 36 | + } | ||
| 34 | 37 | ||
| 35 | - // TODO @xin:一个接口,删除【我的】音乐 | 38 | + @Operation(summary = "删除【我的】音乐记录") |
| 39 | + @DeleteMapping("/delete-my") | ||
| 40 | + @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") | ||
| 41 | + public CommonResult<Boolean> deleteMusicMy(@RequestParam("id") Long id) { | ||
| 42 | + musicService.deleteMusicMy(id, getLoginUserId()); | ||
| 43 | + return success(true); | ||
| 44 | + } | ||
| 36 | 45 | ||
| 37 | - // TODO @xin:一个接口,获得【我的】音乐 | 46 | + @GetMapping("/get-my") |
| 47 | + @Operation(summary = "获取【我的】音乐") | ||
| 48 | + @Parameter(name = "id", required = true, description = "音乐编号", example = "1024") | ||
| 49 | + public CommonResult<AiMusicRespVO> getMusicMy(@RequestParam("id") Long id) { | ||
| 50 | + AiMusicDO music = musicService.getMusic(id); | ||
| 51 | + if (music == null || ObjUtil.notEqual(getLoginUserId(), music.getUserId())) { | ||
| 52 | + return success(null); | ||
| 53 | + } | ||
| 54 | + return success(BeanUtils.toBean(music, AiMusicRespVO.class)); | ||
| 55 | + } | ||
| 38 | 56 | ||
| 39 | - // TODO @xin:一个接口,修改【我的】音乐,目前只支持修改标题 | 57 | + @PostMapping("/updateTitle-my") |
| 58 | + @Operation(summary = "修改【我的】音乐 目前只支持修改标题") | ||
| 59 | + @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") | ||
| 60 | + public CommonResult<Boolean> updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { | ||
| 61 | + musicService.updateMusicTitle(updateReqVO); | ||
| 62 | + return success(true); | ||
| 63 | + } | ||
| 40 | 64 | ||
| 41 | @PostMapping("/generate") | 65 | @PostMapping("/generate") |
| 42 | @Operation(summary = "音乐生成") | 66 | @Operation(summary = "音乐生成") |
| @@ -44,7 +68,7 @@ public class AiMusicController { | @@ -44,7 +68,7 @@ public class AiMusicController { | ||
| 44 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); | 68 | return success(musicService.generateMusic(getLoginUserId(), reqVO)); |
| 45 | } | 69 | } |
| 46 | 70 | ||
| 47 | - // ================ 绘图管理 ================ | 71 | + // ================ 音乐管理 ================ |
| 48 | 72 | ||
| 49 | @GetMapping("/page") | 73 | @GetMapping("/page") |
| 50 | @Operation(summary = "获得音乐分页") | 74 | @Operation(summary = "获得音乐分页") |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicRespVO.java
| @@ -61,6 +61,9 @@ public class AiMusicRespVO { | @@ -61,6 +61,9 @@ public class AiMusicRespVO { | ||
| 61 | @Schema(description = "错误信息") | 61 | @Schema(description = "错误信息") |
| 62 | private String errorMessage; | 62 | private String errorMessage; |
| 63 | 63 | ||
| 64 | + @Schema(description = "音乐时长") | ||
| 65 | + private Double duration; | ||
| 66 | + | ||
| 64 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | 67 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 65 | private LocalDateTime createTime; | 68 | private LocalDateTime createTime; |
| 66 | 69 |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiMusicUpdateTitleReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +@Schema(description = "管理后台 - AI 音乐修改名称 Request VO") | ||
| 8 | +@Data | ||
| 9 | +public class AiMusicUpdateTitleReqVO { | ||
| 10 | + | ||
| 11 | + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") | ||
| 12 | + private Long id; | ||
| 13 | + | ||
| 14 | + @Schema(description = "音乐名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "夜空中最亮的星") | ||
| 15 | + @NotNull(message = "音乐名称不能为空") | ||
| 16 | + private String title; | ||
| 17 | + | ||
| 18 | +} | ||
| 0 | \ No newline at end of file | 19 | \ No newline at end of file |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/AiSunoGenerateReqVO.java
| @@ -17,24 +17,38 @@ public class AiSunoGenerateReqVO { | @@ -17,24 +17,38 @@ public class AiSunoGenerateReqVO { | ||
| 17 | private String platform; // 参见 AiPlatformEnum 枚举 | 17 | private String platform; // 参见 AiPlatformEnum 枚举 |
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | - * 1. 描述模式:描述词 + 是否纯音乐 + 模型 TODO @xin:目前貌似描述词没弄对?看着不是 prompt 字段(也可能我弄错了)。可以微信再沟通下哈 | ||
| 21 | - * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 TODO @xin:目前这块少传递了标题; | 20 | + * 1. 描述模式:描述词 + 是否纯音乐 + 模型 |
| 21 | + * 2. 歌词模式:歌词 + 音乐风格 + 标题 + 模型 | ||
| 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, | ||
| 28 | - example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | 27 | + @Schema(description = "歌词模式用:用于生成音乐音频的歌词提示", requiredMode = Schema.RequiredMode.NOT_REQUIRED, |
| 28 | + example = """ | ||
| 29 | + [Verse] | ||
| 30 | + 阳光下奔跑 多么欢快 | ||
| 31 | + 假期就要来 心都飞起来 | ||
| 32 | + 朋友在一旁 笑声又灿烂 | ||
| 33 | + 无忧无虑的 每一天甜蜜 | ||
| 34 | + [Chorus] | ||
| 35 | + 马上放假了 快来庆祝 | ||
| 36 | + 一起去旅行 快去冒险 | ||
| 37 | + 日子太短暂 别再等待 | ||
| 38 | + 马上放假了 梦想起飞 | ||
| 39 | + """) | ||
| 29 | private String prompt; | 40 | private String prompt; |
| 30 | 41 | ||
| 42 | + @Schema(description = "描述模式用:用于生成音乐音频的描述", requiredMode = Schema.RequiredMode.NOT_REQUIRED, | ||
| 43 | + example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。") | ||
| 44 | + private String gptDescriptionPrompt; | ||
| 45 | + | ||
| 31 | @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") | 46 | @Schema(description = "是否纯音乐", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "true") |
| 32 | private Boolean makeInstrumental; | 47 | private Boolean makeInstrumental; |
| 33 | 48 | ||
| 34 | - // TODO @xin:看了下这个字段,发现最终还是 model 合适点;因为它其实是模型 | ||
| 35 | - @Schema(description = "模型版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") | 49 | + @Schema(description = "模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "chirp-v3.5") |
| 36 | @NotEmpty(message = "模型不能为空") | 50 | @NotEmpty(message = "模型不能为空") |
| 37 | - private String modelVersion; // 参见 AiModelEnum 枚举 | 51 | + private String model; // 参见 AiModelEnum 枚举 |
| 38 | 52 | ||
| 39 | @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") | 53 | @Schema(description = "音乐风格", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"pop\",\"jazz\",\"punk\"]") |
| 40 | private List<String> tags; | 54 | private List<String> tags; |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/music/AiMusicDO.java
| @@ -30,7 +30,7 @@ public class AiMusicDO extends BaseDO { | @@ -30,7 +30,7 @@ public class AiMusicDO extends BaseDO { | ||
| 30 | 30 | ||
| 31 | /** | 31 | /** |
| 32 | * 用户编号 | 32 | * 用户编号 |
| 33 | - * | 33 | + * <p> |
| 34 | * 关联 AdminUserDO 的 userId 字段 | 34 | * 关联 AdminUserDO 的 userId 字段 |
| 35 | */ | 35 | */ |
| 36 | private Long userId; | 36 | private Long userId; |
| @@ -67,7 +67,7 @@ public class AiMusicDO extends BaseDO { | @@ -67,7 +67,7 @@ public class AiMusicDO extends BaseDO { | ||
| 67 | 67 | ||
| 68 | /** | 68 | /** |
| 69 | * 生成模式 | 69 | * 生成模式 |
| 70 | - * | 70 | + * <p> |
| 71 | * 枚举 {@link AiMusicGenerateModeEnum} | 71 | * 枚举 {@link AiMusicGenerateModeEnum} |
| 72 | */ | 72 | */ |
| 73 | private Integer generateMode; | 73 | private Integer generateMode; |
| @@ -113,4 +113,10 @@ public class AiMusicDO extends BaseDO { | @@ -113,4 +113,10 @@ public class AiMusicDO extends BaseDO { | ||
| 113 | */ | 113 | */ |
| 114 | private String errorMessage; | 114 | private String errorMessage; |
| 115 | 115 | ||
| 116 | + | ||
| 117 | + /** | ||
| 118 | + * 音乐时长 | ||
| 119 | + */ | ||
| 120 | + private Double duration; | ||
| 121 | + | ||
| 116 | } | 122 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/music/AiMusicMapper.java
| @@ -32,4 +32,13 @@ public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { | @@ -32,4 +32,13 @@ public interface AiMusicMapper extends BaseMapperX<AiMusicDO> { | ||
| 32 | .orderByDesc(AiMusicDO::getId)); | 32 | .orderByDesc(AiMusicDO::getId)); |
| 33 | } | 33 | } |
| 34 | 34 | ||
| 35 | + default PageResult<AiMusicDO> selectPageByMy(AiMusicPageReqVO reqVO, Long userId) { | ||
| 36 | + return selectPage(reqVO, new LambdaQueryWrapperX<AiMusicDO>() | ||
| 37 | + // 情况一:公开 | ||
| 38 | + .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiMusicDO::getPublicStatus, reqVO.getPublicStatus()) | ||
| 39 | + // 情况二:私有 | ||
| 40 | + .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiMusicDO::getUserId, userId) | ||
| 41 | + .orderByAsc(AiMusicDO::getId)); | ||
| 42 | + } | ||
| 43 | + | ||
| 35 | } | 44 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicService.java
| 1 | package cn.iocoder.yudao.module.ai.service.music; | 1 | package cn.iocoder.yudao.module.ai.service.music; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicPageReqVO; | ||
| 5 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiMusicUpdatePublicStatusReqVO; | ||
| 6 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 4 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; |
| 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 5 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 8 | import jakarta.validation.Valid; | 6 | import jakarta.validation.Valid; |
| 9 | 7 | ||
| @@ -20,7 +18,7 @@ public interface AiMusicService { | @@ -20,7 +18,7 @@ public interface AiMusicService { | ||
| 20 | * 音乐生成 | 18 | * 音乐生成 |
| 21 | * | 19 | * |
| 22 | * @param userId 用户编号 | 20 | * @param userId 用户编号 |
| 23 | - * @param reqVO 请求参数 | 21 | + * @param reqVO 请求参数 |
| 24 | * @return 生成的音乐ID | 22 | * @return 生成的音乐ID |
| 25 | */ | 23 | */ |
| 26 | List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO); | 24 | List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO); |
| @@ -40,6 +38,13 @@ public interface AiMusicService { | @@ -40,6 +38,13 @@ public interface AiMusicService { | ||
| 40 | void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); | 38 | void updateMusicPublicStatus(@Valid AiMusicUpdatePublicStatusReqVO updateReqVO); |
| 41 | 39 | ||
| 42 | /** | 40 | /** |
| 41 | + * 更新音乐名称 | ||
| 42 | + * | ||
| 43 | + * @param updateReqVO 更新信息 | ||
| 44 | + */ | ||
| 45 | + void updateMusicTitle(@Valid AiMusicUpdateTitleReqVO updateReqVO); | ||
| 46 | + | ||
| 47 | + /** | ||
| 43 | * 删除AI 音乐 | 48 | * 删除AI 音乐 |
| 44 | * | 49 | * |
| 45 | * @param id 编号 | 50 | * @param id 编号 |
| @@ -47,6 +52,22 @@ public interface AiMusicService { | @@ -47,6 +52,22 @@ public interface AiMusicService { | ||
| 47 | void deleteMusic(Long id); | 52 | void deleteMusic(Long id); |
| 48 | 53 | ||
| 49 | /** | 54 | /** |
| 55 | + * 删除【我的】音乐记录 | ||
| 56 | + * | ||
| 57 | + * @param id 音乐编号 | ||
| 58 | + * @param userId 用户编号 | ||
| 59 | + */ | ||
| 60 | + void deleteMusicMy(Long id, Long userId); | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 获得AI 音乐 | ||
| 64 | + * | ||
| 65 | + * @param id 音乐编号 | ||
| 66 | + * @return 音乐内容 | ||
| 67 | + */ | ||
| 68 | + AiMusicDO getMusic(Long id); | ||
| 69 | + | ||
| 70 | + /** | ||
| 50 | * 获得音乐分页 | 71 | * 获得音乐分页 |
| 51 | * | 72 | * |
| 52 | * @param pageReqVO 分页查询 | 73 | * @param pageReqVO 分页查询 |
| @@ -54,4 +75,12 @@ public interface AiMusicService { | @@ -54,4 +75,12 @@ public interface AiMusicService { | ||
| 54 | */ | 75 | */ |
| 55 | PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO); | 76 | PageResult<AiMusicDO> getMusicPage(AiMusicPageReqVO pageReqVO); |
| 56 | 77 | ||
| 78 | + /** | ||
| 79 | + * 获得【我的】音乐分页 | ||
| 80 | + * | ||
| 81 | + * @param pageReqVO 分页查询 | ||
| 82 | + * @param userId 用户编号 | ||
| 83 | + * @return 音乐分页 | ||
| 84 | + */ | ||
| 85 | + PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId); | ||
| 57 | } | 86 | } |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/AiMusicServiceImpl.java
| @@ -2,16 +2,17 @@ package cn.iocoder.yudao.module.ai.service.music; | @@ -2,16 +2,17 @@ package cn.iocoder.yudao.module.ai.service.music; | ||
| 2 | 2 | ||
| 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.ObjUtil; | ||
| 5 | import cn.hutool.core.util.StrUtil; | 6 | import cn.hutool.core.util.StrUtil; |
| 7 | +import cn.hutool.http.HttpUtil; | ||
| 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 8 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | 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.AiMusicUpdatePublicStatusReqVO; | ||
| 10 | -import cn.iocoder.yudao.module.ai.controller.admin.music.vo.AiSunoGenerateReqVO; | 10 | +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.*; |
| 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; | 11 | import cn.iocoder.yudao.module.ai.dal.dataobject.music.AiMusicDO; |
| 12 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; | 12 | import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; |
| 13 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; | 13 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; |
| 14 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; | 14 | import cn.iocoder.yudao.module.ai.enums.music.AiMusicStatusEnum; |
| 15 | +import cn.iocoder.yudao.module.infra.api.file.FileApi; | ||
| 15 | import jakarta.annotation.Resource; | 16 | import jakarta.annotation.Resource; |
| 16 | import lombok.extern.slf4j.Slf4j; | 17 | import lombok.extern.slf4j.Slf4j; |
| 17 | import org.springframework.stereotype.Service; | 18 | import org.springframework.stereotype.Service; |
| @@ -21,6 +22,7 @@ import java.util.*; | @@ -21,6 +22,7 @@ import java.util.*; | ||
| 21 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 22 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 23 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | 24 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| 25 | +import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.IMAGE_NOT_EXISTS; | ||
| 24 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS; | 26 | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.MUSIC_NOT_EXISTS; |
| 25 | 27 | ||
| 26 | /** | 28 | /** |
| @@ -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.getGptDescriptionPrompt(), 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 | // 校验存在 |
| @@ -116,6 +104,14 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -116,6 +104,14 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 116 | } | 104 | } |
| 117 | 105 | ||
| 118 | @Override | 106 | @Override |
| 107 | + public void updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { | ||
| 108 | + // 校验存在 | ||
| 109 | + validateMusicExists(updateReqVO.getId()); | ||
| 110 | + // 更新 | ||
| 111 | + musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle())); | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + @Override | ||
| 119 | public void deleteMusic(Long id) { | 115 | public void deleteMusic(Long id) { |
| 120 | // 校验存在 | 116 | // 校验存在 |
| 121 | validateMusicExists(id); | 117 | validateMusicExists(id); |
| @@ -123,10 +119,20 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -123,10 +119,20 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 123 | musicMapper.deleteById(id); | 119 | musicMapper.deleteById(id); |
| 124 | } | 120 | } |
| 125 | 121 | ||
| 126 | - private void validateMusicExists(Long id) { | ||
| 127 | - if (musicMapper.selectById(id) == null) { | ||
| 128 | - throw exception(MUSIC_NOT_EXISTS); | 122 | + @Override |
| 123 | + public void deleteMusicMy(Long id, Long userId) { | ||
| 124 | + // 1. 校验是否存在 | ||
| 125 | + AiMusicDO music = validateMusicExists(id); | ||
| 126 | + if (ObjUtil.notEqual(music.getUserId(), userId)) { | ||
| 127 | + throw exception(IMAGE_NOT_EXISTS); | ||
| 129 | } | 128 | } |
| 129 | + // 2. 删除记录 | ||
| 130 | + musicMapper.deleteById(id); | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + @Override | ||
| 134 | + public AiMusicDO getMusic(Long id) { | ||
| 135 | + return musicMapper.selectById(id); | ||
| 130 | } | 136 | } |
| 131 | 137 | ||
| 132 | @Override | 138 | @Override |
| @@ -134,4 +140,53 @@ public class AiMusicServiceImpl implements AiMusicService { | @@ -134,4 +140,53 @@ public class AiMusicServiceImpl implements AiMusicService { | ||
| 134 | return musicMapper.selectPage(pageReqVO); | 140 | return musicMapper.selectPage(pageReqVO); |
| 135 | } | 141 | } |
| 136 | 142 | ||
| 143 | + @Override | ||
| 144 | + public PageResult<AiMusicDO> getMusicMyPage(AiMusicPageReqVO pageReqVO, Long userId) { | ||
| 145 | + return musicMapper.selectPageByMy(pageReqVO, userId); | ||
| 146 | + } | ||
| 147 | + | ||
| 148 | + /** | ||
| 149 | + * 构建 AiMusicDO 集合 | ||
| 150 | + * | ||
| 151 | + * @param musicList suno 音乐任务列表 | ||
| 152 | + * @return AiMusicDO 集合 | ||
| 153 | + */ | ||
| 154 | + private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { | ||
| 155 | + return convertList(musicList, musicData -> new AiMusicDO() | ||
| 156 | + .setTaskId(musicData.id()).setModel(musicData.modelName()) | ||
| 157 | + .setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) | ||
| 158 | + .setAudioUrl(createFile(musicData.audioUrl())).setVideoUrl(createFile(musicData.videoUrl())).setImageUrl(createFile(musicData.imageUrl())).setDuration(musicData.duration()) | ||
| 159 | + .setTitle(musicData.title()).setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) | ||
| 160 | + .setStatus(Objects.equals("complete", musicData.status()) ? | ||
| 161 | + AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + /** | ||
| 165 | + * 将生成的音频文件上传到文件服务器 | ||
| 166 | + * | ||
| 167 | + * @param url 音频文件地址 | ||
| 168 | + * @return 内部文件地址 | ||
| 169 | + */ | ||
| 170 | + private String createFile(String url) { | ||
| 171 | + if (StrUtil.isBlank(url)) { | ||
| 172 | + return null; | ||
| 173 | + } | ||
| 174 | + byte[] bytes = HttpUtil.downloadBytes(url); | ||
| 175 | + return fileApi.createFile(bytes); | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + /** | ||
| 179 | + * 校验音乐是否存在 | ||
| 180 | + * | ||
| 181 | + * @param id 音乐编号 | ||
| 182 | + * @return 音乐信息 | ||
| 183 | + */ | ||
| 184 | + private AiMusicDO validateMusicExists(Long id) { | ||
| 185 | + AiMusicDO music = musicMapper.selectById(id); | ||
| 186 | + if (music == null) { | ||
| 187 | + throw exception(MUSIC_NOT_EXISTS); | ||
| 188 | + } | ||
| 189 | + return music; | ||
| 190 | + } | ||
| 191 | + | ||
| 137 | } | 192 | } |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
| @@ -117,7 +117,7 @@ public class SunoApi { | @@ -117,7 +117,7 @@ public class SunoApi { | ||
| 117 | * @param prompt 用于生成音乐音频的提示 | 117 | * @param prompt 用于生成音乐音频的提示 |
| 118 | * @param tags 音乐风格 | 118 | * @param tags 音乐风格 |
| 119 | * @param title 音乐名称 | 119 | * @param title 音乐名称 |
| 120 | - * @param mv 模型 | 120 | + * @param model 模型 |
| 121 | * @param waitAudio false 表示后台模式,仅返回音频任务信息,需要调用 get API 获取详细的音频信息。 | 121 | * @param waitAudio false 表示后台模式,仅返回音频任务信息,需要调用 get API 获取详细的音频信息。 |
| 122 | * true 表示同步模式,API 最多等待 100s,音频生成完毕后直接返回音频链接等信息,建议在 GPT 等 agent 中使用。 | 122 | * true 表示同步模式,API 最多等待 100s,音频生成完毕后直接返回音频链接等信息,建议在 GPT 等 agent 中使用。 |
| 123 | * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | 123 | * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 |
| @@ -127,7 +127,7 @@ public class SunoApi { | @@ -127,7 +127,7 @@ public class SunoApi { | ||
| 127 | String prompt, | 127 | String prompt, |
| 128 | String tags, | 128 | String tags, |
| 129 | String title, | 129 | String title, |
| 130 | - String mv, | 130 | + String model, |
| 131 | @JsonProperty("wait_audio") boolean waitAudio, | 131 | @JsonProperty("wait_audio") boolean waitAudio, |
| 132 | @JsonProperty("make_instrumental") boolean makeInstrumental | 132 | @JsonProperty("make_instrumental") boolean makeInstrumental |
| 133 | ) { | 133 | ) { |
| @@ -136,12 +136,12 @@ public class SunoApi { | @@ -136,12 +136,12 @@ public class SunoApi { | ||
| 136 | this(prompt, null, null, null, false, false); | 136 | this(prompt, null, null, null, false, false); |
| 137 | } | 137 | } |
| 138 | 138 | ||
| 139 | - public MusicGenerateRequest(String prompt, String mv, boolean makeInstrumental) { | ||
| 140 | - this(prompt, null, null, mv, false, makeInstrumental); | 139 | + public MusicGenerateRequest(String prompt, String model, boolean makeInstrumental) { |
| 140 | + this(prompt, null, null, model, false, makeInstrumental); | ||
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | - public MusicGenerateRequest(String prompt, String mv, String tags, String title) { | ||
| 144 | - this(prompt, tags, title, mv, false, false); | 143 | + public MusicGenerateRequest(String prompt, String model, String tags, String title) { |
| 144 | + this(prompt, tags, title, model, false, false); | ||
| 145 | } | 145 | } |
| 146 | 146 | ||
| 147 | } | 147 | } |
| @@ -162,6 +162,7 @@ public class SunoApi { | @@ -162,6 +162,7 @@ public class SunoApi { | ||
| 162 | * @param prompt 生成音乐音频的提示 | 162 | * @param prompt 生成音乐音频的提示 |
| 163 | * @param type 操作类型 | 163 | * @param type 操作类型 |
| 164 | * @param tags 音乐类型标签 | 164 | * @param tags 音乐类型标签 |
| 165 | + * @param duration 音乐时长 | ||
| 165 | */ | 166 | */ |
| 166 | public record MusicData( | 167 | public record MusicData( |
| 167 | String id, | 168 | String id, |
| @@ -176,7 +177,8 @@ public class SunoApi { | @@ -176,7 +177,8 @@ public class SunoApi { | ||
| 176 | @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, | 177 | @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, |
| 177 | String prompt, | 178 | String prompt, |
| 178 | String type, | 179 | String type, |
| 179 | - String tags | 180 | + String tags, |
| 181 | + Double duration | ||
| 180 | ) { | 182 | ) { |
| 181 | } | 183 | } |
| 182 | 184 |
yudao-server/src/main/resources/application.yaml
| @@ -201,8 +201,8 @@ yudao.ai: | @@ -201,8 +201,8 @@ yudao.ai: | ||
| 201 | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify | 201 | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify |
| 202 | suno: | 202 | suno: |
| 203 | enable: true | 203 | enable: true |
| 204 | -# base-url: https://suno-om0w1cy6e-status2xxs-projects.vercel.app | ||
| 205 | - base-url: http://127.0.0.1:3001 | 204 | + base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app |
| 205 | +# base-url: http://127.0.0.1:3001 | ||
| 206 | 206 | ||
| 207 | --- #################### 芋道相关配置 #################### | 207 | --- #################### 芋道相关配置 #################### |
| 208 | 208 |