From e582aaad2eeabe4411eec239b67a83c02ee72325 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Wed, 29 May 2024 15:17:15 +0800 Subject: [PATCH] 【新增】AI:suno音乐生成接口 --- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java | 31 +++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java | 29 +++++++++++++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java | 19 +++++++++++++++++++ yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java | 25 +++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java | 17 +++++++++++------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java | 11 +++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoApi.java | 175 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java | 21 +++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java | 19 +++++++++++++------ yudao-server/src/main/resources/application.yaml | 3 +++ 13 files changed, 450 insertions(+), 187 deletions(-) create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java create mode 100644 yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java new file mode 100644 index 0000000..ae66d71 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/MusicController.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; +import cn.iocoder.yudao.module.ai.service.music.MusicService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - AI 音乐生成") +@RestController +@RequestMapping("/ai/music") +@RequiredArgsConstructor +public class MusicController { + + private final MusicService musicService; + + @PostMapping("/suno-gen") + @Operation(summary = "音乐生成") + public CommonResult musicGen(@RequestBody @Valid SunoReqVO sunoReqVO) { + return success(musicService.musicGen(sunoReqVO)); + } +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java new file mode 100644 index 0000000..ce372af --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 表示单个音乐数据的类 + */ +@Data +public class MusicDataVO { + /** + * 音乐数据的 ID + */ + private String id; + + /** + * 音乐音频的标题 + */ + private String title; + + /** + * 音乐音频的图片 URL + */ + @JsonProperty("image_url") + private String imageUrl; + + /** + * 音乐音频的歌词 + */ + private String lyric; + + /** + * 音乐音频的 URL + */ + @JsonProperty("audio_url") + private String audioUrl; + + /** + * 音乐视频的 URL + */ + @JsonProperty("video_url") + private String videoUrl; + + /** + * 音乐音频的创建时间 + */ + @JsonProperty("created_at") + private String createdAt; + + /** + * 使用的模型名称 + */ + private String model; + + /** + * 生成音乐音频的提示 + */ + private String prompt; + + /** + * 音乐音频的风格 + */ + private String style; +} \ 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/SunoReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java new file mode 100644 index 0000000..ae40cf5 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@JsonInclude(value = JsonInclude.Include.NON_NULL) +public class SunoReqVO { + /** + * 用于生成音乐音频的提示 + */ + private String prompt; + + /** + * 用于生成音乐音频的歌词 + */ + private String lyric; + + /** + * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 + */ + private boolean custom; + + /** + * 音乐音频的标题 + */ + private String title; + + /** + * 音乐音频的风格 + */ + private String style; + + /** + * 音乐音频生成后回调的 URL + */ + private String callbackUrl; +} \ 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/SunoRespVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java new file mode 100644 index 0000000..bbb4264 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.ai.controller.admin.music.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * API 响应的数据 + */ +@Data +public class SunoRespVO { + /** + * 表示请求是否成功 + */ + private boolean success; + + /** + * 任务 ID + */ + @JsonProperty("task_id") + private String taskId; + + /** + * 音乐数据列表 + */ + private List data; + +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java new file mode 100644 index 0000000..c0e7b34 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicService.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.ai.service.music; + +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; + +/** + * @Author xiaoxin + * @Date 2024/5/29 + */ +public interface MusicService { + + /** + * 音乐生成 + * + * @param sunoReqVO 请求实体 + * @return 响应实体 + */ + SunoRespVO musicGen(SunoReqVO sunoReqVO); +} diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java new file mode 100644 index 0000000..64173d2 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.ai.service.music; + +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; +import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +/** + * @Author xiaoxin + * @Date 2024/5/29 + */ +@Service +@RequiredArgsConstructor +public class MusicServiceImpl implements MusicService { + + private final SunoApi sunoApi; + + @Override + public SunoRespVO musicGen(SunoReqVO sunoReqVO) { + SunoApi.SunoRequest req = BeanUtils.toBean(sunoReqVO, SunoApi.SunoRequest.class); + return BeanUtils.toBean(sunoApi.musicGen(req), SunoRespVO.class); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index 55705c4..5472e2f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.ai.config; import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl; +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions; @@ -13,18 +15,14 @@ import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi; import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient; import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions; import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.ai.models.midjourney.MidjourneyConfig; import org.springframework.ai.models.midjourney.MidjourneyMessage; import org.springframework.ai.models.midjourney.api.MidjourneyInteractionsApi; import org.springframework.ai.models.midjourney.webSocket.MidjourneyMessageHandler; import org.springframework.ai.models.midjourney.webSocket.MidjourneyWebSocketStarter; import org.springframework.ai.models.midjourney.webSocket.listener.MidjourneyMessageListener; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.ai.openai.OpenAiImageClient; -import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.retry.RetryUtils; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -150,6 +148,13 @@ public class YudaoAiAutoConfiguration { return new MidjourneyInteractionsApi(midjourneyConfig); } + @Bean + @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") + public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { + // 创建 sunoApi + return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken())); + } + private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, YudaoAiProperties.MidjourneyProperties midjourneyProperties) { Map requestTemplates = new HashMap<>(); diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index 16a6bc4..4a7dd69 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -26,6 +26,7 @@ public class YudaoAiProperties { private YiYanProperties yiyan; private OpenAiImageProperties openAiImage; private MidjourneyProperties midjourney; + private SunoProperties suno; @Data @Accessors(chain = true) @@ -134,4 +135,14 @@ public class YudaoAiProperties { */ private String channelId; } + + @Data + @Accessors(chain = true) + public static class SunoProperties { + private boolean enable = false; + /** + * token + */ + private String token; + } } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoApi.java deleted file mode 100644 index 0198197..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoApi.java +++ /dev/null @@ -1,175 +0,0 @@ -package cn.iocoder.yudao.framework.ai.core.model.suno; - - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Data; -import lombok.experimental.Accessors; -import lombok.extern.slf4j.Slf4j; -import okhttp3.*; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * @Author xiaoxin - * @Date 2024/5/27 - */ -@Slf4j -public class SunoApi { - - public static final String APPLICATION_JSON = "application/json"; - public static final String TOKEN_PREFIX = "Bearer "; - public static final String API_URL = "https://api.acedata.cloud/suno/audios"; - public static final String TEST_TOKEN = "13f13540dd3f4ae9885f63ac9f5d0b9f"; - private static final int READ_TIMEOUT = 160; // 连接超时时间(秒),音乐生成时间较长,设置为 160s,后续可做callback - private final OkHttpClient client; - private final ObjectMapper objectMapper; - - public SunoApi() { - this.client = new OkHttpClient().newBuilder().readTimeout(READ_TIMEOUT, TimeUnit.SECONDS).build(); - this.objectMapper = new ObjectMapper(); - } - - public SunoResponse generateMusic(SunoRequest sunoRequest) throws IOException { - Request request = new Request.Builder() - .url(API_URL) - .header("Authorization", TOKEN_PREFIX + TEST_TOKEN) - .post(RequestBody.create(MediaType.parse(APPLICATION_JSON), objectMapper.writeValueAsString(sunoRequest))) - .build(); - - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) { - log.error("suno调用失败! response: {}", response); - throw new IllegalStateException("suno调用失败!" + response); - } - return objectMapper.readValue(response.body().string(), SunoResponse.class); - } - } - - - /** - * 请求数据对象,用于生成音乐音频 - */ - @Data - @Accessors(chain = true) - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public static class SunoRequest { - /** - * 用于生成音乐音频的提示 - */ - private String prompt; - - /** - * 用于生成音乐音频的歌词 - */ - private String lyric; - - /** - * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 - */ - private boolean custom; - - /** - * 音乐音频的标题 - */ - private String title; - - /** - * 音乐音频的风格 - */ - private String style; - - /** - * 音乐音频生成后回调的 URL - */ - private String callbackUrl; - } - - - /** - * API 响应的数据 - */ - @Data - public static class SunoResponse { - /** - * 表示请求是否成功 - */ - private boolean success; - - /** - * 任务 ID - */ - @JsonProperty("task_id") - private String taskId; - - /** - * 音乐数据列表 - */ - private List data; - - /** - * 表示单个音乐数据的类 - */ - @Data - static class MusicData { - /** - * 音乐数据的 ID - */ - private String id; - - /** - * 音乐音频的标题 - */ - private String title; - - /** - * 音乐音频的图片 URL - */ - @JsonProperty("image_url") - private String imageUrl; - - /** - * 音乐音频的歌词 - */ - private String lyric; - - /** - * 音乐音频的 URL - */ - @JsonProperty("audio_url") - private String audioUrl; - - /** - * 音乐视频的 URL - */ - @JsonProperty("video_url") - private String videoUrl; - - /** - * 音乐音频的创建时间 - */ - @JsonProperty("created_at") - private String createdAt; - - /** - * 使用的模型名称 - */ - private String model; - - /** - * 生成音乐音频的提示 - */ - private String prompt; - - /** - * 音乐音频的风格 - */ - private String style; - } - } - - -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java new file mode 100644 index 0000000..0d717f6 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/SunoConfig.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.framework.ai.core.model.suno; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * @Author xiaoxin + * @Date 2024/5/29 + */ +@Data +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class SunoConfig { + /** + * token信息 + */ + private String token; +} 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 new file mode 100644 index 0000000..5da45fc --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java @@ -0,0 +1,183 @@ +package cn.iocoder.yudao.framework.ai.core.model.suno.api; + + +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @Author xiaoxin + * @Date 2024/5/27 + */ +@Slf4j +public class SunoApi { + + public static final String APPLICATION_JSON = "application/json"; + public static final String TOKEN_PREFIX = "Bearer "; + public static final String API_URL = "https://api.acedata.cloud/suno/audios"; + private static final int READ_TIMEOUT = 160; // 连接超时时间(秒),音乐生成时间较长,设置为 160s,后续可做callback + private final OkHttpClient client; + + + public SunoApi(SunoConfig sunoConfig) { + this.client = new OkHttpClient().newBuilder().readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .addInterceptor(chain -> { + Request originalRequest = chain.request(); + Request requestWithUserAgent = originalRequest.newBuilder() + .header("Authorization", TOKEN_PREFIX + sunoConfig.getToken()) + .build(); + return chain.proceed(requestWithUserAgent); + }) + .build(); + } + + public SunoResponse musicGen(SunoRequest sunoRequest) { + Request request = new Request.Builder() + .url(API_URL) + .post(RequestBody.create(MediaType.parse(APPLICATION_JSON), JsonUtils.toJsonString(sunoRequest))) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + log.error("suno调用失败! response: {}", response); + throw new IllegalStateException("suno调用失败!" + response); + } + return JsonUtils.parseObject(response.body().string(), SunoResponse.class); + } catch (IOException ioException) { + throw new RuntimeException(ioException); + } + } + + + /** + * 请求数据对象,用于生成音乐音频 + */ + @Data + @Accessors(chain = true) + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public static class SunoRequest { + /** + * 用于生成音乐音频的提示 + */ + private String prompt; + + /** + * 用于生成音乐音频的歌词 + */ + private String lyric; + + /** + * 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 + */ + private boolean custom; + + /** + * 音乐音频的标题 + */ + private String title; + + /** + * 音乐音频的风格 + */ + private String style; + + /** + * 音乐音频生成后回调的 URL + */ + private String callbackUrl; + } + + + /** + * SunoAPI 响应的数据 + */ + @Data + public static class SunoResponse { + /** + * 表示请求是否成功 + */ + private boolean success; + + /** + * 任务 ID + */ + @JsonProperty("task_id") + private String taskId; + + /** + * 音乐数据列表 + */ + private List data; + + /** + * 表示单个音乐数据的类 + */ + @Data + static class MusicData { + /** + * 音乐数据的 ID + */ + private String id; + + /** + * 音乐音频的标题 + */ + private String title; + + /** + * 音乐音频的图片 URL + */ + @JsonProperty("image_url") + private String imageUrl; + + /** + * 音乐音频的歌词 + */ + private String lyric; + + /** + * 音乐音频的 URL + */ + @JsonProperty("audio_url") + private String audioUrl; + + /** + * 音乐视频的 URL + */ + @JsonProperty("video_url") + private String videoUrl; + + /** + * 音乐音频的创建时间 + */ + @JsonProperty("created_at") + private String createdAt; + + /** + * 使用的模型名称 + */ + private String model; + + /** + * 生成音乐音频的提示 + */ + private String prompt; + + /** + * 音乐音频的风格 + */ + private String style; + } + } + + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java index 30883a6..58d1609 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java @@ -1,24 +1,31 @@ package cn.iocoder.yudao.framework.ai.suno; -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoApi; +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; +import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; +import org.junit.Before; import org.junit.Test; -import java.io.IOException; - /** * @Author xiaoxin * @Date 2024/5/27 */ public class SunoTests { + private SunoConfig sunoConfig; + + @Before + public void setup() { + String token = "13f13540dd3f4ae9885f63ac9f5d0b9f"; + this.sunoConfig = new SunoConfig(token); + } @Test - public void generateMusic() throws IOException { - SunoApi sunoApi = new SunoApi(); + public void generateMusic() { + SunoApi sunoApi = new SunoApi(sunoConfig); SunoApi.SunoRequest sunoRequest = new SunoApi .SunoRequest() .setPrompt("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); - SunoApi.SunoResponse sunoResponse = sunoApi.generateMusic(sunoRequest); + SunoApi.SunoResponse sunoResponse = sunoApi.musicGen(sunoRequest); System.out.println(sunoResponse); } } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 37e0f5b..417787b 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -199,6 +199,9 @@ yudao.ai: token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw guild-id: 1237948819677904956 channel-id: 1237948819677904960 + suno: + enable: true + token: 13f13540dd3f4ae9885f63ac9f5d0b9f --- #################### 芋道相关配置 #################### -- libgit2 0.21.4