!8 【解决todo】AI 音乐:1.处理失败任务 2.两种生成类型提示词合并,库中区分

Merge pull request !8 from 小新/master-jdk21-ai
This commit is contained in:
芋道源码 2024-07-02 13:05:06 +00:00 committed by Gitee
commit 3f0d823a85
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
10 changed files with 61 additions and 72 deletions

View File

@ -16,7 +16,8 @@ import java.util.Arrays;
public enum AiMusicStatusEnum implements IntArrayValuable { public enum AiMusicStatusEnum implements IntArrayValuable {
IN_PROGRESS(10, "进行中"), IN_PROGRESS(10, "进行中"),
SUCCESS(20, "已完成"); SUCCESS(20, "已完成"),
FAIL(30, "已失败");
/** /**
* 状态 * 状态

View File

@ -38,10 +38,6 @@ public class AiMusicController {
@PostMapping("/generate") @PostMapping("/generate")
@Operation(summary = "音乐生成") @Operation(summary = "音乐生成")
public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) { public CommonResult<List<Long>> generateMusic(@RequestBody @Valid AiSunoGenerateReqVO reqVO) {
if (true) {
musicService.syncMusic();
return null;
}
return success(musicService.generateMusic(getLoginUserId(), reqVO)); return success(musicService.generateMusic(getLoginUserId(), reqVO));
} }
@ -64,12 +60,11 @@ public class AiMusicController {
return success(BeanUtils.toBean(music, AiMusicRespVO.class)); return success(BeanUtils.toBean(music, AiMusicRespVO.class));
} }
// TODO @xin这个搞成 updateMy 修改我的音乐方便后续支持其它字段另外需要校验下更新的音乐是不是我的 @PostMapping("/update-my")
@PostMapping("/updateTitle-my")
@Operation(summary = "修改【我的】音乐 目前只支持修改标题") @Operation(summary = "修改【我的】音乐 目前只支持修改标题")
@Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星") @Parameter(name = "title", required = true, description = "音乐名称", example = "夜空中最亮的星")
public CommonResult<Boolean> updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { public CommonResult<Boolean> updateMy(AiMusicUpdateReqVO updateReqVO) {
musicService.updateMusicTitle(updateReqVO); musicService.updateMyMusic(updateReqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -15,4 +15,7 @@ public class AiMusicUpdateReqVO {
@Schema(description = "是否发布", example = "true") @Schema(description = "是否发布", example = "true")
private Boolean publicStatus; private Boolean publicStatus;
@Schema(description = "音乐名称", example = "夜空中最亮的星")
private String title;
} }

View File

@ -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;
}

View File

@ -24,10 +24,10 @@ public class AiSunoGenerateReqVO {
@NotNull(message = "生成模式不能为空") @NotNull(message = "生成模式不能为空")
private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举 private Integer generateMode; // 参见 AiMusicGenerateModeEnum 枚举
// TODO @xin方案一prompt => lyric 歌词gptDescriptionPrompt => description 描述db 那字段也改下避免和 gpt 直接耦合这样搞完后会更统一好理解一点
// TODO @xin方案二还是之前的做法都用 prompt不过最终 gptDescriptionPrompt 还是存储 description 算描述可以微信一起讨论下
@Schema(description = "用于生成音乐音频的歌词提示", @Schema(description = "用于生成音乐音频的歌词提示",
example = """ example = """
1.描述模式创作一首带有轻松吉他旋律的流行歌曲[verse] 描述夏日海滩的宁静[chorus] 节奏加快表达对自由的向往
2.歌词模式
[Verse] [Verse]
阳光下奔跑 多么欢快 阳光下奔跑 多么欢快
假期就要来 心都飞起来 假期就要来 心都飞起来
@ -39,11 +39,7 @@ public class AiSunoGenerateReqVO {
日子太短暂 别再等待 日子太短暂 别再等待
马上放假了 梦想起飞 马上放假了 梦想起飞
""") """)
private String prompt; // 歌词模式用 private String prompt;
@Schema(description = "用于生成音乐音频的描述",
example = "创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")
private String gptDescriptionPrompt; // 描述模式用
@Schema(description = "是否纯音乐", example = "true") @Schema(description = "是否纯音乐", example = "true")
private Boolean makeInstrumental; private Boolean makeInstrumental;

View File

@ -75,11 +75,7 @@ public class AiMusicDO extends BaseDO {
/** /**
* 描述词 * 描述词
*/ */
private String gptDescriptionPrompt; private String description;
/**
* 提示词
*/
private String prompt;
/** /**
* 平台 * 平台

View File

@ -38,11 +38,11 @@ public interface AiMusicService {
void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO); void updateMusic(@Valid AiMusicUpdateReqVO updateReqVO);
/** /**
* 更新音乐名称 * 更新我的音乐
* *
* @param updateReqVO 更新信息 * @param updateReqVO 更新信息
*/ */
void updateMusicTitle(@Valid AiMusicUpdateTitleReqVO updateReqVO); void updateMyMusic(@Valid AiMusicUpdateReqVO updateReqVO, Long userId);
/** /**
* 删除AI 音乐 * 删除AI 音乐

View File

@ -3,11 +3,14 @@ package cn.iocoder.yudao.module.ai.service.music;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrPool; import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.common.pojo.PageResult; 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.dataobject.music.AiMusicDO;
import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper; import cn.iocoder.yudao.module.ai.dal.mysql.music.AiMusicMapper;
import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum; import cn.iocoder.yudao.module.ai.enums.music.AiMusicGenerateModeEnum;
@ -48,18 +51,18 @@ public class AiMusicServiceImpl implements AiMusicService {
public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) { public List<Long> generateMusic(Long userId, AiSunoGenerateReqVO reqVO) {
// 1. 调用 Suno 生成音乐 // 1. 调用 Suno 生成音乐
SunoApi sunoApi = apiKeyService.getSunoApi(); SunoApi sunoApi = apiKeyService.getSunoApi();
// TODO @xin这两个貌似一直没跑成功你那可以么用的请求是 AiMusicController.http // TODO 芋艿这两个貌似一直没跑成功你那可以么用的请求是 AiMusicController.http --xin大部分ok的补充了error_message
List<SunoApi.MusicData> musicDataList; List<SunoApi.MusicData> musicDataList;
if (Objects.equals(AiMusicGenerateModeEnum.LYRIC.getMode(), reqVO.getGenerateMode())) { if (Objects.equals(AiMusicGenerateModeEnum.DESCRIPTION.getMode(), reqVO.getGenerateMode())) {
// 1.1 歌词模式 // 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( SunoApi.MusicGenerateRequest generateRequest = new SunoApi.MusicGenerateRequest(
reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle()); reqVO.getPrompt(), reqVO.getModel(), CollUtil.join(reqVO.getTags(), StrPool.COMMA), reqVO.getTitle());
musicDataList = sunoApi.customGenerate(generateRequest); 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 { } else {
throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO)); throw new IllegalArgumentException(StrUtil.format("未知生成模式({})", reqVO));
} }
@ -108,9 +111,12 @@ public class AiMusicServiceImpl implements AiMusicService {
} }
@Override @Override
public void updateMusicTitle(AiMusicUpdateTitleReqVO updateReqVO) { public void updateMyMusic(AiMusicUpdateReqVO updateReqVO, Long userId) {
// 校验存在 // 校验音乐是否存在
validateMusicExists(updateReqVO.getId()); 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())); musicMapper.updateById(new AiMusicDO().setId(updateReqVO.getId()).setTitle(updateReqVO.getTitle()));
} }
@ -156,29 +162,38 @@ public class AiMusicServiceImpl implements AiMusicService {
* @return AiMusicDO 集合 * @return AiMusicDO 集合
*/ */
private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) { private List<AiMusicDO> buildMusicDOList(List<SunoApi.MusicData> musicList) {
// TODO @xin它有 status = error 状态表示失败噢 return convertList(musicList, musicData -> {
return convertList(musicList, musicData -> new AiMusicDO() Integer status;
.setTaskId(musicData.id()).setModel(musicData.modelName()) if (Objects.equals("complete", musicData.status())) {
.setPrompt(musicData.prompt()).setGptDescriptionPrompt(musicData.gptDescriptionPrompt()) status = AiMusicStatusEnum.SUCCESS.getStatus();
// TODO @xin只有在完成的状态在下载文件 } else if (Objects.equals("error", musicData.status())) {
.setAudioUrl(downloadFile(musicData.audioUrl())) status = AiMusicStatusEnum.FAIL.getStatus();
.setVideoUrl(downloadFile(musicData.videoUrl())) } else {
.setImageUrl(downloadFile(musicData.imageUrl())) status = AiMusicStatusEnum.IN_PROGRESS.getStatus();
.setTitle(musicData.title()).setDuration(musicData.duration()) }
.setLyric(musicData.lyric()).setTags(StrUtil.split(musicData.tags(), StrPool.COMMA)) return new AiMusicDO()
.setStatus(Objects.equals("complete", musicData.status()) ? .setTaskId(musicData.id()).setModel(musicData.modelName())
AiMusicStatusEnum.SUCCESS.getStatus() : AiMusicStatusEnum.IN_PROGRESS.getStatus())); .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 内部文件地址 * @return 内部文件地址
*/ */
private String downloadFile(String url) { private String downloadFile(Integer status, String url) {
if (StrUtil.isBlank(url)) { if (StrUtil.isBlank(url) || ObjectUtil.notEqual(status, AiMusicStatusEnum.SUCCESS.getStatus())) {
return null; return url;
} }
try { try {
byte[] bytes = HttpUtil.downloadBytes(url); byte[] bytes = HttpUtil.downloadBytes(url);

View File

@ -175,6 +175,7 @@ public class SunoApi {
@JsonProperty("model_name") String modelName, @JsonProperty("model_name") String modelName,
String status, String status,
@JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt,
@JsonProperty("error_message") String errorMessage,
String prompt, String prompt,
String type, String type,
String tags, String tags,

View File

@ -17,8 +17,8 @@ public class SunoTests {
@Before @Before
public void setup() { public void setup() {
// String url = "https://suno-om0w1cy6e-status2xxs-projects.vercel.app"; String url = "https://suno-55ishh05u-status2xxs-projects.vercel.app";
String url = "http://127.0.0.1:3001"; // String url = "http://127.0.0.1:3001";
this.sunoApi = new SunoApi(url); this.sunoApi = new SunoApi(url);
} }