Commit 851c290c0d3240822272757cc0ef940fb2ebfb47
1 parent
c0de6cc5
【增加】AI:使用suno-api服务接入Suno
Showing
7 changed files
with
307 additions
and
80 deletions
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/MusicDataVO.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music.vo; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 3 | +import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; |
| 4 | import com.fasterxml.jackson.annotation.JsonProperty; | 4 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 5 | import lombok.Data; | 5 | import lombok.Data; |
| 6 | 6 | ||
| @@ -66,7 +66,7 @@ public class MusicDataVO { | @@ -66,7 +66,7 @@ public class MusicDataVO { | ||
| 66 | */ | 66 | */ |
| 67 | private String style; | 67 | private String style; |
| 68 | 68 | ||
| 69 | - public static List<MusicDataVO> convertFrom(List<SunoApi.SunoResp.MusicData> musicDataList) { | 69 | + public static List<MusicDataVO> convertFrom(List<AceDataSunoApi.SunoResp.MusicData> musicDataList) { |
| 70 | return musicDataList.stream().map(musicData -> { | 70 | return musicDataList.stream().map(musicData -> { |
| 71 | MusicDataVO musicDataVO = new MusicDataVO(); | 71 | MusicDataVO musicDataVO = new MusicDataVO(); |
| 72 | musicDataVO.setId(musicData.id()); | 72 | musicDataVO.setId(musicData.id()); |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/vo/SunoRespVO.java
| 1 | package cn.iocoder.yudao.module.ai.controller.admin.music.vo; | 1 | package cn.iocoder.yudao.module.ai.controller.admin.music.vo; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 3 | +import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; |
| 4 | import com.fasterxml.jackson.annotation.JsonProperty; | 4 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 5 | import lombok.Data; | 5 | import lombok.Data; |
| 6 | 6 | ||
| @@ -29,7 +29,7 @@ public class SunoRespVO { | @@ -29,7 +29,7 @@ public class SunoRespVO { | ||
| 29 | 29 | ||
| 30 | 30 | ||
| 31 | //把 SunoResp转为本vo类 | 31 | //把 SunoResp转为本vo类 |
| 32 | - public static SunoRespVO convertFrom(SunoApi.SunoResp sunoResp) { | 32 | + public static SunoRespVO convertFrom(AceDataSunoApi.SunoResp sunoResp) { |
| 33 | SunoRespVO sunoRespVO = new SunoRespVO(); | 33 | SunoRespVO sunoRespVO = new SunoRespVO(); |
| 34 | sunoRespVO.setSuccess(sunoResp.success()); | 34 | sunoRespVO.setSuccess(sunoResp.success()); |
| 35 | sunoRespVO.setTaskId(sunoResp.taskId()); | 35 | sunoRespVO.setTaskId(sunoResp.taskId()); |
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/music/MusicServiceImpl.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.ai.core.model.suno.api.SunoApi; | 3 | +import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; |
| 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; | 4 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoReqVO; |
| 5 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; | 5 | import cn.iocoder.yudao.module.ai.controller.admin.music.vo.SunoRespVO; |
| 6 | import lombok.RequiredArgsConstructor; | 6 | import lombok.RequiredArgsConstructor; |
| @@ -14,11 +14,11 @@ import org.springframework.stereotype.Service; | @@ -14,11 +14,11 @@ import org.springframework.stereotype.Service; | ||
| 14 | @RequiredArgsConstructor | 14 | @RequiredArgsConstructor |
| 15 | public class MusicServiceImpl implements MusicService { | 15 | public class MusicServiceImpl implements MusicService { |
| 16 | 16 | ||
| 17 | - private final SunoApi sunoApi; | 17 | + private final AceDataSunoApi aceDataSunoApi; |
| 18 | 18 | ||
| 19 | @Override | 19 | @Override |
| 20 | public SunoRespVO musicGen(SunoReqVO sunoReqVO) { | 20 | public SunoRespVO musicGen(SunoReqVO sunoReqVO) { |
| 21 | - SunoApi.SunoResp sunoResp = sunoApi.musicGen(new SunoApi.SunoReq( | 21 | + AceDataSunoApi.SunoResp sunoResp = aceDataSunoApi.musicGen(new AceDataSunoApi.SunoReq( |
| 22 | sunoReqVO.getPrompt(), | 22 | sunoReqVO.getPrompt(), |
| 23 | sunoReqVO.getLyric(), | 23 | sunoReqVO.getLyric(), |
| 24 | sunoReqVO.isCustom(), | 24 | sunoReqVO.isCustom(), |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| @@ -4,7 +4,7 @@ import cn.hutool.core.io.IoUtil; | @@ -4,7 +4,7 @@ import cn.hutool.core.io.IoUtil; | ||
| 4 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; | 4 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactory; |
| 5 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl; | 5 | import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl; |
| 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; |
| 7 | -import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | 7 | +import cn.iocoder.yudao.framework.ai.core.model.suno.api.AceDataSunoApi; |
| 8 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; | 8 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatClient; |
| 9 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; | 9 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenChatModal; |
| 10 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions; | 10 | import cn.iocoder.yudao.framework.ai.core.model.tongyi.QianWenOptions; |
| @@ -150,8 +150,8 @@ public class YudaoAiAutoConfiguration { | @@ -150,8 +150,8 @@ public class YudaoAiAutoConfiguration { | ||
| 150 | 150 | ||
| 151 | @Bean | 151 | @Bean |
| 152 | @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") | 152 | @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") |
| 153 | - public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { | ||
| 154 | - return new SunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken())); | 153 | + public AceDataSunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { |
| 154 | + return new AceDataSunoApi(new SunoConfig(yudaoAiProperties.getSuno().getToken())); | ||
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, | 157 | private static @NotNull MidjourneyConfig getMidjourneyConfig(ApplicationContext applicationContext, |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/AceDataSunoApi.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.core.model.suno.api; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; | ||
| 4 | +import com.fasterxml.jackson.annotation.JsonInclude; | ||
| 5 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.ai.openai.api.ApiUtils; | ||
| 8 | +import org.springframework.web.reactive.function.client.WebClient; | ||
| 9 | +import reactor.core.publisher.Mono; | ||
| 10 | + | ||
| 11 | +import java.util.List; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Suno API | ||
| 15 | + * <br> | ||
| 16 | + * 文档地址:https://platform.acedata.cloud/documents/d016ee3f-421b-4b6e-989a-8beba8701701 | ||
| 17 | + * | ||
| 18 | + * @Author xiaoxin | ||
| 19 | + * @Date 2024/5/27 | ||
| 20 | + */ | ||
| 21 | +@Slf4j | ||
| 22 | +public class AceDataSunoApi { | ||
| 23 | + | ||
| 24 | + public static final String DEFAULT_BASE_URL = "https://api.acedata.cloud/suno"; | ||
| 25 | + private final WebClient webClient; | ||
| 26 | + | ||
| 27 | + public AceDataSunoApi(SunoConfig config) { | ||
| 28 | + this.webClient = WebClient.builder() | ||
| 29 | + .baseUrl(DEFAULT_BASE_URL) | ||
| 30 | + .defaultHeaders(ApiUtils.getJsonContentHeaders(config.getToken())) | ||
| 31 | + .build(); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + // TODO @芋艿:方法名,要考虑下; | ||
| 35 | + public SunoResp musicGen(SunoReq sunReq) { | ||
| 36 | + return this.webClient.post() | ||
| 37 | + .uri("/audios") | ||
| 38 | + .body(Mono.just(sunReq), SunoReq.class) | ||
| 39 | + .retrieve() | ||
| 40 | + .onStatus(status -> !status.is2xxSuccessful(), | ||
| 41 | + response -> response.bodyToMono(String.class) | ||
| 42 | + .handle((respBody, sink) -> { | ||
| 43 | + log.error("【Suno】调用失败!resp: 【{}】", respBody); | ||
| 44 | + sink.error(new IllegalStateException("【Suno】调用失败!")); | ||
| 45 | + })) | ||
| 46 | + .bodyToMono(SunoResp.class) | ||
| 47 | + .block(); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + /** | ||
| 51 | + * 请求数据对象,用于生成音乐音频。 | ||
| 52 | + * | ||
| 53 | + * @param prompt 用于生成音乐音频的提示 | ||
| 54 | + * @param lyric 用于生成音乐音频的歌词 | ||
| 55 | + * @param custom 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | ||
| 56 | + * @param title 音乐音频的标题 | ||
| 57 | + * @param style 音乐音频的风格 | ||
| 58 | + * @param callbackUrl 音乐音频生成后回调的 URL | ||
| 59 | + */ | ||
| 60 | + @JsonInclude(value = JsonInclude.Include.NON_NULL) | ||
| 61 | + public record SunoReq( | ||
| 62 | + String prompt, | ||
| 63 | + String lyric, | ||
| 64 | + boolean custom, | ||
| 65 | + String title, | ||
| 66 | + String style, | ||
| 67 | + String callbackUrl | ||
| 68 | + ) { | ||
| 69 | + public SunoReq(String prompt) { | ||
| 70 | + this(prompt, null, false, null, null, null); | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /** | ||
| 76 | + * SunoAPI 响应的数据。 | ||
| 77 | + * | ||
| 78 | + * @param success 表示请求是否成功 | ||
| 79 | + * @param taskId 任务 ID | ||
| 80 | + * @param data 音乐数据列表 | ||
| 81 | + */ | ||
| 82 | + public record SunoResp( | ||
| 83 | + boolean success, | ||
| 84 | + @JsonProperty("task_id") String taskId, | ||
| 85 | + List<MusicData> data | ||
| 86 | + ) { | ||
| 87 | + /** | ||
| 88 | + * 单个音乐数据。 | ||
| 89 | + * | ||
| 90 | + * @param id 音乐数据的 ID | ||
| 91 | + * @param title 音乐音频的标题 | ||
| 92 | + * @param imageUrl 音乐音频的图片 URL | ||
| 93 | + * @param lyric 音乐音频的歌词 | ||
| 94 | + * @param audioUrl 音乐音频的 URL | ||
| 95 | + * @param videoUrl 音乐视频的 URL | ||
| 96 | + * @param createdAt 音乐音频的创建时间 | ||
| 97 | + * @param model 使用的模型名称 | ||
| 98 | + * @param prompt 生成音乐音频的提示 | ||
| 99 | + * @param style 音乐音频的风格 | ||
| 100 | + */ | ||
| 101 | + public record MusicData( | ||
| 102 | + String id, | ||
| 103 | + String title, | ||
| 104 | + @JsonProperty("image_url") String imageUrl, | ||
| 105 | + String lyric, | ||
| 106 | + @JsonProperty("audio_url") String audioUrl, | ||
| 107 | + @JsonProperty("video_url") String videoUrl, | ||
| 108 | + @JsonProperty("created_at") String createdAt, | ||
| 109 | + String model, | ||
| 110 | + String prompt, | ||
| 111 | + String style | ||
| 112 | + ) { | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/suno/api/SunoApi.java
| 1 | package cn.iocoder.yudao.framework.ai.core.model.suno.api; | 1 | package cn.iocoder.yudao.framework.ai.core.model.suno.api; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.ai.core.model.suno.SunoConfig; | ||
| 4 | import com.fasterxml.jackson.annotation.JsonInclude; | 3 | import com.fasterxml.jackson.annotation.JsonInclude; |
| 5 | import com.fasterxml.jackson.annotation.JsonProperty; | 4 | import com.fasterxml.jackson.annotation.JsonProperty; |
| 6 | import lombok.extern.slf4j.Slf4j; | 5 | import lombok.extern.slf4j.Slf4j; |
| 7 | -import org.springframework.ai.openai.api.ApiUtils; | 6 | +import org.springframework.core.ParameterizedTypeReference; |
| 7 | +import org.springframework.http.HttpStatusCode; | ||
| 8 | +import org.springframework.http.MediaType; | ||
| 9 | +import org.springframework.web.reactive.function.client.ClientResponse; | ||
| 8 | import org.springframework.web.reactive.function.client.WebClient; | 10 | import org.springframework.web.reactive.function.client.WebClient; |
| 9 | import reactor.core.publisher.Mono; | 11 | import reactor.core.publisher.Mono; |
| 10 | 12 | ||
| 11 | import java.util.List; | 13 | import java.util.List; |
| 14 | +import java.util.function.Function; | ||
| 15 | +import java.util.function.Predicate; | ||
| 12 | 16 | ||
| 13 | /** | 17 | /** |
| 14 | * Suno API | 18 | * Suno API |
| 15 | * <br> | 19 | * <br> |
| 16 | - * 文档地址:https://platform.acedata.cloud/documents/d016ee3f-421b-4b6e-989a-8beba8701701 | 20 | + * 文档地址:https://github.com/status2xx/suno-api/blob/main/README_CN.md |
| 17 | * | 21 | * |
| 18 | * @Author xiaoxin | 22 | * @Author xiaoxin |
| 19 | - * @Date 2024/5/27 | 23 | + * @Date 2024/6/3 |
| 20 | */ | 24 | */ |
| 21 | @Slf4j | 25 | @Slf4j |
| 22 | public class SunoApi { | 26 | public class SunoApi { |
| 23 | 27 | ||
| 24 | - public static final String DEFAULT_BASE_URL = "https://api.acedata.cloud/suno"; | 28 | + public static final String DEFAULT_BASE_URL = "https://suno-9323szg26-status2xxs-projects.vercel.app"; |
| 25 | private final WebClient webClient; | 29 | private final WebClient webClient; |
| 26 | 30 | ||
| 27 | - public SunoApi(SunoConfig config) { | 31 | + |
| 32 | + private final Predicate<HttpStatusCode> STATUS_PREDICATE = status -> !status.is2xxSuccessful(); | ||
| 33 | + private final Function<ClientResponse, Mono<? extends Throwable>> EXCEPTION_FUNCTION = response -> response.bodyToMono(String.class) | ||
| 34 | + .handle((respBody, sink) -> { | ||
| 35 | + log.error("【suno-api】调用失败!resp: 【{}】", respBody); | ||
| 36 | + sink.error(new IllegalStateException("【suno-api】调用失败!")); | ||
| 37 | + }); | ||
| 38 | + | ||
| 39 | + | ||
| 40 | + public SunoApi() { | ||
| 28 | this.webClient = WebClient.builder() | 41 | this.webClient = WebClient.builder() |
| 29 | .baseUrl(DEFAULT_BASE_URL) | 42 | .baseUrl(DEFAULT_BASE_URL) |
| 30 | - .defaultHeaders(ApiUtils.getJsonContentHeaders(config.getToken())) | 43 | + .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) |
| 31 | .build(); | 44 | .build(); |
| 32 | } | 45 | } |
| 33 | 46 | ||
| 34 | - // TODO @芋艿:方法名,要考虑下; | ||
| 35 | - public SunoResp musicGen(SunoReq sunReq) { | 47 | + public List<MusicData> generate(SunoApi.SunoReq sunReq) { |
| 48 | + return this.webClient.post() | ||
| 49 | + .uri("/api/generate") | ||
| 50 | + .body(Mono.just(sunReq), SunoApi.SunoReq.class) | ||
| 51 | + .retrieve() | ||
| 52 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 53 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 54 | + }) | ||
| 55 | + .block(); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + public List<MusicData> doChatCompletion(String prompt) { | ||
| 59 | + return this.webClient.post() | ||
| 60 | + .uri("/v1/chat/completions") | ||
| 61 | + .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class) | ||
| 62 | + .retrieve() | ||
| 63 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 64 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 65 | + }) | ||
| 66 | + .block(); | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + public LyricsData generateLyrics(String prompt) { | ||
| 36 | return this.webClient.post() | 70 | return this.webClient.post() |
| 37 | - .uri("/audios") | ||
| 38 | - .body(Mono.just(sunReq), SunoReq.class) | 71 | + .uri("/api/generate_lyrics") |
| 72 | + .body(Mono.just(new SunoReq(prompt)), SunoApi.SunoReq.class) | ||
| 73 | + .retrieve() | ||
| 74 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 75 | + .bodyToMono(LyricsData.class) | ||
| 76 | + .block(); | ||
| 77 | + } | ||
| 78 | + | ||
| 79 | + | ||
| 80 | + public List<MusicData> selectById(String ids) { | ||
| 81 | + return this.webClient.get() | ||
| 82 | + .uri(uriBuilder -> uriBuilder | ||
| 83 | + .path("/api/get") | ||
| 84 | + .queryParam("ids", ids) | ||
| 85 | + .build()) | ||
| 39 | .retrieve() | 86 | .retrieve() |
| 40 | - .onStatus(status -> !status.is2xxSuccessful(), | ||
| 41 | - response -> response.bodyToMono(String.class) | ||
| 42 | - .handle((respBody, sink) -> { | ||
| 43 | - log.error("【Suno】调用失败!resp: 【{}】", respBody); | ||
| 44 | - sink.error(new IllegalStateException("【Suno】调用失败!")); | ||
| 45 | - })) | ||
| 46 | - .bodyToMono(SunoResp.class) | 87 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) |
| 88 | + .bodyToMono(new ParameterizedTypeReference<List<MusicData>>() { | ||
| 89 | + }) | ||
| 47 | .block(); | 90 | .block(); |
| 48 | } | 91 | } |
| 49 | 92 | ||
| 93 | + | ||
| 94 | + public LimitData selectLimit() { | ||
| 95 | + return this.webClient.get() | ||
| 96 | + .uri("/api/get_limit") | ||
| 97 | + .retrieve() | ||
| 98 | + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION) | ||
| 99 | + .bodyToMono(LimitData.class) | ||
| 100 | + .block(); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + | ||
| 50 | /** | 104 | /** |
| 51 | - * 请求数据对象,用于生成音乐音频。 | 105 | + * 根据提示生成音频 |
| 52 | * | 106 | * |
| 53 | - * @param prompt 用于生成音乐音频的提示 | ||
| 54 | - * @param lyric 用于生成音乐音频的歌词 | ||
| 55 | - * @param custom 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | ||
| 56 | - * @param title 音乐音频的标题 | ||
| 57 | - * @param style 音乐音频的风格 | ||
| 58 | - * @param callbackUrl 音乐音频生成后回调的 URL | 107 | + * @param prompt 用于生成音乐音频的提示 |
| 108 | + * @param tags 自定义模式才需要 | ||
| 109 | + * @param title 自定义模式才需要 | ||
| 110 | + * @param waitAudio false表示后台模式,仅返回音频任务信息,需要调用get API获取详细的音频信息。 | ||
| 111 | + * true表示同步模式,API最多等待100s,音频生成完毕后直接返回音频链接等信息,建议在GPT等agent中使用。 | ||
| 112 | + * @param makeInstrumental 指示音乐音频是否为定制,如果为 true,则从歌词生成,否则从提示生成 | ||
| 59 | */ | 113 | */ |
| 60 | @JsonInclude(value = JsonInclude.Include.NON_NULL) | 114 | @JsonInclude(value = JsonInclude.Include.NON_NULL) |
| 61 | public record SunoReq( | 115 | public record SunoReq( |
| 62 | String prompt, | 116 | String prompt, |
| 63 | - String lyric, | ||
| 64 | - boolean custom, | 117 | + String tags, |
| 65 | String title, | 118 | String title, |
| 66 | - String style, | ||
| 67 | - String callbackUrl | 119 | + @JsonProperty("wait_audio") boolean waitAudio, |
| 120 | + @JsonProperty("make_instrumental") boolean makeInstrumental | ||
| 68 | ) { | 121 | ) { |
| 69 | public SunoReq(String prompt) { | 122 | public SunoReq(String prompt) { |
| 70 | - this(prompt, null, false, null, null, null); | 123 | + this(prompt, null, null, false, false); |
| 71 | } | 124 | } |
| 72 | 125 | ||
| 126 | + public SunoReq(String prompt, String tags, String title) { | ||
| 127 | + this(prompt, tags, title, false, false); | ||
| 128 | + } | ||
| 73 | } | 129 | } |
| 74 | 130 | ||
| 131 | + | ||
| 75 | /** | 132 | /** |
| 76 | - * SunoAPI 响应的数据。 | 133 | + * SunoAPI 响应的音频数据。 |
| 77 | * | 134 | * |
| 78 | - * @param success 表示请求是否成功 | ||
| 79 | - * @param taskId 任务 ID | ||
| 80 | - * @param data 音乐数据列表 | 135 | + * @param id 音乐数据的 ID |
| 136 | + * @param title 音乐音频的标题 | ||
| 137 | + * @param imageUrl 音乐音频的图片 URL | ||
| 138 | + * @param lyric 音乐音频的歌词 | ||
| 139 | + * @param audioUrl 音乐音频的 URL | ||
| 140 | + * @param videoUrl 音乐视频的 URL | ||
| 141 | + * @param createdAt 音乐音频的创建时间 | ||
| 142 | + * @param modelName | ||
| 143 | + * @param status | ||
| 144 | + * @param gptDescriptionPrompt | ||
| 145 | + * @param prompt 生成音乐音频的提示 | ||
| 146 | + * @param type | ||
| 147 | + * @param tags | ||
| 81 | */ | 148 | */ |
| 82 | - public record SunoResp( | ||
| 83 | - boolean success, | ||
| 84 | - @JsonProperty("task_id") String taskId, | ||
| 85 | - List<MusicData> data | 149 | + public record MusicData( |
| 150 | + String id, | ||
| 151 | + String title, | ||
| 152 | + @JsonProperty("image_url") String imageUrl, | ||
| 153 | + String lyric, | ||
| 154 | + @JsonProperty("audio_url") String audioUrl, | ||
| 155 | + @JsonProperty("video_url") String videoUrl, | ||
| 156 | + @JsonProperty("created_at") String createdAt, | ||
| 157 | + @JsonProperty("model_name") String modelName, | ||
| 158 | + String status, | ||
| 159 | + @JsonProperty("gpt_description_prompt") String gptDescriptionPrompt, | ||
| 160 | + String prompt, | ||
| 161 | + String type, | ||
| 162 | + String tags | ||
| 86 | ) { | 163 | ) { |
| 87 | - /** | ||
| 88 | - * 单个音乐数据。 | ||
| 89 | - * | ||
| 90 | - * @param id 音乐数据的 ID | ||
| 91 | - * @param title 音乐音频的标题 | ||
| 92 | - * @param imageUrl 音乐音频的图片 URL | ||
| 93 | - * @param lyric 音乐音频的歌词 | ||
| 94 | - * @param audioUrl 音乐音频的 URL | ||
| 95 | - * @param videoUrl 音乐视频的 URL | ||
| 96 | - * @param createdAt 音乐音频的创建时间 | ||
| 97 | - * @param model 使用的模型名称 | ||
| 98 | - * @param prompt 生成音乐音频的提示 | ||
| 99 | - * @param style 音乐音频的风格 | ||
| 100 | - */ | ||
| 101 | - public record MusicData( | ||
| 102 | - String id, | ||
| 103 | - String title, | ||
| 104 | - @JsonProperty("image_url") String imageUrl, | ||
| 105 | - String lyric, | ||
| 106 | - @JsonProperty("audio_url") String audioUrl, | ||
| 107 | - @JsonProperty("video_url") String videoUrl, | ||
| 108 | - @JsonProperty("created_at") String createdAt, | ||
| 109 | - String model, | ||
| 110 | - String prompt, | ||
| 111 | - String style | ||
| 112 | - ) { | ||
| 113 | - } | ||
| 114 | } | 164 | } |
| 165 | + | ||
| 166 | + | ||
| 167 | + /** | ||
| 168 | + * SunoAPI 响应的歌词数据。 | ||
| 169 | + * | ||
| 170 | + * @param text 歌词 | ||
| 171 | + * @param title 标题 | ||
| 172 | + * @param status 状态 | ||
| 173 | + */ | ||
| 174 | + public record LyricsData( | ||
| 175 | + String text, | ||
| 176 | + String title, | ||
| 177 | + String status | ||
| 178 | + ) { | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + | ||
| 182 | + /** | ||
| 183 | + * SunoAPI 响应的限额数据,目前每日免费50 | ||
| 184 | + */ | ||
| 185 | + public record LimitData( | ||
| 186 | + @JsonProperty("credits_left") Long creditsLeft, | ||
| 187 | + String period, | ||
| 188 | + @JsonProperty("monthly_limit") Long monthlyLimit, | ||
| 189 | + @JsonProperty("monthly_usage") Long monthlyUsage | ||
| 190 | + ) { | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + | ||
| 115 | } | 194 | } |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/suno/SunoTests.java
| @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | @@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; | ||
| 5 | import org.junit.Before; | 5 | import org.junit.Before; |
| 6 | import org.junit.Test; | 6 | import org.junit.Test; |
| 7 | 7 | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 8 | /** | 10 | /** |
| 9 | * @Author xiaoxin | 11 | * @Author xiaoxin |
| 10 | * @Date 2024/5/27 | 12 | * @Date 2024/5/27 |
| @@ -20,12 +22,43 @@ public class SunoTests { | @@ -20,12 +22,43 @@ public class SunoTests { | ||
| 20 | } | 22 | } |
| 21 | 23 | ||
| 22 | @Test | 24 | @Test |
| 23 | - public void generateMusic() { | ||
| 24 | - SunoApi sunoApi = new SunoApi(sunoConfig); | ||
| 25 | - SunoApi.SunoReq sunoReq = new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); | 25 | + public void selectById() { |
| 26 | + SunoApi sunoApi = new SunoApi(); | ||
| 27 | + System.out.println(sunoApi.selectById("d460ddda-7c87-4f34-b751-419b08a590ca,ff90ea66-49cd-4fd2-b44c-44267dfd5551")); | ||
| 28 | + | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Test | ||
| 32 | + public void generate() { | ||
| 33 | + SunoApi sunoApi = new SunoApi(); | ||
| 34 | + List<SunoApi.MusicData> generate = sunoApi.generate(new SunoApi.SunoReq("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。")); | ||
| 35 | + System.out.println(generate); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + @Test | ||
| 40 | + public void doChatCompletion() { | ||
| 41 | + SunoApi sunoApi = new SunoApi(); | ||
| 42 | + List<SunoApi.MusicData> generate = sunoApi.doChatCompletion("创作一首带有轻松吉他旋律的流行歌曲,[verse] 描述夏日海滩的宁静,[chorus] 节奏加快,表达对自由的向往。"); | ||
| 43 | + System.out.println(generate); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + | ||
| 47 | + @Test | ||
| 48 | + public void generateLyrics() { | ||
| 49 | + SunoApi sunoApi = new SunoApi(); | ||
| 50 | + SunoApi.LyricsData lyricsData = sunoApi.generateLyrics("A soothing lullaby"); | ||
| 51 | + System.out.println(lyricsData); | ||
| 52 | + } | ||
| 26 | 53 | ||
| 27 | - SunoApi.SunoResp sunoResp = sunoApi.musicGen(sunoReq); | ||
| 28 | - System.out.println(sunoResp); | 54 | + |
| 55 | + | ||
| 56 | + @Test | ||
| 57 | + public void selectLimit() { | ||
| 58 | + SunoApi sunoApi = new SunoApi(); | ||
| 59 | + SunoApi.LimitData limitData = sunoApi.selectLimit(); | ||
| 60 | + System.out.println(limitData); | ||
| 29 | } | 61 | } |
| 30 | 62 | ||
| 63 | + | ||
| 31 | } | 64 | } |