|
|
|
@ -0,0 +1,260 @@
|
|
|
|
|
package com.dsideal.aiSupport.Util.Suno;
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
import com.dsideal.aiSupport.Plugin.YamlProp;
|
|
|
|
|
import com.dsideal.aiSupport.Util.Midjourney.Kit.MjCommon;
|
|
|
|
|
import com.jfinal.kit.Prop;
|
|
|
|
|
import okhttp3.*;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
import static com.dsideal.aiSupport.AiSupportApplication.getEnvPrefix;
|
|
|
|
|
|
|
|
|
|
public class SunoMusicGenerator {
|
|
|
|
|
|
|
|
|
|
private static final String GENERATE_URL = "https://goapi.gptnb.ai/suno/v2/generate";
|
|
|
|
|
private static final String FEED_URL = "https://goapi.gptnb.ai/suno/v2/feed";
|
|
|
|
|
private static final int MAX_RETRIES = 30; // 最大重试次数
|
|
|
|
|
private static final int RETRY_INTERVAL = 5000; // 重试间隔(毫秒)
|
|
|
|
|
private static final Logger log = LoggerFactory.getLogger(SunoMusicGenerator.class);
|
|
|
|
|
// 获取项目根目录路径
|
|
|
|
|
protected static String projectRoot = System.getProperty("user.dir").replace("\\","/")+"/dsAiSupport";
|
|
|
|
|
// 拼接相对路径
|
|
|
|
|
protected static String basePath = projectRoot + "/src/main/java/com/dsideal/aiSupport/Util/Suno/Example/";
|
|
|
|
|
|
|
|
|
|
protected static String ak; // 填写access key
|
|
|
|
|
public static Prop PropKit; // 配置文件工具
|
|
|
|
|
protected static final String BASE_URL = "https://goapi.gptnb.ai";
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
//加载配置文件
|
|
|
|
|
String configFile = "application_{?}.yaml".replace("{?}", getEnvPrefix());
|
|
|
|
|
PropKit = new YamlProp(configFile);
|
|
|
|
|
ak = PropKit.get("GPTNB.sk");
|
|
|
|
|
}
|
|
|
|
|
public static void main(String[] args) throws IOException, InterruptedException {
|
|
|
|
|
|
|
|
|
|
// 创建OkHttpClient
|
|
|
|
|
OkHttpClient client = new OkHttpClient().newBuilder()
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
// 1. 生成音乐
|
|
|
|
|
String musicDescription = "an anthemic dancepop song about dancing all night long";
|
|
|
|
|
System.out.println("开始生成音乐: " + musicDescription);
|
|
|
|
|
|
|
|
|
|
// 构建JSON请求体
|
|
|
|
|
JSONObject requestJson = new JSONObject();
|
|
|
|
|
requestJson.put("gpt_description_prompt", musicDescription);
|
|
|
|
|
requestJson.put("mv", "chirp-v3-5");
|
|
|
|
|
requestJson.put("prompt", "");
|
|
|
|
|
requestJson.put("make_instrumental", true);
|
|
|
|
|
|
|
|
|
|
// 创建请求
|
|
|
|
|
MediaType mediaType = MediaType.parse("application/json");
|
|
|
|
|
RequestBody body = RequestBody.create(mediaType, requestJson.toJSONString());
|
|
|
|
|
Request generateRequest = new Request.Builder()
|
|
|
|
|
.url(GENERATE_URL)
|
|
|
|
|
.method("POST", body)
|
|
|
|
|
.addHeader("Content-Type", "application/json")
|
|
|
|
|
.addHeader("Authorization", "Bearer " + ak)
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
// 执行生成请求
|
|
|
|
|
Response generateResponse = client.newCall(generateRequest).execute();
|
|
|
|
|
|
|
|
|
|
// 处理生成响应
|
|
|
|
|
if (!generateResponse.isSuccessful()) {
|
|
|
|
|
System.out.println("音乐生成请求失败,状态码: " + generateResponse.code());
|
|
|
|
|
System.out.println("错误信息: " + generateResponse.message());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String generateResponseBody = generateResponse.body().string();
|
|
|
|
|
System.out.println("音乐生成响应: " + generateResponseBody);
|
|
|
|
|
|
|
|
|
|
// 从响应中提取任务ID
|
|
|
|
|
JSONObject generateJson = JSON.parseObject(generateResponseBody);
|
|
|
|
|
String taskId = null;
|
|
|
|
|
|
|
|
|
|
// 根据响应格式提取任务ID
|
|
|
|
|
// 注意:这里需要根据实际的响应格式进行调整
|
|
|
|
|
if (generateJson.containsKey("id")) {
|
|
|
|
|
taskId = generateJson.getString("id");
|
|
|
|
|
} else if (generateJson.containsKey("task_id")) {
|
|
|
|
|
taskId = generateJson.getString("task_id");
|
|
|
|
|
} else if (generateJson.containsKey("clip_id")) {
|
|
|
|
|
taskId = generateJson.getString("clip_id");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (taskId == null) {
|
|
|
|
|
System.out.println("无法从响应中提取任务ID,无法继续查询");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("音乐生成任务已提交,任务ID: " + taskId);
|
|
|
|
|
System.out.println("开始查询任务状态...");
|
|
|
|
|
|
|
|
|
|
// 存储任务ID列表
|
|
|
|
|
List<String> taskIds = new ArrayList<>();
|
|
|
|
|
taskIds.add(taskId);
|
|
|
|
|
|
|
|
|
|
// 2. 查询任务状态
|
|
|
|
|
boolean isComplete = false;
|
|
|
|
|
int retryCount = 0;
|
|
|
|
|
String audioUrl = null;
|
|
|
|
|
|
|
|
|
|
while (!isComplete && retryCount < MAX_RETRIES) {
|
|
|
|
|
// 等待一段时间再查询
|
|
|
|
|
Thread.sleep(RETRY_INTERVAL);
|
|
|
|
|
|
|
|
|
|
// 构建查询URL,添加ids参数
|
|
|
|
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(FEED_URL).newBuilder();
|
|
|
|
|
|
|
|
|
|
// 添加ids参数,用逗号分隔多个ID
|
|
|
|
|
if (!taskIds.isEmpty()) {
|
|
|
|
|
// 注意:这里我们查询的是clips的ID,而不是生成任务的ID
|
|
|
|
|
// 从生成响应中获取clips的ID
|
|
|
|
|
List<String> clipIds = new ArrayList<>();
|
|
|
|
|
if (generateJson.containsKey("clips")) {
|
|
|
|
|
JSONArray clipsArray = generateJson.getJSONArray("clips");
|
|
|
|
|
for (int i = 0; i < clipsArray.size(); i++) {
|
|
|
|
|
JSONObject clip = clipsArray.getJSONObject(i);
|
|
|
|
|
if (clip.containsKey("id")) {
|
|
|
|
|
clipIds.add(clip.getString("id"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果找到了clips的ID,则使用这些ID进行查询
|
|
|
|
|
if (!clipIds.isEmpty()) {
|
|
|
|
|
String idsParam = String.join(",", clipIds);
|
|
|
|
|
urlBuilder.addQueryParameter("ids", idsParam);
|
|
|
|
|
log.info("使用clips ID查询: {}", idsParam);
|
|
|
|
|
} else {
|
|
|
|
|
// 否则使用任务ID
|
|
|
|
|
String idsParam = String.join(",", taskIds);
|
|
|
|
|
urlBuilder.addQueryParameter("ids", idsParam);
|
|
|
|
|
log.info("使用任务ID查询: {}", idsParam);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建查询请求
|
|
|
|
|
Request feedRequest = new Request.Builder()
|
|
|
|
|
.url(urlBuilder.build())
|
|
|
|
|
.method("GET", null)
|
|
|
|
|
.addHeader("Authorization", "Bearer " + ak)
|
|
|
|
|
.addHeader("Accept", "application/json") // 添加Accept头
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
log.info("查询URL: {}", urlBuilder.build());
|
|
|
|
|
|
|
|
|
|
// 执行查询请求
|
|
|
|
|
Response feedResponse = client.newCall(feedRequest).execute();
|
|
|
|
|
|
|
|
|
|
// 处理查询响应
|
|
|
|
|
if (!feedResponse.isSuccessful()) {
|
|
|
|
|
System.out.println("查询任务状态失败,状态码: " + feedResponse.code());
|
|
|
|
|
System.out.println("错误信息: " + feedResponse.message());
|
|
|
|
|
retryCount++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String feedResponseBody = feedResponse.body().string();
|
|
|
|
|
JSONObject jsonResponse = JSON.parseObject(feedResponseBody);
|
|
|
|
|
JSONArray clips = jsonResponse.getJSONArray("clips");
|
|
|
|
|
|
|
|
|
|
if (clips != null && !clips.isEmpty()) {
|
|
|
|
|
// 遍历所有返回的音乐片段
|
|
|
|
|
for (int i = 0; i < clips.size(); i++) {
|
|
|
|
|
JSONObject clip = clips.getJSONObject(i);
|
|
|
|
|
String clipId = clip.getString("id");
|
|
|
|
|
String status = clip.getString("status");
|
|
|
|
|
String title = clip.getString("title");
|
|
|
|
|
|
|
|
|
|
System.out.println("\n查询结果 (第" + (retryCount + 1) + "次):");
|
|
|
|
|
System.out.println("ID: " + clipId);
|
|
|
|
|
System.out.println("标题: " + title);
|
|
|
|
|
System.out.println("状态: " + status);
|
|
|
|
|
|
|
|
|
|
// 检查是否是我们要查询的任务
|
|
|
|
|
if (taskIds.contains(clipId)) {
|
|
|
|
|
// 检查是否完成
|
|
|
|
|
if ("complete".equals(status)) {
|
|
|
|
|
audioUrl = clip.getString("audio_url");
|
|
|
|
|
System.out.println("音乐生成已完成!");
|
|
|
|
|
System.out.println("音频URL: " + audioUrl);
|
|
|
|
|
isComplete = true;
|
|
|
|
|
break; // 找到了我们要查询的任务,并且已完成
|
|
|
|
|
} else if ("streaming".equals(status)) {
|
|
|
|
|
System.out.println("音乐生成中,继续等待...");
|
|
|
|
|
} else if ("failed".equals(status)) {
|
|
|
|
|
System.out.println("音乐生成失败!");
|
|
|
|
|
isComplete = true; // 任务失败,不再继续查询
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
System.out.println("未找到音乐片段,继续等待...");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retryCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 下载音频文件(如果生成成功)
|
|
|
|
|
if (isComplete && audioUrl != null) {
|
|
|
|
|
String fileName = "suno_music_" + System.currentTimeMillis() + ".mp3";
|
|
|
|
|
String savePath = "./downloads/" + fileName;
|
|
|
|
|
downloadAudio(client, audioUrl, savePath);
|
|
|
|
|
} else if (retryCount >= MAX_RETRIES) {
|
|
|
|
|
System.out.println("达到最大重试次数,任务可能仍在处理中");
|
|
|
|
|
System.out.println("请稍后手动查询任务ID: " + taskId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 下载音频文件
|
|
|
|
|
* @param client OkHttpClient实例
|
|
|
|
|
* @param audioUrl 音频URL
|
|
|
|
|
* @param savePath 保存路径
|
|
|
|
|
*/
|
|
|
|
|
public static void downloadAudio(OkHttpClient client, String audioUrl, String savePath) throws IOException {
|
|
|
|
|
System.out.println("开始下载音频文件...");
|
|
|
|
|
|
|
|
|
|
Request request = new Request.Builder()
|
|
|
|
|
.url(audioUrl)
|
|
|
|
|
.method("GET", null)
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
Response response = client.newCall(request).execute();
|
|
|
|
|
|
|
|
|
|
if (response.isSuccessful()) {
|
|
|
|
|
// 确保目录存在
|
|
|
|
|
java.io.File file = new java.io.File(savePath);
|
|
|
|
|
java.io.File parentDir = file.getParentFile();
|
|
|
|
|
if (parentDir != null && !parentDir.exists()) {
|
|
|
|
|
parentDir.mkdirs();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 保存文件
|
|
|
|
|
try (java.io.InputStream inputStream = response.body().byteStream();
|
|
|
|
|
java.io.FileOutputStream outputStream = new java.io.FileOutputStream(savePath)) {
|
|
|
|
|
byte[] buffer = new byte[4096];
|
|
|
|
|
int bytesRead;
|
|
|
|
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
|
|
|
outputStream.write(buffer, 0, bytesRead);
|
|
|
|
|
}
|
|
|
|
|
outputStream.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
System.out.println("音频下载成功,保存路径: " + savePath);
|
|
|
|
|
} else {
|
|
|
|
|
System.out.println("下载音频失败,状态码: " + response.code());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|